# Category mapping — starter file

`category-map-starter.csv` is a **proposal, not an answer.** It was derived from the
live catalogue by matching what Shopify actually carries against the eight categories
on Royce's Square reports. Whoever owns those categories needs to confirm it.

## Why a mapping file exists at all

Measured on the live 387-product catalogue:

| Source | Result |
|---|---|
| `product_type` | 285 products carry channel labels (`In Store Purchase`, `Ecommerce`) |
| `collection`, no allowlist | first match assigned 83 products to `Boutique Exclusives Only` |
| `collection`, Square names | matched **27 of 387**, and the names differ anyway |

The Square categories were maintained in Square and never migrated into Shopify. No
rule over existing fields reconstructs them, so the mapping is data the client owns.

## How the rules work

Evaluated by `priority`, lowest first; **the first match wins**. A product in both
`Best Sellers` and `Nama Chocolates` therefore lands in exactly one category rather
than being counted twice.

The tiers in the starter file:

- **10 — collections that name real merchandise.** Most reliable: someone curated them.
- **20 — product types that are genuinely merchandise.** `Nama Chocolates`,
  `Pure Chocolate`, `Bars`, `ROYCE' Collection`. Deliberately excludes
  `In Store Purchase` and `Ecommerce`, which are channels, not categories.
- **30–35 — product title fallbacks.** The only way to reach `PCC and Potechi`,
  `Truffes` and `Wafers`, which have no usable collection.
- **90 — non-merchandise.** Shipping, insurance and gift cards get their own named
  buckets so they are visible and separate rather than hidden inside `Uncategorized`.

Note `Bar Chocolate` (30) outranks `Matcha` (35) on purpose: Square's own item report
files *Matcha Bar Chocolate* under **Bars**.

## Collections deliberately NOT used

`Boutique Exclusives Only` (256), `Corporate Gift` (114), `Gifts Collection` (92),
`Best Sellers` (62), `All Products` (62), `Limited edition` (56), `test price` (42) and
the seasonal sets. They cut across merchandise types — mapping them would swallow the
catalogue and produce categories that mean nothing.

## Using it

```bash
php tools/category-map.php --shop=<domain> --import=category-map-starter.csv
php jobs/sync-products.php --shop=<domain> --dry-run --strategy=mapping
```

The dry run prints the resulting distribution and writes nothing. Compare it against a
Square category report, adjust the CSV, re-import, repeat.

When it looks right:

```bash
mysql -u <user> -p smartprinter_db -e "
INSERT INTO device_settings (connection_code, settings, created_at, updated_at)
VALUES ('<domain>', JSON_OBJECT('category_source','mapping'), NOW(), NOW())
ON DUPLICATE KEY UPDATE settings = JSON_SET(COALESCE(settings, JSON_OBJECT()), '\$.category_source', 'mapping');"

php jobs/sync-products.php --shop=<domain>
php jobs/aggregate.php
```

That is a re-aggregate, not a re-backfill: order facts are untouched, and a test
asserts that relabelling never changes the money.

## What remains uncategorized is a question, not a bug

Anything still unmapped after this is a product nobody has assigned to a category. Run
`--suggest` again to list them by size and take those back to the client.
