Skip to main content
gold.decision_vector is the scoring layer. It reads decision_context and emits one row per (organization_id, variant_id, shop_id, snapshot_date, domain) carrying a risk vector — scores in [0, 1] — plus the action gate (allowed_actions / forbidden_actions) and an audit trail (applied_rules). variant_id / shop_id are NOT NULL here (the MERGE key requires it), even though they are nullable upstream on decision_context.

The scores STRUCT

A single NOT NULL STRUCT carries every domain’s score fields side-by-side. Sub-fields are nullable so a row populated by one domain leaves the others’ sub-fields NULL — keeping the table single-shape across domains. Non-nullness for the populated domain is guaranteed by construction: every NULL upstream input is coalesced to a neutral default before scoring.

The action gate

Each row also carries three NOT NULL arrays:
  • allowed_actions — baseline emits the domain itself, e.g. ["restock"].
  • forbidden_actions[] in v1.1; future SOURCING / SIZING rules populate it.
  • applied_rules — audit trail, always non-empty (size(applied_rules) > 0). The first element is a domain qualifier (e.g. markdown_domain_qualifier:v1); subsequent elements are the IDs of any scoring business-logic rules that fired.

Scoring by domain

Source: build_decision_vector/scoring.py. All weights are module constants.restock_urgency[0, 1]
stockout_risk[0, 1]
overstock_risk[0, 1]
NULL defaults: days_of_cover → 30 (neutral), forecast_30d → 0 (no demand), gross_margin_pct → 0, aged_stock_flag → false. An all-NULL row scores (0, 0, 0).

Validation

  • Errors (fail the task): decision_vector_not_empty, decision_vector_required_fields, decision_vector_pk_unique, decision_vector_applied_rules_non_empty, decision_vector_domain_allowed_v11, and NULL-safe BETWEEN 0 AND 1 bounds on the rebalance scores (surplus_score, deficit_score, transfer_urgency).
  • Warnings: range checks on the restock scores (restock_urgency, stockout_risk, overstock_risk).
Scores are clamped by construction, so an out-of-range failure means a real bug — fail loud.
Scoring weights are module-level constants today; tuning is a reviewed PR, not a settings knob. A future calibration path may surface them via gold settings once production data shows it is needed.

Source

  • Schema: pipelines/shared/schemas/gold/decision_vector.py
  • Scoring: build_decision_vector/{scoring.py, scoring_markdown.py}, build_decision_vector_rebalance/scoring_rebalance.py
  • Markdown lookup: pipelines/shared/config/markdown_discount_lookup.yaml
  • Repo doc: docs/gold/decision-vector.md