Passer au contenu principal
gold.decision_context est le socle de la couche décision. Il matérialise une ligne par (organization_id, variant_id, shop_id, snapshot_date), consolidant l’état du stock, la marge, la prévision et les clés de dimension en une forme unique que chaque scorer du vecteur de décision lit. On le construit une fois, on le score de trois façons.
ConceptValeur
Grainune ligne par (organization_id, variant_id, shop_id, snapshot_date)
Clé primaire["organization_id", "variant_id", "shop_id", "snapshot_date"]
Mode d’écritureMERGE sur la PK (reruns idempotents le même jour)
Clustering(organization_id, snapshot_date, variant_id)
Rétention90 jours de snapshots quotidiens par position (imposée par le writer)
WriterBuildDecisionContextTask (pipelines/layers/gold/tasks/build_decision_context/)

Lignage — trois entrées gold

Le builder s’ancre sur gold.stock_snapshot (une position = une ligne de stock) et y joint en LEFT JOIN la marge et la prévision, après avoir agrégé chaque entrée sur size_taxonomy_id.
  • stock_snapshot est l’ancre : aucune position → rien à construire (skip_task_if_empty).
  • sales_kpis fournit gross_margin_pct (LEFT JOIN sur la clé de position).
  • sales_forecasts est best-effort — filtré à granularity = 'daily', sommé sur la fenêtre des 30 prochains jours ; une prévision manquante laisse la colonne NULL.

Référence des colonnes

Les colonnes d’audit (created_at, updated_at) sont omises. aged_stock_flag est la seule colonne hors position en NOT NULL.

État du stock

ColonneTypeSignificationSource / transformation
current_stockDOUBLEQuantité en stock à snapshot_datestock_snapshot.current_stock_qty, SUM sur les tailles
target_stockDOUBLEStock cible de la positionv1 : toujours NULL (hook Phase 2)
days_of_coverDOUBLEJours de couverture au rythme actuelstock_snapshot.days_of_supply, MIN sur les tailles
lead_time_daysINTDélai d’approvisionnementv1 : toujours NULL (arrive avec la dimension fournisseur)

Ventes et marge

ColonneTypeSignificationSource / transformation
gross_margin_pctDOUBLEMarge brute % réaliséeLEFT JOIN sales_kpis
sell_through_7d / 30d / 90dDOUBLETaux d’écoulementv1 : toujours NULL (calcul par position en Phase 2)
aged_stock_flagBOOLEANIndicateur de stock dormant (NOT NULL)(snapshot_date − last_sale_date) ≥ aged_stock_threshold_days (défaut 90) ; false si parse échoue

Prévision

ColonneTypeSignificationSource / transformation
forecast_30dDOUBLEPrévision de demande 30 joursSUM sales_forecasts.quantity sur (snapshot_date, +30]
forecast_confidenceDOUBLEConfiance de la prévision 30 joursAVG forecast_confidence sur la même fenêtre

Clés de dimension (matching de périmètre des règles)

ColonneTypeSignificationSource
brand_idSTRINGMarque (dénormalisée)passthrough depuis la ligne stock_snapshot ancre
category_idSTRINGCatégoriev1 : toujours NULL
supplier_idSTRINGFournisseurv1 : toujours NULL

Slots toujours-NULL en v1

Sept colonnes sont typées et réservées mais toujours NULL en v1target_stock, lead_time_days, sell_through_7d/30d/90d, category_id, supplier_id. Leur type et leur nullabilité sont stables pour que le writer puisse les remplir plus tard sans migration DDL.
Comme target_stock est toujours NULL en v1, les ratios surplus/déficit du rebalance s’effondrent à 0 sur tout le catalogue (une position sans cible ne peut véritablement pas être classée surplus ou déficit). C’est un comportement correct et déterministe — les vrais scores de rebalance arrivent le jour où target_stock est rempli. Voir la page vecteur de décision.
La couche de scoring traite NULL comme « ignorer ce signal pour cette position » en ramenant chaque entrée NULL à une valeur neutre avant les calculs (documenté par domaine sur la page suivante).

Validation

Les règles sont embarquées sur le schéma et appliquées par le writer :
  • Erreurs (échouent la tâche) : decision_context_not_empty, decision_context_required_fields (organization_id, snapshot_date, aged_stock_flag non-NULL), decision_context_pk_unique.
  • Avertissements : contrôles de non-négativité sur current_stock, days_of_cover, forecast_30d.
Un DecisionContextHealthTask lève des brèches [freshness] (pas de snapshot récent) ou [row-count-drift] (la tranche du jour diverge de celle de la veille) pour le triage on-call.

Source

  • Schéma : pipelines/shared/schemas/gold/decision_context.py
  • Writer : pipelines/layers/gold/tasks/build_decision_context/task.py
  • Doc repo : docs/gold/decision-context.md