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:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user