Drift workbench

16 live cases · 14 need a decision today, 2 blocked on a sibling · every close posts a compensating entry and none of them edits a row · Wed 29 July 2026 14:41 GST

2 P1 cases block GATE-1, day 6 of 7. DRF-0142 is a gate read at G1 with no ops2 dispatch posting, and DRF-0141 is a refund with zero line items, so no SKU is knowable yet. Neither can close on a narrative: the gate says the unit left the building and the ledger still says it is here. Approval queue →
Value at risk · live cases
AED 17.1K
42 units short · 16 units over · net 26 short · landed cost basis, frozen at detection
Shortage
AED 11.0K
the ledger claims more than the world does
Overage
AED 6.1K
never auto-accepted · R-13.8
Already corrected · 30d
AED 9.4K
41 posted entries, every one signed
Accepted · 30d
AED 3.2K
real loss, posted to WRITTEN_OFF, not left disagreeing
View Recurrence is the view v2 does not have. It is the one that would have caught the Mirdif mapping bug in a week instead of after 4,500 units.
Sort
3 selected · There is no bulk resolve. Each case has its own evidence, its own money and its own explanation; one click over many facts is exactly how INV1 wrote 4,456 unsigned reconciliation rows.
Case Detected State Sev Detector Explanation SKU Loc i3 Other Δ AED Evid Age / SLA
DRF-0142 29-07 14:36 Investigating P1 gate_read_without_posting unexplained MSR31-07 HQ_319 118 117 −1 892 4 L 22m / 4h
DRF-0141 29-07 13:22 Open P1 ghost_exchange_no_lines unexplained order #MIN10482 HQ_319 1 0 −1 749 2 1h19 / 4h
DRF-0139 29-07 11:04 Proposed P2 count_variance sku_confusion LBA01 MCC 84 72 −12 1,840 5 3h / 24h
DRF-0138 29-07 10:47 Proposed P2 count_variance sku_confusion LBA02 MCC 82 93 +11 1,685 5 3h / 24h
DRF-0136 29-07 09:12 Awaiting approval P2 count_variance breakage_unticketed MIN-VAS-022 CCZ 28 26 −2 360 3 5h / 24h
DRF-0134 29-07 08:31 Investigating P2 shopify_level_divergence admin_manual_adjust MIN-CNL-019 HQ_319 560 550 −10 1,800 4 S 6h / 24h
DRF-0131 29-07 07:48 Open P2 transfer_pair_unbalanced unrecorded_transfer LBA01 HQ_319 46 44 −2 306 3 6h / 24h
DRF-0129 29-07 06:20 Blocked P2 replacement_without_return unexplained LBA01 HQ_319 2 0 −2 2,050 3 8h / paused
DRF-0127 28-07 22:10 Investigating P3 grn_receipt_variance short_receipt PP02 HQ_319 48 46 −2 288 4 16h / 72h
DRF-0124 28-07 18:44 Open P3 classifier_unmatched unexplained MIN-2159 HQ_319 34 32 −2 220 2 20h / 72h
DRF-0122 28-07 16:02 Open P3 rfid_cycle_count_variance shrinkage_suspected JAE17 HQ_319 54 52 −2 470 4 L 23h / 72h
DRF-0119 28-07 12:31 Investigating P3 shopify_level_divergence pos_refund_no_restock MIN-MRR-003 AJM 18 19 +1 205 3 26h / 72h
DRF-0117 28-07 09:15 Open P3 unexpected_unit_read impossible_transition MST-CRM-42-BLK YAS 32 33 +1 1,520 3 L 29h / 72h
DRF-0113 27-07 19:58 Blocked P3 csd_reservation_mismatch csd_single_sku_defect FYN01 HQ_319 6 4 −2 840 2 43h / paused
DRF-0108 27-07 11:22 Open P3 grn_over_receipt supplier_over_ship LLW-BAG-047 HQ_319 86 89 +3 2,670 3 51h / 72h
DRF-0104 26-07 17:40 Investigating P3 stale_in_transit unrecorded_transfer MIN-TBL-014 TRANSIT 12 8 −4 1,180 4 69h / 72h
DRF-0098 26-07 09:10 Resolved P2 balance_chain_mismatch poller_double_post FYB01 HQ_319 88 89 +1 375 5 closed 14h
DRF-0091 25-07 14:55 Accepted P3 rfid_cycle_count_variance confirmed_shrinkage MIN-CNL-019 MCC 208 207 −1 180 4 closed 22h
16 live · 2 terminal rows shown for context · 27 resolved and 6 accepted in the last 7 days Both terminal rows name the ledger entry that closed them. A case that cannot name one cannot reach a terminal state.

Drift trend · 12 ISO weeks

Opened against closed per week, with the open backlog as a line. A widening gap means detection is outrunning resolution.

backlog 16
W20 · opened 21 W20 · closed 18 W21 · opened 24 W21 · closed 21 W22 · opened 19 W22 · closed 23 W23 · opened 28 W23 · closed 20 W24 · opened 17 W24 · closed 25 W25 · opened 20 W25 · closed 21 W26 · opened 31 · the RFID gate detector left shadow mode W26 · closed 16 W27 · opened 27 W27 · closed 29 W28 · opened 22 W28 · closed 27 W29 · opened 18 W29 · closed 23 W30 · opened 16 W30 · closed 21 W31 to date · opened 14 W31 to date · closed 17 backlog 22backlog 25backlog 21backlog 29backlog 21backlog 20backlog 35 · detector went livebacklog 33backlog 28backlog 23backlog 18backlog 16 today
W20W21W22W23W24W25W26W27W28W29W30W31
OpenedClosedOpen backlog at week endThe W26 spike is the gate detector leaving shadow mode, not a change in the warehouse.

Root-cause Pareto · 30 days

Over the closed explanation_catalog, never free text. Four causes carry 80% of the money.

Open incidents ↗
unrecorded_transfer · AED 6,140 · 21 cases sku_confusion · AED 4,310 · 9 cases admin_manual_adjust · AED 3,120 · 12 cases short_receipt · AED 2,380 · 7 cases confirmed_shrinkage · AED 1,640 · 14 cases · the only real loss on this chart poller_double_post · AED 1,190 · 6 cases supplier_over_ship · AED 820 · 3 cases everything else · AED 540 · 5 cases 31% cumulative53%69%81% · four causes carry 80%89%95%98%100% 80% cumulative line
unrecorded
transfer
sku
confusion
admin
adjust
short
receipt
confirmed
shrinkage
poller
double
supplier
over-ship
other
AED at riskCumulative percent, 80% dashedOnly confirmed_shrinkage is real loss. The other seven are bugs wearing a loss costume.

Drift rate by source and by location

Cases per 1,000 events for sources, cases per 1,000 units handled for locations. Raw counts flatter whoever is quiet.

30-day window
csdcountrfidgrnops2iwmsshopify csd · 8.4 cases per 1,000 events · 11 cases on 1,310 events · the single-SKU reservation defect count · 6.1 per 1,000 counted lines · 12 cases on 1,960 lines rfid · 4.4 per 1,000 · 19 cases on 4,280 reads grn · 3.7 per 1,000 · 7 cases on 1,890 lines ops2 · 2.5 per 1,000 · 9 cases on 3,610 events iwms · 1.8 per 1,000 · 5 cases on 2,780 movements shopify · 0.9 per 1,000 · 21 cases on 23,400 webhooks
Shopify raises the most cases in absolute terms (21) and is the cleanest per event. CSD raises 11 and is the worst, because its reservation webhook sends one SKU at quantity 1 with HQ_319 hardcoded regardless of the case.
LocationUnits handled 30dCasesPer 1,000uAED at riskSignal
MCC18,420170.926,240Concentrated
TRANSIT4,21040.952,880Watch
AJM6,88060.871,420Watch
YAS9,34040.432,190Clean
BAS2,41010.41180Clean
CCZ7,66030.39640Clean
HQ_31996,110340.358,910Baseline
HQ_32511,90040.34910Clean
SUPPLIER_HOLD38000.000Low volume
DISCONTINUED12000.000Low volume
3PL_ARAMEX00n/aunknownNo detector
MCC runs at 2.6 times the HQ_319 rate on a fifth of the volume. That is the signal a raw count hides.MCC detail ↗
Detector coverage shopify · 6 live rfid · 5 live grn · 3 live count · 2 live i3 invariants · 4 live iwms · 3 shadow ops2 reverse · 4 dark csd repair · 2 dark Dark means the topic does not exist upstream: ops2's registered event list is six inbound-warehouse strings and none of the reverse-logistics topics is emittable today. Zero findings from a dark source is not health, so it is never rendered as a zero.
A family is a style_group. Siblings counted on the same day at the same location are checked together.

Alyazia Handbag family (LBA)

Likely miscount

3 SKUs · counted 29-07 09:40 at MCC by Ramesh K. · cases DRF-0139 and DRF-0138 · same shape, same size, different colour

Net family delta is −1 unit across 3 SKUs. Offsetting counts of −12 and +11 read as SKU confusion, not shrinkage.
Family ledger
248
across 3 SKUs at MCC
Family counted
247
blind count, single pass
Exposure if read as shrinkage
AED 3,525
against AED 155 as a miscount

A family resolution still posts one compensating entry per SKU. Nothing here closes two cases with one row.

SKUVariantRoleLedgerCountedΔAEDCaseRead
LBA01Black and Tan · 38 x 24Source drift8472−121,840DRF-0139Short
LBA02Navy and Tan · 38 x 24Sibling8293+111,685DRF-0138Likely confused
LBA03Ink and Tan · 38 x 24Sibling828200noneClean
Family totals248247−11552 of 3 reconcile if the paired reclass is signed

Prior resolutions in this family

14 offsetting-pair cases since Nov 2025, 12 closed as sku_confusion

high confidence
DatePairNetExplanationClosed with
21-06-2026LBA01 / LBA020sku_confusionCOR-0812 pair
04-06-2026LBA02 / LBA03+1sku_confusionCOR-0744 pair
19-05-2026LBA01 / LBA030sku_confusionCOR-0690 pair
02-05-2026LBA01 / LBA02−2confirmed_shrinkageWO-0418 write-off
14-04-2026LBA02 / LBA030sku_confusionCOR-0602 pair
28-03-2026LBA01 / LBA020sku_confusionCOR-0551 pair
09-03-2026LBA02 / LBA03−1confirmed_shrinkageWO-0377 write-off
12 of 14 closed as confusion, 2 as real loss. The pattern is evidence, not a decision: the money still needs a signature.

Why the family view exists

A blind counter who confuses two colours of the same bag produces two cases that each look like a problem and together look like nothing. Judged one at a time, LBA01 reads as AED 1,840 of shrinkage.

The heuristic is only ever a ranking input. Choosing sku_confusion still writes a paired reclass, still names an approver, and still leaves both cases traceable to the entries that closed them.

Recorded as recurrence group RG-0022: 5 occurrences in 30 days at MCC, above the auto-accept block threshold, so nothing in this family can be auto-accepted.

Home fragrance · drift heatmap

Unit drift per SKU per location. Empty means no drift. Row and column totals included, because the column total is the question worth asking.

−28 units
HQ_319HQ_325MCCYASCCZAJMBASRow Δ
MIN-CNL-019 −8 −1 −3 · −1 ·· −13
PP02 −4 · −1 −1 ··· −6
MIN-CNL-024 −3 · −1 ···· −4
MIN-DIF-007 −2 ······ −2
MIN-CNL-031 −1 ·· +1 ··· 0
MIN-DIF-012 −1 ······ −1
MIN-CNL-002 −2 ······ −2
Col Δ −21 −1 −5 0 −1 0 0 −28
Magnitude1 unit to 8 unitsOverage

Concentration

Where the money actually sits

  • MIN-CNL-01946%
  • PP0221%
  • MIN-CNL-02414%
  • 4 others19%

Concentrated. 67% of the category sits on two SKUs. But 75% sits at one location, and those are two different findings with two different fixes.

Location-specific, not SKU-specific

HQ_319 carries 21 of the 28 units. HQ_325 stocks a comparable assortment and carries 1. When drift concentrates on a location across unrelated SKUs, the cause is a process at that location, not a property of the products.

Recommended: a full recount of the fragrance bay at HQ_319 and a review of who counts it. Both are actions, and both are recorded on the cases they came from.

A drift that repeats is a bug, not shrinkage. Three groups are at or above 3 occurrences in 30 days. Every one of them is blocked from auto-accept and has an incident requested.
GroupKeyExplanation30dAll timeUnits 30dAED 30dFirst seenLast seenIncidentAuto-accept
RG-0031MIN-CNL-019 · HQ_319admin_manual_adjust723315,58008-02-202629-07 08:31INC-0109Blocked
RG-0022LBA family · MCCsku_confusion514115521-11-202529-07 09:40INC-0114Blocked
RG-0044TRANSIT · HQ_319 to MCCunrecorded_transfer49143,91002-05-202626-07 17:40INC-0121Blocked
RG-0038PP02 · HQ_319short_receipt26457614-03-202628-07 22:10not yet1 from block
RG-0051MIN-MRR-003 · AJMpos_refund_no_restock23241019-06-202628-07 12:31not yet1 from block
RG-0009JAE17 · HQ_319confirmed_shrinkage211370530-09-202528-07 16:02not yet1 from block
RG-0071MIN-VAS-022 · CCZbreakage_unticketed15236002-01-202629-07 09:12not yetEligible
RG-0057FYN01 · HQ_319csd_single_sku_defect14284011-04-202627-07 19:58not yetOver cap
RG-0062LLW-BAG-047 · HQ_319supplier_over_ship1232,67003-07-202627-07 11:22not yetOverage · never
RG-0066MSR31-07 · HQ_319unexplained11189229-07-202629-07 14:36not yetImmutable evidence · never
RG-0074MIN-2159 · HQ_319unexplained12222018-05-202628-07 18:44not yetUnexplained · never
RG-0079MST-CRM-42-BLK · YASimpossible_transition1111,52028-07-202628-07 09:15not yetOverage · never
12 groups tracked · 3 blocked on recurrence · 5 permanently ineligible for auto-acceptA per-event cap alone would have auto-accepted every one of RG-0031's 7 occurrences: each was under 2 units and under AED 150. The 30-day budget is what stops it.

What a closed case means here

The four terminal states and what each one has to produce

Terminal stateMeaningMust produceKey reconciles after?
ResolvedThe ledger was wrong. A compensating entry brings it back to the truth.ledger row plus a signatureYes
AcceptedThe difference is real. It is posted as a loss or a find, never left standing.WRITTEN_OFF or found row plus a signatureYes
Accepted · adoptedAnother module already posted the units, typically a module 09 write-off.pointer to that entry, no second rowYes
VoidThe case was never real. The detector was wrong and is named.explanation in category detector_defectNothing to reconcile

INV1 has a fifth outcome that i3 does not: a status string changes and the ledger is untouched. Its resolve handler issues a single UPDATE and writes no ledger row, so a case marked resolved there still has the units missing. The resolution_ledger_entry_id column that would have caught it exists in the migration, is rendered inside a template conditional, and has zero writers, which is why the page has always looked complete.

Verified nightly: the query returning closed cases with no posted entry and no named detector defect must return zero rows for 7 consecutive days.

The invariant this page enforces

i3 never silently repairs anything. A disagreement becomes a case with evidence, a ranked explanation, a proposed compensating entry and a signature that names either a human or a versioned rule.

Auto-accept does not break that. It changes who signs, never whether anything is written. Every artefact a human closure produces is produced: the case, the evidence, the explanation, the entry, and an approval row with signer_role = 'rule'.

Three guards make the threshold safe. A per-event cap of 2 units and AED 150. A 30-day budget of 5 cases and AED 500 per key. And negative deltas only, because unexplained found stock is almost always an unrecorded inbound, and crediting it invents stock.

The budget is the important one. The Mirdif POS mapping bug misrouted roughly 4,500 units and would have passed any per-event cap, because it arrived as thousands of individually tiny drifts.

State variants

Six of the eight states this page can render

Pattern library ↗
Loading

The variance strip and the KPI tiles hold their heights, so the money never jumps as the query lands.

Empty

Every key reconciles.

No live cases. Last full sweep 03:12, 14 detectors, 1.9M rows scanned, 0 raised. This is the state the module exists to produce.

Anomaly explorerClosed cases
Error

Couldn't load the case queue.

The drift service returned 503 after 8s. Nothing was changed and no case moved. Cases keep accruing in the database while this page is blind.

Error id drf-91c4e0 · 29-07-2026 14:38:11 GST
Service health
Zero-filter

No cases match.

4 filters are hiding all 16 live cases.

location: BASseverity: P1source: grnvalue over AED 1,000
Permission

You can read cases but not close them.

Closing a case posts a ledger row, so it needs role >= manager plus the drift_resolve feature. Your grant on app i3 is viewer.

apps.i3.role = viewer · features = [read]
Request accessWho can do what
Gate-blocked

Shopify column is not yet authoritative.

GATE-1 has held for 6 of 7 days. Until it closes, shopify_level_divergence runs in shadow and its cases are marked provisional rather than counted in the KPI row.

3 provisional cases held out of Value at risk · GATE-1 day 6/7
Connected to Case detail Anomaly explorer Conflict inspector Correction workbench Approvals Incidents Damages Stock count 3-way reconcile Gates and feed Live inventory Location detail SKU detail Transfers Returns GRN and QC CSD events Classifier Audit log Reports Dashboard