NERP-AI
Business Systems

Welcome. Sign in to continue.

⚠ Development environment. Access is by invitation only, and there's a button that attacks the database on purpose. Not validated — not for real batch records.
← All modules
Raw materialg / servingg / unitExcel Total kgExcel% of blend

This is TRG's real formula, scaled by the engine. The “Excel” columns are what the spreadsheet says. Change the unit count and watch the batch scale — fill weight and capsule count don't move, because they're properties of the recipe, not the batch.

Look at the % sum. Ours is exactly 1; the spreadsheet's is 0.999999999958937 — Excel just displays “1”. That's why we store grams and derive the percentage, never the reverse.

Capsules per serving = fill weight ÷ capsule capacity, rounded up. 40.0001022 ÷ 0.74 = 54.05 → 55. Rounding down would ship a serving under its label claim.

← All modules
Activity
Tenants
Access
Platform admins
Invitations
Usage
Qualification
Logs
Database
Health
Services
Costs
Everything that has happened, newest first.
WhenWhoActionWhereDetail

Platform events (tenant created, admin granted) and every tenant's audit trail — memberships, assumes, and manufacturing (lots received/released, weigh-outs, runs) — merged and sorted. Read-only; the chains are append-only and hash-verified.

TenantNameKindTimezoneMembersAudit entries

Add a tenant — this is "add a customer"

Assume a tenant — written into their audit trail

Assuming a tenant shows exactly what they see, for support — and writes a READ entry into their audit trail with your identity and reason. It cannot sign anything.

EmailSubjectTenantRoleCan sign?Granted by

Grant access directly — for someone who already signed in

This is the screen that crosses tenant boundaries — the one place a bug could show one manufacturer another's formulations. So it's read-mostly, it shows counts rather than contents, and every grant is written into that tenant's own audit chain.

That last part is the point. When a customer asks “can you see our formulations?”, “we wouldn't do that” is worthless. “Yes — and it's in your audit trail, which you can read, and which we cannot alter” is a real answer, and it's only true because of this.

A platform admin is not a tenant admin. Todd is admin on TRG — he runs it, and he must never see Acme. This is a separate identity check, not a role flag, so the two can't drift into each other.

A grant requires a reason. And roles are per tenant — the same person can be admin on TRG and viewer on Acme, or absent from Acme entirely.

EmailSubjectGranted byReason

A platform admin sees every tenant. Revoking it demotes rather than deletes — it removes that view and leaves their own tenant memberships exactly as they were. Someone who was admin on TRG goes back to being admin on TRG, and nothing more.

Root cannot be removed. Not by another admin, not by the app, not by root. A Postgres trigger refuses the delete, so it holds even against the database owner — the only account that can repair a broken admin screen must not be deletable by a broken admin screen. Only root can add or remove other platform admins.

Invite someone — to a tenant, with a role

EmailTenantRoleInvited byReasonStatus

Access arrives exactly one way: an invitation, to a tenant, with a role. An uninvited account signs in fine and sees nothing until it is invited.

What each tenant stores and uses — the raw material for a cost report.
TenantSeatsMembersItemsLots RunsConsumptionsLocationsAudit entriesAudit size

Row counts and audit-trail bytes per tenant, straight from the database — no sampling, no estimate. Seats is the member cap on their plan; blank is unlimited. This is deliberately labelled by tenant so a platform bill can be drawn from it. Storage grows with the audit trail (append-only, never pruned) far faster than with row counts — that's the number a Part-11 record keeps.

IQ/OQ evidence — what the system proves about itself, for the validator.

Loading…

Live evidence, gathered on request: the installed version and schema (IQ), every tenant's audit chain re-verified with hashes recomputed not trusted, the Part 11 controls with their evidence, and the automated test suite result (OQ, from the last npm run validate). PQ — the customer running real workflows in their own environment — is theirs to perform; the software attests to IQ/OQ, not PQ.

The last 24 hours of application logs from CloudWatch — read-only, in-app, so you never open the AWS console. Ephemeral operational telemetry (redacted; 14-day retention), not the Part 11 audit trail, which lives in Postgres and is immutable.

The numbers behind the green dot — how much headroom, how far behind, how stale.

Neon retains only 6 hours of history, so for anything older than this morning the nightly S3 dump is the only copy of the database. Backup age is the number to watch here. A long-running transaction matters more than it looks: every audit append takes a per-tenant advisory lock, so one stuck transaction stalls every mutation in the system.

Live, from this deployment’s /health — the dependencies the app can actually reach.

Probed server-side, so this is what the RUNNING app can reach, not what your browser can. Postgres is the Neon database this build is wired to; migrations checks the schema is the one this code was built against; Clerk is the auth provider (a soft check — if it’s out nobody can sign in, but the app still serves, so it shows amber, not red). The vendor consoles on Services can’t be probed from here, which is why they are links and not dots.

What the platform costs, and what each tenant uses. Two measurements — never multiplied.

Vendor spend — the whole account, by vendor. Not attributable to a tenant.

AWS by service — this account has no cost-allocation tags, so service is as fine as the split goes

ServiceThis monthLast month

Per-tenant usage — measured quantities. Deliberately not dollars.

TenantStorageLotsRunsContainers Audit entriesShipmentsOrders

There is no per-tenant cost column here, on purpose. Nothing above can produce one. The AWS account carries no cost-allocation tags, Neon is a single project behind a single pooler that every tenant's queries land in, and the Lambda and buckets are shared by construction — so no vendor knows which tenant spent what. A dollar figure per tenant could only come from choosing an allocation rule (by rows? by bytes? by requests? evenly?), and each choice yields a different answer from the same measurements. Printed in a column beside figures that were genuinely measured, it would be indistinguishable from one. When per-tenant billing is actually needed, the usage columns are the defensible basis and the allocation rule becomes a written business decision — not something this page invented.

← All modules
IdSeriesNameUoMPrice/kgReorderSafetyLead

The 1000–8000 series is TRG's, not ours. Raw materials, components, labels, formulations, capsules, finished goods, jobs, shipments — the numbering Todd already uses.

Click a manufactured item (4000/5000/6000) to explode its bill of materials. ProBasic Choc (6023) is the real one: a bottle contains 770 capsules, each capsule is a blend plus an empty shell, and the blend is 52 raw materials.

A capsule is its own tier, independently stocked and lotted — not a step inside "make the finished good". Todd called that crucial, and it is why the tree has four levels rather than two.

Read only, deliberately. Editing an item or a formula needs the lot decision (NERPAI-17) and a signing story. This is here to be argued with, not typed into.

← All modules

Receive a raw material

A lot is identified by supplier + vendor lot + expiration (NERPAI-54) and lands in quarantine. The same vendor batch received twice is one lot — the second delivery adds to its balance and is not re-sampled. The vendor list is the approved-supplier list for that material; receiving from an unlisted supplier is allowed but recorded as a deviation and held for QC. Choosing an expiry fills the retest date 10 days before it.

Quarantine — QC's queue

LotItemSupplier lotVendorQtyReceivedStatus

Stopped material — held, rejected, reprocessing, destroyed

LotItemSupplier lotQtyStatus

SOP-025 asks for a weekly reconciliation of rejected and withheld inventory. This is that list — nothing stopped is invisible.

Weekly reconciliation — SOP-025 and SOP-035, and the list above is what it counts

What is counted is not the counter's to choose — the lines are taken from the stopped-material queue when the count opens, so the lot quietly left off a hand-written list cannot be. A count cannot be closed while a lot on it has not been looked at, and a discrepancy cannot be recorded without the investigation both SOPs ask for. Being late is shown and blocks nothing: no SOP states a consequence for a late reconciliation, and inventing one would stop the floor on a rule TRG never wrote.

Tags outstanding — material the system stopped but nobody has confirmed segregating

"A computer block alone is not acceptable" — SOP-025 and SOP-035, twice. Setting a status did nothing in the warehouse. Until the tag is on the drum and the drum is in the designated area, the lot cannot move on.

Container labels outstanding — drums the system has labelled on paper but nobody has labelled in the warehouse

Receiving step 3 labels EACH container — RM number, product name, lot number, date received, expiration date. One tick on the checklist covers five drums; these are the five acts it claims. A lot cannot be released while any of its labels is unapplied.

Refrigerated storage — daily logs, and what an excursion does by itself

The acceptable range is printed on every reading, stamped on by the database along with the verdict and the probe's calibration date — so widening a room's limits next year cannot turn last year's excursions into good readings. An excursion places the lots stored there ON HOLD through the same state machine every other hold goes through, raises the physical hold tag, and opens a quality-unit task; the lot cannot leave hold until the quality unit has ruled (SOP-024). A day with no reading is shown as a gap and blocks nothing — no software can make yesterday have a reading.

Retained samples — receiving step 8, the Retained Sample Log

Step 8 is "retain samples and record them in the Retained Sample Log" — the log is half the step, so a raw-material lot cannot be released with an empty one. One entry per jar. How many samples a lot needs is not computed here: the sampling programme's "square root of n+1" is ambiguous — √(n+1) and √n + 1 give different counts — and is still an open question for QA.

A lot cannot be released without a CoA and a named approver — and that is a database trigger, not just this screen. Approving records who released it and when. Release state lives in the lot's state, never inside its identifier — the lot is still called by the vendor's lot code, so a string can never forge "released" (NERPAI-54).

Receiving is an operator action; releasing is QC's. A viewer sees the queue and can do neither — the server enforces the role, this screen only reflects it.

Rejected is terminal. A rejected lot can never be approved, and the material does not enter production.

← All modules
Raw materialRequiredOn handAllocatedAvailableShort byBinStatusReleased lots

This is the build sheet, not the run. It scales the blend to your batch size — the same engine, the same maths proven to the gram — and checks it against the lots QC has released. Green means you can weigh it; red means you cannot, and says which ingredient is short.

Only released lots count. A quarantined or rejected lot is not weighable, so it does not close a shortfall — the same SOP-042 gate as intake, one tier up.

What this does not do yet, on purpose: it commits nothing — no blend lot is minted, no quantity deducted, no BPR generated. That step needs two decisions (does a blend lot carry the job number or its own lot, and the consumption ledger), which are Todd's to make. Until then, “on hand” is the approved received quantity.

← All modules

Open a run

Runs

Job / blend lotProductUnitsState

A run is a job, and the job number IS the blend lot. Weigh released lots into it by the exact kilograms taken — a bag is opened, drawn from, resealed and reused on the next job, so each lot carries a running balance. The database refuses weighing a quarantined lot, or more than a lot has left.

Both directions. A run shows the lots that went into its blend; a lot shows the jobs that drew it down. That is the recall spine — raw material to blend, and blend back to raw material.

Next: packaging the blend into capsules and finished goods (the next consumption step), and the BPR document + wiring the run into the audit trail (NERPAI-19).

← All modules

Open a capsule run

Capsule runs

Job / capsule lotCapsuleTargetState

Stage 2: the intermediate. A capsule run draws a completed blend (4000) through the encapsulator and yields a counted 5000 capsule lot — the job number IS the capsule lot. The blend is drawn by exact kilograms and carries a running balance, the same model as a raw-material lot one tier down. The database refuses drawing an uncompleted blend, or more than a blend produced.

Recall crosses the tier both ways: a capsule lot shows the blend it came from (and thus its raw materials); a blend shows the capsule runs that drew it. Open the Traceability module on any job to walk the whole chain.

Empty capsule shells deduct by exact count (Todd, NERPAI-41): a shell is a received count component, drawn off its own lot's ledger as a second IMR line beside the blend — no weight conversion. The on-floor draw control lands next; the deduction and its lot balance are enforced now (server + database).

Not yet: the packaging tier count lines (5000 → 6000, Stage 3) and the IMR/BPR document (NERPAI-19).

← All modules

Open a finished-good run

Finished-good runs

Job / FG lotFinished goodTargetState

Stage 3: the finished good. A finished-good run bottles a completed capsule lot (5000) — drawn by the each against the capsule lot's running count — and yields a counted 6000 finished-good lot, the job number being the FG lot. The database refuses bottling an uncompleted capsule lot, or more capsules than the lot produced.

The chain is whole now: raw material → blend → capsule → finished good, every tier its own job/lot and its own consumption ledger, traceable in both directions. Open Traceability on any job to walk it end to end.

Packaging deducts by exact count (Todd, NERPAI-41): bottles, lids, labels and neck bands are received count components, drawn off their own lots as BPR lines beside the capsule draw — no weight conversion. Not yet: the MMR/IMR/BPR documents (NERPAI-19).

← All modules

Suppliers

IDNameCodeState

Approved for a material

SupplierMOQLead timeApproved by

A vendor must be qualified for the material before it is received. The intake screen's vendor list is exactly this approved list, per item. Receiving from an unlisted supplier is not blocked — it is recorded as a deviation and the lot is held for QC (NERPAI-45), because refusing the receipt would leave material on the dock unrecorded.

Adding a supplier is an operator action; qualifying one for a material is QA's. A supplier is retired, never deleted — its lots are history.

← All modules

Locations

Add a location

The physical inventory, rebuilt from Binly onto the lot model. A tree of warehouse → area → shelf → bin; a received lot is placed in a bin, and every move is written to the audit trail — so "where is this vendor lot, and who put it there" has an answer, and a recall knows which shelf to walk to.

Scanning works today. A hardware barcode/QR scanner types a code into the box above and the app resolves it — a bin label to its contents, a lot to its shelf, a supplier barcode (UPC/EAN) to the item. Give a bin a code and an item a barcode to make them scannable.

Camera scanning uses the browser's native barcode reader (QR and UPC/EAN), so there's no app to install — tap 📷 on a phone. Where the browser lacks it, the code box and a hardware scanner still work.

← All modules

Members

EmailRoleCan sign?

Invite a member

Pending invitations — sent, not yet accepted

EmailRoleInvited by

Your team, your workspace. Invites and roles here affect only your tenant — a platform admin sees everyone; you see your own. Every change is written to your audit trail. Operator / QA / Admin can sign and run production; Viewer is read-only. You can't remove the last admin, and a member count above your seat limit is refused.

← All modules

Recent receipts

LotItemVendorReceivedState

Recent runs

JobProductStateProduced

A live snapshot over what's recorded — stock, the QC queue, production, and lots needing attention. Click any receipt or run to open it in Traceability. Numbers come straight from the append-only ledger.

← All modules
ItemSeriesReleasedQuarantineLotsNearest expiry

Released is what a batch can draw from — received minus everything the ledger has consumed, summed across a lot's approved receipts. Raw materials are held in kg; capsule shells and packaging are held by exact count (ea), never weighed (NERPAI-41). Quarantine is received but not yet passed by QC, so it is on hand but not consumable. A rejected lot is not stock at all.

To trace any single lot — where it came from, where it went, its mass balance — open it in Traceability. QR labels and camera scanning at intake are still to come (NERPAI-3).

← All modules

Recall, both directions. From a raw-material lot: every job that weighed from it. From a job (blend lot): every raw-material lot that went in, with the vendor's lot and CoA. Click any lot or job in the result to pivot to it — that is the recall walk.

The genealogy sits next to the record. Each result carries the audit history of the thing — received, released, weighed, completed, and by whom — read from the append-only chain.

The key flips at the make step: a received lot is keyed on [supplier + vendor lot + expiration], a made batch on its job number. The chain today spans raw material ↔ blend, and extends through capsule and finished good as packaging lands.

← All modules

Take an order

Master contracts

ContractCustomerProductTotal ReleasedRemainingState

Orders

JobCustomerProductUnits Unit priceTotalContract ShippedOutstandingState

Price tiers — on the formula

Volume breaks for the selected product's formula, in whole cents. The applicable tier is the greatest break at or below the ordered quantity. Editing this changes future orders only — an order already taken keeps the price it was agreed at.

Shipments — the 8000 tier

ShipmentDateCustomerCarrierTrackingLots that left

An order IS the production job. Taking one assigns the 7000 job number that carries through to the MMR, the IMR and the BPR — the same number the customer is invoiced against. There is no separate order number to reconcile.

The price is frozen when the order is taken. It is resolved from the formula's tier list and then copied onto the order. Editing the price list afterwards changes future orders only — an order already agreed is never silently repriced.

A shipment records the exact finished-good lot AND the order it settles. Those are different job numbers: the order is the job that was planned and run, the lot is the bottling run that filled the box. Both are needed — one answers “who has this lot” in a recall, the other answers “what does this delivery settle” on an invoice. Partial deliveries are the ordinary case.

D2C fulfillment billing is not here. That is Phase 5b — ShipStation is the system of record for D2C, and NERP-AI syncs it to decrement finished goods and raise a monthly per-client invoice.

← All modules

Process parameters — the specification

#StageParameterType TargetLCLUCL Units2-personIf out of spec

An MMR is the master specification a batch is built to. Parameters are the control points SOP-031 asks for — a target with its limits, the units, and what to do when the spec is not met. These are Todd's “set points” (NERPAI-35), and the Blending Page displays them beside what was actually done.

Only a draft can be edited. The database refuses a parameter written against an approved revision — a specification that can change after approval is not a specification. To change an approved MMR, raise a new revision: it copies the current parameters into a fresh draft, and the old revision is kept because batches were built to it.

Approving is an electronic signature, not a button. It re-verifies your password at the moment of signing (21 CFR 11.200) and writes a hash-chained record of who approved what, when. Approving supersedes the previously approved revision, so exactly one is issuable at a time.

← All modules

Monthly invoice

ParcelDateItemsShipping FeeExtras$0 reasonLine total

Import a parcel from the feed

Pricing & storage

D2C is not order entry. The fulfillment provider is the system of record for what shipped; this is the copy NERP-AI bills from and decrements finished goods against. Re-importing the same export is safe — a parcel already held is recognised rather than billed twice.

The fee is frozen when the parcel is imported, from the schedule then in force. Editing the schedule changes future parcels only. Shipping is passed through at cost, and extras are ad-hoc charges (or, negative, credits) against one parcel.

A $0 charge must name its reason. A free re-ship is ordinary — a warranty replacement, a lost parcel, our own error — but each has a different owner, so the database refuses a waived fee with no reason recorded. Shipping is still passed through: the carrier still charged us.

The invoice is derived, never stored — the sum per parcel of (shipping + fee + extras), plus pallet storage once any grace period has elapsed.

← All modules

Quote a job

Leave profit blank to use the policy default. Profit is dollars per unit — it is the volume lever, and it is recorded on the quote rather than retyped from memory.

Rates in force

Estimates

EstimateCustomerProductUnits CostPriceMargin StateOrder

Profit dollars in, margin out. You type the profit per unit; the margin percentage is computed and read-only. The Estimate Master stored a “Margin” that was really markup — it read 77% where the margin was 44% — because a derived number had been stored as a fact and was free to drift. There is nothing here to drift.

Commission is 10% of the sale, not 10% on top of cost. The old formula multiplied by 1.10, which collects 9.09% of the sale — about $0.146/unit short.

Freight and overhead are separate. The old ×1.15 was inbound freight all along; overhead has never been priced, so it starts at zero until it is set deliberately.

An estimate keeps the rates it was priced with. Change the policy and old quotes still say what they said — which is how a quote from March can explain itself in September.

← All modules
SeqActionEntityActorReason VerPrev hashHash

Try the red button. It runs UPDATE audit_entries… as the database owner — the most privileged role there is. Postgres refuses: a trigger binds every role, so history cannot be rewritten even with full database access. The hash chain proves a tamper; this prevents one.

“UPDATE with no reason” is refused — §11.10(e) says a changed record must say why, enforced in code and as a database constraint.

Switch tenants (top right): same page, same code, different data. Postgres row-level security enforces it — a query that forgot its WHERE clause still cannot leak.