diff --git a/Agents/COMPUTE_PLAN.md b/Agents/COMPUTE_PLAN.md index 3d72abd..6646a14 100644 --- a/Agents/COMPUTE_PLAN.md +++ b/Agents/COMPUTE_PLAN.md @@ -109,8 +109,7 @@ make smoke | **Pumping-unit kinematics** (Svinos tables, crank motion from `PumpingUnitID`) | Harmonic default; unit geometry unused | | **Inverse calibration** | Fit damping / friction to measured downhole card | | **Fourier** analytical diagnostic | Optional `comparison.fourier` | -| **GUI** 3D survey + layout | **Partial done** — Results tab includes 3D projected wellbore + rod/pump overlay + DLS contour; not a full 3D engine (no camera controls / mesh terrain yet) | -| **GUI diagnostic workflow** | Tabbed UI ships predictive solve end-to-end; diagnostic requires surface-card upload path in Kinematics (calls `POST /solve/validate-card` + `POST /solve` with `workflow=diagnostic`) — not wired in this pass | +| **GUI** 3D survey + layout | **Partial done** — Results tab includes 3D projected wellbore + rod/pump overlay, depth-hue and side-load overlays; not a full 3D engine (no camera controls / mesh terrain yet) | | **GUI Pump / Fluid / Kinematics first-class mapping** | Tabs render editable fields but rely on `rawFields` round-trip rather than dedicated serializer logic; audit once solver-api adds explicit fields for `PumpFillageOption`, pumping-unit kinematics, etc. | | **GUI fatigue / API RP 11BR table** | Backend does not emit a fatigue payload yet; surface in Results tab when `solver.fatigue` exists | @@ -121,15 +120,12 @@ make smoke - Fixed engineering gate in Solver tab: - run blocked when `|PumpDepth - sum(TaperLengthArray)| > 15 m`. - survey MD monotonicity and minimum station-count checks. -- Fixed DLS bad-section threshold: - - warnings + 3D contour use `15 deg/100` as the "bad section" limit. - Results tab now shows: - uPlot dynacard overlays, - 3D projected wellbore with rod gradient and pump marker, - - interactive 3D view controls (rotate, pan, zoom, perspective/orthographic toggle, reset), - - highlighted bad-DLS segments, + - depth-hue and side-load risk overlays, + - segment highlight + clear-highlight controls, - trajectory analytics table with row↔3D segment cross-highlight, - - side-load overlay mode (when `solver.profiles.sideLoadProfile` is available), - pump-placement diagnostics panel + navigation actions, - export actions (3D SVG, 3D PNG, summary JSON). - keyboard-accessible trajectory segment selection (`Enter`/`Space`) and clear-highlight control. diff --git a/gui-ts/src/App.test.tsx b/gui-ts/src/App.test.tsx index 43b8de5..a31a670 100644 --- a/gui-ts/src/App.test.tsx +++ b/gui-ts/src/App.test.tsx @@ -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", diff --git a/gui-ts/src/state/__tests__/engineeringChecks.test.ts b/gui-ts/src/state/__tests__/engineeringChecks.test.ts index 737194b..b1fb809 100644 --- a/gui-ts/src/state/__tests__/engineeringChecks.test.ts +++ b/gui-ts/src/state/__tests__/engineeringChecks.test.ts @@ -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, diff --git a/gui-ts/src/state/engineeringChecks.ts b/gui-ts/src/state/engineeringChecks.ts index 0cdc0ce..40c7e04 100644 --- a/gui-ts/src/state/engineeringChecks.ts +++ b/gui-ts/src/state/engineeringChecks.ts @@ -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; +} + diff --git a/gui-ts/src/ui/App.tsx b/gui-ts/src/ui/App.tsx index 342524a..6629924 100644 --- a/gui-ts/src/ui/App.tsx +++ b/gui-ts/src/ui/App.tsx @@ -225,7 +225,9 @@ export function App() { diff --git a/gui-ts/src/ui/tabs/KinematicsTab.tsx b/gui-ts/src/ui/tabs/KinematicsTab.tsx index 81bf5fc..ae70f5e 100644 --- a/gui-ts/src/ui/tabs/KinematicsTab.tsx +++ b/gui-ts/src/ui/tabs/KinematicsTab.tsx @@ -29,9 +29,9 @@ export function KinematicsTab({ <>

- Drives the polished-rod boundary condition via pumping speed (SPM). - Diagnostic workflow with measured surface-card upload is planned for - a future pass (see solver-api POST /solve/validate-card). + 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 + diagnostic before running.

{ + it("maps fiberglass rod type 3 to fiberglass properties", () => { + expect(rodTypeToProps(3)).toEqual({ + E: 5.5e9, + rho: 1900 + }); + }); + + it("maps sinker and steel rod types to steel-like properties", () => { + expect(rodTypeToProps(2)).toEqual({ + E: 2.05e11, + rho: 7850 + }); + expect(rodTypeToProps(0)).toEqual({ + E: 2.05e11, + rho: 7850 + }); + expect(rodTypeToProps(1)).toEqual({ + E: 2.05e11, + rho: 7850 + }); + }); +});