# Rods Cursor — Rod-string solver (math-first) Deterministic **C** numerical core (FDM + FEA), **Node** API for XML and orchestration, **TypeScript** GUI for workflow. See **[AGENTS.md](AGENTS.md)** for agent rules and **[Agents/MATH_SPEC.md](Agents/MATH_SPEC.md)** for equations and paper citations. ## Repository layout | Directory | Purpose | |-----------|---------| | `solver-c/` | C: damped-wave FDM, dynamic bar FEM, diagnostic transfer, trajectory preprocess, **JSON stdin** drivers | | `solver-api/` | Express: `POST /solve`, `GET /solve/default`, `POST /case/parse`, `POST /solve/validate-card`, XML → SI | | `gui-ts/` | Vite + React tabbed case editor + uPlot dynacards | | `data/cases/` | `base-case.xml` and regression inputs | | `data/golden/` | Golden SHA-256 fingerprint for default solve regression | | `Agents/` | `MATH_SPEC.md`, `COMPUTE_PLAN.md` (handoff) | | `docs/engineering/` | Architecture, schema, units, validation | | `references/papers/` | Literature citations and access notes for the solver math backbone | ## Vision Build a transparent, deterministic rod-string and wellbore mechanics platform that is field-usable and research-grade. - Physically explainable pumping-system simulations, not black-box outputs. - Dynamometer cards as the primary interpretation surface. - Inspectability of imported case data and solver assumptions. - Multi-model validation (FDM vs FEA) with clear comparison metadata. ## Prerequisites - **Local:** `gcc`, `make`, Node 20+, `npm` - **Docker:** Docker Engine + Compose plugin ## Run locally ### Build and run C solver (stdin JSON) The API spawns `solver-c/solver_main` and pipes **one JSON object** on stdin (`schemaVersion: 2`). Legacy 9-argument CLI is **removed**. ```bash gcc -std=c99 -I./solver-c/include \ ./solver-c/src/solver_common.c \ ./solver-c/src/json_stdin.c \ ./solver-c/src/trajectory.c \ ./solver-c/src/solver_diagnostic.c \ ./solver-c/src/solver.c \ ./solver-c/src/solver_fea.c \ ./solver-c/src/solver_fourier.c \ ./solver-c/src/main.c -lm -o ./solver-c/solver_main gcc -std=c99 -I./solver-c/include \ ./solver-c/src/solver_common.c \ ./solver-c/src/json_stdin.c \ ./solver-c/src/trajectory.c \ ./solver-c/src/solver_diagnostic.c \ ./solver-c/src/solver.c \ ./solver-c/src/solver_fea.c \ ./solver-c/src/solver_fourier.c \ ./solver-c/src/main_fea.c -lm -o ./solver-c/solver_fea_main ./solver-c/test_solver ``` ### API + GUI ```bash cd solver-api && npm install && npm run dev cd gui-ts && npm install && npm run dev ``` - API: `http://localhost:4400/health` - GUI: `http://localhost:5173` ## Run with Docker ```bash make run # or: docker compose up --build make smoke # requires API on 4400 make down ``` ## Validation ```bash make test # solver-api vitest + gui-ts tests + solver-c test_solver ./solver-c/test_solver ``` Golden hash: `solver-api` tests assert `/solve/default` body matches `data/golden/default.solve.sha256` (after normalizing `generatedAt`). ## API examples ### Predictive (default base case) ```bash curl -sS "http://localhost:4400/solve/default?solverModel=both" | jq '.schemaVersion, .runMetadata, .comparison | keys' ``` ### Predictive (POST XML) ```bash curl -sS -X POST http://localhost:4400/solve \ -H "Content-Type: application/json" \ -d "{\"xml\": $(jq -Rs . < data/cases/base-case.xml), \"solverModel\": \"fdm\"}" | jq '.solver.pointCount, .schemaVersion' ``` ### Extended physics outputs (profiles/diagnostics/fourier) ```bash curl -sS -X POST http://localhost:4400/solve \ -H "Content-Type: application/json" \ -d "{\"xml\": $(jq -Rs . < data/cases/base-case.xml), \"solverModel\": \"both\", \"options\": {\"enableProfiles\": true, \"enableDiagnosticsDetail\": true, \"enableFourierBaseline\": true, \"fourierHarmonics\": 10}}" \ | jq '.solver.profiles.nodeCount, .solver.diagnostics.valveStates[0], .comparison.fourier.harmonics' ``` ### Diagnostic (measured surface card) Build `surfaceCard` from a predictive run or field data: ```bash CARD=$(curl -sS "http://localhost:4400/solve/default?solverModel=fdm") # Then POST xml + workflow + surfaceCard (see solver-api/tests/api.test.js) ``` ### Surface card QA only ```bash curl -sS -X POST http://localhost:4400/solve/validate-card \ -H "Content-Type: application/json" \ -d '{"surfaceCard":{"position":[0,1,2],"load":[10,11,12]}}' | jq . ``` ### Parse XML only (no solve) ```bash curl -sS -X POST http://localhost:4400/case/parse \ -H "Content-Type: application/json" \ -d "{\"xml\": $(jq -Rs . < data/cases/base-case.xml)}" | jq '.schemaVersion, .model.wellName, (.unsupportedFields | length)' ``` Returns `{ model, rawFields, unsupportedFields, warnings, schemaVersion: 2 }` — same shape as `GET /case/default`, but for an uploaded XML string. Used by the GUI to hydrate its case editor from an import. ## GUI tabbed case editor `gui-ts` renders a tabbed UI backed by a single `CaseState` that round-trips to/from the XML document. On first load it pulls `GET /case/default` and populates every tab; editing any field mutates `CaseState`, which is serialized back into `` on solve or export. Untouched XML fields (fatigue, IPR blocks, pumping-unit catalog keys, etc.) are preserved verbatim in the output. | Tab | Contents | |-----|----------| | Well | Well name, company, units selection, pump depth, tubing anchor / size | | Trajectory | Editable MD / Inc / Az survey table with vertical + deviated presets | | Kinematics | Pumping speed (SPM), speed option, unit ID, upstroke/downstroke percentages | | Rod String | Taper table (diameter, length, modulus, rod type) with base-case preset | | Pump | Plunger diameter, friction, intake pressure, fillage option + percent | | Fluid | Water cut, water SG, oil API, tubing gradient | | Solver | `solverModel` (fdm/fea/both), workflow selector, damping + friction knobs, engineering checks gate, **Run Solver** | | Results | KPI banner, uPlot dynacard (polished + downhole, with FEA overlay when applicable), FDM↔FEA comparison, warnings, unsupported-field list, 3D wellbore/rod/pump view with DLS or side-load contour, trajectory analytics table, pump diagnostics, export tools | | Advanced / XML | File upload + paste box (POST `/case/parse`), export current state as XML, raw-field inspector | ### Built-in engineering checks - **Pump depth vs total rod length:** solver run is blocked if the absolute mismatch exceeds **15 m**. - **Trajectory integrity:** requires at least 2 stations and strictly increasing measured depth. - **DLS warning threshold:** if max dogleg severity exceeds **15 deg/100**, the UI surfaces a warning. These are fixed guardrails (not user configurable) to keep behavior deterministic and consistent across sessions. ### 3D wellbore visualization The Results tab includes a 3D projected wellbore panel: - Tubing trajectory polyline colored by DLS contour (green/yellow/red). - **Bad sections** highlighted in red for DLS >= **15 deg/100**. - Rod string overlay drawn from surface to rod total length with depth color gradient. - Pump marker placed along trajectory at `PumpDepth`. - Interactive controls: drag to rotate, `Shift+drag` to pan, mouse wheel / buttons to zoom, projection toggle (perspective/orthographic), and reset view. - Overlay modes: - `DLS`: uses fixed bad-section threshold `15 deg/100` - `Side-load risk`: colors by normalized side-load profile returned from solver outputs (`options.enableProfiles=true`) ### Trajectory analytics + cross-highlight - Results includes a per-segment trajectory table (`MD start/end`, `ΔMD`, `DLS`, severity). - Clicking a segment row highlights the corresponding 3D trajectory segment. - Clicking a segment in 3D highlights the corresponding table row. - Filter toggle supports "bad sections only". - Keyboard accessibility: segment rows are focusable and selectable with `Enter` / `Space`. ### Pump placement diagnostics Results tab now reports: - nearest survey station to pump depth, - pump-to-station `ΔMD`, - survey-end to pump `ΔMD`, - rod-total to pump `Δ`, - tubing-anchor to pump `Δ`, with quick navigation buttons back to Well / Trajectory / Rod tabs for correction. ### Visualization artifact export Results tab export buttons: - 3D wellbore **SVG** - 3D wellbore **PNG** - run/check summary **JSON** These are generated client-side from the rendered SVG and current run/check state. ### Diagnostic workflow (GUI wired) - Kinematics tab accepts measured surface card points as `position,load` rows. - `Validate Surface Card` calls `POST /solve/validate-card`. - Solver tab `workflow=diagnostic` now sends `surfaceCard` to `POST /solve`. - Solve calls include `options.enableProfiles=true` so side-load overlays can be rendered. ## Solver modes | `solverModel` | Behavior | |----------------|----------| | `fdm` | Finite-difference damped wave + variable rod + trajectory friction | | `fea` | 1D bar FEM + Newmark + Rayleigh damping | | `both` | Runs FDM + FEA; returns `solvers` and extended `comparison` | | `workflow` | Behavior | |-------------|----------| | `predictive` | Harmonic surface motion (unless overridden later) | | `diagnostic` | Surface card BC; FDM in C; FEA uses bisection on pump load to match measured top load | ## Optional CI image ```bash docker build -t rods-ci . docker run --rm rods-ci ``` ## Where to read next 1. [AGENTS.md](AGENTS.md) 2. [Agents/MATH_SPEC.md](Agents/MATH_SPEC.md) 3. [Agents/COMPUTE_PLAN.md](Agents/COMPUTE_PLAN.md) 4. [docs/engineering/units.md](docs/engineering/units.md) 5. [docs/engineering/validation.md](docs/engineering/validation.md)