fix: normalize engineering checks across units

Convert pump-depth vs rod-length mismatch gating to meter-equivalent comparisons, add imperial coverage, and correct rod material mapping with exported helper tests while refreshing related GUI/docs copy.

Made-with: Cursor
This commit is contained in:
2026-04-16 23:28:00 -06:00
parent 64e9d31373
commit 2a6cee21f8
8 changed files with 73 additions and 19 deletions

View File

@@ -5,7 +5,7 @@ import { App } from "./App";
const DEFAULT_CASE = {
model: {
wellName: "PLACEHOLDER-WELL",
company: "Veren",
company: "Majic",
measuredDepth: [0, 100, 200],
inclination: [0, 10, 20],
azimuth: [0, 90, 180],
@@ -14,7 +14,7 @@ const DEFAULT_CASE = {
},
rawFields: {
WellName: "PLACEHOLDER-WELL",
Company: "Veren",
Company: "Majic",
PumpDepth: "1727",
PumpingSpeed: "5",
UnitsSelection: "2",

View File

@@ -14,9 +14,10 @@ const taperBase = {
};
describe("engineering checks fixed thresholds", () => {
it("blocks run when pump depth and rod length mismatch exceeds 15 m", () => {
it("blocks run when pump depth and rod length mismatch exceeds 15 m (SI)", () => {
const state = {
...EMPTY_CASE_STATE,
unitsSelection: 1,
pumpDepth: 1000,
taper: [{ ...taperBase, length: 980, rodType: 3 }],
survey: [
@@ -31,6 +32,23 @@ describe("engineering checks fixed thresholds", () => {
expect(checks.issues.some((i) => i.code === "PUMP_ROD_MISMATCH_15M")).toBe(true);
});
it("does not block run for small mismatch in imperial native units", () => {
const state = {
...EMPTY_CASE_STATE,
unitsSelection: 2,
pumpDepth: 1000,
taper: [{ ...taperBase, length: 980, rodType: 3 }],
survey: [
{ md: 0, inc: 0, azi: 0 },
{ md: 1000, inc: 0, azi: 0 }
]
};
const checks = runEngineeringChecks(state);
expect(checks.hasBlockingError).toBe(false);
expect(checks.issues.some((i) => i.code === "PUMP_ROD_MISMATCH_15M")).toBe(false);
});
it("warns when survey ends shallower than pump depth", () => {
const state = {
...EMPTY_CASE_STATE,

View File

@@ -1,6 +1,7 @@
import type { CaseState } from "./caseModel";
export const PUMP_ROD_MISMATCH_M = 15;
const FT_TO_M = 0.3048;
export type EngineeringIssue = {
severity: "warning" | "error";
@@ -20,14 +21,19 @@ export function runEngineeringChecks(state: CaseState): EngineeringChecks {
const rodTotal = activeTaper.reduce((acc, t) => acc + t.length, 0);
const pumpDepth = state.pumpDepth;
if (rodTotal > 0 && pumpDepth > 0) {
const diff = Math.abs(pumpDepth - rodTotal);
if (diff > PUMP_ROD_MISMATCH_M) {
const depthScale = toMetersScale(state.unitsSelection);
const pumpDepthM = pumpDepth * depthScale;
const rodTotalM = rodTotal * depthScale;
const diffM = Math.abs(pumpDepthM - rodTotalM);
if (diffM > PUMP_ROD_MISMATCH_M) {
issues.push({
severity: "error",
code: "PUMP_ROD_MISMATCH_15M",
message: `Pump depth (${pumpDepth.toFixed(1)}) and total rod length (${rodTotal.toFixed(
message: `Pump depth (${pumpDepth.toFixed(1)} native, ${pumpDepthM.toFixed(
1
)}) differ by ${diff.toFixed(1)} m (> ${PUMP_ROD_MISMATCH_M} m limit).`
)} m) and total rod length (${rodTotal.toFixed(1)} native, ${rodTotalM.toFixed(
1
)} m) differ by ${diffM.toFixed(1)} m (> ${PUMP_ROD_MISMATCH_M} m limit).`
});
}
}
@@ -68,3 +74,7 @@ export function runEngineeringChecks(state: CaseState): EngineeringChecks {
};
}
function toMetersScale(unitsSelection: number): number {
return unitsSelection === 1 ? 1 : FT_TO_M;
}

View File

@@ -225,7 +225,9 @@ export function App() {
<footer className="app-statusbar">
<span>{statusMessage}</span>
<span>Company: {store.state.company || "—"}</span>
<span>
Well: {store.state.wellName || "—"} · Company: {store.state.company || "—"}
</span>
<span>Taper sections: {store.state.taper.filter((t) => t.length > 0).length}</span>
<span>Survey stations: {store.state.survey.length}</span>
</footer>

View File

@@ -29,9 +29,9 @@ export function KinematicsTab({
<>
<Fieldset legend="Pumping Unit / Surface Motion">
<p className="panel-note">
Drives the polished-rod boundary condition via pumping speed (SPM).
Diagnostic workflow with measured surface-card upload is planned for
a future pass (see <code>solver-api POST /solve/validate-card</code>).
Drives the polished-rod boundary condition via pumping speed (SPM). For measured-card
diagnostics, paste card data below, validate it, then switch Solver workflow to
<code> diagnostic</code> before running.
</p>
<Row label="Pumping Speed (SPM)" htmlFor="pumpingSpeed">
<NumberField