Skip to main content
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.
ConceptValue
Primary key(organization_id, variant_id, shop_id, snapshot_date, domain)
domain"restock", "markdown", "rebalance"
Write modeMERGE on the PK (idempotent)
BuilderBuildActionVectorTask (pipelines/layers/gold/tasks/build_action_vector/)
Lakebase sync<env>.gold_lakebase.action_vector (triggered CDC, merge-on-PK)

Columns

ColumnTypeMeaning
recommended_qtyINT, nullableUnits to reorder (restock) / transfer (rebalance); NULL for markdown
recommended_discount_pctDOUBLE, nullableMarkdown discount in [0, 100]; NULL for restock / rebalance
recommendation_confidenceDOUBLE, nullablePer-recommendation confidence in [0, 1]
decision_vector_snapshotSTRING (JSON), NOT NULLThe enriched “why” — context / formula / applied_rules arrays
Exactly one of 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

  • Restockresolve_score_driven_position reads the latest restock decision vector and stock_snapshot.current_stock_qty per position and computes recommended_qty. With no decision-vector row, it degrades gracefully to FillToTarget math against a fallback target.
  • Markdownresolve_recommended_discount_position consumes markdown_score and the discount fraction from the decision vector.
  • Rebalanceapply_rebalance_matching is a global, per-org optimization: it pairs surplus shops with deficit shops via greedy bipartite matching (highest transfer_urgency deficit pulls from highest surplus_score source, qty = min(surplus_units, deficit_units), tie-break on shop_id). It writes one destination-keyed row per matched transfer. SOURCING-phase rules can veto specific pairings.
Positions without a decision-vector row are intentionally not written — the app finds no row and falls back to the on-demand resolver.

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 precomputed action_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 familyDefault strategy
RESTOCKScoreDrivenQuantityStrategy(fallback_target=30)
REBALANCEScoreDrivenMatchingStrategy(surplus_threshold=0.5, deficit_threshold=0.5)
MARKDOWNRecommendedDiscountStrategy(max_discount_pct=70.0)
The app passes its own chosen strategy; these are only the pre-selected defaults. A strategy of the wrong family for the requested action is rejected.

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_confidence in [0, 1]; recommended_discount_pct in [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