diff --git a/config/contracts/order.v1.yaml b/config/contracts/order.v1.yaml
index f9b6c734..9b4beed1 100644
--- a/config/contracts/order.v1.yaml
+++ b/config/contracts/order.v1.yaml
@@ -19,11 +19,12 @@ fields:
- name: total_amount
type: float
required: true
- unit: USD
+ unit: RUB
- name: currency
type: enum
required: true
values:
+ - RUB
- USD
- EUR
- GBP
diff --git a/config/contracts/order.v2.yaml b/config/contracts/order.v2.yaml
index c701be79..348567b0 100644
--- a/config/contracts/order.v2.yaml
+++ b/config/contracts/order.v2.yaml
@@ -19,11 +19,12 @@ fields:
- name: total_amount
type: float
required: true
- unit: USD
+ unit: RUB
- name: currency
type: enum
required: true
values:
+ - RUB
- USD
- EUR
- GBP
diff --git a/docs/dataflow.html b/docs/dataflow.html
index 910328e9..eb290051 100644
--- a/docs/dataflow.html
+++ b/docs/dataflow.html
@@ -360,10 +360,10 @@
AgentFlow — движение данных
];
/* DV2 demo lane */
-const DV2={x:40,w:2280,y:1226,title:"Контур DV2.0 — Data Vault 2.0 на реальных данных X5",
+const DV2={x:40,w:2280,y:1226,title:"Контур DV2.0 — Data Vault 2.0 на синтетических данных",
chip:"демо · kind-кластер · хост 8 GB",
flow:[
- {t:"X5 Retail Hero CSV",c:"45.8M строк",d:"clients · products · purchases (4.5 GB), Kaggle-датасет"},
+ {t:"Синтетический генератор",c:"кухонная легенда · ₽",d:"clients · products · purchases · детерминир. seed, масштабируемый объём"},
{t:"loader.py",c:"pandas + clickhouse-driver",d:"чанки 50K · PartsThrottle (--max-active-parts) — backpressure по system.parts"},
{t:"ClickHouse",c:"25.5 · k8s",d:"Raw Vault: хабы · линки · сателлиты (ReplacingMergeTree, ветки __1c__/__bitrix__); merge-throttle в config.d"},
{t:"Business Vault",c:"SQL-вьюхи",d:"bv_order_canonical (argMax-дедуп) · bv_customer_mdm × 5 веток"},
diff --git a/docs/domain.md b/docs/domain.md
index 81ffcad0..d743b9dc 100644
--- a/docs/domain.md
+++ b/docs/domain.md
@@ -149,8 +149,10 @@ requirements:
the ops layer.
3. **Kill-the-five-programs triage.** Where is order X? Which orders are stuck
between confirmation and shipment longer than the stage SLA? Which failed
- events need a manual decision? These map to the three planned ops surfaces:
- **Order 360 timeline**, **stuck-orders worklist**, and **exception inbox**.
+ events need a manual decision? These map to the three ops surfaces —
+ **Order 360 timeline**, **stuck-orders worklist**, and **exception inbox** —
+ now live (`GET /v1/entity/order/{id}/timeline`, `/v1/ops/stuck-orders`,
+ `/v1/ops/exceptions`).
## 5. Reinterpretation dictionary
@@ -224,10 +226,10 @@ the operational-layer roadmap; everything else is live API surface.
| ------- | ------------------ | ------- |
| **Owner / CEO** | "Revenue today vs yesterday? Orders during the НГ peak? Is AOV holding after the price move?" | `GET /v1/metrics/revenue` · `order_count` · `avg_order_value`; `POST /v1/query` (NL: "top products this week") |
| **B2B account manager** | "What has dealer X ordered this quarter? Are they on track for the retro-bonus threshold? Which of my contacts has a birthday before March 8?" | `GET /v1/entity/user/{id}`; `POST /v1/query`; vault: `bv_customer_mdm`, `sat_customer_loyalty__bitrix__*` |
-| **E-com / marketplace manager** | "Site conversion this week? Are WB orders flowing or did the feed break? Top SKUs by orders during the sale event?" | `GET /v1/metrics/conversion_rate` · `active_sessions` · `error_rate`; `GET /v1/analytics/top-entities`; `POST /v1/query` |
+| **E-com / marketplace manager** | "Site conversion this week? Are WB orders flowing or did the feed break? Top SKUs by orders during the sale event?" | `GET /v1/metrics/conversion_rate` · `active_sessions` · `error_rate`; `GET /v1/admin/analytics/top-entities` (admin-key); `POST /v1/query` |
| **Operations manager** | "Everything about ORD-20260404-1001 — now, in one place. Which orders are stuck pre-shipment past SLA? What failed overnight and needs a human?" | `GET /v1/entity/order/{id}`; `/v1/alerts` (+history/test); `/v1/deadletter` (+replay/dismiss); `GET /v1/entity/order/{id}/timeline`; `GET /v1/ops/stuck-orders`; `GET /v1/ops/exceptions` (+stats/acknowledge/resolve) |
| **Category / procurement manager** | "Which SKUs sell fastest per branch? What is on hand vs reserved? When does the next container land?" | `GET /v1/search`; `GET /v1/entity/product/{id}`; `POST /v1/query`; vault: `sat_product_stock__wms__*`; container ETA — **planned** (today: `excel__*` manifests) |
-| **Data engineer / analyst** | "Which events move this metric? What is the contract and its staleness budget? What changed between contract v3 and v4? Are we inside SLO?" | `GET /v1/catalog`; `/v1/contracts/*` (+versions/diff/validate); `/v1/lineage/*`; `/v1/slo`; `/v1/analytics/*` |
+| **Data engineer / analyst** | "Which events move this metric? What is the contract and its staleness budget? What changed between contract v3 and v4? Are we inside SLO?" | `GET /v1/catalog`; `/v1/contracts/*` (+versions/diff/validate); `/v1/lineage/*`; `/v1/slo`; `/v1/admin/analytics/*` (admin-key) |
| **AI agent / integration** | Any of the above, programmatically — the agent is one consumer, not the product | Python/TS SDKs, MCP/LangChain/LlamaIndex integrations over the same API: `POST /v1/query` + `/v1/query/explain`, `/v1/entity/*`, `POST /v1/batch`, `/v1/webhooks`, `/v1/stream` |
| **PII officer (per jurisdiction)** | "Who can read dealer-contact PII in `dxb`? Prove RU data never crosses the border" | Not REST: DV2 governance — jurisdiction-scoped officer roles, column-limited analyst grants, per-branch row policies (`warehouse/agentflow/dv2/governance/`, ClickHouse and Postgres variants) |
diff --git a/docs/dv2-multi-branch/demo_evidence.md b/docs/dv2-multi-branch/demo_evidence.md
index efa3185a..28037103 100644
--- a/docs/dv2-multi-branch/demo_evidence.md
+++ b/docs/dv2-multi-branch/demo_evidence.md
@@ -170,7 +170,8 @@ SELECT count() FROM rv.lnk_order_product; -- 14853
basket profile: marketplace and D2C orders are predominantly single-item,
while the B2B dealer orders carry multi-line baskets, pulling the mean just
above 1. `hub_product` holds **160 SKU**; `hub_marking_code` holds **12,160**
-per-unit Chestny ZNAK marking codes (issued / in-circulation / withdrawn).
+Chestny ZNAK marking codes — **160** SKU-level GTIN templates + **12,000**
+per-unit codes (issued / in-circulation / withdrawn).
## 8. Business Vault — populated views with MDM conflict resolution
diff --git a/scripts/generate_contracts.py b/scripts/generate_contracts.py
index cc6f5a67..cb0a4332 100644
--- a/scripts/generate_contracts.py
+++ b/scripts/generate_contracts.py
@@ -92,7 +92,7 @@ class ContractSpec:
"values": [status.value for status in OrderStatus],
},
"total_amount": {
- "unit": "USD",
+ "unit": "RUB",
},
"currency": {
"type": "enum",
@@ -125,7 +125,7 @@ class ContractSpec:
"values": [status.value for status in OrderStatus],
},
"total_amount": {
- "unit": "USD",
+ "unit": "RUB",
},
"currency": {
"type": "enum",
diff --git a/src/ingestion/producers/event_producer.py b/src/ingestion/producers/event_producer.py
index 2d679f63..5dc31f45 100644
--- a/src/ingestion/producers/event_producer.py
+++ b/src/ingestion/producers/event_producer.py
@@ -46,17 +46,24 @@ class ProducerConfig(BaseSettings):
model_config = {"env_prefix": "PRODUCER_"}
+# Live catalog = the 10 pinned kitchen-appliance SKUs of the serving demo
+# (generator-spec.md §9, mirrored row-for-row in the duckdb/clickhouse seeds):
+# EN names, §3 category slugs, RUB retail prices, and each SKU's seeded
+# stock. PROD-001 (kettle) is the deliberately out-of-stock bestseller — the
+# oversell/freshness story needs it, so generate_product emits this stock
+# verbatim rather than randomising it.
+# (id, name, category slug, price ₽, stock_quantity)
PRODUCT_CATALOG = [
- ("PROD-001", "Wireless Headphones", "electronics", Decimal("79.99")),
- ("PROD-002", "Running Shoes", "footwear", Decimal("129.99")),
- ("PROD-003", "Coffee Maker", "kitchen", Decimal("49.99")),
- ("PROD-004", "Mechanical Keyboard", "electronics", Decimal("149.99")),
- ("PROD-005", "Yoga Mat", "fitness", Decimal("34.99")),
- ("PROD-006", "Backpack", "accessories", Decimal("89.99")),
- ("PROD-007", "Water Bottle", "fitness", Decimal("24.99")),
- ("PROD-008", "Desk Lamp", "home", Decimal("44.99")),
- ("PROD-009", "Bluetooth Speaker", "electronics", Decimal("59.99")),
- ("PROD-010", "Sunglasses", "accessories", Decimal("119.99")),
+ ("PROD-001", "Electric Kettle 1.7L 2200W", "kettles", Decimal("2190.00"), 0),
+ ("PROD-002", "Air Fryer Grill 5.5L", "grills", Decimal("5490.00"), 58),
+ ("PROD-003", "Immersion Blender Set 800W", "blenders", Decimal("2490.00"), 203),
+ ("PROD-004", "Stand Mixer 5L Planetary", "mixers", Decimal("6990.00"), 37),
+ ("PROD-005", "Drip Coffee Maker 1.2L", "coffee", Decimal("3490.00"), 94),
+ ("PROD-006", "Waffle Maker Double", "multibakers", Decimal("2290.00"), 142),
+ ("PROD-007", "Mini Chopper 500ml", "choppers", Decimal("1490.00"), 315),
+ ("PROD-008", "Cold-Press Juicer", "juicers", Decimal("4490.00"), 72),
+ ("PROD-009", "Digital Kitchen Scale 5kg", "scales", Decimal("990.00"), 421),
+ ("PROD-010", "Vacuum Sealer Compact", "vacuum-dry", Decimal("3290.00"), 167),
]
PAGES = ["/", "/products", "/cart", "/checkout", "/account", "/search"]
@@ -111,7 +118,7 @@ def generate_order() -> tuple[str, OrderEvent]:
status=OrderStatus.PENDING,
items=items,
total_amount=total,
- currency=Currency.USD,
+ currency=Currency.RUB,
)
return "orders.raw", event
@@ -126,8 +133,10 @@ def generate_payment(order_id: str | None = None) -> tuple[str, PaymentEvent]:
payment_id=f"PAY-{_uuid()[:8]}",
order_id=oid,
user_id=f"USR-{random.randint(10000, 99999)}",
- amount=Decimal(str(round(random.uniform(20, 500), 2))),
- currency=Currency.USD,
+ # ₽-scale single-appliance retail range (§3 RRC band 790–7990),
+ # deliberately below the 10k–25k bimodality dead-zone (§12 #4).
+ amount=Decimal(str(round(random.uniform(790, 7990), 2))),
+ currency=Currency.RUB,
method=random.choice(list(PaymentMethod)),
status="initiated",
)
@@ -155,6 +164,7 @@ def generate_click() -> tuple[str, ClickstreamEvent]:
def generate_product() -> tuple[str, ProductEvent]:
product = random.choice(PRODUCT_CATALOG)
+ stock_quantity = product[4]
event = ProductEvent(
event_id=_uuid(),
event_type=EventType.PRODUCT_UPDATED,
@@ -164,9 +174,12 @@ def generate_product() -> tuple[str, ProductEvent]:
name=product[1],
category=product[2],
price=product[3],
- currency=Currency.USD,
- in_stock=random.random() > 0.1,
- stock_quantity=random.randint(0, 500),
+ currency=Currency.RUB,
+ # Emit the SKU's seeded stock so the out-of-stock bestseller (PROD-001)
+ # stays out of stock even under the live feed — the oversell story
+ # depends on it, and random stock would clobber the seeded витрина.
+ in_stock=stock_quantity > 0,
+ stock_quantity=stock_quantity,
)
return "products.cdc", event
diff --git a/src/ingestion/schemas/events.py b/src/ingestion/schemas/events.py
index 71a34437..4eaf08c3 100644
--- a/src/ingestion/schemas/events.py
+++ b/src/ingestion/schemas/events.py
@@ -38,6 +38,11 @@ class CdcSource(StrEnum):
class Currency(StrEnum):
+ # RUB is the house currency of the kitchen-appliance importer legend
+ # (docs/domain.md §1, generator-spec.md §10); the live producer and every
+ # serving sink default to it. USD/EUR/GBP stay valid enum members so
+ # historical fixtures and cross-border edge cases still parse.
+ RUB = "RUB"
USD = "USD"
EUR = "EUR"
GBP = "GBP"
@@ -96,7 +101,7 @@ class OrderEvent(BaseEvent):
status: OrderStatus
items: list[OrderItem] = Field(..., min_length=1)
total_amount: Decimal = Field(..., gt=0)
- currency: Currency = Currency.USD
+ currency: Currency = Currency.RUB
@field_validator("total_amount")
@classmethod
@@ -121,7 +126,7 @@ class PaymentEvent(BaseEvent):
order_id: str = Field(..., pattern=r"^ORD-\d{8}-\d{4,}$")
user_id: str
amount: Decimal = Field(..., gt=0)
- currency: Currency = Currency.USD
+ currency: Currency = Currency.RUB
method: PaymentMethod
status: str = Field(..., pattern=r"^(initiated|completed|failed|refunded)$")
failure_reason: str | None = None
@@ -142,6 +147,6 @@ class ProductEvent(BaseEvent):
name: str = Field(..., min_length=1, max_length=500)
category: str
price: Decimal = Field(..., ge=0)
- currency: Currency = Currency.USD
+ currency: Currency = Currency.RUB
in_stock: bool
stock_quantity: int = Field(..., ge=0)
diff --git a/src/processing/clickhouse_sink.py b/src/processing/clickhouse_sink.py
index 14e8f1d9..4c8b66ef 100644
--- a/src/processing/clickhouse_sink.py
+++ b/src/processing/clickhouse_sink.py
@@ -110,7 +110,7 @@ def upsert_order(self, event: dict) -> None:
"user_id": event["user_id"],
"status": event["status"],
"total_amount": float(event["total_amount"]),
- "currency": event.get("currency", "USD"),
+ "currency": event.get("currency", "RUB"),
"created_at": datetime.fromisoformat(event["timestamp"]),
}
],
diff --git a/src/processing/iceberg_sink.py b/src/processing/iceberg_sink.py
index 9128544c..e0eef8f9 100644
--- a/src/processing/iceberg_sink.py
+++ b/src/processing/iceberg_sink.py
@@ -264,7 +264,7 @@ def _normalize_order(self, record: dict[str, Any]) -> dict[str, Any]:
"user_id": str(record["user_id"]),
"status": str(record["status"]),
"total_amount": float(record["total_amount"]),
- "currency": str(record.get("currency", "USD")),
+ "currency": str(record.get("currency", "RUB")),
"item_count": int(
derived.get(
"item_count",
@@ -291,7 +291,7 @@ def _normalize_payment(self, record: dict[str, Any]) -> dict[str, Any]:
"order_id": str(record["order_id"]),
"user_id": str(record["user_id"]),
"amount": float(record["amount"]),
- "currency": str(record.get("currency", "USD")),
+ "currency": str(record.get("currency", "RUB")),
"method": str(record["method"]),
"status": str(record["status"]),
"risk_score": (float(derived["risk_score"]) if "risk_score" in derived else None),
@@ -331,7 +331,7 @@ def _normalize_inventory(self, record: dict[str, Any]) -> dict[str, Any]:
"name": str(record["name"]),
"category": str(record["category"]),
"price": float(record["price"]),
- "currency": str(record.get("currency", "USD")),
+ "currency": str(record.get("currency", "RUB")),
"in_stock": bool(record["in_stock"]),
"stock_quantity": int(record["stock_quantity"]),
"created_at": self._coerce_timestamp(record.get("timestamp")),
diff --git a/src/processing/local_pipeline.py b/src/processing/local_pipeline.py
index 1f348451..fc5f00fe 100644
--- a/src/processing/local_pipeline.py
+++ b/src/processing/local_pipeline.py
@@ -49,7 +49,7 @@ def _ensure_tables(conn: duckdb.DuckDBPyConnection) -> None:
user_id VARCHAR,
status VARCHAR,
total_amount DECIMAL(10,2),
- currency VARCHAR DEFAULT 'USD',
+ currency VARCHAR DEFAULT 'RUB',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
@@ -327,7 +327,7 @@ def _upsert_order(conn: duckdb.DuckDBPyConnection, event: dict) -> None:
event["user_id"],
event["status"],
float(event["total_amount"]),
- event.get("currency", "USD"),
+ event.get("currency", "RUB"),
datetime.fromisoformat(event["timestamp"]),
],
)
diff --git a/tests/unit/test_event_schemas.py b/tests/unit/test_event_schemas.py
index f1d73831..372ce969 100644
--- a/tests/unit/test_event_schemas.py
+++ b/tests/unit/test_event_schemas.py
@@ -14,7 +14,7 @@ def test_order_valid_minimal(sample_order_event) -> None:
event = OrderEvent.model_validate(payload)
- assert event.currency is Currency.USD
+ assert event.currency is Currency.RUB
assert event.total_amount == Decimal("209.97")
assert event.items[0].product_id == "PROD-001"
@@ -50,13 +50,13 @@ def test_order_rejects_negative_amount(sample_order_event) -> None:
)
-def test_order_currency_defaults_to_usd(sample_order_event) -> None:
+def test_order_currency_defaults_to_rub(sample_order_event) -> None:
payload = dict(sample_order_event)
payload.pop("currency")
event = OrderEvent.model_validate(payload)
- assert event.currency is Currency.USD
+ assert event.currency is Currency.RUB
def test_order_rejects_unknown_currency(sample_order_event) -> None:
diff --git a/warehouse/agentflow/dv2/business_vault/bv_order_canonical.sql b/warehouse/agentflow/dv2/business_vault/bv_order_canonical.sql
index ff17cd3a..c64000e2 100644
--- a/warehouse/agentflow/dv2/business_vault/bv_order_canonical.sql
+++ b/warehouse/agentflow/dv2/business_vault/bv_order_canonical.sql
@@ -32,8 +32,8 @@ WITH
UNION ALL
SELECT order_hk, order_date, channel, order_status, total_amount, load_ts
FROM rv.sat_order_header__bitrix__ala WHERE is_deleted = 0
- -- 1C / X5 Retail Hero order headers (per-branch), so real X5 volume
- -- flows through to bv_order_canonical and the branch_pnl mart.
+ -- 1C order headers (per-branch), so the full order volume flows
+ -- through to bv_order_canonical and the branch_pnl mart.
UNION ALL
SELECT order_hk, order_date, channel, order_status, total_amount, load_ts
FROM rv.sat_order_header__1c__msk WHERE is_deleted = 0
diff --git a/warehouse/agentflow/dv2/dbt/models/marts/branch_pnl.sql b/warehouse/agentflow/dv2/dbt/models/marts/branch_pnl.sql
index 4f930599..8f647778 100644
--- a/warehouse/agentflow/dv2/dbt/models/marts/branch_pnl.sql
+++ b/warehouse/agentflow/dv2/dbt/models/marts/branch_pnl.sql
@@ -15,9 +15,14 @@ SELECT
sum(toFloat64(subtotal_amount)) AS net_revenue,
sum(toFloat64(discount_amount)) AS discounts,
sum(toFloat64(shipping_cost)) AS shipping,
- countIf(order_status = 'returned') AS returned_orders,
+ -- The legend has no dedicated 'returned' status (§2 vocabulary is
+ -- pending/confirmed/shipped/delivered/cancelled). Its terminal-negative
+ -- bucket 'cancelled' carries both cancellations and marketplace returns
+ -- (§2 "cancel/return allowance"; satellite_seed.sql — "marketplace cancels
+ -- dominate the last bucket"), so returns are measured off 'cancelled'.
+ countIf(order_status = 'cancelled') AS returned_orders,
sumIf(toFloat64(total_amount),
- order_status = 'returned') AS returned_value,
+ order_status = 'cancelled') AS returned_value,
round(sum(toFloat64(tax_amount)) /
nullIf(sum(toFloat64(subtotal_amount)), 0),
4) AS effective_tax_rate
diff --git a/warehouse/agentflow/dv2/dbt/models/marts/customer_360.sql b/warehouse/agentflow/dv2/dbt/models/marts/customer_360.sql
index ec4cbd66..08ffaffe 100644
--- a/warehouse/agentflow/dv2/dbt/models/marts/customer_360.sql
+++ b/warehouse/agentflow/dv2/dbt/models/marts/customer_360.sql
@@ -1,6 +1,6 @@
{#
customer_bk first in the sort key: the mart's serving pattern is a point
- lookup by business key (load-test 03_customer360_point). At X5 scale the
+ lookup by business key (load-test 03_customer360_point). At benchmark scale the
old (branch, customer_hk) key meant every bk lookup full-scanned the mart:
p99 250-470 ms vs the 200 ms point budget.
@@ -49,9 +49,12 @@ order_agg AS (
sum(toFloat64(total_amount)) AS lifetime_value,
min(order_date) AS first_order_dt,
max(order_date) AS last_order_dt,
- countIf(order_status = 'returned') AS returned_orders,
+ -- Returns off the 'cancelled' bucket: no dedicated 'returned' status
+ -- exists in the §2 vocabulary; 'cancelled' folds cancellations and
+ -- marketplace returns together (satellite_seed.sql).
+ countIf(order_status = 'cancelled') AS returned_orders,
sumIf(toFloat64(total_amount),
- order_status = 'returned') AS returned_value
+ order_status = 'cancelled') AS returned_value
FROM {{ source('rv', 'bv_order_canonical_mat') }}
WHERE customer_hk != toFixedString('', 16)
GROUP BY customer_hk, branch
diff --git a/warehouse/agentflow/dv2/dbt/models/marts/returns_velocity.sql b/warehouse/agentflow/dv2/dbt/models/marts/returns_velocity.sql
index 175071e4..0b12a4a1 100644
--- a/warehouse/agentflow/dv2/dbt/models/marts/returns_velocity.sql
+++ b/warehouse/agentflow/dv2/dbt/models/marts/returns_velocity.sql
@@ -11,13 +11,17 @@ SELECT
channel AS channel,
toStartOfWeek(order_date) AS week,
count() AS orders,
- countIf(order_status = 'returned') AS returned_orders,
- round(countIf(order_status = 'returned') * 1.0 /
+ -- Returns are measured off the 'cancelled' bucket: the legend has no
+ -- dedicated 'returned' status (§2), and its terminal-negative bucket folds
+ -- cancellations and marketplace returns together (§2 "cancel/return
+ -- allowance"; satellite_seed.sql — "marketplace cancels dominate").
+ countIf(order_status = 'cancelled') AS returned_orders,
+ round(countIf(order_status = 'cancelled') * 1.0 /
count(), 4) AS return_rate,
sumIf(toFloat64(total_amount),
- order_status = 'returned') AS returned_value,
+ order_status = 'cancelled') AS returned_value,
sumIf(toFloat64(tax_amount),
- order_status = 'returned') AS returned_tax_unrecovered
+ order_status = 'cancelled') AS returned_tax_unrecovered
FROM {{ source('rv', 'bv_order_canonical_mat') }}
WHERE order_date IS NOT NULL
AND channel IS NOT NULL
diff --git a/warehouse/agentflow/dv2/postgres/03_business_vault.sql b/warehouse/agentflow/dv2/postgres/03_business_vault.sql
index 2233d077..d092b3aa 100644
--- a/warehouse/agentflow/dv2/postgres/03_business_vault.sql
+++ b/warehouse/agentflow/dv2/postgres/03_business_vault.sql
@@ -35,8 +35,8 @@ WITH
UNION ALL
SELECT order_hk, order_date, channel, order_status, total_amount, load_ts
FROM rv.sat_order_header__bitrix__ala WHERE is_deleted = 0
- -- 1C / X5 Retail Hero order headers (per-branch), so real X5 volume
- -- flows through to bv_order_canonical and the branch_pnl mart.
+ -- 1C order headers (per-branch), so the full order volume flows
+ -- through to bv_order_canonical and the branch_pnl mart.
UNION ALL
SELECT order_hk, order_date, channel, order_status, total_amount, load_ts
FROM rv.sat_order_header__1c__msk WHERE is_deleted = 0
diff --git a/warehouse/agentflow/dv2/postgres/smoke/README.md b/warehouse/agentflow/dv2/postgres/smoke/README.md
index 3326b739..392afeda 100644
--- a/warehouse/agentflow/dv2/postgres/smoke/README.md
+++ b/warehouse/agentflow/dv2/postgres/smoke/README.md
@@ -38,16 +38,19 @@ The standalone no-Docker recipe used for the governance verifies
## Expected canonical output (derived by hand from `order_smoke_seed.sql`)
Eight orders, one canonical row each. RUB amounts; VAT is RU 20 % / UAE 5 %.
+`total_amount` is **net of VAT** — it mirrors the pricing subtotal (the
+production `satellite_seed.sql` convention: "subtotal mirrors header.total_amount
+(pre-tax)"); tax is a separate column, never folded into the total.
| order_bk | branch | channel | status | total | subtotal | tax | ship | wb_status | header_src | pricing_src | mkt_src |
|---|---|---|---|---|---|---|---|---|---|---|---|
-| mp__msk__0000001 | msk | marketplace | delivered | 2400.00 | 2000.00 | 400.00 | 199.00 | delivered | bitrix__msk | 1c__msk | wb__msk |
-| mp__msk__0000002 | msk | marketplace | **shipped** | 2600.00 | **2166.67** | **433.33** | 199.00 | **delivering** | bitrix__msk | 1c__msk | wb__msk |
-| site__msk__0008901 | msk | d2c | delivered | 3500.00 | 3000.00 | 600.00 | 299.00 | — | bitrix__msk | 1c__msk | — |
-| bitrix__msk__0009181 | msk | b2b | **confirmed** | 60000.00 | 50000.00 | 10000.00 | 500.00 | — | bitrix__msk | 1c__msk | — |
-| bitrix__spb__0009541 | spb | b2b | delivered | 48000.00 | 40000.00 | 8000.00 | 800.00 | — | bitrix__spb | 1c__spb | — |
-| bitrix__ekb__0009721 | ekb | b2b | delivered | 36000.00 | 30000.00 | 6000.00 | 500.00 | — | bitrix__ekb | 1c__ekb | — |
-| bitrix__dxb__0009851 | dxb | b2b | delivered | 42000.00 | 40000.00 | **2000.00** | 500.00 | — | bitrix__dxb | 1c__dxb | — |
+| mp__msk__0000001 | msk | marketplace | delivered | 2000.00 | 2000.00 | 400.00 | 199.00 | delivered | bitrix__msk | 1c__msk | wb__msk |
+| mp__msk__0000002 | msk | marketplace | **shipped** | **2166.67** | **2166.67** | **433.33** | 199.00 | **delivering** | bitrix__msk | 1c__msk | wb__msk |
+| site__msk__0008901 | msk | d2c | delivered | 3000.00 | 3000.00 | 600.00 | 299.00 | — | bitrix__msk | 1c__msk | — |
+| bitrix__msk__0009181 | msk | b2b | **confirmed** | 50000.00 | 50000.00 | 10000.00 | 500.00 | — | bitrix__msk | 1c__msk | — |
+| bitrix__spb__0009541 | spb | b2b | delivered | 40000.00 | 40000.00 | 8000.00 | 800.00 | — | bitrix__spb | 1c__spb | — |
+| bitrix__ekb__0009721 | ekb | b2b | delivered | 30000.00 | 30000.00 | 6000.00 | 500.00 | — | bitrix__ekb | 1c__ekb | — |
+| bitrix__dxb__0009851 | dxb | b2b | delivered | 40000.00 | 40000.00 | **2000.00** | 500.00 | — | bitrix__dxb | 1c__dxb | — |
| bitrix__ala__0009925 | ala | b2b | cancelled | 30000.00 | **—** | **—** | **—** | — | bitrix__ala | **—** | — |
Bold cells are the invariants the smoke targets:
@@ -69,7 +72,8 @@ Bold cells are the invariants the smoke targets:
- **Per-jurisdiction VAT** — dxb effective rate `tax/subtotal = 5 %` (UAE), the
RU branches `20 %`.
- **Branch derivation** — `split_part(record_source,'__',2)` yields
- msk×4 / spb / ekb / dxb / ala; `total_amount` sums to `224500.00`.
+ msk×4 / spb / ekb / dxb / ala; `total_amount` (net of VAT) sums to
+ `197166.67`.
## Honest scope
diff --git a/warehouse/agentflow/dv2/postgres/smoke/order_smoke_seed.sql b/warehouse/agentflow/dv2/postgres/smoke/order_smoke_seed.sql
index 034c7395..e4cb3d7b 100644
--- a/warehouse/agentflow/dv2/postgres/smoke/order_smoke_seed.sql
+++ b/warehouse/agentflow/dv2/postgres/smoke/order_smoke_seed.sql
@@ -104,31 +104,33 @@ ON CONFLICT (link_hk) DO NOTHING;
-- hash_diff distinguishes SCD2 versions (msk O2 v1/v2, O4 v1/v2).
INSERT INTO rv.sat_order_header__bitrix__msk
(order_hk, load_ts, hash_diff, order_date, channel, order_status, total_amount, is_deleted) VALUES
- -- O1: single delivered marketplace header
- (decode(md5('mp__msk__0000001'),'hex'), '2026-07-01 09:00:00', decode(md5('mp__msk__0000001|hdr|v1'),'hex'), '2026-06-28 14:12:00', 'marketplace', 'delivered', 2400.00, 0),
- -- O2: two versions — pending@09:00 then shipped@12:00 (latest wins)
- (decode(md5('mp__msk__0000002'),'hex'), '2026-07-01 09:00:00', decode(md5('mp__msk__0000002|hdr|v1'),'hex'), '2026-06-29 10:05:00', 'marketplace', 'pending', 2600.00, 0),
- (decode(md5('mp__msk__0000002'),'hex'), '2026-07-01 12:00:00', decode(md5('mp__msk__0000002|hdr|v2'),'hex'), '2026-06-29 10:05:00', 'marketplace', 'shipped', 2600.00, 0),
+ -- O1: single delivered marketplace header. total_amount = subtotal (net of
+ -- VAT), mirroring satellite_seed.sql — tax lives only in the pricing sat.
+ (decode(md5('mp__msk__0000001'),'hex'), '2026-07-01 09:00:00', decode(md5('mp__msk__0000001|hdr|v1'),'hex'), '2026-06-28 14:12:00', 'marketplace', 'delivered', 2000.00, 0),
+ -- O2: two versions — pending@09:00 then shipped@12:00 (latest wins). Net
+ -- totals track each version's pricing subtotal (2100 -> 2166.67).
+ (decode(md5('mp__msk__0000002'),'hex'), '2026-07-01 09:00:00', decode(md5('mp__msk__0000002|hdr|v1'),'hex'), '2026-06-29 10:05:00', 'marketplace', 'pending', 2100.00, 0),
+ (decode(md5('mp__msk__0000002'),'hex'), '2026-07-01 12:00:00', decode(md5('mp__msk__0000002|hdr|v2'),'hex'), '2026-06-29 10:05:00', 'marketplace', 'shipped', 2166.67, 0),
-- O3: D2C delivered
- (decode(md5('site__msk__0008901'),'hex'), '2026-07-01 10:00:00', decode(md5('site__msk__0008901|hdr|v1'),'hex'), '2026-06-30 18:40:00', 'd2c', 'delivered', 3500.00, 0),
+ (decode(md5('site__msk__0008901'),'hex'), '2026-07-01 10:00:00', decode(md5('site__msk__0008901|hdr|v1'),'hex'), '2026-06-30 18:40:00', 'd2c', 'delivered', 3000.00, 0),
-- O4: active confirmed@10:00, then a soft-delete tombstone@14:00 (is_deleted=1, must NOT win)
- (decode(md5('bitrix__msk__0009181'),'hex'), '2026-07-01 10:00:00', decode(md5('bitrix__msk__0009181|hdr|v1'),'hex'), '2026-06-27 09:15:00', 'b2b', 'confirmed', 60000.00, 0),
- (decode(md5('bitrix__msk__0009181'),'hex'), '2026-07-01 14:00:00', decode(md5('bitrix__msk__0009181|hdr|v2'),'hex'), '2026-06-27 09:15:00', 'b2b', 'cancelled', 60000.00, 1)
+ (decode(md5('bitrix__msk__0009181'),'hex'), '2026-07-01 10:00:00', decode(md5('bitrix__msk__0009181|hdr|v1'),'hex'), '2026-06-27 09:15:00', 'b2b', 'confirmed', 50000.00, 0),
+ (decode(md5('bitrix__msk__0009181'),'hex'), '2026-07-01 14:00:00', decode(md5('bitrix__msk__0009181|hdr|v2'),'hex'), '2026-06-27 09:15:00', 'b2b', 'cancelled', 50000.00, 1)
ON CONFLICT (order_hk, load_ts) DO NOTHING;
INSERT INTO rv.sat_order_header__bitrix__spb
(order_hk, load_ts, hash_diff, order_date, channel, order_status, total_amount, is_deleted) VALUES
- (decode(md5('bitrix__spb__0009541'),'hex'), '2026-07-01 10:00:00', decode(md5('bitrix__spb__0009541|hdr|v1'),'hex'), '2026-06-26 11:00:00', 'b2b', 'delivered', 48000.00, 0)
+ (decode(md5('bitrix__spb__0009541'),'hex'), '2026-07-01 10:00:00', decode(md5('bitrix__spb__0009541|hdr|v1'),'hex'), '2026-06-26 11:00:00', 'b2b', 'delivered', 40000.00, 0)
ON CONFLICT (order_hk, load_ts) DO NOTHING;
INSERT INTO rv.sat_order_header__bitrix__ekb
(order_hk, load_ts, hash_diff, order_date, channel, order_status, total_amount, is_deleted) VALUES
- (decode(md5('bitrix__ekb__0009721'),'hex'), '2026-07-01 10:00:00', decode(md5('bitrix__ekb__0009721|hdr|v1'),'hex'), '2026-06-25 16:30:00', 'b2b', 'delivered', 36000.00, 0)
+ (decode(md5('bitrix__ekb__0009721'),'hex'), '2026-07-01 10:00:00', decode(md5('bitrix__ekb__0009721|hdr|v1'),'hex'), '2026-06-25 16:30:00', 'b2b', 'delivered', 30000.00, 0)
ON CONFLICT (order_hk, load_ts) DO NOTHING;
INSERT INTO rv.sat_order_header__bitrix__dxb
(order_hk, load_ts, hash_diff, order_date, channel, order_status, total_amount, is_deleted) VALUES
- (decode(md5('bitrix__dxb__0009851'),'hex'), '2026-07-01 10:00:00', decode(md5('bitrix__dxb__0009851|hdr|v1'),'hex'), '2026-06-24 08:20:00', 'b2b', 'delivered', 42000.00, 0)
+ (decode(md5('bitrix__dxb__0009851'),'hex'), '2026-07-01 10:00:00', decode(md5('bitrix__dxb__0009851|hdr|v1'),'hex'), '2026-06-24 08:20:00', 'b2b', 'delivered', 40000.00, 0)
ON CONFLICT (order_hk, load_ts) DO NOTHING;
INSERT INTO rv.sat_order_header__bitrix__ala
@@ -140,7 +142,7 @@ ON CONFLICT (order_hk, load_ts) DO NOTHING;
-- collapse across sources — the newer Bitrix shipped@12:00 row must still win.
INSERT INTO rv.sat_order_header__1c__msk
(order_hk, load_ts, hash_diff, order_date, channel, order_status, total_amount, is_deleted) VALUES
- (decode(md5('mp__msk__0000002'),'hex'), '2026-07-01 08:00:00', decode(md5('mp__msk__0000002|hdr|1c'),'hex'), '2026-06-29 10:05:00', 'marketplace', 'pending', 2350.00, 0)
+ (decode(md5('mp__msk__0000002'),'hex'), '2026-07-01 08:00:00', decode(md5('mp__msk__0000002|hdr|1c'),'hex'), '2026-06-29 10:05:00', 'marketplace', 'pending', 2050.00, 0)
ON CONFLICT (order_hk, load_ts) DO NOTHING;
-- ============ ORDER PRICING (1C — RUB, per-jurisdiction VAT) ============
diff --git a/warehouse/agentflow/dv2/postgres/smoke/verify_bv_order.sh b/warehouse/agentflow/dv2/postgres/smoke/verify_bv_order.sh
index 61a20d98..b58f4696 100644
--- a/warehouse/agentflow/dv2/postgres/smoke/verify_bv_order.sh
+++ b/warehouse/agentflow/dv2/postgres/smoke/verify_bv_order.sh
@@ -55,12 +55,12 @@ assert_eq "no branch escapes the five jurisdictions" "0" \
"SELECT count(*) FROM $BV WHERE $SCOPE AND branch NOT IN ('msk','spb','ekb','dxb','ala')"
assert_eq "customer + store links all resolved" "8" \
"SELECT count(*) FILTER (WHERE customer_hk IS NOT NULL AND store_hk IS NOT NULL) FROM $BV WHERE $SCOPE"
-assert_eq "total_amount sum (RUB)" "224500.00" \
+assert_eq "total_amount sum (RUB, net of VAT)" "197166.67" \
"SELECT sum(total_amount) FROM $BV WHERE $SCOPE"
echo
echo "=== SCD2: latest load_ts wins ==="
-assert_eq "O2 header collapses across UNION to newest Bitrix version" "shipped|2600.00" \
+assert_eq "O2 header collapses across UNION to newest Bitrix version" "shipped|2166.67" \
"SELECT order_status||'|'||total_amount FROM $BV WHERE order_bk='mp__msk__0000002'"
assert_eq "O2 pricing collapses to newest 1C version" "2166.67|433.33" \
"SELECT subtotal_amount||'|'||tax_amount FROM $BV WHERE order_bk='mp__msk__0000002'"
diff --git a/warehouse/agentflow/dv2/postgres_oltp/fanout/02_seed.sql b/warehouse/agentflow/dv2/postgres_oltp/fanout/02_seed.sql
index 97e5b646..8fec0cc3 100644
--- a/warehouse/agentflow/dv2/postgres_oltp/fanout/02_seed.sql
+++ b/warehouse/agentflow/dv2/postgres_oltp/fanout/02_seed.sql
@@ -21,37 +21,41 @@ INSERT INTO customers (customer_id, first_name, last_name, email, phone) VALUES
('msk-c-010', 'Tatiana','Fedorova','fedorova@example.ru','+74951110010')
ON CONFLICT (customer_id) DO NOTHING;
+-- Status vocabulary = generator-spec.md §2 ladder (pending / confirmed /
+-- shipped / delivered / cancelled). msk amounts sit in the marketplace/d2c
+-- retail bands (1.5k-5k ₽) with a few b2b tickets (30k-80k ₽); none fall in
+-- the 10k-25k bimodality dead-zone (§12 #4).
INSERT INTO orders (order_id, customer_id, status, total, currency) VALUES
- ('msk-o-001','msk-c-001','paid', 4500.00,'RUB'),
- ('msk-o-002','msk-c-001','paid', 2100.00,'RUB'),
- ('msk-o-003','msk-c-002','paid', 8900.00,'RUB'),
- ('msk-o-004','msk-c-003','pending', 1200.00,'RUB'),
- ('msk-o-005','msk-c-003','paid', 6700.00,'RUB'),
- ('msk-o-006','msk-c-004','paid', 3300.00,'RUB'),
- ('msk-o-007','msk-c-004','refunded', 1850.00,'RUB'),
- ('msk-o-008','msk-c-005','paid', 12500.00,'RUB'),
- ('msk-o-009','msk-c-005','paid', 2400.00,'RUB'),
- ('msk-o-010','msk-c-006','pending', 5600.00,'RUB'),
- ('msk-o-011','msk-c-006','paid', 7800.00,'RUB'),
- ('msk-o-012','msk-c-007','paid', 1900.00,'RUB'),
- ('msk-o-013','msk-c-007','paid', 15300.00,'RUB'),
- ('msk-o-014','msk-c-008','paid', 4100.00,'RUB'),
- ('msk-o-015','msk-c-008','paid', 6200.00,'RUB'),
- ('msk-o-016','msk-c-009','paid', 2700.00,'RUB'),
- ('msk-o-017','msk-c-009','pending', 3400.00,'RUB'),
- ('msk-o-018','msk-c-009','paid', 8100.00,'RUB'),
- ('msk-o-019','msk-c-010','paid', 1100.00,'RUB'),
- ('msk-o-020','msk-c-010','paid', 9300.00,'RUB'),
- ('msk-o-021','msk-c-010','paid', 5700.00,'RUB'),
- ('msk-o-022','msk-c-001','paid', 3800.00,'RUB'),
- ('msk-o-023','msk-c-002','paid', 7400.00,'RUB'),
- ('msk-o-024','msk-c-003','paid', 2900.00,'RUB'),
- ('msk-o-025','msk-c-005','paid', 11200.00,'RUB'),
- ('msk-o-026','msk-c-007','paid', 4600.00,'RUB'),
- ('msk-o-027','msk-c-008','paid', 8500.00,'RUB'),
- ('msk-o-028','msk-c-009','paid', 3100.00,'RUB'),
- ('msk-o-029','msk-c-010','paid', 6900.00,'RUB'),
- ('msk-o-030','msk-c-004','paid', 2200.00,'RUB')
+ ('msk-o-001','msk-c-001','delivered', 2450.00,'RUB'),
+ ('msk-o-002','msk-c-001','delivered', 2100.00,'RUB'),
+ ('msk-o-003','msk-c-002','shipped', 2890.00,'RUB'),
+ ('msk-o-004','msk-c-003','pending', 1650.00,'RUB'),
+ ('msk-o-005','msk-c-003','delivered', 3300.00,'RUB'),
+ ('msk-o-006','msk-c-004','delivered', 2750.00,'RUB'),
+ ('msk-o-007','msk-c-004','cancelled', 1850.00,'RUB'),
+ ('msk-o-008','msk-c-005','delivered',42000.00,'RUB'),
+ ('msk-o-009','msk-c-005','delivered', 2400.00,'RUB'),
+ ('msk-o-010','msk-c-006','pending', 4600.00,'RUB'),
+ ('msk-o-011','msk-c-006','confirmed', 3800.00,'RUB'),
+ ('msk-o-012','msk-c-007','delivered', 1900.00,'RUB'),
+ ('msk-o-013','msk-c-007','delivered',58000.00,'RUB'),
+ ('msk-o-014','msk-c-008','delivered', 2650.00,'RUB'),
+ ('msk-o-015','msk-c-008','shipped', 4200.00,'RUB'),
+ ('msk-o-016','msk-c-009','delivered', 2700.00,'RUB'),
+ ('msk-o-017','msk-c-009','pending', 3400.00,'RUB'),
+ ('msk-o-018','msk-c-009','delivered', 2950.00,'RUB'),
+ ('msk-o-019','msk-c-010','delivered', 1600.00,'RUB'),
+ ('msk-o-020','msk-c-010','delivered', 4700.00,'RUB'),
+ ('msk-o-021','msk-c-010','delivered', 3900.00,'RUB'),
+ ('msk-o-022','msk-c-001','delivered', 2200.00,'RUB'),
+ ('msk-o-023','msk-c-002','shipped', 4400.00,'RUB'),
+ ('msk-o-024','msk-c-003','delivered', 2900.00,'RUB'),
+ ('msk-o-025','msk-c-005','delivered',71000.00,'RUB'),
+ ('msk-o-026','msk-c-007','delivered', 3600.00,'RUB'),
+ ('msk-o-027','msk-c-008','confirmed', 4850.00,'RUB'),
+ ('msk-o-028','msk-c-009','delivered', 3100.00,'RUB'),
+ ('msk-o-029','msk-c-010','delivered', 4900.00,'RUB'),
+ ('msk-o-030','msk-c-004','delivered', 2200.00,'RUB')
ON CONFLICT (order_id) DO NOTHING;
\c ops_dxb_db
@@ -67,25 +71,28 @@ INSERT INTO customers (customer_id, first_name, last_name, email, phone) VALUES
('dxb-c-008','Mariam', 'Al-Suwaidi','mariam@example.ae','+97150100008')
ON CONFLICT (customer_id) DO NOTHING;
+-- dxb is the b2b re-export branch (§1): every order is wholesale, priced in
+-- AED at the export-pallet scale (≈ 2.5k-5.1k AED ≈ 60k-125k ₽ at the §10 FX
+-- of 24.5 ₽/AED). §2 status ladder; no retail-scale tickets.
INSERT INTO orders (order_id, customer_id, status, total, currency) VALUES
- ('dxb-o-001','dxb-c-001','paid', 850.00,'AED'),
- ('dxb-o-002','dxb-c-001','paid', 1240.00,'AED'),
- ('dxb-o-003','dxb-c-002','paid', 420.00,'AED'),
- ('dxb-o-004','dxb-c-003','paid', 2300.00,'AED'),
- ('dxb-o-005','dxb-c-003','pending', 680.00,'AED'),
- ('dxb-o-006','dxb-c-004','paid', 1100.00,'AED'),
- ('dxb-o-007','dxb-c-005','paid', 1850.00,'AED'),
- ('dxb-o-008','dxb-c-005','paid', 590.00,'AED'),
- ('dxb-o-009','dxb-c-006','paid', 920.00,'AED'),
- ('dxb-o-010','dxb-c-006','refunded', 380.00,'AED'),
- ('dxb-o-011','dxb-c-007','paid', 2700.00,'AED'),
- ('dxb-o-012','dxb-c-007','paid', 750.00,'AED'),
- ('dxb-o-013','dxb-c-008','paid', 1450.00,'AED'),
- ('dxb-o-014','dxb-c-008','paid', 980.00,'AED'),
- ('dxb-o-015','dxb-c-001','paid', 310.00,'AED'),
- ('dxb-o-016','dxb-c-002','paid', 1670.00,'AED'),
- ('dxb-o-017','dxb-c-003','paid', 820.00,'AED'),
- ('dxb-o-018','dxb-c-004','paid', 2150.00,'AED'),
- ('dxb-o-019','dxb-c-005','paid', 460.00,'AED'),
- ('dxb-o-020','dxb-c-006','paid', 1320.00,'AED')
+ ('dxb-o-001','dxb-c-001','delivered', 3200.00,'AED'),
+ ('dxb-o-002','dxb-c-001','delivered', 4100.00,'AED'),
+ ('dxb-o-003','dxb-c-002','shipped', 2800.00,'AED'),
+ ('dxb-o-004','dxb-c-003','delivered', 4600.00,'AED'),
+ ('dxb-o-005','dxb-c-003','pending', 2500.00,'AED'),
+ ('dxb-o-006','dxb-c-004','delivered', 3400.00,'AED'),
+ ('dxb-o-007','dxb-c-005','delivered', 4900.00,'AED'),
+ ('dxb-o-008','dxb-c-005','delivered', 2650.00,'AED'),
+ ('dxb-o-009','dxb-c-006','delivered', 3700.00,'AED'),
+ ('dxb-o-010','dxb-c-006','cancelled', 2450.00,'AED'),
+ ('dxb-o-011','dxb-c-007','delivered', 5100.00,'AED'),
+ ('dxb-o-012','dxb-c-007','delivered', 2900.00,'AED'),
+ ('dxb-o-013','dxb-c-008','shipped', 4300.00,'AED'),
+ ('dxb-o-014','dxb-c-008','delivered', 3550.00,'AED'),
+ ('dxb-o-015','dxb-c-001','delivered', 2750.00,'AED'),
+ ('dxb-o-016','dxb-c-002','delivered', 4750.00,'AED'),
+ ('dxb-o-017','dxb-c-003','confirmed', 3150.00,'AED'),
+ ('dxb-o-018','dxb-c-004','delivered', 4400.00,'AED'),
+ ('dxb-o-019','dxb-c-005','delivered', 2600.00,'AED'),
+ ('dxb-o-020','dxb-c-006','delivered', 3900.00,'AED')
ON CONFLICT (order_id) DO NOTHING;
diff --git a/warehouse/agentflow/dv2/postgres_oltp/seed.sql b/warehouse/agentflow/dv2/postgres_oltp/seed.sql
index 7db41a7b..9f5e745c 100644
--- a/warehouse/agentflow/dv2/postgres_oltp/seed.sql
+++ b/warehouse/agentflow/dv2/postgres_oltp/seed.sql
@@ -52,6 +52,11 @@ CREATE TABLE IF NOT EXISTS ops_dxb.orders (
);
-- ============ Seed: 50 msk customers + 200 msk orders ============
+-- Channels / statuses / amounts mirror satellite_seed.sql (generator-spec.md
+-- §1/§2): msk carries the marketplace-dominant mix (marketplace 1.5k-3k, d2c
+-- 2k-5k, b2b 30k-80k ₽ net-of-VAT), status ladder pending/confirmed/shipped/
+-- delivered/cancelled at 8/10/12/62/8. Amounts stay clear of the 10k-25k
+-- bimodality dead-zone (§12 #4).
INSERT INTO ops_msk.customers (customer_id, first_name, last_name, email, phone)
SELECT
'CUST-MSK-' || lpad(n::text, 4, '0'),
@@ -67,13 +72,31 @@ SELECT
'OLTP-MSK-' || lpad(n::text, 6, '0'),
'CUST-MSK-' || lpad(((n % 50) + 1)::text, 4, '0'),
now() - (n || ' hour')::interval,
- (ARRAY['web','mobile','retail','call-center'])[(n % 4) + 1],
- (ARRAY['new','paid','shipped'])[(n % 3) + 1],
- (500 + (n * 31) % 24500)::numeric(18, 2)
+ CASE
+ WHEN n <= 186 THEN 'marketplace'
+ WHEN n <= 193 THEN 'd2c'
+ ELSE 'b2b'
+ END,
+ CASE
+ WHEN n % 100 < 8 THEN 'pending'
+ WHEN n % 100 < 18 THEN 'confirmed'
+ WHEN n % 100 < 30 THEN 'shipped'
+ WHEN n % 100 < 92 THEN 'delivered'
+ ELSE 'cancelled'
+ END,
+ CASE
+ WHEN n <= 186 THEN (1500 + (n * 17) % 1501)::numeric(18, 2) -- marketplace 1.5k-3.0k
+ WHEN n <= 193 THEN (2000 + (n * 23) % 3001)::numeric(18, 2) -- d2c 2.0k-5.0k
+ ELSE (30000 + (n * 137) % 50001)::numeric(18, 2) -- b2b msk 30k-80k
+ END
FROM generate_series(1, 200) AS n
ON CONFLICT (order_id) DO NOTHING;
-- ============ Seed: 20 dxb customers + 80 dxb orders ============
+-- dxb is the b2b re-export branch (generator-spec.md §1: no marketplace/D2C
+-- volume). All orders are 'b2b'; amounts follow the export-pallet band
+-- (60k-130k ₽ net, mirrors satellite_seed_all_branches.sql), well above the
+-- 10k-25k dead-zone.
INSERT INTO ops_dxb.customers (customer_id, first_name, last_name, email, phone)
SELECT
'CUST-DXB-' || lpad(n::text, 4, '0'),
@@ -89,8 +112,14 @@ SELECT
'OLTP-DXB-' || lpad(n::text, 6, '0'),
'CUST-DXB-' || lpad(((n % 20) + 1)::text, 4, '0'),
now() - (n || ' hour')::interval,
- (ARRAY['web','mobile','retail'])[(n % 3) + 1],
- (ARRAY['new','paid','shipped'])[(n % 3) + 1],
- (500 + (n * 31) % 24500)::numeric(18, 2)
+ 'b2b',
+ CASE
+ WHEN n % 100 < 8 THEN 'pending'
+ WHEN n % 100 < 18 THEN 'confirmed'
+ WHEN n % 100 < 30 THEN 'shipped'
+ WHEN n % 100 < 92 THEN 'delivered'
+ ELSE 'cancelled'
+ END,
+ (60000 + (n * 191) % 70001)::numeric(18, 2) -- b2b dxb export pallets 60k-130k
FROM generate_series(1, 80) AS n
ON CONFLICT (order_id) DO NOTHING;
diff --git a/warehouse/agentflow/dv2/reference/__init__.py b/warehouse/agentflow/dv2/reference/__init__.py
index 026da4dd..b1a2e942 100644
--- a/warehouse/agentflow/dv2/reference/__init__.py
+++ b/warehouse/agentflow/dv2/reference/__init__.py
@@ -1,9 +1,9 @@
"""AgentFlow DV2 supplier / product reference.
-A real, reproducible grocery reference (suppliers, products, GS1 marking
-codes, sourcing) that fills the catalog / tnved / marking slots the
-transactional X5 feed leaves empty, and is published to cloud object storage
-(a Hugging Face Dataset) as the project's genuine cloud component.
+A real, reproducible kitchen-appliance reference (suppliers, products, GS1
+marking codes, sourcing) that fills the catalog / tnved / marking slots the
+transactional order feed leaves empty, and is published to cloud object
+storage (a Hugging Face Dataset) as the project's genuine cloud component.
Public API:
build_reference -> deterministic reference tables