On the Dashboard → IO tab, the "Stock Value" KPI showed 4.4M
while the IO parameter's max_budget was 15,000. The user expected
these two numbers to be directly comparable — they were both served from the same IO
parameter set, so the 300× gap looked like a bug.
| Field (before fix) | Value |
|---|---|
| "Stock Value" KPI (Dashboard IO tab) | 4,400,000 |
max_budget (IO parameter-set field) | 15,000 |
| Budget Cap chip (next to KPI) | not rendered (hidden) hidden |
The Dashboard IO tab's "Stock Value" KPI is computed at read time as:
stock_value = Σ (committed_buffer × unit_cost)
summed over every (item_id, site_id) row in PIPE_meio_results for the
active pipeline. The value is an absolute total of safety-stock investment.
The Budget Cap chip (Dashboard.jsx:4238) renders only when
kpis?.maxBudget != null. That field is populated from the
/api/meio/fr-heatmap endpoint, whose group-results pre-fetch in
main.py:~27710 selected:
SELECT group_name, segment_id, achieved_fill_rate, fill_rate_target FROM PIPE_meio_group_results WHERE pipeline_id = %s AND scenario_id = %s
— only four columns, never achieved_budget or
max_budget. So maxBudget was always null, the chip was
always hidden, and the only 15,000 the user ever saw was the configured
parameter-set field value, not a live KPI.
Even if both were served, they are not the same metric:
| Metric | Definition | Source |
|---|---|---|
| Stock Value (KPI) | Σ committed_buffer × unit_cost — absolute total, recomputed at read time | read-time aggregation |
achieved_budget (optimizer) | cumulative net investment: Σ (final − initial) buffer × unit_cost, via target.rs commit_groups | stored optimizer output |
max_budget | inventory-investment cap the optimizer respects (default 1e12 = unlimited) | parameter-set field |
achieved_budget equals the absolute total only on a fresh run
(initial buffers = 0). After any re-run with non-zero initial buffers, the optimizer's
stored value is a delta, not an absolute. So comparing Stock Value directly to
achieved_budget would be wrong in steady state.
The label "Stock Value" was vague — it did not convey that the figure is
safety-stock investment, i.e. the same quantity the max_budget cap is
meant to bound.
Three distinct issues combined to produce the symptom:
/api/meio/fr-heatmap
selected only group_name, segment_id, achieved_fill_rate, fill_rate_target —
not achieved_budget or max_budget. The Budget Cap chip's
maxBudget gate (kpis?.maxBudget != null) was therefore always
false, so the chip never rendered.achieved_budget (net delta) are not
directly comparable except on a fresh run. max_budget is a cap, not a
live KPI.max_budget.Files: files/api/main.py, files/frontend/src/components/Dashboard.jsx
achieved_budget, max_budget to the heatmap group_results SELECT so the
Budget Cap chip can render:SELECT group_name, segment_id, achieved_fill_rate, fill_rate_target,
achieved_budget, max_budget
FROM PIPE_meio_group_results
WHERE pipeline_id = %s AND scenario_id = %s
(item_id, site_id) → segment_id map fetch from PIPE_segment_membership,
guarded by if _pre_group_results: and scoped to
archive_date = (SELECT max(archive_date) ...) to avoid a full-history scan.
This lets per-group absolute inventory value be computed.achieved_budget_abs = Σ committed_buffer × unit_cost from
the heatmap's fetched rows, using the same unit-cost fallback chain the heatmap rows
loop uses:achieved_budget_abs += ss_v_db if ss_v_db > 0 else (buf * ucost)
This makes the chip numerator directly comparable to the Safety Stock Value KPI (both are
absolute totals over the same (item_id, site_id) set).
achieved_budget_abs ?? achieved_budget —
falls back gracefully to the optimizer's stored delta when the absolute computation
is unavailable.Stock Value → Safety Stock Value.achieved_budget for the
diagnostic/completion view (not deleted, just no longer the chip numerator).Problem: The PIPE_* table → scenario-type mapping was duplicated across three places that had already diverged:
_TABLE_SCENARIO_TYPE in service.pysql_generator.py (missing PIPE_allocation_explanations,
PIPE_backtest_fingerprints, PIPE_series_hashes)AGENTS.mdFiles: files/mirabelle/config.py, files/mirabelle/service.py,
files/mirabelle/sql_generator.py
TABLE_SCENARIO_TYPE and SCENARIO_COL maps to
config.py as the single source of truth.service.py: _TABLE_SCENARIO_TYPE / _SCENARIO_COL are
now thin aliases of config.TABLE_SCENARIO_TYPE / config.SCENARIO_COL.sql_generator.py: _build_system_prompt() now generates the
"SCENARIO_ID PER TABLE" per-type table list from
config.TABLE_SCENARIO_TYPE / config.SCENARIO_COL instead of a
hardcoded copy. The three previously divergent tables now appear in the prompt.# config.py — single source of truth
TABLE_SCENARIO_TYPE = {
"PIPE_meio_results": "meio_scenario_id",
"PIPE_supply_orders": "supply_scenario_id",
"PIPE_causal_forecast": "causal_scenario_id",
# ... every PIPE_* table
}
SCENARIO_COL = "scenario_id"
| File | Change |
|---|---|
files/api/main.py | Heatmap group_results SELECT gains achieved_budget, max_budget; (item_id, site_id) → segment_id map fetch from PIPE_segment_membership (scoped to max(archive_date)); per-group achieved_budget_abs = Σ committed_buffer × unit_cost with heatmap's unit-cost fallback. |
files/frontend/src/components/Dashboard.jsx | Budget Cap chip numerator uses achieved_budget_abs ?? achieved_budget; KPI label Stock Value → Safety Stock Value. |
files/mirabelle/config.py | New home for TABLE_SCENARIO_TYPE and SCENARIO_COL (single source of truth). |
files/mirabelle/service.py | _TABLE_SCENARIO_TYPE / _SCENARIO_COL now alias config.TABLE_SCENARIO_TYPE / config.SCENARIO_COL. |
files/mirabelle/sql_generator.py | _build_system_prompt() builds the per-type table list from config.TABLE_SCENARIO_TYPE / config.SCENARIO_COL instead of a hardcoded copy. |
files/mirabelle/ imports load cleanly —
config.TABLE_SCENARIO_TYPE resolved from both service.py and
sql_generator.py.PIPE_allocation_explanations, PIPE_backtest_fingerprints,
PIPE_series_hashes).max_budget
once the heatmap endpoint returns the new columns; numerator uses
achieved_budget_abs so it is directly comparable to the Safety Stock
Value KPI.files/api/main.py:27947: the segment_membership map is pinned to
max(archive_date), but the PIPE_meio_results rows feeding
achieved_budget_abs have no archive_date filter.
A partition mismatch can drop SKUs in edge cases (e.g. MEIO re-run without
re-segmentation), so the absolute sum under-counts. The archive_date
filter should be applied consistently to both sides of the join.files/main.py:294 / files/alerts/engine.py:17:
utils/server_config.py and alerts/config.py are
untracked in git. Deploying from a clean checkout raises
ModuleNotFoundError (server_config) or silently defeats env config
(alerts). They must be git added before commit.allocation_cover_weeks silent default flip —
files/supply/src/engine.rs:124: allocation_cover_weeks
defaults to 0, silently flipping every existing pipeline from the old
lead_time + 3 cover buffer to exactly-safety-stock sizing. This is a
service-level behavior change with no opt-in — existing pipelines
that relied on the forward cover will see smaller transfer orders with no warning.
Either preserve the legacy default or expose an explicit migration flag.committed_buffer recalculation (unchanged — remains the SS source of truth).target.rs commit_groups net-investment accounting (preserved as-is).max_budget parameter-set editor UI (no schema change).