Add CONTRIBUTING/SECURITY/CHANGELOG, Gitea issue and PR templates, and README guidance for Gitea metadata and releases. Made-with: Cursor
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 for agent rules and Agents/MATH_SPEC.md for equations and paper citations.
Contributor workflow: CONTRIBUTING.md · Security: SECURITY.md · History: CHANGELOG.md
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.
Forge metadata (Gitea)
Repository title, description, topics, default branch protections, and Releases are configured in the Gitea UI (they are not stored as files in git).
Suggested values you can paste into the repo settings page for connermajic/rods:
- Name / title:
Rods(orRods — rod-string solver stack) - Description:
Deterministic C core (FDM+FEA) + Node XML/SI API + TypeScript GUI for sucker-rod dynamometer workflows. - Topics / keywords:
sucker-rod,dynamometer,rod-pumping,finite-difference,finite-element,wellbore-trajectory,oil-and-gas,typescript,nodejs,c - Website: leave blank until you publish a demo URL
Releases
Releases are optional until you want a downloadable snapshot. When you are ready:
- Pick a version (for example
v0.1.0). - Tag the commit on
main:
git tag -a v0.1.0 -m "v0.1.0 — initial published snapshot"
git push origin v0.1.0
- In Gitea: Releases → New Release, select the tag, paste notes from
CHANGELOG.md, and attach artifacts if you build any.
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.
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
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
make run # or: docker compose up --build
make smoke # requires API on 4400
make down
Validation
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)
curl -sS "http://localhost:4400/solve/default?solverModel=both" | jq '.schemaVersion, .runMetadata, .comparison | keys'
Predictive (POST XML)
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)
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:
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
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)
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 <INPRoot><Case>…</Case></INPRoot> 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+dragto pan, mouse wheel / buttons to zoom, projection toggle (perspective/orthographic), and reset view. - Overlay modes:
DLS: uses fixed bad-section threshold15 deg/100Side-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,loadrows. Validate Surface CardcallsPOST /solve/validate-card.- Solver tab
workflow=diagnosticnow sendssurfaceCardtoPOST /solve. - Solve calls include
options.enableProfiles=trueso 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
docker build -t rods-ci .
docker run --rm rods-ci