feat: gate heavy solver JSON, field traceability API, GUI rod/export depth

- C: emit profiles/diagnostics/fourier only when enable flags are set; null otherwise
- API: fieldTraceability on case parse/default and solve; fix GET /solve/default options
- Tests: golden fingerprint, quality gates, C diagnostics invariants; cardQa mean empty guard
- Makefile: test-solver-sanitize ASan/UBSan target; README and COMPUTE_PLAN updates
- GUI: taper weight/MTS/guides/sinker round-trip, rod catalog, solver output toggles,
  results (profiles/diagnostics/Fourier/traceability), engineering checks and tabs
- Restore canonical WellName in base-case for regression; trace TaperGuidesCountArray

Made-with: Cursor
This commit is contained in:
2026-04-16 23:19:00 -06:00
parent 10f6ae1c2b
commit 64e9d31373
39 changed files with 1318 additions and 369 deletions

View File

@@ -0,0 +1,76 @@
import { MVP_FIELDS } from "./schema.js";
/**
* Static MVP field classification aligned with docs/engineering/field-traceability.md.
*/
const CATEGORY_BY_FIELD = {
WellName: "metadata",
Company: "metadata",
PumpingSpeed: "physics",
PumpDepth: "physics",
TubingAnchorLocation: "physics",
RodFrictionCoefficient: "physics",
StuffingBoxFriction: "physics",
PumpFriction: "physics",
WaterCut: "physics",
MeasuredDepthArray: "physics",
InclinationFromVerticalArray: "physics",
AzimuthFromNorthArray: "physics",
UnitsSelection: "parseCalibration",
UpStrokeDampingFactor: "physics",
DownStrokeDampingFactor: "physics",
NonDimensionalFluidDamping: "physics",
TaperDiameterArray: "physics",
TaperLengthArray: "physics",
TaperModulusArray: "physics",
TaperWeightArray: "physics",
TaperMTSArray: "physics",
TaperGuidesCountArray: "physics",
RodTypeArray: "physics",
MoldedGuideFrictionRatio: "physics",
WheeledGuideFrictionRatio: "physics",
OtherGuideFrictionRatio: "physics",
PumpDiameter: "physics",
PumpIntakePressure: "physics",
TubingSize: "payloadInactive",
TubingGradient: "parsedUnused",
PumpFillageOption: "physics",
PercentPumpFillage: "physics",
PercentageUpstrokeTime: "payloadInactive",
PercentageDownstrokeTime: "payloadInactive",
PumpingUnitID: "parsedUnused",
PumpingSpeedOption: "parsedUnused",
FluidLevelOilGravity: "physics",
WaterSpecGravity: "physics",
RodGuideTypeArray: "parsedUnused",
RodGuideWeightArray: "physics",
SinkerBarDiameter: "physics",
SinkerBarLength: "physics"
};
const NOTES_BY_FIELD = {
TubingSize: "Forwarded as tubing_id_m; annular refinements not yet in primary equations.",
TubingGradient: "Parsed to tubingGradientPaM; not included in C stdin payload.",
PercentageUpstrokeTime: "Payload percent_upstroke_time; harmonic kinematics still dominant.",
PercentageDownstrokeTime: "Payload percent_downstroke_time; harmonic kinematics still dominant.",
PumpingUnitID: "Parsed for future pumping-unit geometry tables.",
PumpingSpeedOption: "Parsed for future drive/kinematics modes.",
RodGuideTypeArray: "Parsed string; type-specific friction law not yet wired to C.",
UnitsSelection: "Controls imperial/SI normalization during XML parse."
};
/**
* @param {{ rawFields?: Record<string, unknown> } | null | undefined} parsed
*/
export function buildFieldTraceability(parsed) {
const raw = parsed && typeof parsed === "object" && parsed.rawFields ? parsed.rawFields : {};
return {
schemaVersion: 2,
fields: MVP_FIELDS.map((xmlKey) => ({
xmlKey,
category: CATEGORY_BY_FIELD[xmlKey] ?? "physics",
presentInXml: Object.prototype.hasOwnProperty.call(raw, xmlKey),
notes: NOTES_BY_FIELD[xmlKey] ?? ""
}))
};
}