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

@@ -1,26 +1,50 @@
import { useMemo } from "react";
import type { CaseStore } from "../../state/useCaseStore";
import { Fieldset } from "../common/Fieldset";
import { Row } from "../common/Row";
import { NumberField } from "../common/NumberField";
import { SelectField } from "../common/SelectField";
import {
PUMP_PLUNGER_INCH_OPTIONS,
formatPumpDiameterDual,
pumpDiameterMmFromInches
} from "../../state/unitsDisplay";
type Props = { store: CaseStore };
export function PumpTab({ store }: Props) {
const { state, update } = store;
const pumpOptions = useMemo(() => {
const mmVals = PUMP_PLUNGER_INCH_OPTIONS.map((inchVal) => pumpDiameterMmFromInches(inchVal));
const opts = PUMP_PLUNGER_INCH_OPTIONS.map((inchVal, i) => ({
value: mmVals[i],
label: formatPumpDiameterDual(mmVals[i], state.unitsSelection)
}));
const known = opts.some((o) => Math.abs(o.value - state.pumpDiameter) < 0.001);
if (!known && state.pumpDiameter > 0) {
opts.unshift({
value: state.pumpDiameter,
label: formatPumpDiameterDual(state.pumpDiameter, state.unitsSelection)
});
}
return opts;
}, [state.pumpDiameter, state.unitsSelection]);
return (
<div className="tab-grid two">
<Fieldset legend="Pump Geometry">
<Row
label="Plunger Diameter"
label="Plunger diameter"
htmlFor="plungerDiam"
hint="mm in base-case XML (converted to m if > 2)"
hint="Stored as mm in the case file when above 2 (same convention as rod OD)."
>
<NumberField
<SelectField
id="plungerDiam"
value={state.pumpDiameter}
step={0.25}
options={pumpOptions}
onChange={(v) => update("pumpDiameter", v)}
ariaLabel="Pump plunger diameter"
/>
</Row>
<Row label="Pump Friction" htmlFor="pumpFric" hint="lbf (imperial)">