Skip to content

Scenario Review & Version History

Route: /history

Scenario Review is the governance layer of Mirabelle — it tracks how plans evolve over time, lets you compare frozen snapshots side-by-side, and provides an approval workflow for controlling which version of the plan becomes the new baseline.

Scenario Review Timeline


Overview

Every time a pipeline runs, its outputs (forecasts, MEIO buffers, supply orders) can change. Without a mechanism to freeze and compare, planners have no way to answer:

  • What changed between last week's plan and this week's?
  • Which items drove the inventory increase?
  • Did the actuals match what we planned?
  • Who approved the change?

Scenario Review solves this by providing:

Capability Description
Freeze Capture a point-in-time snapshot of all pipeline outputs
Compare Side-by-side KPI and row-level deltas between two versions
Plan vs Actual Reconcile planned values against observed outcomes
Stability Track how nervous the plan is across consecutive runs
Approval Request / approve / reject version changes
Explainability Attribute KPI deltas to root-cause drivers

Navigate to /history or click Scenario Review in the sidebar to open the screen. The page is organised into seven tabs:

Tab Purpose
Timeline Chronological list of all frozen versions
Compare KPIs KPI-level deltas between two versions
Delta Explorer Row-level diff across six dimensions
Snapshot Replay Reconstruct the dashboard for a past version
Stability Churn, nervousness, and MASE trends
Pareto Two-KPI scatter with dominance filtering
Plan vs Actual Planned vs observed outcomes per dimension

Core Concepts

Archive (formerly "Version")

An archive is an immutable snapshot of pipeline results captured at a point in time. Archives are identified by their archive_date — always the Monday of the calendar week in which the freeze was taken (e.g. 2026-06-02). Once frozen, the underlying ClickHouse partitions are immutable — no subsequent pipeline run can modify them.

Rename: pipeline_version_id → archive_date

As of June 2026, the integer pipeline_version_id has been replaced by archive_date (a Date always equal to the Monday of the freeze week). API parameters and response fields now use archive_date instead of pipeline_version_id or version_id.

Each archive records:

Field Description
archive_date Monday of the freeze week (e.g. 2026-06-02) — primary identifier
pipeline_id Parent pipeline
scenario_id Scenario context (nullable)
snapshot_ts Exact timestamp when the freeze was taken
created_by User who triggered the freeze
comment Free-text annotation
frozen Whether the archive is immutable
pinned Whether the archive is protected from auto-replacement and deletion
parent_archive_date Optional link to the prior archive
row_count_total / table_counts Statistics about captured data
kpi_* fields Nine pre-computed KPIs (see below)

Freeze

Freezing a pipeline captures its current state across all output tables into a new version. The freeze operation:

  1. Copies all rows from pipeline output tables (forecasts, MEIO results, supply orders, inventory projections, exceptions, best methods, on-hand snapshot) into version-partitioned ClickHouse partitions.
  2. Computes nine aggregate KPIs and writes them to the pipeline_version_index table.
  3. Marks the version as frozen = TRUE.

Freeze captures the live state

Freeze snapshots the data as it exists at freeze time. If a pipeline run is in progress, the snapshot may contain partial results. Always freeze after a pipeline run completes.

KPIs

Each frozen version stores nine pre-computed KPIs:

KPI Key Direction Description
Revenue kpi_revenue ↑ better Total projected revenue
Inventory Value kpi_inventory_value ↓ better Total inventory investment
Fill Rate kpi_fill_rate ↑ better Average service fill rate
Shortage Qty kpi_shortage_qty ↓ better Total shortage units
Forecast MASE kpi_forecast_mase ↓ better Mean Absolute Scaled Error
Forecast Bias kpi_forecast_bias ↓ better Mean forecast bias
Nervousness kpi_nervousness ↓ better Plan change magnitude vs prior
Supply Churn kpi_supply_churn_pct ↓ better Percentage of orders changed
Capacity Utilisation kpi_capacity_util ↑ better Capacity utilisation rate

Direction indicators in the UI use these conventions: green for favourable movement, red for unfavourable.

Timeline

The timeline merges three data sources into a single chronological feed:

  • Archives — with KPI sparklines, identified by archive_date
  • Planning events — disruptive or informational events (e.g. supplier shutdown, promo launch)
  • Approvals — recommendation approval/rejection decisions

Approval Workflow

Mirabelle supports a recommendation approval workflow:

  1. A planner or automated system requests approval for a version change.
  2. An approver reviews the KPI deltas and row-level changes.
  3. The approver approves or rejects the change, with an optional remark.
  4. If approved, the recommended payload becomes the new live plan.

Each approval decision is logged with: recommendation_type, source, recommended_payload, approved_payload, decision, approver, remark, duration_ms, and optional features_at_decision.

Auto-Approval Rules

To reduce manual approval overhead, you can configure auto-approval rules that automatically approve specific recommendation types when conditions are met:

Rule Field Description
name Human-readable rule name
recommendation_type Which recommendation type this rule covers
condition_expr JSON expression defining when the rule fires
action Action to take (typically approve or reject)
action_payload Optional payload overrides
scope_segments / scope_pipelines Restrict rule scope to specific segments or pipelines
is_active Enable / disable the rule without deleting it
priority Higher priority rules are evaluated first

Simulate before activating

Use the simulate endpoint (POST /api/auto-rules/simulate) to preview how a rule would have performed over the last N days before enabling it. This dry-run returns sample matches and would-be decisions without persisting anything.


Freezing a Version

From the UI

  1. Navigate to /history.
  2. Click ❄ Freeze Now to create a standard snapshot, or 📌 Freeze + Pin to create a pinned (deletion-protected) baseline.

Freeze Button

Via API

POST /api/history/freeze

Request body:

{
  "pipeline_id": 4,
  "comment": "Weekly planning cycle W26",
  "pin": false
}
Parameter Type Required Description
pipeline_id int Yes Pipeline to snapshot
comment string No Annotation for the archive
pin bool No Pin the archive (prevents auto-replacement and deletion)

Response (201): returns the created archive object including archive_date, snapshot_ts, and computed KPIs.

{
  "archive_date": "2026-06-02",
  "snapshot_ts": "2026-06-07T10:00:00Z",
  "replaced_archive_date": null,
  "kpis": { "kpi_fill_rate": 0.94, "kpi_shortage_qty": 1250 },
  "comment": "Weekly planning cycle W26",
  "pinned": false
}

What Gets Captured

The freeze operation snapshots data from the following ClickHouse tables:

Table Scenario-scoped? Key data
PIPE_forecast_point_values Yes Point forecasts, quantiles, best method
PIPE_supply_orders Yes Order qty, release/arrival weeks, status
PIPE_supply_inventory_projection Yes Projected inventory, shortages, safety stock
PIPE_meio_results Yes Committed buffers, fill rates, marginal values
PIPE_supply_exceptions Yes Exception type, severity, quantity
PIPE_series_best_methods No Best method selection and scores
PIPE_on_hand_snapshot No Current on-hand inventory

Managing Archives

Action Endpoint Notes
List archives GET /api/history/versions Filter by pipeline_id, date range (from, to)
Get single archive GET /api/history/versions/2026-06-02?pipeline_id=4 Returns full record with all KPIs
Update comment/pin PATCH /api/history/versions/2026-06-02?pipeline_id=4 Only comment and pinned are editable
Recompute KPIs POST /api/history/versions/2026-06-02/compute-kpis?pipeline_id=4 Re-runs KPI aggregation
Delete archive DELETE /api/history/versions/2026-06-02?pipeline_id=4 Pinned archives cannot be deleted (returns 409)

Deletion is permanent

Deleting a version removes its ClickHouse partitions and the index row. This cannot be undone. Pin important baselines before cleaning up old versions.


Comparing Versions

KPI Comparison

The Compare KPIs tab shows side-by-side KPI deltas between a base and target archive:

Compare KPIs

GET /api/compare/versions?base=2026-05-26&target=2026-06-02&dimension=kpi

Each delta row includes:

Field Description
metric KPI name (e.g. revenue, fill_rate)
base Value in the base version
target Value in the target version
abs Absolute difference (target − base)
pct Percentage difference
direction up, down, flat, pivot, or unknown

Direction logic:

  • up — value increased
  • down — value decreased
  • pivot — sign changed (e.g. positive to negative)
  • flat — no change

The UI colours deltas based on whether the direction is favourable (green) or unfavourable (red), using the per-KPI lowerBetter flag.

Drill into dimensions

Each KPI row in the Compare table has a → dimension link that jumps to the Delta Explorer tab for that dimension (e.g. clicking → order next to Supply Churn).

Row-Level Delta Explorer

The Delta Explorer tab performs a full outer join between two versions for a given dimension and classifies every row:

Status Meaning
added Row exists in target but not in base
removed Row exists in base but not in target
changed Row exists in both, but at least one value column differs
unchanged Row is identical in both versions
GET /api/compare/versions?base={base_id}&target={target_id}&dimension={dim}&page=1&page_size=100

Six dimensions are available:

| Key columns | item_id, site_id, method, forecast_week | | Value columns | point_forecast | | Filters | method = best_method |

| Key columns | id | | Value columns | qty, arrival_week, release_week | | Labels | item_id, site_id, item_name, site_name, order_type |

| Key columns | item_id, site_id, week | | Value columns | projected_inventory, shortage, safety_stock | | Labels | item_name, site_name, week_date |

| Key columns | item_id, site_id | | Value columns | committed_buffer, fill_rate, marginal_value | | Labels | item_name, site_name |

| Key columns | item_id, site_id, week, exception_type | | Value columns | qty | | Labels | item_name, site_name, severity |

| Key columns | item_id, site_id | | Value columns | best_method, best_score | | Labels | item_name, site_name |

The response includes:

  • summary — counts of added / removed / changed / unchanged rows
  • rows — paginated list of differing rows with base and target values, delta, and percentage

Pareto Analysis

The Pareto tab plots all frozen versions on a two-axis scatter (default: Inventory Value vs Fill Rate) and computes dominance:

Pareto Scatter

GET /api/pareto/versions?pipeline_id={pid}&x_metric=kpi_inventory_value&y_metric=kpi_fill_rate

A version is dominated when another version has both metrics at least as good, with at least one strictly better. Dominated versions appear as grey dots; non-dominated (Pareto-efficient) versions appear in blue; pinned versions in amber.

Mixed-direction metrics

For metrics where lower is better (e.g. Inventory Value), the scatter plot still uses the raw value. Dominance is computed assuming higher is better for both axes — invert the metric mentally, or use a lower-is-better metric on the Y axis.


Plan vs Actual

Plan vs Actual (PvA) reconciles the values that were planned against the outcomes that actually occurred. This is essential for measuring forecast accuracy, lead time reliability, and service level adherence.

Plan vs Actual

Ingesting Actuals

Before comparing, actual outcomes must be ingested into ClickHouse:

POST /api/actuals/lead-time
[
  {
    "item_id": 101,
    "site_id": 5,
    "source_site_id": 2,
    "planned_lead_time": 14.0,
    "actual_lead_time": 18.0,
    "as_of_date": "2026-05-20"
  }
]

The API automatically computes lead_time_delta = actual - planned.

POST /api/actuals/service
[
  {
    "item_id": 101,
    "site_id": 5,
    "week_date": "2026-06-01",
    "planned_fill_rate": 0.98,
    "actual_fill_rate": 0.95,
    "planned_otif": 0.97,
    "actual_otif": 0.93,
    "shortage_qty": 12.0,
    "backlog_qty": 5.0
  }
]
POST /api/actuals/order-arrival
[
  {
    "order_id": 9042,
    "item_id": 101,
    "site_id": 5,
    "planned_arrival_week": 24,
    "planned_arrival_date": "2026-06-14",
    "actual_arrival_date": "2026-06-18",
    "qty_planned": 500,
    "qty_received": 480
  }
]

The API computes arrival_delta_days and qty_delta automatically.

Demand actuals are ingested via the standard ETL pipeline into master.demand_actuals (PostgreSQL). No separate actuals ingestion endpoint is needed.

Comparing Plan vs Actual

GET /api/compare/plan-vs-actual?pipeline_id={pid}&version_id={vid}&dimension={dim}

Five dimensions are supported:

Dimension Plan source Actual source Key metrics
demand Forecast point values (best method) demand_actuals MAPE, WAPE, bias
inventory Inventory projection at version On-hand snapshot at next frozen version Deviation per (item, site)
lead_time Planned lead time PIPE_lead_time_actuals lead_time_delta
service Planned fill rate / OTIF PIPE_service_actuals Fill rate gap, OTIF gap
order_arrival Planned arrival date/qty PIPE_order_arrival_actuals arrival_delta_days, qty_delta

The demand dimension additionally computes aggregate accuracy metrics:

Metric Formula Interpretation
MAPE Σ forecast − actual
WAPE Same as MAPE (sum-normalised) Weighted absolute percentage error
Bias Σ(forecast − actual) / N Systematic over/under-forecasting

The UI renders a time-series chart with planned (blue line), actual (green line), and deviation bars (red/green), plus a tabular detail view.


Stability Metrics

Stability measures how much the plan changed between consecutive frozen runs — high nervousness means planners are constantly chasing a moving target.

Stability Chart

GET /api/stability/pipeline/{pipeline_id}?window=8&metric=all
Parameter Default Range Description
window 8 2–52 Number of recent frozen versions to include
metric all churn, nervousness, forecast_volatility, all Which metric to return

Three stability indicators are tracked:

Metric Source Interpretation
Supply Churn (churn_pct) kpi_supply_churn_pct % of supply orders that changed vs prior version
Nervousness kpi_nervousness Magnitude of plan changes relative to prior version
Forecast MASE kpi_forecast_mase Forecast accuracy relative to a naive baseline; near 1.0 means the forecast is only as good as a random walk

The UI renders a dual-axis Plotly chart (churn/nervousness on left Y, MASE on right Y) with a tabular detail view below.

Low churn and nervousness = stable plan

A stable plan builds trust with suppliers and reduces operational disruption. If churn spikes, investigate which dimension drove the change using the Delta Explorer.


Approval Workflow

Logging Approval Decisions

Every recommendation decision (approve or reject) is recorded:

POST /api/approvals
{
  "pipeline_id": 4,
  "pipeline_version_id": 42,
  "recommendation_type": "safety_stock_increase",
  "recommendation_id": "rec-1234",
  "source": "meio_engine",
  "item_id": 101,
  "site_id": 5,
  "recommended_payload": {"committed_buffer": 200},
  "approved_payload": {"committed_buffer": 180},
  "decision": "approved_with_modification",
  "approver": "jane.doe@example.com",
  "remark": "Reduced buffer to 180 — excess coverage",
  "duration_ms": 3400
}
Field Description
recommendation_type Category (e.g. safety_stock_increase, order_reschedule)
recommendation_id Unique identifier for the recommendation
source Origin system (e.g. meio_engine, supply_planner)
recommended_payload What was originally recommended
approved_payload What was actually approved (may differ)
decision approved, rejected, approved_with_modification
approver User who made the decision
duration_ms Time from request to decision
auto_rule_id If auto-approved, which rule fired

Querying Approvals

GET /api/approvals?pipeline_id={pid}&decision=approved&from=2026-01-01&to=2026-06-01

Supported filters: recommendation_type, pipeline_id, item_id, site_id, approver, decision, source, date range. Results are paginated.

Auto-Approval Rules

Create rules to automate approval decisions:

POST /api/auto-rules
{
  "name": "Auto-approve small buffer changes",
  "description": "Approve safety stock increases under 5%",
  "recommendation_type": "safety_stock_increase",
  "condition_expr": {
    "field": "pct_change",
    "op": "<=",
    "value": 0.05
  },
  "action": "approve",
  "scope_pipelines": [4],
  "is_active": false,
  "priority": 100
}
Endpoint Method Description
/api/auto-rules GET List all rules, filterable by recommendation_type and is_active
/api/auto-rules POST Create a new rule
/api/auto-rules/{id} PUT Update a rule (partial)
/api/auto-rules/{id} DELETE Delete a rule
/api/auto-rules/simulate POST Dry-run a rule against historical decisions

Rules are inactive by default

New rules are created with is_active = false. Use the simulate endpoint to validate, then set is_active = true via the update endpoint.

Simulating a Rule

Before activating a rule, simulate its effect:

POST /api/auto-rules/simulate
{
  "recommendation_type": "safety_stock_increase",
  "condition_expr": {
    "field": "pct_change",
    "op": "<=",
    "value": 0.05
  },
  "lookback_days": 90,
  "sample_size": 50
}

Returns: how many historical recommendations would have matched, what decisions would have been made, and any mismatches against actual human decisions.


Timeline View

The Timeline tab provides a unified chronological view of all versions, events, and approvals for a pipeline.

Timeline Detail

GET /api/history/timeline?pipeline_id={pid}&from=2026-01-01&to=2026-06-30

The timeline merges three entry types:

Entry type Fields shown
version_frozen version_id, comment, KPIs (inventory value, fill rate, forecast MASE)
event event_id, event_type, severity, description, item_id, site_id
approval approval_id, rec_type, decision, approver, remark

Entries are sorted by timestamp (newest first). Each version card in the UI displays:

  • Version ID and freeze status badge (frozen / draft)
  • Pin indicator (📌)
  • Bucket period and start date
  • Comment / annotation
  • Timestamp, author, and total row count
  • KPI chip bar with all nine metrics

From each version card you can:

  • Replay — open the Snapshot Replay tab for this version
  • Δ vs prev — compare this version against its predecessor
  • Pin / Unpin — toggle deletion protection
  • Delete — permanently remove (pinned versions cannot be deleted)

Planning Events

Events contextualise timeline entries with real-world occurrences:

POST /api/events
{
  "event_type": "supplier_shutdown",
  "event_date": "2026-05-20",
  "pipeline_id": 4,
  "severity": "high",
  "description": "Supplier X factory fire — 4-week disruption expected"
}
Endpoint Method Description
/api/events GET List events, filterable by pipeline, item, site, type, date
/api/events POST Create a new event
/api/events/{id} DELETE Delete an event

Snapshot Replay

The Snapshot Replay tab reconstructs the dashboard for a past frozen version, letting you explore what the plan looked like at that point in time.

Snapshot Replay

GET /api/history/replay/{version_id}?view={view}

Four views are available:

View Description Key data
summary Pipeline-wide row counts Forecast, order, MEIO, exception counts
series Forecast points for a single (item_id, site_id) Methods, point forecasts, quantiles
supply Supply orders Order type, qty, release/arrival weeks, status
meio MEIO buffer results Committed buffer, fill rate, marginal value, wait time

The UI fetches summary, supply, and meio views in parallel and renders:

  • A counts card grid (forecast / orders / MEIO / exceptions)
  • A supply orders table (up to 200 rows)
  • A MEIO buffers table (up to 200 rows)
  • Warning banners if any sub-view failed

Delta Explainability

The explain endpoint attributes the KPI delta between two versions to root-cause drivers.

Delta Explainability

GET /api/explain/delta?base={base_id}&target={target_id}&kpi=kpi_inventory_value

Response Structure

{
  "kpi": "kpi_inventory_value",
  "base_version_id": 40,
  "target_version_id": 42,
  "base_value": 1250000.0,
  "target_value": 1380000.0,
  "abs_delta": 130000.0,
  "pct_delta": 0.104,
  "drivers": [
    {
      "name": "safety_stock_change",
      "abs_contribution": 95000.0,
      "contribution_pct": 0.731
    },
    {
      "name": "forecast_uplift",
      "abs_contribution": 20000.0,
      "contribution_pct": 0.154
    },
    {
      "name": "unexplained",
      "abs_contribution": 15000.0,
      "contribution_pct": 0.115
    }
  ],
  "correlated_events": [
    {
      "event_id": 7,
      "event_type": "lead_time_increase",
      "severity": "high",
      "description": "Supplier X lead time increased from 14 to 21 days",
      "event_date": "2026-05-20"
    }
  ]
}

Drivers for Inventory Value

When kpi=kpi_inventory_value, the explain engine computes two drivers:

Driver Method
Safety stock change Σ (target_buffer − base_buffer) × unit_cost across all (item, site) pairs
Forecast uplift Σ (target_forecast − base_forecast) × unit_cost for best-method rows
Unexplained Residual = abs_delta − total_explained

The contribution_pct for each driver expresses its share of the total delta. The correlated_events list includes planning events that occurred between the two version timestamps, providing human-readable context for the change.

Heuristic v1

The current explain engine is a heuristic approximation. Future versions will incorporate causal attribution and what-if simulation.


API Reference Summary

Endpoint Method Description
/api/history/freeze POST Freeze a pipeline snapshot
/api/history/versions GET List versions
/api/history/versions/{id} GET Get single version
/api/history/versions/{id} PATCH Update comment / pin
/api/history/versions/{id} DELETE Delete version
/api/history/versions/{id}/compute-kpis POST Recompute KPIs
/api/compare/versions GET Compare two versions (KPI or row-level)
/api/compare/plan-vs-actual GET Plan vs Actual reconciliation
/api/history/timeline GET Unified timeline feed
/api/history/replay/{id} GET Replay a past version
/api/stability/pipeline/{id} GET Stability metrics series
/api/pareto/versions GET Two-KPI Pareto scatter
/api/explain/delta GET Delta explainability
/api/approvals GET List approval decisions
/api/approvals POST Log approval decision
/api/auto-rules GET List auto-approval rules
/api/auto-rules POST Create auto-approval rule
/api/auto-rules/{id} PUT Update rule
/api/auto-rules/{id} DELETE Delete rule
/api/auto-rules/simulate POST Simulate a rule
/api/events GET List planning events
/api/events POST Create planning event
/api/events/{id} DELETE Delete event
/api/actuals/lead-time POST Ingest lead time actuals
/api/actuals/service POST Ingest service actuals
/api/actuals/order-arrival POST Ingest order arrival actuals