One weighment. Every register updates itself.
A truck crosses the weighbridge once. AquaOps posts that number to inventory, opens the QC queue, and feeds the production batch — so the books cannot drift apart, because there is only one book.
A plant keeps six books. None of them agree by Friday.
Each one is written by a different person, at a different hour, from a slip that has already been copied twice. Nothing is wrong on purpose — the numbers simply have no reason to match, and no one finds out until stock is counted.
Record it once, and the rest is bookkeeping the system does.
This is the whole idea. A weighment is not a note in a register — it is an event, and every downstream book is derived from it.
Gate Entry
The officer records vehicle, party, material and two weights. Net is arithmetic, not a judgement call. Completing the entry is the only manual act in the chain.
Inventory
The entry posts itself as a receipt against the mapped material, converting units on the way in — diesel booked in MT lands in the stock card as litres. A ledger row records the balance the receipt left behind.
Production
Starting a batch issues raw fish. Logging an hourly interval issues packaging — bag counts derive from the output weight, nobody types them. Readings outside the normal band raise an alert as they are saved.
QC report
Grading is computed from the intervals and the boiler readings that belong to the batch's day — moisture and oil averaged, protein derived, the grade assigned against the spec. The report carries its own corrective actions.

Inventory, mid-shift. The strip along the top is not a summary someone typed — it is the last three gate entries, posting themselves as receipts.
Watch a shift move through it.
The truck at the gate, the receipt posting itself, the ledger row it leaves behind, the batch it feeds, and the report at the end of the day.
Every figure on screen is from the demonstration operating day the system seeds on first boot.
Every gram carries its reason.
A stock figure on its own is a claim. A stock figure with the movement that produced it, the batch that caused it and the person who logged it is a record — and that is what a buyer's auditor asks for.
Stock movement ledger — the source column distinguishes a gate receipt from a production issue from a manual adjustment.
Balance after every move
The running balance is stored on the row, not recomputed later. Reading history never depends on replaying it.
Linked to its cause
Bags point at the batch. Coal points at the boiler session. A receipt points at the gate entry. Nothing floats.
The rules hold even when two people are wrong at once.
Validation in a form is a suggestion. These are constraints the database itself refuses to break, so a second operator on a second screen cannot undo the first one's work.
Stock never goes negative
A deduction larger than the balance clamps at zero — and the ledger still records what was asked for, so the discrepancy is visible instead of silently absorbed.
One running batch per line
Starting a second batch on a line that is already running is rejected, not warned about.
Concurrent weighments cannot collide
Every movement locks the material row for the length of its transaction, so two simultaneous postings queue instead of overwriting one another.
Thresholds raise their own alerts
Crossing a minimum downward creates the alert as part of the movement — there is no sweep job to miss it, and no one has to notice.
Eight modules over one database.
Not eight systems that sync. One schema, with eight ways into it — which is why the gate officer's entry is already in the supervisor's stock card.
Dashboard
Today against yesterday, every line's status, boiler uptime, low stock, a seven-day output chart and the day's activity.
Gate Entry
Weighbridge capture, material routing, and a printable ticket for the driver.
Fish Intake
Species, grade and allocation to batches, with photos and documents attached to the landing.
Production
Batches per line, hourly intervals, and the packaging and raw-material issues they cause.
Boiler & Utilities
Steam and pressure readings, fuel burn, chemical dosing and blowdown logs.
Inventory
Stock cards and the movement ledger, with manual adjustments kept distinguishable.
QC / QA
Computed grading against spec, with corrective actions attached to the report.
Reports
Batch and shift reporting, printable and exportable to CSV, with the numbers traced back to their movements.

Boiler & Utilities — hourly readings; fuel burn issues itself from stock.

QC / QA — grading computed from the batch's own intervals and readings.
One command on the box in the office.
Three containers — Postgres, the API, and nginx serving the built front end. Migrations apply themselves at boot behind an advisory lock, so several API replicas can start at the same time without racing each other.
$ docker compose up -d ✔ db postgres 16 ✔ api fastify ✔ web nginx # the plant grew — add API capacity, same schema $ docker compose up -d --scale api=4
Stateless API
Sessions live in Postgres and are revocable, so any replica can serve any request.
Replica-safe boot
One advisory lock decides which replica runs the migrations; the others wait and carry on.
Round-robin front door
nginx re-resolves the API service, so replicas added later are used without a restart.
| Layer | What it runs on |
|---|---|
| Front end | React 18 · Vite 6 · Tailwind 4 · React Router 7 · Recharts |
| API | Node · Fastify 5 · TypeScript · Zod · JWT with revocable sessions |
| Data | PostgreSQL 16 · Drizzle ORM · plain-SQL migrations |
| Delivery | Multi-stage Docker builds · nginx · docker compose |
| Verification | 7 integration suites · 40 tests against a real Postgres · browser smoke test in CI |
| Operations | Prometheus metrics · OpenAPI 3 + Swagger UI · structured audit trail · CSV on every list and report |
Configured to your plant. Built to pass the IT questionnaire.
Nothing customer-specific is compiled in. Lines, gate materials, stock routing, every alert threshold and every role are rows an administrator edits. Around them sits the plumbing a buyer's IT team will ask about.
Your lines, your materials
One line or twelve. Any gate material, routed into any stock item with its unit conversion. Limits that grade QC and raise live alerts.
Roles that bite
Admin, Supervisor, Operator plus per-user modules, enforced in the API. Suspend a user and their session ends on the next request.
Audit trail
Who did what, from where, to which record, with the diff, written in the same transaction as the change. Exportable.
Passwords done properly
bcrypt, complexity policy, expiry, forced first-login change, e-mail reset links, idle timeouts, rate-limited sign-in.
Open data
CSV from every list and report, a full JSON export, and a documented REST API with Swagger UI for ERP and Tally integration.
Your instance
One isolated stack per plant, hosted in your region or on a server in the office. Health probes, metrics and backups included.
It arrives with a day already running.
On an empty database AquaOps writes a demonstration operating day — gate entries, a batch mid-run, boiler readings, the stock they consumed — through the real service layer rather than raw inserts. If the seed completes, the business logic worked; the first boot is also the first test.
9
materials tracked, from raw fish to filter cloth
3
roles — admin, supervisor, operator — gated per module
40
integration tests run against a real database, not mocks
Seeding is skipped once the database has data, and can be switched off entirely for a production install.