# Machine mirror of BR-006. Values are semantic contracts, not live inventory.
rule_id: BR-006
status: ratified
owner: business owner
effective_date: 2026-07-04
implementation:
  forecast: data_api/routers/reports/shipment/_calc.py
  allocation: data_api/routers/reports/shipment/calc.py
  distribution: data_api/routers/reports/shipment_distribution.py
  corrective: data_api/routers/reports/shipment/corrective.py
source_rule: docs/domain/business-rules/BR-006-shipment.md
decisions: [DNR-001, DNR-003, DNR-004, DNR-005, DNR-007]
forecast:
  sales_basis: realized_only
  exposure_denominator: "max(exposure_days, min_exposure_days)"
  skip_if: "exposure_days <= 0"
  formula: "forecast = ceil(min((sales / max(exposure_days, min_exposure_days)) * coverage_days * season_curr / season_last, sales * forecast_cap))"
  constants: {min_exposure_days: 7, forecast_cap: 3, confidence_k_days: 28}
  no_overstock: "mp_stock >= forecast implies ship_qty = 0"
  safety: "max(safety_min, ceil(need * safety_percent)) only when need > 0"
  supplier_cap: "ship_qty = min(need + safety_add, supplier_stock)"
allocation:
  order: [rating_adj_desc, realized_sales_desc]
  limits: [limit_qty_maximum, limit_rub_maximum]
  stop: "stop when either requested active limit is exhausted"
distribution:
  basis: warehouse stock-consumption delta
  zero_delta: equal_split
  rounding_remainder: leading_warehouse
  conservation: "sum(warehouse_qty) == actual_qty"
corrective:
  eligibility: "supplier_stock >= 5 and mp_stock == 0 and sales_30d == 0"
  quotas: {"1": 10, "3": 5, "6": 5, "9": 10, "default": 5}
  quantity: "min(quota, supplier_stock)"
invariants:
  - Forecast and allocation do not use demand/orders as sales evidence.
  - Ozon primary group is [3,4,5]; do not double-count aggregate FBO rows.
  - Non-positive unit margin produces ship_qty = 0.
  - Manager-controlled limits are maxima, not targets.
verification:
  golden: docs/domain/golden-cases/BASELINE-2026-07.md
  tests: [data_api/tests/test_shipment_forecast.py, data_api/tests/test_shipment_corrective.py]
