Product breakdown aliases and exclusions
Launchpad V2 mirrors V1's ProductBreakdownConfigService for two client-level
rules that affect the Product breakdown:
- An alias says that a raw CRM product name is another spelling of a canonical product. Matching is exact after trimming and lowercasing. The matching buckets are combined before the report returns product totals.
- An exclusion says that a canonical product is not an advertised reporting product. Only that explicitly configured bucket is removed. It is not folded into "Other" and its value is not redistributed.
Unmapped products remain visible. Aliasing never changes total dollars or order
weight; it only changes which product bucket owns them. Exclusions are the only
configuration that changes the displayed totals. Revenue remains decimal
dollars, and initial/upsell lifecycle classification remains owned by the
canonical marketing_sales ledger rather than this display configuration.
Configuration is stored in Supabase tables product_crm_aliases and
product_crm_exclusions. Migration 045_v1_operational_parity.sql owns their
schema: each row has a V2 client UUID and the original V1 numeric v1_id, with
uniqueness enforced for both the imported row identity and its client-scoped
product value. Migration 061_product_breakdown_config.sql is deliberately an
upgrade-safe addition that creates case-insensitive lookup indexes without
redefining or replacing those tables. The server trims and lowercases values
while loading them.
Some current connector spellings did not exist in the V1 configuration. Those
aliases use a separate, explicit provenance namespace: source_provenance is
v1_cloud_sql with a non-null v1_id for exact imports, while a verified V2
source alias has a named provenance and a null v1_id. Migration 062 adds
only that schema capability and remains safe on an empty client table. The
post-client port:v1-product-config importer installs four Top Speed spellings
proven from retained attribution events: b29bb / b29 blue brick map to
SCG - B29 Blue Brick, and tsg-bfb / ball first board map to BFB.
The reproducible read-only query and retained result are checked in under
architecture/evidence/top-speed-product-alias-audit-2026-08-26.md.
The V1 verifier deliberately compares only rows carrying a non-null V1
identity, so native source alignment does not weaken or create false drift in
the V1 audit. This also keeps the dry-run verifier compatible while migration
062 is awaiting deployment.
The importer checks normalized (client, lower(trim(source))) identity before
writing, refuses missing tenants, conflicting canonical decisions, or a row
owned by the V1 identity/provenance namespace, and verifies all four native
mappings have null v1_id plus the exact native provenance after apply. Runtime performs the same
normalized duplicate rejection and preserves raw buckets if configuration is
ambiguous. The production duplicate preflight and result are retained in
architecture/evidence/product-alias-normalized-duplicate-preflight-2026-08-26.md.
The server loads rules only for resolved, tenant-scoped clients. Product filters are expanded to include raw aliases before ClickHouse aggregation, so selecting a canonical product also finds its raw spellings and then combines them exactly once. Each client receives its own bound product predicate: the same raw name can map to different products in two clients without either mapping leaking across tenants. Aggregate access loads only clients already authorized by the report scope. If one configuration cannot be resolved, the original requested filter remains bound for that client; the query never broadens to unfiltered product data. For an unfiltered breakdown, unavailable configuration preserves raw buckets and avoids silent data loss.
V1 production configuration can be reconciled with the dry-run-first command:
npm run port:v1-product-config
npm run port:v1-product-config -- --verify
npm run port:v1-product-config -- --apply
The command requires the governed/local Cloud SQL proxy and Supabase service
role environment. It maps V1 numeric client IDs through clients.v1_import,
preserves every valid V1 row and timestamp, and performs idempotent upserts on
(client_id, v1_id). --verify compares the imported source/canonical values
and detects missing or extra imported identities. --apply is a production
data write and must follow migration review; the default run is read-only.