All projects Client work

Catching Product Data Drift Across Four Sales Systems

537 units sold out of a warehouse that did not know it held them, and nobody noticed for 34 days, because no single system was wrong on its own. Four records of one product, one link nothing enforced. A nightly check now reports the break the next morning.

Client
Consumer electronics brand
Engagement
Assessment, then build
Shipped
September 2026
Stack
Python · Postgres · Shopify, Amazon, 3PL APIs
Code
Client-owned. Public: workflow-agent

Problem

This brand sells on its own storefront, on Amazon in four countries, and ships everything from a third-party warehouse. All four systems keep their own record of what a product is, and they are supposed to line up on one product code. Nothing makes them. There is no integrity check between a company's separate software vendors and no job that fails, so when two systems disagree, all four keep running perfectly, each on its own version of the truth.

Four systems, four sets of identifiers, one link nothing enforces

Shopify, Amazon, ShipMonk and the product master each hold their own identifiers and their own status vocabulary for the same product. They are supposed to join on a canonical SKU, but no foreign key enforces that join, so when it breaks nothing errors and a human discovers it weeks later. ONE PRODUCT ON THE SHELF Shopify system of record product + variant id variant sku barcode ACTIVE / ARCHIVED / UNLISTED / DRAFT Amazon per marketplace ASIN + parent ASIN seller SKU GTIN / UPC Active / Inactive / Incomplete ShipMonk third-party logistics ShipMonk SKU its own alias map is_active Product master being decommissioned canonical SKU canonical casing is_active supposed to join on one canonical SKU no foreign key · no referential integrity · nothing raises when it breaks SO IT BREAKS QUIETLY Format, not value 745809965326 0745809965326 same product A name only one system knows cmg the 3PL's own SKU, in no alias map The master is a spreadsheet left off the import template, so the warehouse never had it

scroll to see the full diagram →

Each system also has its own words for whether a product is live, with no defined mapping between them. Every failure below is a real case from the client's history.

What It Costs

One product was left off the spreadsheet that loads items into the warehouse. It went live, sold, and built up stock. A month later it showed up in a report holding 537 units and real revenue, unknown to the warehouse supposedly holding it. It surfaced only when someone tried to create a shipment and could not find it, 34 days after the mistake.

The fix took minutes. Noticing took 34 days. Everything expensive about this kind of bug lives in that gap.
34 days
to notice one missing product
the fix itself: minutes
537
units stranded meanwhile
selling, unknown to the warehouse
50 days
longest support thread
closed unresolved
~900 vs 2
reviews earned vs displayed
split across two IDs

Another product had nine hundred reviews and displayed two, because the reviews sat on a parent product ID while the listing carried a separate variant ID. Every vendor checked their own side, found it correct, and said so. Nobody was responsible for looking across the boundary. That thread ran fifty days and closed unresolved.

Objective

The engagement started open-ended: where could AI help here? I analyzed a year of the operations team's correspondence, with their consent, and found no repetitive pile worth automating. The work was one-off diagnosis, nearly every case unlike the last.

So I recommended against the general-purpose agent that had been the obvious pitch. It needed standing read access to five vendors, a permanent liability in exchange for speeding up work that was never the bottleneck. One problem did survive, showing up in about a fifth of operational threads: this one. Deterministic, single domain, and buildable on a pipeline that already pulled two of the four systems every night.

Decisions

A nightly job pulls all four systems, matches them to one spine, and reports disagreements in six categories: missing, conflicting identifier, conflicting status, orphan, broken variation, and a listing left live after the product was retired. It writes to nothing and holds no credential that can change a product anywhere. It reports the disagreement and a person decides which side is wrong.

Two choices did most of the work:

  • Normalize identifiers before comparing them. Barcodes differ between systems by a leading zero far more often than they differ in substance. After normalizing, exactly 15 were genuinely different.
  • Pull Amazon from the bulk listings report, not the catalog endpoint. That endpoint only returns products that have sold, which is the exact blind spot the check exists to cover.

Tuning the First Run

Run one produced a few hundred findings against 433 products, as expected. A check like this is worthless until someone who knows the business explains which oddities are intentional, and that calibration is the difference between a report someone reads and one they filter to a folder.

Three rule corrections, all from client review

Three rule corrections. Judging Amazon status against the home storefront only cut findings from 177 to 1. Suppressing orphans inactive everywhere cut 22 to 5. Separating retired listings from live ones cut 2 to 1. explained as normal kept as a real finding Amazon status judged against the home storefront only 177 → 1 176 of 177 were ordinary international rollout timing, not drift Orphans that are inactive everywhere suppressed 22 → 5 17 relics: archived bundles, recovery SKUs, retired country variants Retired listings separated from live ones 2 → 1 one product carrying a dead and a live seller SKU on one storefront

scroll to see the full diagram →

Each correction came from the client reading the first run and explaining why something was normal. The top rule alone removed 176 findings that were ordinary international rollout timing.

Two more categories were suppressed in the logic rather than by hand. Amazon variation parents and multi-item bundles are absent from the warehouse by design and would have produced around eighty permanent false alarms. A mute list is something a person has to maintain forever, and nobody does.

Outcome

A few hundred findings on the first run became nineteen worth a human's attention: three rule corrections from client review, two categories suppressed in the logic, and no mute list to maintain. Two of the nineteen: a product the warehouse knows by a name that appears in no alias map, and two products selling on both the storefront and Amazon that exist in no product master at all.

It ships in non-failing mode on purpose. It emails, it does not break the pipeline, and it stays that way until the backlog is small enough that red means something new happened. A check that is red on day one gets muted within a week.

One caveat printed on the report itself: I reconstructed what it would have said the day that product went missing, and it names it the next morning. That reconstruction uses today's data rather than a stored snapshot of that day, so it is a plausibility argument, not a backtest.