Owned quantity at FIFO layer cost, per location and stock type. Sums over is_owned, never over on-hand.
28 definitions · every one derived from the ledger, every one stamped with an as-of, a cut id and a reconciliation status. A report that cannot state all three does not render.
Owned quantity at FIFO layer cost, per location and stock type. Sums over is_owned, never over on-hand.
Opening plus categories must equal closing exactly. Frozen against a cut id, so a re-run reproduces the same hash.
Period and account grain. Every delta assigned to timing, costing method, scope or error. Only error opens a drift case.
Units sold over average owned quantity, weekly. Cover sorts no-velocity first, never as infinite.
No movement for 90 days or more, valued at the layer the units arrived on. AED 684K across 84 SKUs.
Loss from counts, write-offs and confirmed-lost serials. Needs the unit ledger before the number means anything.
OTIF, short-delivery rate, QC reject rate and lead-time drift. Joins through item codes, never a bare SKU string.
Every actor action, flag flip and correction. Hash-chained, infinite retention, sealed with a SHA-256 on export.
Closing is computed independently. The categories are a decomposition that must add up to it, and the residual is a first-class line rather than a rounding.
| Category | Qty delta | Value delta | Entries | Source types rolled up |
|---|---|---|---|---|
| Opening | 128,904 | AED 5,742,100 | - | carried from the frozen June close |
| Receipt | +4,182 | +AED 612,400 | 14 | grn_receipt, rni_release |
| Sale | -3,918 | -AED 498,200 | 3,918 | order_sale, pos_sale, excel_sale |
| Refund | +412 | +AED 52,800 | 388 | refund_restock, quarantine_release |
| Exchange | -88 | -AED 11,400 | 72 | exchange_out, exchange_in |
| Transfer out | -1,204 | -AED 158,600 | 42 | transfer_ship |
| Transfer in | +206 | +AED 28,100 | 18 | transfer_receive |
| Damage | -118 | -AED 18,900 | 31 | damage_qc_fail, damage_transit, damage_display |
| Write-off | -88 | -AED 18,200 | 6 | drift_writeoff · finance approved |
| Stock count | -24 | -AED 3,900 | 9 | stock_count_adjust · variances only |
| Recipe transform | -204 | -AED 26,400 | 118 | recipe_transform · component rows only |
| Program out | -42 | -AED 8,100 | 28 | influencer_dispatch, sample_out, trial_out |
| Program in | +18 | +AED 3,400 | 14 | influencer_return, trial_return |
| Consignment | -206 | -AED 34,200 | 11 | consignment_out |
| RTS | -64 | -AED 12,800 | 4 | rts_ship |
| Opening correction | +38 | +AED 4,900 | 2 | opening_correction · signed by the location manager |
| Uncategorised | +41 | +AED 6,800 | 7 | consignment_partner_sale, rfid_found_unexpected |
| Closing · computed independently | 133,127 | AED 5,663,800 | 4,682 | SUM(quantity_delta) up to the cut |
Two landed-cost revisions touched June layers after it was frozen. Neither modified June. Both were booked into July as adjustments naming June as their origin, split into the part that revalues stock still on hand and the part that is a COGS variance on units already sold. June's pack hash is unchanged after 27 days, which is the test that matters.
A run stores its as-of, its cut ids and its result hash, so any figure can be reproduced or shown to have changed.
| Report | Trigger | As of | Cut id | State | Took | Rows | Reconcile | Formats | Delivered to | |
|---|---|---|---|---|---|---|---|---|---|---|
| Valuation by location | schedule | 30-07 04:12 | L-4,186,204 | success | 4.2s | 184 | 4 keys excluded | xlsx pdf | finance@ · #n8n-updates | Open |
| Turnover rate | schedule | 30-07 04:14 | L-4,186,204 | success | 2.8s | 2,159 | clean | csv xlsx | #n8n-updates | Open |
| Dead stock | schedule | 30-07 04:18 | L-4,186,204 | success | 1.4s | 84 | clean | csv | planning@ | Open |
| Reorder plan | schedule | 30-07 05:00 | L-4,186,388 | success | 6.1s | 1,517 | clean | csv | ops@ · procurement@ | Open |
| Close pack · propose | schedule | 29-07 23:00 | not taken | failed | 3m 40s | 4,682 | residual 41 | - | finance@ | Detail |
| NetSuite recon | schedule | 30-07 06:00 | L-4,186,441 | skipped | 0.2s | 0 | no extract | - | finance@ | Detail |
| Shrinkage | schedule | - | - | gate-blocked | - | - | GATE-6 0/7 | - | paused | Gate |
| Fulfilment SLA | schedule | 29-07 07:00 | L-4,182,118 | delivery failed | 1.8s | 1,284 | clean | finance@ 554 bounce x3 | Resend | |
| Reverse-flow drift | schedule | 30-07 04:40 | L-4,186,204 | success | 8.4s | 412 | stale: ops2 reverse | json | #n8n-updates · ops@ | Open |
| Supplier scorecard | schedule | 29-07 10:00 | L-4,182,441 | success | 3.2s | 14 | clean | xlsx csv | procurement@ | Open |
| Stock count variance | schedule | 28-07 07:30 | L-4,180,204 | success | 2.1s | 188 | clean | xlsx | wh-ops@ · retail-ops@ | Open |
| Balance verifications | schedule | 30-07 04:44 | L-4,186,204 | success | 0.9s | 1,204 | clean | csv | wh-ops@ | Open |
| Cost variance | manual | 29-07 15:14 | C-118,402 | success | 1.1s | 42 | clean | xlsx | ahmad@ | Open |
| Customer-order ledger | manual | 29-07 11:04 | L-4,182,904 | success | 0.6s | 18 | clean | csv | namratha@ | Open |
| Forecast accuracy | schedule | 29-07 12:00 | L-4,182,441 | not reproducible | 14.2s | 2,159 | model unpinned | xlsx | planning@ | Open |
| Influencer ROI | schedule | - | - | failed | - | - | source never seen | - | marketing@ | Detail |
| Audit log export | manual | 28-07 22:15 | A-248,904 | success | 18.4s | 248,904 | chain valid | json | legal@ · sealed | Open |
| Format | Carries | Est size |
|---|---|---|
| csv | rows only · as-of in the filename | 184 KB |
| xlsx | rows + a provenance sheet | 312 KB |
| rows + charts + a signed footer | 1.1 MB | |
| json | rows + full run metadata | 248 KB |
| parquet | rows only · archive tier | 64 KB |
Every format stamps as-of, cut id and reconciliation status into the file itself, not only onto the page. A spreadsheet that leaves this system and loses its provenance becomes an argument six weeks later.
14 recipients across 11 reports. Open rate is tracked so a dead subscription can be pruned rather than quietly bouncing forever.
| Recipient | Valuation | Close | Recon | Turnover | Dead stock | Shrink | Supplier | Count var | SLA | Drift | Audit | Open 30d |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ahmad@minimalist.ae admin | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | 99% |
| finance@minimalist.ae | Y | Y | Y | - | Y | Y | - | Y | B | - | - | 92% |
| fatima@minimalist.ae ops_manager | Y | - | - | Y | Y | Y | Y | Y | Y | Y | - | 88% |
| procurement@minimalist.ae | - | - | - | Y | Y | - | Y | - | - | - | - | 81% |
| wh-ops@minimalist.ae | - | - | - | Y | - | Y | - | Y | Y | Y | - | 76% |
| retail-ops@minimalist.ae | - | - | - | Y | Y | Y | - | Y | - | - | - | 71% |
| planning@minimalist.ae | - | - | - | Y | Y | - | Y | - | - | - | - | 68% |
| ops@minimalist.ae | - | - | - | - | - | - | Y | - | Y | Y | - | 62% |
| namratha@minimalist.ae | - | - | - | - | - | - | - | - | Y | - | - | 58% |
| marketing@minimalist.ae | - | - | - | Y | - | - | - | - | - | - | - | 41% |
| legal@minimalist.ae | - | - | - | - | - | - | - | - | - | - | Y | 19% |
| #n8n-updates slack | Y | Y | - | Y | - | - | Y | - | - | Y | - | 84% |
| #n8n-rfid slack | - | - | - | - | - | Y | - | Y | - | Y | - | 72% |
| #n8n-hr slack | - | - | - | - | - | - | - | - | - | - | - | - |
A number nobody can reproduce is an opinion with a timestamp. Five things make one reproducible, and a definition that cannot supply all five is marked not reproducible on its own card rather than being quietly published alongside the rest.
| Ingredient | Why it is load-bearing |
|---|---|
| as-of timestamp | the wall-clock the figure describes, printed on the page and in the file |
| ledger cut id | replaying rows with id ≤ cut gives the same answer no matter what arrived since |
| cost cut id | a later landed-cost revision cannot retroactively change a past valuation |
| parameters | range, location, category, brand, unit, stored with the run |
| null ordering | stated in the column header, because a nullable measure sorts two defensible ways |
| exclusion disclosure | unreconciled keys are dropped, and the count and quantity of what was dropped is printed |
The incumbent's monthly close is stable only by accident: it prices historical rows at whatever the cost table says today, so re-running last March after a cost edit returns a different number with no record that it moved.
78% of counts find nothing wrong. A clean count moves no stock, so it cannot write a ledger row: a zero-quantity row is forbidden, and there is nothing to compensate.
The consequence, if counting only ever wrote ledger rows, would be perverse: a location that counts diligently and keeps passing would generate no evidence at all and would drift to never verified precisely because it keeps being right.
So counting produces two different artefacts, and any report that claims to count verified balances reads the second one. The Balance verifications row in the run table above returned 1,204 rows this morning. The Stock count variance row returned 188. Those are not two views of the same thing.
| Count outcome | Artefact | Read by |
|---|---|---|
| variance found | ledger_entries | close pack · shrinkage · drift |
| clean, no variance | balance_verifications | provenance · verified-balance reports |
| a zero-delta ledger row | cannot exist | nothing · forbidden by constraint |
The catalogue is seeded from the definition registry at migration time, so an empty catalogue means the seed has not run rather than that nobody has built a report. There are no user-authored reports in i3: a definition names its grain, its measures, its gate and its owner, and those are code, reviewed like code. Registry status →
Job reports.daily_pack has not heartbeated since 04:44. The catalogue below is readable and every card is showing its last completed run, correctly stamped with that run's as-of, not with now. Nothing here is extrapolated forward.
The finance reports need role ≥ manager plus the finance feature, and the close pack needs admin to freeze. Your derived persona is store manager · MCC, so you see the 17 operational definitions and the MCC slice of each. Personas are derived at login from role, features and rota locations, never granted, so this is a feature request rather than a role change. Role mapping →
You have Compliance and Reads value and Failing selected together, and no compliance report reads value. Nothing is wrong: the intersection is genuinely empty.
Postgres minor upgrade, 02:00 to 02:40 GST. Definitions are readable and cached artifacts are downloadable. Scheduled runs are queued rather than skipped, so the 04:12 valuation will fire at 02:50 with an as-of that says 02:50 and not 04:12.
Shrinkage, Serial valuation, Repair spend, Stock turn by serial and Gate compliance all need GATE-6 to have held for 7 days. It has held 0. Each renders its shadow measurement with a not authoritative stamp instead of hiding, so you can watch them converge, but none of them may be attached to a close. Gate tracker →