Companion to domain.md (the business legend). That document
fixes the frame — company, channels, footprint, seasonality; this one pins the
numbers and distributions every seed and generator must reproduce, and the
invariants that keep them mutually consistent. When a seed value and this spec
disagree, this spec wins.
Consumers, in execution order:
- Generator & seeds rebuild —
warehouse/agentflow/dv2/reference/(generator.py, tnved.py; gs1.py is unchanged),synthetic_seed.sql,satellite_seed*.sql,postgres_oltp/seed.sql. Faux-PII mechanics are preserved (§8). - Record-source rename — retired external-dataset prefix →
mp__*(see domain.md §5.4). - Serving demo repin — the four demo tables, NL demo answers,
ORD-*values (§9). - Evidence regeneration — demo_evidence and live-verify counts re-pinned on the new seeds.
All money net of VAT, in ₽ — every branch is seeded in ₽; the pinned demo FX constants of §10 are documentation-only. "Baseline day" = seasonal multiplier 1.0; the seasonal calendar (§4) modulates it and averages to exactly 1.0 over the year.
| Channel | Branch | Orders/day | Avg check, ₽ | Revenue, ₽/day |
|---|---|---|---|---|
| B2B wholesale | msk |
70 | 52,000 | 3,640,000 |
| B2B wholesale | spb |
35 | 52,000 | 1,820,000 |
| B2B wholesale | ekb |
25 | 52,000 | 1,300,000 |
| B2B re-export (Gulf) | dxb |
15 | 90,000 | 1,350,000 |
| B2B / EAEU | ala |
15 | 45,000 | 675,000 |
| Marketplace FBS (WB ~60% / Ozon ~40%) | msk |
1,750 | 2,150 | 3,762,500 |
| Own D2C site | msk |
55 | 3,300 | 181,500 |
| Total | 1,965 | 12,729,000 |
Roll-ups that follow (and must keep following) from this table:
- Annual revenue ≈ 12.729M × 365 ≈ 4.65 B ₽ — inside the 3–5 B ₽ legend corridor.
- Revenue mix: B2B 69.0% · marketplaces 29.6% · D2C 1.4%.
- Order-count mix: marketplaces 89.1% · B2B 8.1% · D2C 2.8%.
- Branch revenue: msk 59.6% · spb 14.3% · dxb 10.6% · ekb 10.2% · ala 5.3%.
- Branch order count: msk ≈ 95.4% (it fulfils all e-com), the rest is regional B2B. This asymmetry is deliberate: branch diversity in the vault lives in customers, PII, loyalty, shipments and B2B orders, not in marketplace order volume.
The old seed's 40/25/15/10/10 branch distribution of orders does not
survive the legend: all FBS/D2C fulfils from the msk central warehouse, so the
consolidated marketplace feed (mp__*) is msk-only. 40/25/15/10/10-style
spreads remain valid only for the dealer book (§7).
| Channel | Lines/order | Units/line | Notes |
|---|---|---|---|
| B2B RU | 3–10 (avg ~6) | 4–24 (avg ~5.5) | ≈ 33 units/order at wholesale prices; deferred payment flag; retro-bonus accrual 3% |
| B2B dxb | 4–12 | 8–48 | export pallets; ≈ 56 units/order |
| B2B ala | 3–8 | 4–24 | ≈ 28 units/order |
| Marketplace FBS | 1 (95%), 2 (5%) | 1 | single-item retail; 3% cancel/return allowance |
| D2C site | 1–3 (avg 1.3) | 1–2 | gift orders skew to 2+ lines in peak weeks |
Status flow everywhere = the serving contract's
pending → confirmed → shipped → delivered / cancelled. Steady-state status
distribution for a seeded snapshot: delivered 62%, shipped 12%, confirmed 10%,
pending 8%, cancelled 8% (marketplace cancels dominate the last bucket).
Retail prices are RRC (recommended retail), ₽, x,x90-style endings.
ТН ВЭД at real 4-digit heading granularity, 10-digit form zero-padded —
the established honesty convention of tnved.py is preserved.
| # | Category (RU source systems) | EN slug (serving) | SKUs | RRC band, ₽ | HS/ТН ВЭД heading |
|---|---|---|---|---|---|
| 1 | Электрочайники | kettles | 22 | 1,490–3,990 | 8516 |
| 2 | Аэрогрили и грили | grills | 20 | 3,490–7,990 | 8516 |
| 3 | Блендеры | blenders | 20 | 1,690–4,490 | 8509 |
| 4 | Миксеры (вкл. планетарные) | mixers | 14 | 1,990–7,990 | 8509 |
| 5 | Кофеварки и кофемолки | coffee | 18 | 1,990–6,990 | 8516 |
| 6 | Мультипекари, вафельницы, сэндвичницы | multibakers | 16 | 1,790–3,490 | 8516 |
| 7 | Измельчители (чопперы) | choppers | 12 | 1,290–2,490 | 8509 |
| 8 | Соковыжималки | juicers | 10 | 2,490–5,990 | 8509 |
| 9 | Кухонные весы | scales | 12 | 790–1,490 | 8423 |
| 10 | Вакууматоры и сушилки | vacuum-dry | 16 | 2,290–5,490 | 8422 (вакууматоры) / 8516 (сушилки) |
- Volume skew (ABC): top 24 SKUs ≈ 55% of marketplace unit volume, next 56 ≈ 35%, tail 80 ≈ 10%. Bestsellers concentrate in categories 1, 3, 6, 7, 9 — exactly the 1.5–3k ₽ marketplace-check zone.
- Naming — no brand token (decision). The importer is deliberately unnamed (domain.md §1), so product names carry no brand string — eliminates any trademark-collision risk and nothing in the pipeline needs one. Names are built from category + attributes: RU (1С side): «Чайник электрический 1,7 л, 2200 Вт»; EN (serving side): "Electric Kettle 1.7L 2200W". Attribute pools per category (volume, power, bowl count, wattage…) are the generator implementer's choice; names must stay deterministic per seed.
- RU vs EN split (decision): DV2/warehouse content is RU-flavored (that is what 1С/Битрикс emit); the serving demo store and NL-queried catalog stay EN (that is the product-team surface the docs and SDKs speak). One SKU id maps both.
- SKU id shapes stay as-is to minimize churn: reference catalog
RC%06d(RC000001…RC000160), DV2/serving seedsSKU-#####. Only the count changes: 800 → 160 products (expect seed-count test pins to move).
Two monthly-multiplier curves, each averaging exactly 1.0. The B2B curve leads the retail curve by ~1 month — dealers stock up ahead of consumer peaks; that lead-lag is the shape analysts should be able to find in the data.
| Month | Retail (MP + D2C) | B2B (all branches) | Why |
|---|---|---|---|
| Jan | 0.70 | 0.60 | post-NY trough |
| Feb | 1.10 | 1.15 | Feb 23 retail; dealers stock for Mar 8 |
| Mar | 1.20 | 0.95 | Mar 8 gift peak |
| Apr | 0.85 | 0.85 | |
| May | 0.80 | 0.80 | |
| Jun | 0.75 | 0.85 | low season |
| Jul | 0.80 | 0.95 | first NY containers ordered |
| Aug | 0.90 | 1.05 | |
| Sep | 0.95 | 1.20 | dealer NY stocking starts |
| Oct | 1.05 | 1.40 | peak dealer stocking |
| Nov | 1.45 | 1.30 | 11.11; late dealer top-ups |
| Dec | 1.45 | 0.90 | consumer NY peak; too late to restock B2B |
Day-level spikes on top of the retail curve: Nov 11 ×2.5 (marketplace sale), Dec 10–25 ramp to ×1.6, Mar 1–7 ×1.8, Feb 14–22 ×1.25. Supply echo: containers land 40–60 days after FOB (§6) — procurement for the December peak is committed by early October.
Per-SKU price ladder, expressed as a share of RRC. Every SKU must satisfy the chain FOB < landed < wholesale < marketplace-net < RRC:
| Rung | Share of RRC | Meaning |
|---|---|---|
| FOB purchase price (CNY, converted) | 24–30% | contract factory price |
| Landed cost | 32–40% | FOB + sea freight + duty + Chestny ZNAK marking + inbound handling |
| Wholesale (B2B price list) | 60–65% | dealer price before retro-bonus |
| Marketplace net proceeds | ≈ 78% | RRC − commission (~17%) − FBS logistics (~135 ₽) − returns allowance (3%) |
| RRC | 100% | own site price = RRC |
Per-average-order contribution (baseline, net of VAT):
| Channel | Avg check, ₽ | Main deductions | Contribution, ₽ | Margin |
|---|---|---|---|---|
| Marketplace FBS | 2,150 | commission 366 · FBS 135 · returns 65 · marking/pack 18 · landed 774 | ≈ 790 | ~37% |
| B2B RU | 52,000 | landed 30,190 · retro-bonus 1,560 · delivery/credit 800 | ≈ 19,450 | ~37% |
| B2B dxb | 90,000 | export pricing is thinner | ≈ 18,000 | ~20% |
| B2B ala | 45,000 | ≈ 13,500 | ~30% | |
| D2C site | 3,300 | acquiring 66 · delivery 250 · marketing ~400 · landed 1,188 | ≈ 1,400 | ~42% |
Sanity roll-up: annual contribution ≈ 1.6 B ₽ (~35% of revenue) — a healthy mid-size importer; nothing in the data should contradict this order of magnitude.
The reference was originally grocery-shaped (dairy/bakery supplier stems, food brands, gram weights, food ТН ВЭД headings); it has since been replaced wholesale with the kitchen-appliance reference specified below (see CHANGELOG for the swap):
- 30 suppliers: 22 CN contract factories (Guangdong/Zhejiang-style names,
e.g. "Foshan …", "Ningbo …", "Cixi … Electric Appliance Co., Ltd." —
synthetic and labelled as such, per the generator's honesty convention),
5 RU (packaging, manuals, cords/components), 2 AE (JAFZA trading
consolidators — the dxb re-export leg), 1 KZ (local services distributor).
COUNTRY_WEIGHTS→(("CN", 72), ("RU", 16), ("AE", 8), ("KZ", 4)). - Tax-id shapes: RU INN-10 keeps its real check digit (implemented); CN = 18-char USCC — implement the real GB 32100-2015 check character if cheap, otherwise a labelled structural placeholder (document which, keep the genuine-vs-synthetic note accurate); AE TRN 15 digits and KZ BIN 12 digits stay as today.
- Sourcing: 1–2 suppliers per SKU (primary + backup), MOQ 300–1,000
units,
lead_time_days40–60 (sea) with ~10% of rows at 12–18 (air), quarterlyvalid_fromrepricing. Purchase prices follow the §5 ladder. - GS1 stays exactly as-is (
gs1.pyuntouched): the EAEU prefix range 460–469 is correct for an own-brand importer — GTINs belong to the RU brand owner registered with GS1 RUS, regardless of where manufacturing happens. The module docstring already records this rationale. tnved.py: the former grocery headings were replaced by the four appliance headings of §3 (8516, 8509, 8423, 8422) with RU descriptions close to official wording, heading-granularity honesty note preserved.
Dealer book (B2B) — 500 active accounts:
| Branch | Accounts | Note |
|---|---|---|
| msk | 190 | incl. federal chains' central offices |
| spb | 100 | |
| ekb | 70 | Urals/Siberia dealers |
| dxb | 60 | Gulf wholesale buyers |
| ala | 80 | KZ + EAEU neighbours |
Ordering frequency: 200 core accounts ≈ 4 orders/week (regional chains place per-outlet restocks), 200 mid ≈ 1.5/week, 100 tail ≈ 0.5/week → ≈ 164 B2B orders/day, consistent with §1. Each account carries 1–3 decision-maker contacts in Bitrix24 (≈ 900 contact persons) with birth dates — the gift-campaign trigger.
Retail identities: ~150k marketplace buyer ids (12% repeat within 90
days) and ~9k D2C accounts (35% repeat). Retail customers belong to the msk
legal entity (it runs all RU e-com), so their PII lives in *__msk
satellites; regional branches hold dealer customers only.
The existing mechanics carry over verbatim (only populations/semantics
change): deterministic name arrays per jurisdiction, @example.test /
@example.kz emails, city phone prefixes (+7495/+7812/+7343 RU, +7727 KZ,
+971 AE), birth-date spreads, hash_diff idempotency, customer_hk = MD5(number) linkage across hubs/satellites. New requirements:
- dxb satellites use AE-appropriate names/phones (+971, latin transliteration) — dealer contacts there are Gulf trading companies' buyers;
- dealer contacts (the ~900) must populate
birth_datedensely — campaigns query it; retail birth dates may stay sparse (~40% filled); - loyalty satellites (
sat_customer_loyalty__bitrix__*) now mean dealer retro-bonus state:loyalty_segment∈ {core, mid, tail},loyalty_points= accrued quarterly bonus in ₽ (3% of quarter's purchases, resets quarterly),last_visit_at= last order date. Only dealer customers get loyalty rows; msk/spb/ekb only (as today — dxb/ala dealers are on contract terms, not the bonus program).
The four demo tables keep their shapes and row counts; values move to the legend. Targets:
products_current(10 rows): representative SKUs across §3 categories, EN names, EN category slugs, RUB prices from the RRC bands,stock_quantity= central-warehouse shared pool; exactly one out-of-stock bestseller stays in the seed — the oversell/freshness story needs it.orders_v2(8 rows): bimodality must be visible even in 8 rows — 5 marketplace-scale orders (1,500–3,000 ₽), 1 D2C (~4,000 ₽), 2 wholesale (≈ 48,000 and ≈ 76,000 ₽).currency = 'RUB'everywhere (branch currencies live in the vault, not the serving demo).ORD-YYYYMMDD-NNNNformat and the relative-NOW()timestamps stay.users_enriched(5 rows): 2 dealer contacts (lifetime spend ~1.2M and ~460k ₽,preferred_categoryfrom §3 slugs) + 3 retail buyers (3–40k ₽).sessions_aggregated(6 rows): unchanged mechanics — D2C-site-only telemetry per the legend; funnel stages as today.- NL demo answers, README curl examples, and any pinned revenue/count values
are recomputed from the new rows (that is the demo-repin step's whole job);
avg_order_valuedemos should showcase the bimodality (segment before averaging — domain.md §5.1).
- All seeded amounts in the main vault seeds are ₽, in every branch.
synthetic_seed.sqlandpostgres_oltp/seed.sql(the seeds that back the vault/serving demo and §1's rates) store only the ₽ figures — no generator or seed in that path performs an FX conversion at runtime, and cross-branch aggregates there work directly in ₽. In the legend narrative dxb invoices in AED and ala in KZT, but nothing in the main seed path materializes that. Exception:postgres_oltp/fanout/02_seed.sql. This is a separate, intentional CDC/multi-currency replication fixture (the per-branch Postgres→ClickHouse fan-out demo) — it seedsorders.currencyas the local tag per branch (msk = RUB, dxb = AED) on purpose, to prove the fan-out carries a real per-row currency column through CDC.postgres_oltp/fanout/04_ch_bridge.sqlonly replicates each branch's rows into its own ClickHouse database, preserving whatever currency tag was seeded — it never sums AED and RUB into one figure. The AED amounts there are converted to ₽ only in this doc's/that file's comments, for illustrative reference, using the FX constants below — never at runtime or in any aggregation query. The pinned demo FX constants (not live rates; internally consistent with a 90 ₽/USD world):AED = 24.50 ₽,KZT = 0.175 ₽,CNY = 12.40 ₽— kept inreference/legend.pysolely as the fixed conversion basis for any doc/evidence sentence that quotes a non-₽ figure (e.g. FOB in CNY, or the fanout fixture's AED totals). If a future revision stores branch-local currencies in the main seed path, these are the constants it must use. - Generator seed constant stays
20260626; everything derives deterministically from it. Timestamps keep today's mechanics (relativeNOW()in serving demo,load_ts = now64()in vault seeds).
Target row counts for the rebuilt synthetic_seed.sql + satellites
(≈ 5 baseline days of orders; old values in parentheses):
| Object | Target | Was |
|---|---|---|
hub_store |
6 store codes, unchanged | 6 |
hub_customer |
2,500 = 500 dealers + 2,000 retail | 2,000 |
hub_product |
160 | 800 |
hub_order |
10,000 ≈ 5.1 baseline days: 8,900 mp + 280 site + 820 B2B (per-branch: msk 360, spb 180, ekb 130, dxb 75, ala 75 — §1 rates × 5.1, matches §7's ≈164/day) | 10,000 |
lnk_order_product rows |
≈ 14,600 (§2 shapes: mp ~1.05 lines, site ~1.3, B2B ~6) | ~25,000 |
hub_marking_code |
160 SKU GTINs + ~12,000 per-unit code sample (≈ one container), statuses issued 25 / in_circulation 60 / withdrawn 15 | per-product only |
hub_supplier |
30 | 40 |
Order dates spread uniformly over a ~122-hour (≈ 5.1-day) window ending at load time — 10,000 orders / 5.1 days ≈ 1,965 orders/day, exactly §1's baseline rate. Baseline days carry seasonal multiplier 1.0 by definition, so §4's monthly curves are deliberately not encoded in this seed: a 5-day snapshot cannot express a 12-month shape; the seasonality belongs to the long-horizon generator narrative, not the vault seed.
Customer→branch and order→channel assignments follow §1/§7 proportions; the
multiIf(number % 100 < …) slicing technique stays, only the cut points
move. Order record_source reflects the channel: mp__msk (marketplace
feed), site__msk, bitrix__<branch> / 1c__<branch> (B2B). The Postgres
OLTP hot-tier seed mirrors the same populations at smaller scale.
Machine-checkable assertions the generator rebuild must encode as tests — this list is the definition of "цифры взаимно согласованы":
- Annual revenue (Σ channels × 365 × seasonal avg 1.0) ∈ [3.5, 5.0] B ₽.
- Order-count mix: marketplaces 88–90%, B2B 7–9%, D2C 2–4%.
- Revenue mix: B2B 65–72% of ₽; marketplaces 27–33%.
- Order-weighted avg B2B check (all B2B branches together) ∈ [30k, 80k] ₽ — §1 puts it at ≈ 54.9k. Per-branch B2B avg checks span 45k (ala) to 90k (dxb): the RU + EAEU wholesale channels each sit inside [30k, 80k], while dxb's 90k export-pallet check sits above that band by design (§1) and is not a violation. Avg marketplace check ∈ [1.5k, 3.0k] ₽. The AOV distribution is bimodal with no channel average between 10k and 25k.
- Per SKU: FOB < landed < wholesale < marketplace-net < RRC (§5 ladder).
- Each seasonal curve's 12 multipliers average exactly 1.0.
- Every GTIN passes
is_valid_gtin13, prefix ∈ 460–469 — both the reference-catalog GTINs (minted viags1.make_gtin13) and the vault seed'sgs1_gtinliterals insynthetic_seed.sql, whose check digits are precomputed with the same GS1 mod-10 algorithm and pinned by the invariant tests. - Every
tnved_codeis one of the §3 headings, 10-digit zero-padded form. - Dealer accounts × ordering frequency ⇒ 150–200 B2B orders/day.
- Branch revenue shares sum to 100%; msk ∈ [55%, 65%].
- Faux-PII locale rules hold per jurisdiction (names / phone prefixes /
email TLDs per §8);
hash_diffidempotency andcustomer_hklinkage preserved. - Loyalty rows exist only for dealer customers in msk/spb/ekb;
loyalty_points≤ 3% of that dealer's trailing-quarter purchases.
- IoT / device telemetry — narrative only, never in data (domain.md §1).
- Inbound container receipts as shipment rows — the container storyline
stays in
excel__*manifests; a structured inbound leg onhub_shipmentis an operational-layer roadmap item, not a seed requirement. - KZ marketplace (Kaspi) channel for ala — ala stays B2B/EAEU wholesale in v1.
- Company-level P&L (OPEX, payroll) — unit economics stop at per-order contribution (§5).