docs(products): DOC-4 dialect-adapter notes (SLICE-8)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,42 @@ Option names live both in `CanonicalProduct.option_names` (the values) and in
|
||||
`fields{}` (the file-presence marker the diff needs for the absent-vs-clear
|
||||
distinction).
|
||||
|
||||
## Dialects (INV-17, SLICE-8)
|
||||
|
||||
A file is normalized to canonical names **at the codec boundary**, so every stage
|
||||
downstream of `parse_csv` (validate → diff → apply) is dialect-agnostic — it only
|
||||
ever sees canonical cells. Two dialects exist: `canonical` and `shopify`.
|
||||
|
||||
`dialect_shopify.py` is the Shopify adapter and the source of truth for the
|
||||
mapping (mirrored by `tests/test_products_dialect_shopify.py` and
|
||||
`tests/fixtures/shopify-export.csv`):
|
||||
|
||||
- **Detection** — `is_shopify_header()` returns `shopify` iff the header carries a
|
||||
Shopify-only *signature* column (`Body (HTML)`, `Variant Grams`, `Cost per item`,
|
||||
`Gift Card`, a `Google Shopping / …` column, …). Everything else is `canonical`.
|
||||
Detection is by signature, not an exact header set, so extra market/region columns
|
||||
don't defeat it; an ambiguous header (only columns common to both dialects) falls
|
||||
through to `canonical`, which maps those shared columns identically — so it never
|
||||
misparses (§7.4).
|
||||
- **Mapping** — `map_shopify_header(header)` returns `(mapped, not_imported)`:
|
||||
`mapped[i]` is the canonical name for column `i` (or `None` when it has no
|
||||
canonical home), and `not_imported` is the warning list surfaced as the draft's
|
||||
`unknown_columns`. The rule per column, in order: in `SHOPIFY_RENAME` →
|
||||
canonical name; in `SHOPIFY_DROP` (`Type`, `Variant Weight Unit`) → not imported;
|
||||
in `KNOWN_COLUMNS` → pass through; else → not imported (this last branch absorbs
|
||||
the unbounded `… / <Market>` price columns without enumerating them).
|
||||
- **The two name-collision overrides** are why `SHOPIFY_DROP` exists: Shopify's
|
||||
`Type` is a free-text category, but canonical `Type` is *structural*
|
||||
(`standalone`/kits), so it is warned rather than folded in; and Shopify's
|
||||
`Variant Weight Unit` is dropped because the `Variant Grams` → `Variant Weight`
|
||||
rename is the weight source.
|
||||
- **The one value transform** lives in `parse_csv`: when a Shopify row has a mapped
|
||||
`Variant Weight` (from `Variant Grams`), the codec synthesizes
|
||||
`Variant Weight Unit = "g"`, since Shopify grams are unitless integers.
|
||||
|
||||
`detect_dialect()` in `codec.py` is the INV-17 seam; a new dialect is a new adapter
|
||||
module plus a branch there, with no change to validate/diff/apply.
|
||||
|
||||
## Error granularity
|
||||
|
||||
`validate.py` never raises on a row problem: every violation is recorded as a
|
||||
|
||||
Reference in New Issue
Block a user