gold.action_vector is the resolution layer. It turns the decision vector’s
abstract scores into a concrete recommendation — a quantity to reorder, units to transfer,
or a discount percentage — plus a confidence and an enriched “why” snapshot the app renders.
It is precomputed daily, right after decision_vector, so the app reads recommendations
instantly over Postgres instead of running the ~60 s on-demand resolver per article.
| Concept | Value |
|---|---|
| Primary key | (organization_id, variant_id, shop_id, snapshot_date, domain) |
domain | "restock", "markdown", "rebalance" |
| Write mode | MERGE on the PK (idempotent) |
| Builder | BuildActionVectorTask (pipelines/layers/gold/tasks/build_action_vector/) |
| Lakebase sync | <env>.gold_lakebase.action_vector (triggered CDC, merge-on-PK) |
Columns
| Column | Type | Meaning |
|---|---|---|
recommended_qty | INT, nullable | Units to reorder (restock) / transfer (rebalance); NULL for markdown |
recommended_discount_pct | DOUBLE, nullable | Markdown discount in [0, 100]; NULL for restock / rebalance |
recommendation_confidence | DOUBLE, nullable | Per-recommendation confidence in [0, 1] |
decision_vector_snapshot | STRING (JSON), NOT NULL | The enriched “why” — context / formula / applied_rules arrays |
recommended_qty / recommended_discount_pct is populated per row, enforced
by the action_vector_domain_payload_consistent validation rule.
The decision_vector_snapshot is an opaque JSON string (the nested arrays are
arbitrary-shaped). The app’s decision-explanation UI renders the context (input
conditions), formula (intermediate values), and applied_rules (rule IDs that fired). For
rebalance, the source/destination shops travel inside this JSON
(source_shop_id / destination_shop_id).
How scores become recommendations
- Restock —
resolve_score_driven_positionreads the latest restock decision vector andstock_snapshot.current_stock_qtyper position and computesrecommended_qty. With no decision-vector row, it degrades gracefully toFillToTargetmath against a fallback target. - Markdown —
resolve_recommended_discount_positionconsumesmarkdown_scoreand the discount fraction from the decision vector. - Rebalance —
apply_rebalance_matchingis a global, per-org optimization: it pairs surplus shops with deficit shops via greedy bipartite matching (highesttransfer_urgencydeficit pulls from highestsurplus_scoresource,qty = min(surplus_units, deficit_units), tie-break onshop_id). It writes one destination-keyed row per matched transfer. SOURCING-phase rules can veto specific pairings.
Batch vs on-demand — same cores, one source of truth
The daily batch and the app’s interactive simulation call the same per-position resolver cores, so a precomputedaction_vector row matches what an on-demand simulation
would produce.
simulate_action() (pipelines/shared/workflow_execution/simulate.py) is a read-only wrapper
— it never persists a plan. It validates the ruleset_id belongs to the org (raising
RulesetNotOwnedError on a cross-tenant id) before any rule fetch.
Default strategy per action family
| Action family | Default strategy |
|---|---|
RESTOCK | ScoreDrivenQuantityStrategy(fallback_target=30) |
REBALANCE | ScoreDrivenMatchingStrategy(surplus_threshold=0.5, deficit_threshold=0.5) |
MARKDOWN | RecommendedDiscountStrategy(max_discount_pct=70.0) |
Confidence
recommendation_confidence ∈ [0, 1] is computed from the score vector — inputs include the
restock score separation (how clearly one score dominates), data sufficiency (how complete the
inputs were), and whether constraints forbid the action. It is the value the app gates task
materialization and pre-fill on. (Restock rows may legitimately carry a NULL confidence, which
downstream consumers treat as “always keep”.)
Validation
- Errors:
action_vector_not_empty,action_vector_required_fields,action_vector_pk_unique,action_vector_domain_allowed_v1,action_vector_domain_payload_consistent(qty XOR discount per domain). - Warnings:
recommendation_confidencein[0, 1];recommended_discount_pctin[0, 100].
Source
- Schema:
pipelines/shared/schemas/gold/action_vector.py - Builder:
pipelines/layers/gold/tasks/build_action_vector/{task.py, executor.py} - Resolver cores:
pipelines/shared/workflow_execution/{items_resolver_score_driven.py, items_resolver_recommended_discount.py, scoring/rebalance_matching.py} - On-demand entry:
pipelines/shared/workflow_execution/simulate.py

