Retail & consumer
One customer record behind the till, the app and every campaign
A real-time billing feed, a campaign manager marketing can run without an engineering ticket, and a recommendation engine that still works on a customer's first visit.
- Campaign launch, brief to live
- weeks → same day
- Recommendation click-through
- roughly tripled
- Profiles per real customer
- several fragmented records → one
- Sector
- Multi-category retail chain
- Estate
- 180 stores plus app and web, ~2.4m loyalty members
- Engagement
- Discovery sprint, then delivery pod
- Duration
- 12 months, phased by module
Stack
- Kafka
- Snowflake
- dbt
- Redis
- LightFM
- FastAPI
- React
- Airflow
- Metabase
Practices involved
Discuss a similar problemThe situation
Store billing ran on regional point-of-sale systems that only reconciled overnight, loyalty points sat in a legacy add-on that marketing could not touch without filing an engineering ticket, and personalisation meant a spreadsheet segment emailed to whoever ran that week's push notification. The same person could appear as three or four different customer records depending on whether they had paid in-store, used the app, or scanned a loyalty card, and nobody could say with confidence which number in which system was the real one.
The constraint
The counter could not go down. A billing migration that breaks the printer at checkout is a P1 incident in any retailer, not a bug ticket. Loyalty rules also change constantly — seasonal multipliers, tier bonuses, expiry — and marketing needed to launch them without a release, but every rule is also a financial liability, since a point is a promise of a future discount, so self-service could not mean unsupervised. And a large share of transactions are still walk-in or a customer's first loyalty scan, so recommendations had to be useful with no purchase history at all, not just for the well-known regulars.
What we built
One customer identity across till, app and loyalty card
Deterministic matching on card and phone number, probabilistic matching where those are missing, and a documented survivorship rule for what happens when two records disagree. This is the same identity discipline we apply to reader accounts in publishing or patients in a clinical system — a merge rule someone can explain, not a fuzzy join nobody wants to look at.
Billing as an event stream, not a batch export
Store bills stream as structured events in near real time, replacing the nightly export that used to mean today's numbers were not available until tomorrow. The migration ran store cohort by store cohort, with the legacy export kept live as a fallback until each cohort was verified.
A campaign manager marketing can actually run
A rule-based points and campaign engine with a UI for defining tiers, multipliers, expiry and targeted offers, gated by a dry-run mode that projects the point liability before anything goes live, and a staged sign-off for rules above a threshold. Marketing gets to move fast on the rules that cannot hurt the business, and a checkpoint on the ones that can.
A recommendation engine that does not need history to be useful
Collaborative filtering for customers with a purchase record, an association-rule fallback for first-time or anonymous customers, refreshed often enough to reflect this week's stock and promotions rather than last quarter's.
Serving inside the time a bill takes to print
A cached, precomputed candidate layer means a next-best-offer or a points nudge resolves within the checkout flow's latency budget, so the offer is on the receipt or the screen before the customer has already turned to leave.
What changed
A campaign that used to take weeks from brief to launch now ships the same day, with the liability known in advance rather than discovered in the numbers afterwards. Recommendation click-through roughly tripled, and the average customer went from several fragmented profiles to one.
What we would do differently
The liability simulator shipped after self-service launch access did, not before. In month two, marketing launched a multiplier campaign more generous than intended, and the gap between projected and actual liability was not caught for a week. The simulate-before-launch guardrail should have gated the first self-serve campaign, not the fiftieth.
Outcomes
- Campaign launch, brief to live
- weeks → same day
- Recommendation click-through
- roughly tripled
- Profiles per real customer
- several fragmented records → one
Client identity withheld under a mutual NDA. Figures are illustrative — rounded and directional, meant to show the shape of the change rather than an audited result. We will walk through the real numbers, and how they were measured, under NDA on a call.
More work
Other engagements.
Case studiesCutting stockouts across 240 stores with a forecast the buyers trust
A hierarchical demand forecast, a promo-aware feature store and a replenishment workflow the category team can override — because a model nobody overrides is a model nobody uses.
Read the case studyShipping firmware to 6,000 imaging consoles without a truck roll
A signed, resumable, rollback-safe update channel for regulated ultrasound hardware sitting on hospital networks that block almost everything.
Read the case studyA reproducible pipeline for a diagnostics lab that had outgrown its scripts
Genomic and assay data moving from instruments to reportable results, with provenance for every derived value and a turnaround clock the lab director can see.
Read the case studyNext step
Tell us what you're trying to ship.
Send the brief, the RFP, or three messy sentences about the problem. You get a written point of view from an architect within two working days — not a sales deck.