# One page per real product: what I decided and how to start it
Rian, 12 September 2026. For me and for any session I open about this. The working brief is
`.logs/planning/streams/M-merging.md`; the session command is `/stream-m`.

## The decision
One page per real product. Sizes and variations are chosen on that page, not given pages of their
own. Every listing we collect (one product at one shop; a bottle at three airports is three listings)
gets standardised into four fields: **brand | name | variation | size**. Where all four agree, the
match is automatic. Everything else is a suggestion, and a person confirms or rejects it. Merges
happen at every level: brands (Paco Rabanne is Rabanne), names, variations (Eau de Toilette is EDT),
and sizes (10cl is 100 ml). At each level the person also picks the preferred spelling. The machine
suggests, I plough through and confirm. When two listings merge, the barcode and the image one of
them has become the product's, and so serve all of them.

## Why
Looking at Paco Rabanne 1 Million on `/collectors`: fourteen rows that are really one brand, one
line, three fragrances (EDT, Parfum, Elixir) and a handful of sizes. The 100 ml EDT exists three
times because three shops spell it three ways and two of them call the brand Rabanne. It should be
one product compared across five airports; today it compares nothing. That pattern is the catalogue's
biggest hidden cost, and the fix is a human confirming what the machine can only suspect.

## What is already true, so nobody rebuilds it
- Listings are already the atomic thing, and the tile as collected is kept for every one.
- Brand aliases already exist as rows pointing at a house.
- The merge service already repoints listings, keeps a forwarding record, never deletes, and gives
  the survivor the other row's barcode and image. Two different barcodes are refused, not guessed:
  that stays, and if I override it, which barcode won gets recorded.
- 300 suggested pairs are already queued from the rules; nobody has had a page to decide them on.
- Comparisons stay at the exact variation and size. A 50 ml against a 100 ml is not a saving.

## What the stream builds, in order
1. A listings view: the tile as collected beside what we made of it.
2. The line above the product, and a vocabulary for variations.
3. Matching that stops splitting the same bottle over brand prefixes and concentration words.
4. Aliases at every level, with a preferred name; Paco Rabanne to Rabanne as my first confirmation.
5. Suggestions from rules, each with a reason I can read.
6. The merge session: keyboard confirm, reject, skip, my account recorded on every decision.
7. The barcode and image inheritance rules, written down and tested.

The line page itself (the toggles, and every old product address redirecting to its line) is
deliberately **not** in this stream. It changes the addresses Mark agreed on 9 September, so it is a
separate decision, to take after my first merge session shows what the catalogue really collapses to.

## How to start it
```
cd /srv/apps/dutyfreeprofessor && claude
/stream-m
```
When the first turn ends, paste the goal line printed at the bottom of `.claude/commands/stream-m.md`.
Do not run it while Stream A is running; they share the identity layer. Leave `dfp-devdb` in place;
every migration and backfill rehearses there first.

## What to expect
Fewer pages than today, but not dramatically fewer: drinks are mostly one size and stay one line each.
The real gain is comparables, split products becoming multi-airport products. The stream must report
real numbers from the rehearsal, not predictions. When it hands back, I deploy (dump first; it carries
a migration and names its after-deploy commands), then sit down at `/collectors`, Merge, and work
through the queue.

## Afterwards
- Decide the line page and its redirects (running list; Stream B builds it).
- Consider an AI pass over the suggestions once the rules have found what they can.
