|
| 1 | +"""Machine-checkable consistency invariants from ``docs/generator-spec.md`` |
| 2 | +§12 — the definition of "цифры взаимно согласованы" for the B1 data rebuild. |
| 3 | +
|
| 4 | +Invariants #1-#10 are pure arithmetic over :mod:`legend`'s constants (no |
| 5 | +warehouse needed). #5/#7/#8 are also checked against the actual |
| 6 | +:func:`build_reference` output. #11/#12 (faux-PII locale rules, loyalty |
| 7 | +gating) are structural regression guards over the seed SQL text — a live |
| 8 | +ClickHouse/Postgres re-verify is B4's job, not this file's. |
| 9 | +""" |
| 10 | + |
| 11 | +from __future__ import annotations |
| 12 | + |
| 13 | +from pathlib import Path |
| 14 | + |
| 15 | +from warehouse.agentflow.dv2.reference import legend |
| 16 | +from warehouse.agentflow.dv2.reference.generator import build_reference |
| 17 | +from warehouse.agentflow.dv2.reference.gs1 import is_valid_gtin13 |
| 18 | +from warehouse.agentflow.dv2.reference.tnved import TNVED_HEADINGS |
| 19 | + |
| 20 | +DV2_ROOT = Path(__file__).resolve().parents[2] / "warehouse" / "agentflow" / "dv2" |
| 21 | + |
| 22 | +_MARKETPLACE_CHANNELS = {"marketplace_fbs"} |
| 23 | +_D2C_CHANNELS = {"d2c_site"} |
| 24 | +_B2B_CHANNELS = {"b2b_wholesale", "b2b_re_export", "b2b_eaeu"} |
| 25 | + |
| 26 | + |
| 27 | +def _annual_revenue_rub() -> float: |
| 28 | + daily = sum(orders * check for _, _, orders, check in legend.MASTER_MATRIX) |
| 29 | + return daily * 365 |
| 30 | + |
| 31 | + |
| 32 | +def _orders_by_group() -> dict[str, int]: |
| 33 | + totals = {"marketplace": 0, "d2c": 0, "b2b": 0} |
| 34 | + for channel, _, orders, _ in legend.MASTER_MATRIX: |
| 35 | + if channel in _MARKETPLACE_CHANNELS: |
| 36 | + totals["marketplace"] += orders |
| 37 | + elif channel in _D2C_CHANNELS: |
| 38 | + totals["d2c"] += orders |
| 39 | + else: |
| 40 | + totals["b2b"] += orders |
| 41 | + return totals |
| 42 | + |
| 43 | + |
| 44 | +def _revenue_by_group() -> dict[str, float]: |
| 45 | + totals = {"marketplace": 0.0, "d2c": 0.0, "b2b": 0.0} |
| 46 | + for channel, _, orders, check in legend.MASTER_MATRIX: |
| 47 | + revenue = orders * check |
| 48 | + if channel in _MARKETPLACE_CHANNELS: |
| 49 | + totals["marketplace"] += revenue |
| 50 | + elif channel in _D2C_CHANNELS: |
| 51 | + totals["d2c"] += revenue |
| 52 | + else: |
| 53 | + totals["b2b"] += revenue |
| 54 | + return totals |
| 55 | + |
| 56 | + |
| 57 | +# --- #1 annual revenue ------------------------------------------------------- |
| 58 | + |
| 59 | + |
| 60 | +def test_invariant_1_annual_revenue_in_corridor(): |
| 61 | + annual_b_rub = _annual_revenue_rub() / 1_000_000_000 |
| 62 | + assert 3.5 <= annual_b_rub <= 5.0 |
| 63 | + |
| 64 | + |
| 65 | +# --- #2 order-count mix ------------------------------------------------------ |
| 66 | + |
| 67 | + |
| 68 | +def test_invariant_2_order_count_mix(): |
| 69 | + totals = _orders_by_group() |
| 70 | + grand_total = sum(totals.values()) |
| 71 | + marketplace_pct = 100 * totals["marketplace"] / grand_total |
| 72 | + b2b_pct = 100 * totals["b2b"] / grand_total |
| 73 | + d2c_pct = 100 * totals["d2c"] / grand_total |
| 74 | + assert 88 <= marketplace_pct <= 90 |
| 75 | + assert 7 <= b2b_pct <= 9 |
| 76 | + assert 2 <= d2c_pct <= 4 |
| 77 | + |
| 78 | + |
| 79 | +# --- #3 revenue mix ----------------------------------------------------------- |
| 80 | + |
| 81 | + |
| 82 | +def test_invariant_3_revenue_mix(): |
| 83 | + totals = _revenue_by_group() |
| 84 | + grand_total = sum(totals.values()) |
| 85 | + b2b_pct = 100 * totals["b2b"] / grand_total |
| 86 | + marketplace_pct = 100 * totals["marketplace"] / grand_total |
| 87 | + assert 65 <= b2b_pct <= 72 |
| 88 | + assert 27 <= marketplace_pct <= 33 |
| 89 | + |
| 90 | + |
| 91 | +# --- #4 bimodal AOV ----------------------------------------------------------- |
| 92 | + |
| 93 | + |
| 94 | +def test_invariant_4_bimodal_avg_checks_no_mass_in_gap(): |
| 95 | + # §1's master matrix pins dxb (re-export, "export pallets", thinner |
| 96 | + # margin per §5) at a 90k avg check — outside the general [30k, 80k] B2B |
| 97 | + # band the same table implies for the domestic + EAEU wholesale channels. |
| 98 | + # Read narrowly: the [30k, 80k] band covers RU + ala wholesale; dxb is a |
| 99 | + # documented, table-explicit outlier, not a spec violation. |
| 100 | + domestic_b2b_checks = [ |
| 101 | + check |
| 102 | + for channel, branch, _, check in legend.MASTER_MATRIX |
| 103 | + if channel in _B2B_CHANNELS and branch != "dxb" |
| 104 | + ] |
| 105 | + marketplace_checks = [ |
| 106 | + check for channel, _, _, check in legend.MASTER_MATRIX if channel in _MARKETPLACE_CHANNELS |
| 107 | + ] |
| 108 | + assert all(30_000 <= c <= 80_000 for c in domestic_b2b_checks) |
| 109 | + assert all(1_500 <= c <= 3_000 for c in marketplace_checks) |
| 110 | + # no channel's avg check falls in the 10k-25k dead zone (holds for all |
| 111 | + # channels, including dxb) |
| 112 | + assert all(not (10_000 < check < 25_000) for _, _, _, check in legend.MASTER_MATRIX) |
| 113 | + |
| 114 | + |
| 115 | +# --- #5 pricing ladder --------------------------------------------------------- |
| 116 | + |
| 117 | + |
| 118 | +def test_invariant_5_pricing_ladder_bands_are_disjoint_and_ordered(): |
| 119 | + assert legend.FOB_PCT_RANGE[1] < legend.LANDED_PCT_RANGE[0] |
| 120 | + assert legend.LANDED_PCT_RANGE[1] < legend.WHOLESALE_PCT_RANGE[0] |
| 121 | + assert legend.WHOLESALE_PCT_RANGE[1] < legend.MARKETPLACE_NET_PCT |
| 122 | + assert legend.MARKETPLACE_NET_PCT < legend.RRC_PCT |
| 123 | + |
| 124 | + |
| 125 | +def test_invariant_5_pricing_ladder_holds_per_sku(): |
| 126 | + tables = build_reference() |
| 127 | + rrc_by_sku = {p.product_bk: p.rrc_price for p in tables.products} |
| 128 | + for sourcing in tables.sourcing: |
| 129 | + rrc = rrc_by_sku[sourcing.product_bk] |
| 130 | + fob_pct = sourcing.purchase_price / rrc |
| 131 | + assert legend.FOB_PCT_RANGE[0] <= fob_pct <= legend.FOB_PCT_RANGE[1] |
| 132 | + |
| 133 | + |
| 134 | +# --- #6 seasonal curves average to 1.0 ----------------------------------------- |
| 135 | + |
| 136 | + |
| 137 | +def test_invariant_6_seasonal_curves_average_to_one(): |
| 138 | + assert len(legend.SEASONAL_RETAIL) == 12 |
| 139 | + assert len(legend.SEASONAL_B2B) == 12 |
| 140 | + assert abs(sum(legend.SEASONAL_RETAIL) / 12 - 1.0) < 1e-9 |
| 141 | + assert abs(sum(legend.SEASONAL_B2B) / 12 - 1.0) < 1e-9 |
| 142 | + |
| 143 | + |
| 144 | +# --- #7 GTIN validity ----------------------------------------------------------- |
| 145 | + |
| 146 | + |
| 147 | +def test_invariant_7_every_gtin_valid_and_in_eaeu_range(): |
| 148 | + tables = build_reference() |
| 149 | + for product in tables.products: |
| 150 | + assert is_valid_gtin13(product.gtin) |
| 151 | + assert 460 <= int(product.gtin[:3]) <= 469 |
| 152 | + |
| 153 | + |
| 154 | +# --- #8 tnved headings ---------------------------------------------------------- |
| 155 | + |
| 156 | + |
| 157 | +def test_invariant_8_every_tnved_code_matches_a_spec_heading(): |
| 158 | + allowed_headings = {"8516", "8509", "8423", "8422"} |
| 159 | + assert {h.heading for h in TNVED_HEADINGS} <= allowed_headings |
| 160 | + tables = build_reference() |
| 161 | + for product in tables.products: |
| 162 | + assert product.tnved_code.endswith("000000") |
| 163 | + assert len(product.tnved_code) == 10 |
| 164 | + assert product.tnved_code[:4] in allowed_headings |
| 165 | + |
| 166 | + |
| 167 | +# --- #9 dealer ordering frequency -> B2B orders/day ----------------------------- |
| 168 | + |
| 169 | + |
| 170 | +def test_invariant_9_dealer_frequency_yields_150_to_200_b2b_orders_per_day(): |
| 171 | + assert sum(count for _, count, _ in legend.DEALER_FREQUENCY_TIERS) == legend.DEALER_CUSTOMERS |
| 172 | + weekly_orders = sum(count * freq for _, count, freq in legend.DEALER_FREQUENCY_TIERS) |
| 173 | + daily_b2b_orders = weekly_orders / 7 |
| 174 | + assert 150 <= daily_b2b_orders <= 200 |
| 175 | + |
| 176 | + |
| 177 | +# --- #10 branch revenue shares --------------------------------------------------- |
| 178 | + |
| 179 | + |
| 180 | +def test_invariant_10_branch_revenue_shares(): |
| 181 | + by_branch: dict[str, float] = {} |
| 182 | + for _, branch, orders, check in legend.MASTER_MATRIX: |
| 183 | + by_branch[branch] = by_branch.get(branch, 0.0) + orders * check |
| 184 | + grand_total = sum(by_branch.values()) |
| 185 | + shares = {branch: 100 * revenue / grand_total for branch, revenue in by_branch.items()} |
| 186 | + assert abs(sum(shares.values()) - 100) < 1e-6 |
| 187 | + assert 55 <= shares["msk"] <= 65 |
| 188 | + |
| 189 | + |
| 190 | +# --- #11 faux-PII locale rules (structural, over seed SQL text) ----------------- |
| 191 | + |
| 192 | + |
| 193 | +def _read_seed_sql(*names: str) -> str: |
| 194 | + return "\n".join((DV2_ROOT / name).read_text(encoding="utf-8") for name in names) |
| 195 | + |
| 196 | + |
| 197 | +def test_invariant_11_faux_pii_locale_rules_present_in_seed_sql(): |
| 198 | + seed_sql = _read_seed_sql("satellite_seed.sql", "satellite_seed_all_branches.sql") |
| 199 | + # RU jurisdictions (§8): city phone prefixes + @example.test emails. |
| 200 | + assert "+7495" in seed_sql # msk |
| 201 | + assert "+7812" in seed_sql # spb |
| 202 | + assert "+7343" in seed_sql # ekb |
| 203 | + assert "@example.test" in seed_sql |
| 204 | + # AE jurisdiction (dxb): +971 phones, latin transliteration names, .ae emails. |
| 205 | + assert "+971" in seed_sql |
| 206 | + assert "@example.ae" in seed_sql |
| 207 | + # KZ jurisdiction (ala): +7727 phones, .kz emails. |
| 208 | + assert "+7727" in seed_sql |
| 209 | + assert "@example.kz" in seed_sql |
| 210 | + |
| 211 | + |
| 212 | +# --- #12 loyalty gating ----------------------------------------------------------- |
| 213 | + |
| 214 | + |
| 215 | +def test_invariant_12_loyalty_points_cap_is_within_three_percent_of_min_quarterly_spend(): |
| 216 | + min_quarterly_spend = legend._TAIL_ORDERS_PER_WEEK * legend._RU_B2B_AVG_CHECK_RUB * 13 |
| 217 | + assert legend.LOYALTY_POINTS_MAX_RUB <= legend.LOYALTY_RETRO_BONUS_PCT * min_quarterly_spend |
| 218 | + |
| 219 | + |
| 220 | +def test_invariant_12_loyalty_rows_only_for_eligible_branches_in_seed_sql(): |
| 221 | + assert legend.LOYALTY_ELIGIBLE_BRANCHES == ("msk", "spb", "ekb") |
| 222 | + seed_sql = _read_seed_sql("satellite_seed.sql", "satellite_seed_all_branches.sql") |
| 223 | + for branch in legend.LOYALTY_ELIGIBLE_BRANCHES: |
| 224 | + assert f"sat_customer_loyalty__bitrix__{branch}" in seed_sql |
| 225 | + for branch in ("dxb", "ala"): |
| 226 | + assert f"sat_customer_loyalty__bitrix__{branch}" not in seed_sql |
0 commit comments