Database Schema¶
Mirabelle uses three PostgreSQL schemas across two databases, plus one ClickHouse database for analytics. This page documents every table, its purpose, and the relationships between them.
Schema Architecture Overview¶
Mirabelle's PostgreSQL layer is organised into three schemas, each with a distinct role and lifecycle:
| Schema | Database | Purpose | Can be dropped? |
|---|---|---|---|
scenario |
Tenant DB (e.g. thebicycle) |
Primary application working schema. Holds all pipeline outputs, forecast scenarios, configuration, user accounts, overrides, allocation results, and interaction tables. | No — dropping it destroys the entire tenant. Every query the API makes targets this schema. |
master |
Tenant DB + forecastai_master |
Scenario-independent reference data (items, sites, routes, BOMs, on-hand, calendars, demand actuals). In the master database, it holds multi-tenancy control tables (accounts, superadmins, user_accounts, parameters). |
No in master DB (breaks authentication). In tenant DB, it can be re-seeded from the ERP, but this is a destructive operation. |
import |
Tenant DB | ETL staging area. Flat, forgiving landing tables for ERP/external data before the ETL pipeline processes them into scenario.* and master.*. Stateless drop zone — truncating or dropping and re-creating is safe. |
Yes — it is a stateless staging area. Re-run files/DDL/import_schema.sql to recreate. No business data is lost. |
scenario must never be dropped
The scenario schema is the core of the application. It contains all configuration (config_*), pipeline outputs (pipe_*), scenario definitions (scen_*), user-editable tables (interact_*), and system tables (sys_*). Dropping it is equivalent to deleting the tenant entirely. There is no recovery short of restoring from backup.
Schema placeholder {schema}
In DDL files under files/DDL/, every table reference uses {schema} which is substituted at runtime with the tenant's actual schema name. In production the schema is always scenario unless overridden in master.accounts.schema_name.
Table Naming Convention¶
Tables in the scenario schema use a prefix convention to indicate their domain. The DDL files create tables with these prefixes; the prefix is part of the actual PostgreSQL table name.
| Prefix | Meaning | Example | Mutable? |
|---|---|---|---|
master_ |
Immutable reference/master data (shared across pipelines) | master_item, master_demand_actuals, master_route |
Written by ETL only; pipeline steps only read |
pipe_ |
Pipeline output facts (one row per series per run) | pipe_series_characteristics, pipe_supply_orders |
Overwritten on each pipeline run |
scen_ |
Scenario definition and configuration | scen_forecast_scenarios, scen_causal_scenarios |
User-edited (scenario manager UI) |
config_ |
System configuration and parameter routing | config_parameters, config_segment, config_scenario_pipeline |
User-edited (settings UI) |
interact_ |
User-editable interaction/workflow tables | interact_forecast_adjustments, interact_exception_log |
User-edited (planner overrides, approvals) |
sys_ |
System/housekeeping tables | sys_users, sys_audit_log, sys_revoked_tokens |
System-managed |
allocation_ |
Allocation engine tables | allocation_parameter_set, allocation_result |
User-edited (config) + pipeline output (results) |
meio_ |
MEIO parameter sets (segment-routable) | meio_parameter_set, meio_parameter_set_segment |
User-edited (settings UI) |
demand_profile |
Demand profile templates | demand_profile |
User-edited |
replay_ |
Replay engine tables | replay_run, replay_iteration |
System-managed (API creates/updates) |
DDL vs. documentation names
This documentation often drops the prefix for readability (e.g. writing {schema}.item instead of {schema}.master_item). When querying the database directly, always use the full prefixed name (e.g. SELECT * FROM scenario.master_item).
Database Topology¶
flowchart TD
subgraph masterDB["forecastai_master (PostgreSQL)"]
MA["master.accounts"]
MS["master.superadmins"]
MP["master.parameters"]
MU["master.user_accounts"]
end
subgraph tenantDB["Tenant DB (e.g. thebicycle)"]
subgraph scenarioSchema["scenario schema"]
MASTER["master_* tables\nItems · Sites · Routes · BOM\nDemand actuals · On-hand"]
PIPE["pipe_* tables\nForecasts · Backtests · Supply\nMEIO · Exceptions · Allocation"]
SCEN["scen_* tables\nForecast scenarios · Causal\nMaintenance · Inventory"]
CONFIG["config_* tables\nParameters · Segments\nPipelines · ABC"]
INTERACT["interact_* tables\nAdjustments · Approvals\nExceptions · Events"]
SYS["sys_* tables\nUsers · Audit · Tokens · Process log"]
ALLOC["allocation_* tables\nPriority · Starvation\nReservation · Fairness · Results"]
MEIO["meio_* tables\nMEIO parameter sets"]
end
subgraph masterSchema["master schema (tenant DB)"]
MPV["master.pipeline_version_index\nFreeze registry + KPIs"]
end
subgraph importSchema["import schema"]
IMP["import.* tables\n29 staging tables\nERP flat files → ETL"]
end
end
subgraph clickhouse["ClickHouse (optional analytics)"]
CH_PIPE["PIPE_* tables\n54 fact tables\nPartitioned by (pipeline_id, scenario_id, archive_date)"]
CH_MASTER["MASTER_* tables\nPostgreSQL-engine mirrors"]
CH_CONFIG["CONFIG_* tables\nPostgreSQL-engine mirrors"]
CH_INTERACT["INTERACT_* tables\nPostgreSQL-engine mirrors"]
end
IMP -->|ETL| MASTER
IMP -->|ETL| SCEN
PIPE -->|freeze| CH_PIPE
MASTER -->|PG engine mirror| CH_MASTER
Pipeline outputs (forecasts, supply plans, netting, exceptions) are also stored in ClickHouse for sub-second analytics — see ClickHouse PIPE_ Tables at the bottom of this page.
Master Database (forecastai_master)¶
The master database holds only multi-tenancy control data. It has no per-tenant business data.
master.accounts¶
Purpose: One row per registered tenant. Maps the tenant account UUID to the PostgreSQL database name, schema, and optional connection overrides.
| Column | Type | Description |
|---|---|---|
id |
UUID PK | Unique account identifier (embedded in every JWT) |
display_name |
TEXT UNIQUE | Human-readable tenant name shown in the UI toolbar |
db_name |
TEXT UNIQUE | PostgreSQL database name for this tenant (e.g. thebicycle) |
schema_name |
TEXT | Schema inside the tenant DB (default scenario) |
connection_params |
JSONB | Optional connection overrides: host, port, user, password, sslmode. NULL means use same server as master DB |
is_active |
BOOLEAN | Set to FALSE to disable a tenant without deleting data |
created_at |
TIMESTAMPTZ | Account creation timestamp |
updated_at |
TIMESTAMPTZ | Last modification timestamp |
master.superadmins¶
Purpose: Global administrator accounts that can log into any tenant. Never stored in tenant databases.
| Column | Type | Description |
|---|---|---|
id |
UUID PK | SuperAdmin UUID |
email |
TEXT UNIQUE | Login email |
display_name |
TEXT | Display name in UI |
hashed_password |
TEXT | bcrypt hash; NULL for OAuth-only accounts |
auth_provider |
TEXT | local / microsoft / google |
is_active |
BOOLEAN | Disable without deleting |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last modification |
master.parameters¶
Purpose: Shared configuration values — primarily the JWT secret and token expiry. Mirrors the structure of {schema}.parameters in tenant DBs.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Row identifier |
parameter_type |
TEXT | Type key (e.g. auth) |
name |
TEXT | Parameter set name (e.g. Default) |
parameters_set |
JSONB | Configuration values. For auth: {"jwt_secret": "...", "token_expiry_minutes": 480} |
is_default |
BOOLEAN | Whether this is the active default for its type |
created_at |
TIMESTAMPTZ | Creation timestamp |
master.user_accounts¶
Purpose: Junction table mapping user emails to the accounts they can access. Used by the login flow when a user has access to multiple tenants.
| Column | Type | Description |
|---|---|---|
email |
TEXT | User email (matches {schema}.users.email) |
account_id |
UUID FK | References master.accounts.id |
created_at |
TIMESTAMPTZ | Assignment timestamp |
Tenant Database ({schema})¶
All tables below live in the scenario schema of a specific tenant's PostgreSQL database.
Domain: Master / Lookup Data¶
These tables are immutable (not pipeline-scoped). They represent the physical world: what items exist, where sites are, who customers are.
{schema}.item_type¶
Purpose: Lookup table for classifying items (e.g. finished good, component, service part).
| Column | Type | Description |
|---|---|---|
id |
BIGINT PK | Type identifier |
xuid |
TEXT | External unique identifier |
name |
TEXT | Type name |
description |
TEXT | Human-readable description |
{schema}.site_type¶
Purpose: Lookup table for classifying sites (e.g. warehouse, factory, retail).
| Column | Type | Description |
|---|---|---|
id |
BIGINT PK | Type identifier |
xuid |
TEXT | External unique identifier |
name |
TEXT | Type name |
description |
TEXT | Human-readable description |
{schema}.item¶
Purpose: Item (SKU) master data. Every forecasted series is associated with an item.
| Column | Type | Description |
|---|---|---|
id |
BIGINT PK | Item identifier |
xuid |
TEXT | External unique identifier (ERP key) |
name |
TEXT | Item name |
description |
TEXT | Item description |
attributes |
JSONB | Flexible key-value attributes |
type_id |
BIGINT FK → item_type |
Item type classification |
image_url |
TEXT | Optional product image URL |
price |
DOUBLE PRECISION | Selling price |
cost |
DOUBLE PRECISION | Unit cost |
{schema}.site¶
Purpose: Site (location) master data. Sites include warehouses, factories, repair shops, and customer locations.
| Column | Type | Description |
|---|---|---|
id |
BIGINT PK | Site identifier |
xuid |
TEXT | External unique identifier |
name |
TEXT | Site name |
description |
TEXT | Description |
attributes |
JSONB | Flexible key-value attributes |
type_id |
BIGINT FK → site_type |
Site type classification |
longitude |
NUMERIC(10,7) | Geographic longitude |
latitude |
NUMERIC(10,7) | Geographic latitude |
{schema}.customer¶
Purpose: Customer master. In bicycle demo data, customers are teams (professional / amateur). In aerospace, customers are airlines with fleets.
| Column | Type | Description |
|---|---|---|
customer_id |
SERIAL PK | Customer identifier |
code |
TEXT UNIQUE | Short code |
name |
TEXT | Customer name |
customer_type |
TEXT | professional, amateur, team, etc. |
country |
TEXT | ISO country code |
contact_email |
TEXT | Primary contact |
attributes |
JSONB | Flexible: num_riders, num_bikes, sla_level, billing_currency, credit_limit_eur, notes |
created_at |
TIMESTAMPTZ | Creation timestamp |
{schema}.customer_site¶
Purpose: Defines return logistics parameters for each customer-site pair (e.g. wash rate and lead time for product returns).
| Column | Type | Description |
|---|---|---|
customer_id |
BIGINT FK → customer |
Customer |
site_id |
BIGINT FK → site |
Site |
wash_rate |
DOUBLE PRECISION | Fraction of returned units lost (0–1) |
lead_time |
DOUBLE PRECISION | Days to return asset/part back to site |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.demand_type¶
Purpose: Qualifies the nature of demand. Seeded with four standard values.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Type identifier |
name |
TEXT UNIQUE | Regular demand, Urgent demand, Promotional demand, Service demand |
{schema}.channel¶
Purpose: Sales channel lookup (e.g. retail, wholesale, online). Referenced by demand_actuals.channel and the ETL pipeline.
| Column | Type | Description |
|---|---|---|
xuid |
TEXT PK | External unique identifier |
name |
TEXT | Channel name |
description |
TEXT | Channel description |
{schema}.currency_conversion¶
Purpose: Exchange rate table for multi-currency cost/price display. Used by useValueMode.js (frontend) and fmtValue() to convert values from the global default currency to the user's selected currency.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Conversion record |
from_currency |
TEXT | Source currency code (e.g. USD) |
to_currency |
TEXT | Target currency code (e.g. EUR) |
rate |
DOUBLE PRECISION | Conversion rate |
effective_date |
DATE | Date the rate is valid from |
{schema}.item_location (also referenced as item_site)¶
Purpose: Item-site level cost parameters used by MEIO, supply planning, and allocation. Stores order cost, holding rate, cost price, selling price, and unit cost per item per site.
| Column | Type | Description |
|---|---|---|
item_id |
BIGINT PK (composite) | Item |
site_id |
BIGINT PK (composite) | Site |
order_cost |
DOUBLE PRECISION | Fixed cost per order placement |
holding_rate |
DOUBLE PRECISION | Annual holding cost rate (fraction of unit cost) |
cost_price |
DOUBLE PRECISION | Standard cost price |
price |
DOUBLE PRECISION | Selling price |
cost |
DOUBLE PRECISION | Unit cost |
Domain: Demand¶
{schema}.demand_actuals¶
Purpose: Core historical demand fact table. One row per item/site/customer/date/channel combination. This table is immutable — the ETL writes it and downstream processes only read from it or write corrected values to demand_corrections.
pg_mooncake columnar storage
If pg_mooncake is installed, this table is wrapped in a columnar extension table called mooncake_demand_actuals for analytical query performance. The standard heap table is always created as the canonical source.
| Column | Type | Description |
|---|---|---|
item_id |
BIGINT FK → item |
Item |
site_id |
BIGINT FK → site |
Site |
customer_id |
BIGINT FK → customer |
Customer (may be NULL for site-aggregated demand) |
demand_type_id |
BIGINT FK → demand_type |
Demand qualifier |
channel |
TEXT | Sales channel |
date |
DATE | Week start date (Monday) |
qty |
DOUBLE PRECISION | Raw demand quantity |
corrected_qty |
DOUBLE PRECISION | Inline corrected quantity (pipeline-level corrections are in demand_corrections) |
item_name |
TEXT | Denormalised item name |
site_name |
TEXT | Denormalised site name |
unique_id |
TEXT | Series identifier — format: {item_id}_{site_id} or {item_id}_{site_id}_{customer_id} |
Key indexes: idx_demand_unique_id, idx_demand_item_site, idx_demand_item_site_d (descending date for recent queries).
{schema}.demand_corrections¶
Purpose: Pipeline-scoped outlier corrections. When the outlier detection step decides a demand value should be replaced, it writes the corrected quantity here rather than modifying demand_actuals. Each pipeline has its own set of corrections.
| Column | Type | Description |
|---|---|---|
unique_id |
TEXT | Series identifier |
date |
DATE | The week being corrected |
corrected_qty |
DOUBLE PRECISION | Replacement quantity |
pipeline_id |
BIGINT NOT NULL | Pipeline this correction belongs to (part of PK) |
note |
TEXT | Optional correction note |
updated_at |
TIMESTAMPTZ | Last update |
Primary key: (unique_id, date, pipeline_id) — multiple pipelines can have different corrections for the same data point.
Domain: Pipeline Outputs¶
These tables receive one row per series per pipeline run. All have pipeline_id and scenario_id columns as part of their scope.
{schema}.demand_corrected¶
Purpose: Pipeline-scoped outlier corrections written by the embedded pre-forecast outlier detection step. Each pipeline maintains its own set of corrections; re-running a pipeline overwrites only its rows.
Written by: Outlier detection pre-step inside run_pipeline.py --only forecast
| Column | Type | Description |
|---|---|---|
item_id |
BIGINT | Item (parsed from unique_id) |
site_id |
BIGINT | Site (parsed from unique_id) |
date |
DATE | The corrected week |
pipeline_id |
BIGINT NOT NULL | Pipeline scope (part of PK) |
original_value |
DOUBLE PRECISION | Raw demand value before correction |
corrected_value |
DOUBLE PRECISION | Replacement value after correction strategy |
detection_method |
TEXT | iqr, zscore, or stl |
z_score |
DOUBLE PRECISION | Standardised distance from series mean |
lower_bound |
DOUBLE PRECISION | Lower detection fence |
upper_bound |
DOUBLE PRECISION | Upper detection fence |
Primary key: (item_id, site_id, date, pipeline_id)
unique_id parsing rule
Never JOIN demand_corrected against demand_actuals on item_id + site_id — this creates a cross-product. Always parse item_id / site_id directly from the unique_id string and query with WHERE item_id = %s AND site_id = %s.
{schema}.series_characteristics¶
Purpose: Stores the full characterisation output for each series. One row per (series, scenario, pipeline) combination.
Written by: Characterisation step (files/characterization/)
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Row identifier |
unique_id |
TEXT | Series identifier |
n_observations |
INTEGER | Number of non-null demand data points |
date_range_start |
TEXT | Earliest demand date |
date_range_end |
TEXT | Latest demand date |
mean |
DOUBLE PRECISION | Mean demand |
std |
DOUBLE PRECISION | Standard deviation |
has_seasonality |
BOOLEAN | Whether significant seasonality detected |
seasonal_periods |
JSONB | List of detected seasonal periods (e.g. [52]) |
seasonal_strength |
DOUBLE PRECISION | STL seasonal strength (0–1) |
has_trend |
BOOLEAN | Whether a significant trend is present |
trend_direction |
TEXT | up, down, or flat |
trend_strength |
DOUBLE PRECISION | Trend component strength (0–1) |
is_intermittent |
BOOLEAN | Whether ADI > threshold or zero_ratio > threshold |
zero_ratio |
DOUBLE PRECISION | Proportion of zero-demand periods |
adi |
DOUBLE PRECISION | Average Demand Interval (periods between non-zero demand) |
cov |
DOUBLE PRECISION | Coefficient of variation (σ/μ) |
is_stationary |
BOOLEAN | ADF test result |
adf_pvalue |
DOUBLE PRECISION | p-value of the ADF test |
complexity_score |
DOUBLE PRECISION | Composite complexity score |
complexity_level |
TEXT | low, medium, high |
sufficient_for_ml |
BOOLEAN | Whether enough data for ML methods |
sufficient_for_deep_learning |
BOOLEAN | Whether enough data for deep learning |
recommended_methods |
JSONB | List of recommended method names |
frequency |
TEXT | Time series frequency (W for weekly) |
scenario_id |
BIGINT NOT NULL FK → forecast_scenarios |
Scenario scope |
pipeline_id |
BIGINT | Pipeline scope |
Unique index: (unique_id, scenario_id, pipeline_id)
{schema}.forecast_results¶
Purpose: Stores the point forecast and quantile forecasts for each (series, method) combination.
Written by: Forecasting step (files/forecasting/)
Columnar storage
This table is optionally wrapped by pg_mooncake (mooncake_forecast_results) for analytical queries.
| Column | Type | Description |
|---|---|---|
unique_id |
TEXT | Series identifier |
method |
TEXT | Model name (e.g. AutoARIMA, NHITS) |
point_forecast |
JSONB | Array of 24 weekly forecast values |
quantiles |
JSONB | Dict of quantile → array mappings (e.g. {"0.1": [...], "0.9": [...]}) |
hyperparameters |
JSONB | Fitted model hyperparameters |
training_time |
DOUBLE PRECISION | Model training duration in seconds |
scenario_id |
BIGINT NOT NULL | Scenario scope |
customer_id |
BIGINT | Customer scope (for customer-level causal forecasts) |
pipeline_id |
BIGINT | Pipeline scope |
site_id |
BIGINT | Site scope (causal V2 — coverage-spread results) |
{schema}.series_backtest_metrics¶
Purpose: Rolling-window backtesting results — one row per (series, method, origin date, horizon).
Written by: Evaluation step (files/evaluation/)
| Column | Type | Description |
|---|---|---|
unique_id |
TEXT | Series identifier |
method |
TEXT | Model name |
forecast_origin |
DATE | The date from which this backtest window was projected |
horizon |
INTEGER | Forecast horizon step (1 = next week) |
mae |
DOUBLE PRECISION | Mean Absolute Error |
rmse |
DOUBLE PRECISION | Root Mean Squared Error |
mape |
DOUBLE PRECISION | Mean Absolute Percentage Error |
smape |
DOUBLE PRECISION | Symmetric MAPE |
mase |
DOUBLE PRECISION | Mean Absolute Scaled Error |
bias |
DOUBLE PRECISION | Mean forecast bias (positive = over-forecast) |
crps |
DOUBLE PRECISION | Continuous Ranked Probability Score |
winkler_score |
DOUBLE PRECISION | Winkler interval score |
coverage_50/80/90/95 |
DOUBLE PRECISION | Interval coverage at respective confidence levels |
quantile_loss |
DOUBLE PRECISION | Pinball / quantile loss |
aic |
DOUBLE PRECISION | Akaike Information Criterion (statistical models only) |
bic |
DOUBLE PRECISION | Bayesian Information Criterion |
aicc |
DOUBLE PRECISION | AIC corrected for small samples |
metric_source |
TEXT | rolling_window (default) |
scenario_id |
BIGINT NOT NULL FK → forecast_scenarios |
Scenario scope |
pipeline_id |
BIGINT | Pipeline scope |
{schema}.forecasts_by_origin¶
Purpose: Point forecasts paired with actual values for each backtesting origin date. Used to render the ridge chart in TimeSeriesViewer.jsx.
Written by: Evaluation step
| Column | Type | Description |
|---|---|---|
unique_id |
TEXT | Series identifier |
method |
TEXT | Model name |
forecast_origin |
DATE | Origin date of this backtest window |
horizon_step |
INTEGER | Step within the horizon (1–24) |
point_forecast |
DOUBLE PRECISION | Forecast value at this step |
actual_value |
DOUBLE PRECISION | Realised demand at this step |
scenario_id |
BIGINT NOT NULL | Scenario scope |
pipeline_id |
BIGINT | Pipeline scope |
{schema}.series_best_methods¶
Purpose: The winner of the best-method selection competition per series. Also stores the runner-up and full ranking. Supports method locking by planners.
Written by: Best Method Selection (files/selection/best_method.py)
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Row identifier |
unique_id |
TEXT | Series identifier |
best_method |
TEXT | Name of the best-scoring model |
best_score |
DOUBLE PRECISION | Composite score of the winner (lower is better) |
runner_up_method |
TEXT | Name of the second-best model |
runner_up_score |
DOUBLE PRECISION | Composite score of runner-up |
all_rankings |
JSONB | Full ranked list of all evaluated methods |
scenario_id |
BIGINT NOT NULL | Scenario scope |
pipeline_id |
BIGINT | Pipeline scope |
locked_method |
TEXT | If set, this method is used regardless of scoring |
locked_by |
TEXT | Email of the planner who locked the method |
locked_at |
TIMESTAMPTZ | When the method was locked |
Unique index: (unique_id, scenario_id, pipeline_id)
{schema}.fitted_distributions¶
Purpose: Fitted parametric demand distributions for MEIO safety-stock calculation. The best-fitting distribution is selected by KS statistic.
Written by: Distribution Fitting (files/distribution/fitting.py)
| Column | Type | Description |
|---|---|---|
unique_id |
TEXT | Series identifier |
method |
TEXT | Forecasting method whose residuals were fitted |
forecast_horizon |
INTEGER | Horizon over which distribution was computed |
distribution_type |
TEXT | normal, gamma, lognormal, negbin, weibull |
mean |
DOUBLE PRECISION | Distribution mean |
std |
DOUBLE PRECISION | Distribution standard deviation |
params |
JSONB | Full parameter set (shape, scale, etc.) |
ks_statistic |
DOUBLE PRECISION | Kolmogorov-Smirnov test statistic (lower = better fit) |
ks_pvalue |
DOUBLE PRECISION | KS test p-value |
service_level_quantiles |
JSONB | Pre-computed quantiles at common service levels |
scenario_id |
BIGINT NOT NULL | Scenario scope |
pipeline_id |
BIGINT | Pipeline scope |
{schema}.forecast_hyperparameters¶
Purpose: Model training hyperparameters per (series × method × pipeline × scenario). One row per (unique_id, method, pipeline_id, scenario_id). Stores the fitted model configuration for reproducibility and audit.
Written by: Forecasting step
| Column | Type | Description |
|---|---|---|
unique_id |
TEXT | Series identifier |
method |
TEXT | Model name |
hyperparameters |
JSONB | Fitted model hyperparameters |
computed_at |
TIMESTAMPTZ | When this record was created |
scenario_id |
BIGINT NOT NULL | Scenario scope |
pipeline_id |
BIGINT | Pipeline scope |
{schema}.series_detected_outliers¶
Purpose: Raw outlier detection results before correction. Stores the detection method, z-score, and bounds for each flagged data point, separate from the corrected output in demand_corrected.
Written by: Outlier detection (embedded in Forecasting step)
| Column | Type | Description |
|---|---|---|
item_id |
BIGINT | Item |
site_id |
BIGINT | Site |
date |
DATE | The flagged week |
pipeline_id |
BIGINT NOT NULL | Pipeline scope |
original_value |
DOUBLE PRECISION | Raw demand value |
detection_method |
TEXT | iqr, zscore, or stl |
z_score |
DOUBLE PRECISION | Standardised distance from mean |
lower_bound |
DOUBLE PRECISION | Detection lower fence |
upper_bound |
DOUBLE PRECISION | Detection upper fence |
{schema}.demand_profile¶
Purpose: Demand profile templates that define segment-level demand shaping parameters (e.g. growth rate, seasonal multiplier). Used by the segmentation and pipeline steps for profile-based routing.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Profile identifier |
name |
TEXT | Profile name |
description |
TEXT | Description |
parameters |
JSONB | Profile parameters (growth rate, seasonal factors, etc.) |
Domain: MEIO / Inventory¶
{schema}.inventory_scenario_tag¶
Purpose: Lookup table classifying MEIO scenario types.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Tag identifier |
code |
TEXT UNIQUE | kcurve, safety_stock, reorder_point |
label |
TEXT | Display label |
description |
TEXT | Description |
{schema}.inventory_scenario¶
Purpose: A named inventory optimisation scenario. Stores both the input parameters and the computed results as JSONB.
| Column | Type | Description |
|---|---|---|
scenario_id |
BIGSERIAL PK | Scenario identifier |
name |
TEXT | Scenario name |
tag_id |
INTEGER FK → inventory_scenario_tag |
Type classification |
parameters |
JSONB | Input parameters (e.g. k_factor, target_budget, target_service_level) |
results |
JSONB | Output results (e.g. inv_val, orders_per_year, per-item EOQ quantities) |
pipeline_id |
BIGINT | Last pipeline that ran this scenario |
created_by |
TEXT | User who created the scenario |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.on_hand_type¶
Purpose: Lookup for on-hand inventory categories (usable, quarantine, in-transit, etc.).
| Column | Type | Description |
|---|---|---|
id |
BIGINT PK | Type identifier |
xuid |
TEXT | External identifier |
name |
TEXT | Type name |
description |
TEXT | Description |
planning_type |
TEXT | Planning-relevant type code |
allocation_sequence |
SMALLINT | Priority order for allocation |
{schema}.on_hand¶
Purpose: Current on-hand inventory balances by item, site, and type.
| Column | Type | Description |
|---|---|---|
id |
BIGINT PK | Balance record identifier |
item_id |
BIGINT | Item |
site_id |
BIGINT | Site |
type_id |
BIGINT FK → on_hand_type |
Inventory type |
tag |
TEXT | Optional tag |
qty |
DOUBLE PRECISION | On-hand quantity |
unit_cost |
DOUBLE PRECISION | Unit cost |
unit_cost_currency_id |
BIGINT | Currency reference |
expiry_date |
DATE | Expiry date (for perishables) |
attributes |
JSONB | Additional attributes |
feature_id |
BIGINT | Feature/lot tracking reference |
{schema}.item_site¶
Purpose: Item-site level cost parameters used by MEIO and supply planning (order cost, holding rate, cost price).
| Column | Type | Description |
|---|---|---|
item_id |
BIGINT PK (composite) | Item |
site_id |
BIGINT PK (composite) | Site |
order_cost |
DOUBLE PRECISION | Fixed cost per order placement |
holding_rate |
DOUBLE PRECISION | Annual holding cost rate (fraction of unit cost) |
cost_price |
DOUBLE PRECISION | Standard cost price |
price |
DOUBLE PRECISION | Selling price |
cost |
DOUBLE PRECISION | Unit cost |
{schema}.meio_parameter_set¶
Purpose: Named MEIO (Multi-Echelon Inventory Optimisation) parameter sets. Similar structure to config_parameters but specifically for MEIO-related configuration (service level targets, K-curve settings, reorder point parameters). Supports segment-level routing via meio_parameter_set_segment.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Parameter set identifier |
scenario_id |
BIGINT NOT NULL | Inventory scenario scope |
parameter_type |
TEXT | Type key (e.g. meio, kcurve, safety_stock) |
name |
TEXT NOT NULL | Set name |
label |
TEXT | Display label |
parameters_set |
JSONB | Configuration values |
is_default |
BOOLEAN | Whether this is the scenario-level default |
sort_order |
SMALLINT | Display order |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.meio_parameter_set_segment¶
Purpose: Segment-level parameter routing for MEIO. Links a non-default MEIO parameter set to a segment so that items in that segment use this set.
| Column | Type | Description |
|---|---|---|
parameter_set_id |
BIGINT FK → meio_parameter_set |
MEIO parameter set |
segment_id |
BIGINT NOT NULL | Target segment |
Primary key: (parameter_set_id, segment_id)
Domain: Supply Planning¶
{schema}.supply_run¶
Purpose: Log of each supply planning run. Records metadata, timing, and summary metrics.
Written by: Supply planning engine (files/api/supply_router.py)
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Run identifier |
scenario_id |
BIGINT | Supply scenario used |
pipeline_id |
BIGINT | Pipeline this run belongs to |
started_at |
TIMESTAMPTZ | Run start time |
completed_at |
TIMESTAMPTZ | Run completion time |
status |
VARCHAR(20) | running / completed / failed |
sku_count |
INTEGER | Total SKUs in scope |
active_sku_count |
INTEGER | SKUs that generated orders |
order_count |
INTEGER | Total orders generated |
horizon_weeks |
SMALLINT | Planning horizon (default 52) |
parameters |
JSONB | Full parameter snapshot |
metrics |
JSONB | Summary metrics (total cost, total shortage, fill rate) |
error_msg |
TEXT | Error details if status = failed |
{schema}.supply_order¶
Purpose: Individual planned supply orders. Each row is a single order for one item at one location.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Order identifier |
run_id |
BIGINT FK → supply_run |
Parent run |
scenario_id |
BIGINT | Supply scenario |
sku_id |
BIGINT | Item (maps to item.id) |
location_id |
BIGINT | Destination site (maps to site.id) |
source_location_id |
BIGINT | Source site for TRANSFER orders |
order_type |
VARCHAR(20) | BUY / BUILD / REPAIR / TRANSFER / REPLENISH / RETURN |
qty |
DOUBLE PRECISION | Order quantity |
release_week |
SMALLINT | Week index (0–51) when to place/release |
arrival_week |
SMALLINT | Week index (0–51) when inventory arrives |
unit_cost |
DOUBLE PRECISION | Unit cost at time of order |
order_cost |
DOUBLE PRECISION | Fixed cost for this order |
total_cost |
DOUBLE PRECISION | qty × unit_cost + order_cost |
route_id |
BIGINT | Route used for this order |
status |
VARCHAR(20) | planned / constrained / delayed / expedited |
attributes |
JSONB | Additional order attributes |
{schema}.supply_pegging¶
Purpose: Links each supply order to the demand buckets it satisfies. Enables demand/supply traceability.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Pegging record |
run_id |
BIGINT FK → supply_run |
Parent run |
order_id |
BIGINT FK → supply_order |
Supply order |
sku_id |
BIGINT | Item |
location_id |
BIGINT | Site |
demand_week |
SMALLINT | Week of the demand being covered |
supply_week |
SMALLINT | Week the supply arrives |
qty |
DOUBLE PRECISION | Quantity pegged from this order to this demand |
{schema}.supply_inventory_projection¶
Purpose: Week-by-week inventory projection for each item/site combination in a supply run. The primary output used for inventory position charts.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Row identifier |
run_id |
BIGINT FK → supply_run |
Parent run |
scenario_id |
BIGINT | Supply scenario |
sku_id |
BIGINT | Item |
location_id |
BIGINT | Site |
week |
SMALLINT | Week index (0–51) |
projected_inventory |
DOUBLE PRECISION | Projected on-hand at end of week |
demand |
DOUBLE PRECISION | Demand in this week |
supply_received |
DOUBLE PRECISION | Supply arriving in this week |
shortage |
DOUBLE PRECISION | Unmet demand (positive = shortage) |
safety_stock |
DOUBLE PRECISION | Safety-stock target for this week |
Unique constraint: (run_id, sku_id, location_id, week)
{schema}.supply_exception¶
Purpose: Supply exceptions generated during a planning run (shortages, capacity violations, late orders, unmet demand, empty repair pools).
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Exception identifier |
run_id |
BIGINT FK → supply_run |
Parent run |
scenario_id |
BIGINT | Supply scenario |
sku_id |
BIGINT | Affected item |
location_id |
BIGINT | Affected site |
week |
SMALLINT | Week of the exception |
exception_type |
VARCHAR(50) | Shortage / CapacityExceeded / LateOrder / UnmetDemand / RepairPoolEmpty |
severity |
VARCHAR(20) | Critical / Warning / Info |
qty |
DOUBLE PRECISION | Quantity involved |
message |
TEXT | Human-readable exception description |
attributes |
JSONB | Additional detail |
created_at |
TIMESTAMPTZ | Exception creation timestamp |
{schema}.supply_capacity¶
Purpose: Weekly capacity constraints for suppliers, plants, repair shops, and transport lanes.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Constraint record |
scenario_id |
BIGINT | Supply scenario |
resource_type |
VARCHAR(30) | SUPPLIER / PLANT / REPAIR_SHOP / TRANSPORT |
resource_id |
BIGINT | ID of the resource |
week |
SMALLINT | Week index |
capacity_qty |
DOUBLE PRECISION | Maximum capacity for this week |
used_qty |
DOUBLE PRECISION | Capacity consumed by planned orders |
Supply route calendars → master.calendar + master.calendar_entry¶
Purpose: Working calendars for supply route availability (plant shutdowns, holidays, supplier closures). Uses the same shared Mirabelle calendar model as forecasting calendars. Managed via Settings → Master Data → Calendars.
master.route.ship_calendar_id references master.calendar.id. The supply engine reads the date-range entries and converts them to 52-week bitmasks at runtime relative to the current planning horizon start.
| Table | Key columns | Notes |
|---|---|---|
master.calendar |
id, name, type (working/end2end), mode (available/not_available) |
Header row; not_available means listed dates are closed |
master.calendar_entry |
calendar_id, date_from, date_to, label |
One row per closed (or open) date range |
Removed:
{schema}.supply_calendar(bitmask table) was consolidated intomaster.calendarin June 2026. Any existingmaster.supply_calendarrows should be migrated:DROP TABLE master.supply_calendar;
{schema}.supply_scenario¶
Purpose: A named supply planning scenario. Links to a default parameter set and supports segment-level parameter overrides via supply_scenario_segment.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Scenario identifier |
name |
TEXT | Scenario name |
description |
TEXT | Description |
parameter_id |
INTEGER FK → parameters |
Default supply parameter set |
is_base |
BOOLEAN | Whether this is the base scenario |
created_by |
TEXT | Creator email |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.supply_scenario_segment¶
Purpose: Segment-level parameter overrides within a supply scenario. The resolution order walks rows by sort_order ASC; the first segment a SKU belongs to wins. Unmatched SKUs use the scenario's default parameter_id.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Row identifier |
scenario_id |
BIGINT FK → supply_scenario |
Parent scenario |
segment_id |
INTEGER FK → segment |
Target segment |
parameter_id |
INTEGER FK → parameters |
Override parameter set |
sort_order |
SMALLINT | Resolution priority (ascending) |
{schema}.master_supply_orders_firm¶
Purpose: ERP-originated firm supply orders (purchase orders, work orders, transfers) that are committed and therefore constitute an input to the supply engine. Replaces the legacy mechanism of reading status IN ('firm','released','in_transit') from pipe_supply_orders.
Written by: ERP sync / manual upload / POST /api/supply/firm-orders[/import].
Read by: supply_runner._load_firm_supply_orders() (Python), allocation_runner.load_supplies() via the CH PG-engine mirror MASTER_supply_orders_firm, and GET /api/supply/orders?include_firm=true for UI display.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Row identifier |
external_id |
TEXT | ERP PO/work-order ID for round-tripping. Part of unique key. |
item_id |
BIGINT | Item (no FK — master_item is a view in tenant DBs) |
site_id |
BIGINT | Destination site |
source_site_id |
BIGINT | Source site (TRANSFER orders) |
order_type |
VARCHAR(20) | BUY, BUILD, REPAIR, TRANSFER, REPLENISH, RETURN, BALANCING |
qty |
DOUBLE | Order quantity (must be > 0) |
release_dt |
TIMESTAMPTZ | When the order is released. The supply engine buckets this to a week index relative to today() for the per-type block-horizon check. |
arrival_dt |
TIMESTAMPTZ | When inventory becomes available. Bucketed for scheduled_receipts. |
route_id |
BIGINT | Optional route reference |
unit_cost, order_cost, total_cost |
DOUBLE | Optional cost fields |
erp_status |
VARCHAR(20) | firm / released / in_transit / received. received rows are skipped by the engine and the allocation runner. |
received_qty |
DOUBLE | Partial-receipt tracking (engine subtracts from qty when building scheduled_receipts) |
expiry_date |
DATE | Shelf-life expiry |
attributes |
JSONB | Free-form metadata |
source_system |
TEXT | 'erp_sap' / 'manual_upload' / 'legacy_pipe_supply_orders'. Part of unique key. |
created_at, updated_at |
TIMESTAMPTZ | Audit timestamps |
Unique constraint: (external_id, source_system) — makes ERP imports idempotent.
Indexes:
- idx_firm_so_item_site (item_id, site_id)
- idx_firm_so_arrival (arrival_dt)
- idx_firm_so_type_site (item_id, site_id, order_type, release_dt) — used by _load_firm_supply_orders to compute latest_firm_by_type
- idx_firm_so_status (erp_status)
ClickHouse mirror: MASTER_supply_orders_firm (PostgreSQL engine, read-only). Used by allocation_runner.load_supplies() and the supply orders endpoint to UNION firm rows into responses.
Domain: Allocation¶
The allocation engine distributes limited supply (inventory, production capacity) across competing demands using configurable priority, starvation, reservation, fairness, routing, aging, and lateness policies.
{schema}.allocation_parameter_set¶
Purpose: Named parameter sets for each allocation policy type. Seven parameter_type values are supported:
parameter_type |
Content |
|---|---|
allocation_priority |
PriorityScores + business weight multipliers |
allocation_starvation |
StarvationConfig (threshold, bonus, escalation) |
allocation_reservation |
ReservationConfig (lookahead, fences, penalty) |
allocation_fairness |
FairnessConfig (group_field, target_shares, cap) |
allocation_routing |
RoutingPolicy (one policy per row, keyed by policy_id) |
allocation_aging |
AgingPolicy (one policy per row, keyed by policy_id) |
allocation_lateness |
LatenessCurve (one curve per row, keyed by curve_id) |
Global types (priority/starvation/reservation/fairness): one default per scenario; segment overrides via allocation_parameter_set_segment.
Policy types (routing/aging/lateness): many per scenario; loaded into engine config HashMaps.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Parameter set identifier |
scenario_id |
BIGINT NOT NULL | Supply scenario scope |
parameter_type |
VARCHAR(40) NOT NULL | One of the seven types above |
name |
VARCHAR(120) NOT NULL | Set name |
label |
VARCHAR(120) | Human-readable display name |
params |
JSONB NOT NULL DEFAULT '{}' | Configuration values |
is_default |
BOOLEAN NOT NULL DEFAULT FALSE | Whether this is the scenario-level default for its type |
sort_order |
SMALLINT NOT NULL DEFAULT 0 | Display order in UI |
created_at |
TIMESTAMPTZ NOT NULL DEFAULT NOW() | Creation timestamp |
updated_at |
TIMESTAMPTZ NOT NULL DEFAULT NOW() | Last update |
Unique index: (scenario_id, parameter_type) WHERE is_default = TRUE — only one default per type per scenario.
{schema}.allocation_parameter_set_segment¶
Purpose: Segment-level linkage for allocation parameter sets. For global types: links a non-default set to a segment so that demands in that segment use this set. For policy types: controls which policies are visible when resolving demands within a segment.
| Column | Type | Description |
|---|---|---|
parameter_set_id |
BIGINT FK → allocation_parameter_set |
Parameter set |
segment_id |
BIGINT NOT NULL | Target segment |
Primary key: (parameter_set_id, segment_id)
{schema}.allocation_run¶
Purpose: Log of each allocation run. Records the parameter sets used, timing, and summary metrics.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Run identifier |
pipeline_id |
BIGINT NOT NULL | Pipeline scope |
scenario_id |
BIGINT NOT NULL | Scenario scope |
started_at |
TIMESTAMPTZ NOT NULL DEFAULT NOW() | Run start time |
completed_at |
TIMESTAMPTZ | Run completion time |
status |
VARCHAR(20) NOT NULL DEFAULT 'running' | running / completed / failed |
demand_count |
INTEGER | Total demands in scope |
supply_count |
INTEGER | Total supply lines |
allocated_count |
INTEGER | Successfully allocated demands |
unfulfilled_count |
INTEGER | Demands with shortfall |
fill_rate |
DOUBLE PRECISION | Overall fill rate |
priority_param_id |
BIGINT FK → allocation_parameter_set |
Priority parameter set used |
starvation_param_id |
BIGINT FK → allocation_parameter_set |
Starvation parameter set used |
reservation_param_id |
BIGINT FK → allocation_parameter_set |
Reservation parameter set used |
fairness_param_id |
BIGINT FK → allocation_parameter_set |
Fairness parameter set used |
resolved_config |
JSONB | Full assembled config snapshot (for auditability/replay) |
metrics |
JSONB | Summary metrics |
error_msg |
TEXT | Error details if failed |
{schema}.allocation_result¶
Purpose: Individual demand-to-supply allocation assignments. Each row records which demand was allocated to which supply, the total score, breakdown, and any penalties.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Result record |
run_id |
BIGINT FK → allocation_run |
Parent run |
pipeline_id |
BIGINT NOT NULL | Pipeline scope |
scenario_id |
BIGINT NOT NULL | Scenario scope |
demand_id |
VARCHAR(120) NOT NULL | Demand identifier |
supply_id |
VARCHAR(120) NOT NULL | Supply identifier |
item_id |
BIGINT | Item |
site_id |
BIGINT | Site |
allocated_qty |
DOUBLE PRECISION NOT NULL | Quantity allocated |
total_score |
DOUBLE PRECISION NOT NULL | Composite score |
score_priority |
DOUBLE PRECISION | Priority component |
score_aging |
DOUBLE PRECISION | Aging component |
score_lateness |
DOUBLE PRECISION | Lateness component |
score_strategic |
DOUBLE PRECISION | Strategic weight |
score_revenue |
DOUBLE PRECISION | Revenue weight |
score_criticality |
DOUBLE PRECISION | Criticality weight |
penalty_routing |
DOUBLE PRECISION | Routing penalty |
penalty_fairness |
DOUBLE PRECISION | Fairness penalty |
penalty_reservation |
DOUBLE PRECISION | Reservation penalty |
explanation |
TEXT | Human-readable explanation |
score_breakdown |
JSONB | Detailed score breakdown |
{schema}.allocation_unfulfilled¶
Purpose: Demands that could not be fully satisfied. Records the shortfall quantity and the reasons (insufficient supply, capacity constraint, etc.).
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Unfulfilled record |
run_id |
BIGINT FK → allocation_run |
Parent run |
pipeline_id |
BIGINT NOT NULL | Pipeline scope |
scenario_id |
BIGINT NOT NULL | Scenario scope |
demand_id |
VARCHAR(120) NOT NULL | Demand identifier |
item_id |
BIGINT | Item |
site_id |
BIGINT | Site |
requested_qty |
DOUBLE PRECISION NOT NULL | Quantity demanded |
allocated_qty |
DOUBLE PRECISION NOT NULL DEFAULT 0 | Quantity actually allocated |
shortfall_qty |
DOUBLE PRECISION NOT NULL | requested_qty - allocated_qty |
reasons |
TEXT[] | Array of reason codes |
Domain: Causal / Asset-Driven Demand¶
{schema}.causal_asset_type¶
Purpose: Master table of asset types (e.g. aircraft model, bicycle type). Stores AOG (Aircraft On Ground) cost and mean AOG duration for aerospace use cases.
| Column | Type | Description |
|---|---|---|
asset_type_id |
BIGSERIAL PK | Asset type identifier |
code |
TEXT UNIQUE | Short code |
name |
TEXT | Asset type name |
aog_cost_per_day |
DOUBLE PRECISION | Cost per day when asset is grounded |
mean_aog_days |
DOUBLE PRECISION | Mean duration of an AOG event |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.causal_asset¶
Purpose: Individual asset instances (V2). Each row is a specific physical asset with its own serial number and status.
| Column | Type | Description |
|---|---|---|
asset_id |
TEXT PK | Asset identifier (e.g. tail number) |
asset_type_id |
BIGINT FK → causal_asset_type |
Asset type |
name |
TEXT | Display name |
serial_number |
TEXT | Serial number |
manufactured_at |
DATE | Manufacture date |
status |
TEXT | active, retired, maintenance |
attributes |
TEXT | JSON stored as TEXT (ClickHouse compatibility) |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.causal_bom¶
Purpose: Bill of Materials for causal forecasting — links items (parts) to asset types. Supports LRU→SRU parent chains. V2 adds sub_asset_name and position for position-level failure rate differentiation.
| Column | Type | Description |
|---|---|---|
bom_id |
BIGSERIAL PK | BOM line identifier |
asset_type_id |
BIGINT FK → causal_asset_type |
Asset type this part belongs to |
item_id |
BIGINT | Item (part) |
qty_per_asset |
DOUBLE PRECISION | Quantity of this part per asset |
usage_type_id |
BIGINT FK → causal_usage_type |
Usage type (flying hours, cycles, etc.) |
is_lru |
BOOLEAN | Whether this is a Line Replaceable Unit |
repair_yield |
DOUBLE PRECISION | Fraction repaired successfully (0–1) |
parent_bom_id |
BIGINT FK → causal_bom |
Parent LRU for SRU hierarchies |
sub_asset_name |
TEXT | Sub-assembly name (V2) |
position |
TEXT | Position code (V2 — for position-level failure rates) |
updated_at |
TIMESTAMPTZ | Last update |
Unique index (V2): (asset_type_id, item_id, COALESCE(sub_asset_name,''), COALESCE(position,''))
{schema}.causal_failure_rate¶
Purpose: Failure rates — how often a part fails per unit of asset usage. Uses a 4-level waterfall hierarchy: global → position → site+position → asset+position (most specific wins). V2 adds asset_id, site_id, position columns.
| Column | Type | Description |
|---|---|---|
failure_rate_id |
BIGSERIAL PK | Row identifier |
customer_id |
INT FK → customer |
Customer (NULL = global) |
scenario_id |
BIGINT | Scenario scope (default 1) |
item_id |
BIGINT | Part |
failure_rate |
DOUBLE PRECISION | Failures per unit of usage |
usage_type_id |
BIGINT FK → causal_usage_type |
Usage type |
period_start |
DATE | Validity start |
period_end |
DATE | Validity end |
asset_id |
TEXT | Specific asset (V2 — NULL = any) |
site_id |
BIGINT | Specific site (V2 — NULL = any) |
position |
TEXT | Position code (V2 — NULL = any) |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.causal_asset_deployment¶
Purpose: Records when and where each asset is deployed. Used to compute exposure time for demand calculation.
| Column | Type | Description |
|---|---|---|
deployment_id |
BIGSERIAL PK | Deployment identifier |
asset_id |
TEXT | Asset identifier |
site_id |
BIGINT FK → site |
Base site |
customer_id |
INT FK → customer |
Operating customer |
deploy_date |
DATE | Date of deployment |
qty |
DOUBLE PRECISION | Number of units deployed |
scenario_id |
BIGINT | Scenario scope |
notes |
TEXT | Optional notes |
created_at |
TIMESTAMPTZ | Record creation timestamp |
{schema}.causal_asset_coverage¶
Purpose: Defines which site services which asset (coverage allocation). Used to spread causal demand across sites.
| Column | Type | Description |
|---|---|---|
coverage_id |
BIGSERIAL PK | Coverage record |
asset_id |
TEXT | Specific asset (NULL = any of this type) |
asset_type_id |
BIGINT FK → causal_asset_type |
Asset type |
site_id |
BIGINT NOT NULL FK → site |
Servicing site |
customer_id |
INT FK → customer |
Customer |
coverage_pct |
DOUBLE PRECISION | Fraction of demand serviced by this site (0–1) |
scenario_id |
BIGINT | Scenario scope |
valid_from |
DATE | Coverage validity start |
valid_to |
DATE | Coverage validity end |
created_at |
TIMESTAMPTZ | Record creation timestamp |
{schema}.causal_scenarios¶
Purpose: Named causal forecasting scenarios with fleet, failure rate, usage, and coverage overrides as JSONB.
| Column | Type | Description |
|---|---|---|
scenario_id |
BIGSERIAL PK | Scenario identifier |
name |
TEXT | Scenario name |
description |
TEXT | Description |
is_base |
BOOLEAN | Whether this is the base scenario |
created_by |
TEXT | Creator email |
created_at |
TIMESTAMPTZ | Creation timestamp |
fleet_overrides |
JSONB | Fleet quantity overrides (V2) |
failure_rate_overrides |
JSONB | Failure rate overrides (V2) |
usage_overrides |
JSONB | Usage factor overrides (V2) |
coverage_overrides |
JSONB | Coverage allocation overrides (V2) |
linked_meio_scenario_id |
BIGINT | Links to an MEIO scenario |
{schema}.causal_fleet_plan¶
Purpose: Planned deployment of asset types to customers per period. Used to compute total fleet exposure.
| Column | Type | Description |
|---|---|---|
fleet_plan_id |
BIGSERIAL PK | Plan record |
scenario_id |
BIGINT FK → forecast_scenarios |
Scenario scope |
asset_type_id |
BIGINT FK → causal_asset_type |
Asset type |
customer_id |
INT FK → customer |
Customer operating these assets |
asset_qty |
INTEGER | Number of assets |
period_start |
DATE | Plan period start |
period_end |
DATE | Plan period end |
{schema}.causal_usage_type¶
Purpose: Lookup for causal usage types (e.g. flying hours, cycles, landings). Referenced by causal_bom and causal_failure_rate.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Usage type identifier |
name |
TEXT UNIQUE | Usage type name (e.g. flying_hours, cycles) |
{schema}.causal_asset_usage¶
Purpose: Planned usage (e.g. flying hours) per asset per period. Used by the causal forecasting engine to compute expected demand from fleet exposure and failure rates.
| Column | Type | Description |
|---|---|---|
usage_id |
BIGSERIAL PK | Usage record |
scenario_id |
BIGINT FK → forecast_scenarios |
Scenario scope |
asset_type_id |
BIGINT FK → causal_asset_type |
Asset type |
customer_id |
INT FK → customer |
Customer |
usage_type_id |
BIGINT FK → causal_usage_type |
Usage type |
usage_qty |
DOUBLE PRECISION | Planned usage quantity per period |
period_start |
DATE | Period start |
period_end |
DATE | Period end |
Maintenance Tables¶
{schema}.maintenance_event¶
Purpose: Catalogue of maintenance event types (e.g. "12,000-hour overhaul", "annual inspection").
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Event identifier |
name |
TEXT | Event name |
note |
TEXT | Optional notes |
{schema}.maintenance_bom¶
Purpose: Parts required for each maintenance event, with likelihood-of-removal factor.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Row identifier |
event_id |
BIGINT FK → maintenance_event |
Maintenance event |
item_id |
BIGINT FK → item |
Required part |
qty |
DOUBLE PRECISION | Quantity required |
likelihood_of_removal |
DOUBLE PRECISION | Probability this part is actually replaced (0–1) |
{schema}.maintenance_scenario_event¶
Purpose: Junction table linking forecast scenarios to maintenance events.
| Column | Type | Description |
|---|---|---|
scenario_id |
BIGINT FK → forecast_scenarios |
Forecast scenario (composite PK) |
event_id |
BIGINT FK → maintenance_event |
Event (composite PK) |
{schema}.maintenance_scenario_event_date¶
Purpose: Specific occurrence dates for each scenario/event combination, optionally scoped to a site.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Occurrence record |
scenario_id |
BIGINT | Forecast scenario |
event_id |
BIGINT | Maintenance event |
event_date |
DATE | Date of the maintenance event |
site_id |
BIGINT FK → site |
Where the event occurs |
qty |
DOUBLE PRECISION | Number of assets/units at this occurrence |
Domain: BOM and Routes¶
{schema}.route_type¶
Purpose: Lookup for route planning types (BUY, MAKE, TRANSFER).
| Column | Type | Description |
|---|---|---|
id |
BIGINT PK | Type identifier |
xuid |
TEXT | External identifier |
name |
TEXT | Type name |
description |
TEXT | Description |
planning_type |
TEXT | BUY / MAKE / TRANSFER |
calc_supply_type_id |
BIGINT | Supply type for calculation |
calc_dep_demand_type_id |
BIGINT | Dependent demand type |
{schema}.route¶
Purpose: Supply routes — the lane from source to destination for each item. Defines lead times, costs, order constraints, and calendars.
| Column | Type | Description |
|---|---|---|
id |
BIGINT PK | Route identifier |
item_id |
BIGINT | Item |
site_id |
BIGINT | Destination site |
supplier_id |
BIGINT | Supplier (for BUY routes) |
type_id |
BIGINT FK → route_type |
Route type |
source_item_id |
BIGINT | Source item (for MAKE/TRANSFER) |
source_site_id |
BIGINT | Source site |
min_qty |
DOUBLE PRECISION | Minimum order quantity |
mult_qty |
DOUBLE PRECISION | Order quantity multiple |
max_qty |
DOUBLE PRECISION | Maximum order quantity |
lead_time |
SMALLINT | Lead time in weeks |
pick_pack_time |
SMALLINT | Weeks for pick/pack |
transit_time |
SMALLINT | Weeks in transit |
inspection_time |
SMALLINT | Weeks for inspection |
safety_lead_time |
SMALLINT | Buffer lead time |
ptf |
SMALLINT | Planning time fence in weeks |
yield |
DOUBLE PRECISION | Expected yield fraction |
unit_cost |
DOUBLE PRECISION | Unit cost on this route |
order_cost |
DOUBLE PRECISION | Fixed order cost |
end_date |
DATE | Route expiry date |
*_calendar_id |
BIGINT | Calendar IDs for each lead-time component |
{schema}.bill_of_material¶
Purpose: Parent-child component relationships for manufactured items.
| Column | Type | Description |
|---|---|---|
id |
BIGINT PK | BOM line identifier |
item_id |
BIGINT | Parent item |
site_id |
BIGINT | Parent site |
child_item_id |
BIGINT | Component item |
child_site_id |
BIGINT | Component site |
item_qty |
DOUBLE PRECISION | Parent quantity per BOM |
child_qty |
DOUBLE PRECISION | Component quantity per parent |
attach_rate |
DOUBLE PRECISION | Fraction of parents that use this component |
start_date |
DATE | BOM validity start |
end_date |
DATE | BOM validity end |
"offset" |
SMALLINT | Time offset in weeks |
scrap |
DOUBLE PRECISION | Scrap factor |
{schema}.item_chain¶
Purpose: Item supersession and substitution chains. Links old items to their replacements.
| Column | Type | Description |
|---|---|---|
id |
BIGINT PK | Chain record |
description |
TEXT | Description |
item_id |
BIGINT | Parent (being superseded) |
site_id |
BIGINT | Site |
child_item_id |
BIGINT | Replacement item |
child_site_id |
BIGINT | Replacement site |
policy_id |
BIGINT | Supersession policy |
{schema}.route_resource¶
Purpose: Maps supply routes to resource types (supplier, plant, repair shop, transport). Used by the supply engine for capacity-constrained planning.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Resource mapping record |
route_id |
BIGINT FK → route |
Supply route |
resource_type |
VARCHAR(30) | SUPPLIER / PLANT / REPAIR_SHOP / TRANSPORT |
resource_id |
BIGINT | Resource identifier |
Domain: Configuration¶
{schema}.parameters¶
Purpose: All pipeline and system configuration. Each row is a named parameter set for a specific subsystem. The parameters_set JSONB column holds the actual configuration.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Parameter set identifier |
parameter_type |
TEXT | Subsystem key: data_source, outlier_detection, characterization, forecasting, backtesting, meio, parallel, auth, segmentation, supply |
name |
TEXT | Set name (e.g. Default, High seasonality) |
label |
TEXT | Display label in UI |
parameters_set |
JSONB | Configuration key-value pairs |
description |
TEXT | Human-readable description |
is_default |
BOOLEAN | Active default for this parameter_type |
sort_order |
INTEGER | Display order in UI |
updated_at |
TIMESTAMPTZ | Last update |
created_at |
TIMESTAMPTZ | Creation timestamp |
Unique constraint: (parameter_type, name)
{schema}.parameter_segment¶
Purpose: Associates parameter sets with segments for segment-based parameter routing.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Association identifier |
parameter_id |
INTEGER FK → parameters |
Parameter set |
segment_id |
INTEGER FK → segment |
Target segment |
{schema}.series_parameter_assignment¶
Purpose: Per-series parameter overrides. Allows specific SKUs to use non-default parameter sets for any pipeline step. Part of the 3-level ParameterResolver hierarchy.
| Column | Type | Description |
|---|---|---|
unique_id |
TEXT PK | Series identifier |
item_id |
BIGINT | Item |
site_id |
BIGINT | Site |
forecasting_parameter_id |
INTEGER FK → parameters |
Forecasting parameter override |
outlier_detection_parameter_id |
INTEGER FK → parameters |
Outlier detection override |
characterization_parameter_id |
INTEGER FK → parameters |
Characterisation override |
backtesting_parameter_id |
INTEGER FK → parameters |
Backtesting / method-selection override (renamed from best_method) |
valuation_parameter_id |
INTEGER FK → parameters |
Valuation/MEIO override |
allocation_parameter_id |
INTEGER FK → allocation_parameter_set |
Allocation priority parameter override |
updated_at |
TIMESTAMPTZ | Last update |
Note:
evaluation_parameter_idandbest_method_parameter_idwere dropped (theevaluationtype was purged;best_methodwas renamed tobacktesting). The per-segmentseries_parameter_mapsnapshot (synced to CHPIPE_preprocessed) additionally carriesmeio_parameter_ids,hyperparameter_set_id,supply_parameter_id,meio_target_parameter_id,order_frequency_parameter_id, and the sixallocation_*policy columns.netting_parameter_id/method_selection_parameter_idwere never live (their config lives in the forecast parameter set / scenario JSON respectively).
{schema}.segment¶
Purpose: Named groupings of SKUs. Used to apply different parameter sets, access controls, and ABC classifications to different product families.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Segment identifier |
name |
TEXT UNIQUE | Segment name (seeded with All) |
description |
TEXT | Description |
criteria |
JSONB | Filter criteria for dynamic assignment |
is_default |
BOOLEAN | Whether this is the catch-all default segment |
characterization_horizon |
INTEGER | Horizon for this segment's characterisation (default 24) |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.segment_membership¶
Purpose: Records which series belong to which segment. Populated by the segmentation process.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Membership record |
segment_id |
INTEGER FK → segment |
Segment |
unique_id |
TEXT | Series identifier |
item_id |
BIGINT | Item |
site_id |
BIGINT | Site |
assigned_at |
TIMESTAMPTZ | Assignment timestamp |
{schema}.abc_configuration¶
Purpose: Configuration for ABC/XYZ classification runs. Multiple configurations can coexist.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Configuration identifier |
name |
TEXT UNIQUE | Configuration name |
metric |
TEXT | hits / demand / value |
lookback_months |
INTEGER | Historical window for classification (default 12) |
granularity |
TEXT | item_site or item |
method |
TEXT | cumulative_pct / rank_pct / rank_absolute |
class_labels |
JSONB | Labels array (e.g. ["A","B","C"]) |
thresholds |
JSONB | Threshold percentiles (e.g. [80, 95]) |
segment_id |
INTEGER FK → segment |
Apply results as a segment |
is_active |
BOOLEAN | Whether this config is active |
dynamic_anchor |
BOOLEAN | Use dynamic date anchor for lookback |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.abc_results¶
Purpose: Results of an ABC classification run.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Result record |
config_id |
INTEGER FK → abc_configuration |
Configuration used |
unique_id |
TEXT | Series identifier |
class_label |
TEXT | Assigned class (e.g. A, B, C) |
metric_value |
DOUBLE PRECISION | Metric value for this series |
rank |
INTEGER | Rank within the classification |
cumulative_pct |
DOUBLE PRECISION | Cumulative percentage at this rank |
computed_at |
TIMESTAMPTZ | Computation timestamp |
Domain: Workflow / Exception Management¶
{schema}.interact_exception_log¶
Purpose: Tracks supply chain exceptions at the item/site level with workflow states. Uses the interact_ prefix (user-editable tables). Populated automatically at the end of each Exceptions pipeline step via exceptions_runner.generate_exception_log().
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Exception record |
pipeline_id |
INTEGER | Pipeline scope |
item_id |
INTEGER | Affected item |
site_id |
INTEGER | Affected site |
exception_types |
TEXT[] | Array of exception type codes |
issues |
JSONB | Detailed issue list |
status |
TEXT | open / reviewed / snoozed / actioned |
snoozed_until |
TIMESTAMPTZ | If snoozed, when to re-surface |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
resolved_at |
TIMESTAMPTZ | When resolved |
{schema}.io_overrides¶
Purpose: Inventory optimisation overrides — service-level, EOQ, and fill-rate overrides per item/site/pipeline/scenario.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Override record |
pipeline_id |
INTEGER | Pipeline scope |
scenario_id |
INTEGER | Scenario scope |
item_id |
INTEGER | Item |
site_id |
INTEGER | Site |
sl_override |
NUMERIC | Service-level override (0–1) |
eoq_override |
NUMERIC | EOQ quantity override |
fr_override |
NUMERIC | Fill-rate override (0–1) |
created_by |
TEXT | Creator email |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.order_approvals¶
Purpose: Approval records for supply orders (planned → approved workflow).
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Approval record |
order_id |
INTEGER | Supply order ID |
approved_by |
TEXT | Approver email |
approved_at |
TIMESTAMPTZ | Approval timestamp |
approved_quantity |
NUMERIC | Approved quantity (may differ from planned) |
approved_availability_date |
DATE | Committed availability date |
{schema}.auto_approval_rule¶
Purpose: Rules that automatically approve supply order recommendations matching specific criteria (type, quantity range, item/site patterns). Evaluated by the recommendation approval workflow.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Rule identifier |
recommendation_type |
TEXT | Type of recommendation this rule applies to |
name |
TEXT | Rule name |
description |
TEXT | Rule description |
conditions |
JSONB | Match conditions (item category, quantity range, etc.) |
action |
TEXT | approve / reject |
is_active |
BOOLEAN | Whether the rule is currently enabled |
created_by |
TEXT | Creator email |
created_at |
TIMESTAMPTZ | Creation timestamp |
Indexes: idx_aar_type_active (recommendation_type, is_active)
{schema}.recommendation_approval¶
Purpose: Records of approval/rejection decisions for supply order recommendations. Links to an optional auto-approval rule.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Approval record |
pipeline_id |
BIGINT NOT NULL | Pipeline scope |
scenario_id |
BIGINT | Scenario scope |
recommendation_type |
TEXT | Type of recommendation |
item_id |
BIGINT | Item |
site_id |
BIGINT | Site |
decision |
TEXT | approved / rejected / pending |
decided_by |
TEXT | Approver email (or auto if rule-based) |
decided_at |
TIMESTAMPTZ | Decision timestamp |
auto_rule_id |
BIGINT FK → auto_approval_rule |
Auto-approval rule that triggered this decision (NULL = manual) |
source |
TEXT | Source of the recommendation |
source_detail |
TEXT | Additional source detail |
notes |
TEXT | Approver notes |
Indexes: idx_rec_appr_type (recommendation_type, decided_at DESC), idx_rec_appr_pipeline (pipeline_id, decided_at DESC), idx_rec_appr_item_site (item_id, site_id)
{schema}.planning_event¶
Purpose: Calendar-style planning events (e.g. product launches, promotions, supply disruptions). Used by the planning workflow to annotate and coordinate decisions.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Event identifier |
pipeline_id |
BIGINT NOT NULL FK → scenario_pipeline |
Pipeline scope |
scenario_id |
BIGINT | Scenario scope |
event_date |
DATE NOT NULL | When the event occurs |
event_type |
TEXT NOT NULL | promotion / launch / disruption / shutdown / custom |
title |
TEXT | Event title |
description |
TEXT | Event description |
impact |
JSONB | Expected impact details |
created_by |
TEXT | Creator email |
created_at |
TIMESTAMPTZ | Creation timestamp |
Indexes: idx_event_date (event_date, event_type), idx_event_pipeline (pipeline_id, event_date)
{schema}.series_param_overrides¶
Purpose: Per-series parameter overrides (beyond series_parameter_assignment). Allows granular parameter tuning at the series level for any parameter type.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Override record |
unique_id |
TEXT | Series identifier |
parameter_type |
TEXT | Parameter type key |
overrides |
JSONB | Override key-value pairs |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.annotation¶
Purpose: User-created annotations attached to any entity (series, item, site). Used for collaborative notes and context sharing.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Annotation identifier |
entity_type |
TEXT | Target entity type (series, item, site) |
entity_id |
TEXT | Target entity identifier |
content |
TEXT | Annotation text |
created_by |
TEXT | Creator email |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.bookmark¶
Purpose: User bookmarks for quick navigation to frequently accessed series, items, or pages.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Bookmark identifier |
user_email |
TEXT | User who created the bookmark |
entity_type |
TEXT | Target entity type |
entity_id |
TEXT | Target entity identifier |
label |
TEXT | Display label |
created_at |
TIMESTAMPTZ | Creation timestamp |
{schema}.process_log¶
Purpose: Records every pipeline step execution. Supports live log streaming to the frontend.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Log entry |
run_id |
TEXT | Unique run identifier (UUID string) |
step_name |
TEXT | Step name (e.g. ETL, Outlier Detection, Forecasting) |
status |
TEXT | pending / running / completed / failed |
started_at |
TIMESTAMPTZ | Step start time |
ended_at |
TIMESTAMPTZ | Step end time |
duration_s |
DOUBLE PRECISION | Duration in seconds |
rows_processed |
INTEGER | Number of series/rows processed |
error_message |
TEXT | Error details if status = failed |
log_tail |
TEXT | Last N lines of log output |
pipeline_id |
BIGINT | Pipeline this run belongs to |
scenario_id |
INTEGER | Scenario scope |
created_at |
TIMESTAMPTZ | Record creation timestamp |
{schema}.sys_audit_log¶
Purpose: Immutable audit trail for all significant state changes — configuration,
security, forecasting, pipeline, and model events. The single surface queried by
the /audit UI (GET /api/audit-log). Writes go through the centralized helper
in files/utils/audit.py (audit_log in-transaction, audit_log_standalone
fire-and-forget).
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Audit entry |
entity_type |
TEXT | Type of entity changed (see catalog below) |
entity_id |
INTEGER | ID of the changed entity (NULL for auth / aggregate events) |
action |
TEXT | Verb performed on the entity (see catalog below) |
old_value |
JSONB | Previous state |
new_value |
JSONB | New state |
changed_by |
TEXT | User email or system |
ip |
TEXT | Client IP of the actor (added by enrichment migration) |
user_agent |
TEXT | User-Agent header of the actor |
request_id |
TEXT | Correlation id (X-Request-Id) / workflow job_id |
outcome |
TEXT | success / failure / denied (auth + run lifecycle) |
created_at |
TIMESTAMPTZ | Change timestamp (wall-clock, never planning_today) |
The ip, user_agent, request_id, and outcome columns are added by the
idempotent migration files/DDL/migrations/audit_log_enrich.sql (also ensured
by db.init_schema on every startup). All are nullable; the helper writes them
only when present, so it works before, during, and after the migration.
(entity_type, action) event catalog¶
| entity_type | actions |
|---|---|
parameter |
create, update, delete, reorder |
parameter_segment |
update |
parameter_set |
create, update, delete (forecast & MEIO parameter sets) |
segment |
create, update, delete |
meio_scenario |
create, update, delete, clone |
scenario |
delete (forecast / exception / classification / causal / inventory cascade wipes) |
pipeline |
create, update, delete, clone, copy_from, promote_prod |
pipeline_segment |
attach, detach |
planning_date |
set, delete |
override |
set, delete (param + hyperparameter series overrides) |
adjustment |
set, delete (forecast adjustments / overrides) |
import |
firm_orders, fleet_plan, bom (CSV / bulk data ingress) |
historization |
freeze (mirrored from sys_historization_log) |
process_run |
run, run_start, run_complete, run_fail |
auth |
login, logout, failed_login, change_password, switch_account |
user |
create, update, delete, role_change, disable, enable |
auto_approval_rule |
create, update (rule-set upload + governance transition) |
model |
promote, retrain (auto-approval champion lifecycle) |
export |
csv, excel (data egress — best-effort) |
Sibling audit tables (not in the /audit UI): sys_historization_log
(freeze/pin/unpin operations) and interact_planning_event (planning events +
auto-approval recommendation transition chain, surfaced via
GET /api/auto-approval/{id}/audit).
Domain: Authentication¶
{schema}.users¶
Purpose: Per-tenant user accounts. Admins and regular users. SuperAdmins are stored in master.superadmins, not here.
| Column | Type | Description |
|---|---|---|
id |
UUID PK | User identifier |
email |
TEXT UNIQUE | Login email |
display_name |
TEXT | Display name in UI |
hashed_password |
TEXT | bcrypt hash; NULL for OAuth-only accounts |
auth_provider |
TEXT | local / microsoft / google |
role |
TEXT | admin or user |
is_active |
BOOLEAN | Disable without deleting |
allowed_segments |
JSONB | List of segment IDs the user can view |
can_run_process |
BOOLEAN | Permission to trigger pipeline runs |
can_create_override |
BOOLEAN | Permission to apply forecast overrides |
allowed_segments_edit |
JSONB | List of segment IDs the user can edit |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.revoked_tokens¶
Purpose: Blocklist of revoked JWT token identifiers (jti claims). Checked on every authenticated request to implement logout.
| Column | Type | Description |
|---|---|---|
jti |
TEXT PK | JWT ID from the token's jti claim |
revoked_at |
TIMESTAMPTZ | Revocation timestamp |
{schema}.historization_log¶
Purpose: Log of all historization (freeze) operations. Records each freeze, pin, un-pin, and retention cleanup event for auditability.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Log entry |
pipeline_id |
BIGINT | Pipeline that was frozen |
archive_date |
DATE | Archive date of the operation |
operation |
TEXT | freeze / pin / unpin / retention_delete |
created_by |
TEXT | User or system that triggered the operation |
snapshot_ts |
TIMESTAMPTZ | Exact timestamp |
details |
JSONB | Additional operation details |
created_at |
TIMESTAMPTZ | Log creation timestamp |
Domain: Overrides¶
{schema}.forecast_adjustments¶
Purpose: Planner-applied adjustments and overrides to specific forecast dates. Supports both percentage adjustments and absolute overrides.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Adjustment record |
unique_id |
TEXT | Series identifier |
forecast_date |
DATE | The forecast date being adjusted |
adjustment_type |
TEXT | adjustment (multiplicative %) or override (absolute value) |
value |
DOUBLE PRECISION | Adjustment value |
note |
TEXT | Planner's note explaining the adjustment |
created_by |
TEXT | Creator (ui for UI-driven changes) |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
Unique constraint: (unique_id, forecast_date, adjustment_type)
{schema}.series_hyperparameters_overrides¶
Purpose: Per-series, per-method hyperparameter overrides. Allows specific SKUs to use non-default model hyperparameters.
| Column | Type | Description |
|---|---|---|
id |
SERIAL PK | Override record |
unique_id |
TEXT | Series identifier |
method |
TEXT | Model name |
overrides |
JSONB | Hyperparameter key-value overrides |
updated_at |
TIMESTAMPTZ | Last update |
Unique constraint: (unique_id, method)
Domain: Scenarios and Pipelines¶
{schema}.forecast_scenario_type¶
Purpose: Lookup for scenario types. Seeded with three standard values.
Seeded values: Direct demand, Causal forecasting, Planned maintenance
{schema}.forecast_scenarios¶
Purpose: Named forecast scenarios. Each scenario is associated with a type, demand type, and a set of parameter and demand overrides.
| Column | Type | Description |
|---|---|---|
scenario_id |
BIGSERIAL PK | Scenario identifier (seeded: 1 = Base) |
name |
TEXT | Scenario name |
description |
TEXT | Description |
is_base |
BOOLEAN | Whether this is the base scenario |
scenario_type_id |
BIGINT FK → forecast_scenario_type |
Scenario type |
demand_type_id |
BIGINT FK → demand_type |
Demand type |
status |
TEXT | pending / running / complete / failed |
run_at |
TIMESTAMPTZ | Last run timestamp |
error_msg |
TEXT | Error if failed |
created_by |
TEXT | Creator email |
created_at |
TIMESTAMPTZ | Creation timestamp |
param_overrides |
JSONB | Parameter overrides for this scenario |
demand_overrides |
JSONB | Demand overrides for this scenario |
{schema}.scenario_pipeline¶
Purpose: The central pipeline record that ties all scenarios and runs together. Selected by the user via the left toolbar dropdown in the UI.
| Column | Type | Description |
|---|---|---|
pipeline_id |
BIGSERIAL PK | Pipeline identifier |
name |
TEXT | Pipeline name |
description |
TEXT | Description |
series_scenario_id |
BIGINT FK → forecast_scenarios |
Direct demand scenario |
causal_scenario_id |
BIGINT FK → causal_scenarios |
Causal scenario |
maintenance_scenario_id |
BIGINT FK → forecast_scenarios |
Maintenance scenario |
inventory_scenario_id |
BIGINT FK → inventory_scenario |
MEIO scenario |
supply_scenario_id |
BIGINT | Supply scenario |
meio_scenario_id |
BIGINT | MEIO parameter scenario |
causal_pct |
DOUBLE PRECISION | Blend % from causal forecasting |
series_pct |
DOUBLE PRECISION | Blend % from direct demand |
maintenance_pct |
DOUBLE PRECISION | Blend % from maintenance |
status |
TEXT | draft / running / complete |
data_source_type |
TEXT | master (ETL/ERP) or pipeline (copied from another run) |
data_source_pipeline_id |
BIGINT FK → self | Source pipeline if copying |
created_by |
TEXT | Creator email |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
Constraint: causal_pct + series_pct + maintenance_pct = 100.0 or all three must be 0.
{schema}.scenario_pipeline_segment¶
Purpose: Controls which segments are in scope for each pipeline run.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Record identifier |
pipeline_id |
BIGINT FK → scenario_pipeline |
Pipeline |
segment_id |
BIGINT FK → segment |
Segment in scope |
Domain: Calendar¶
{schema}.calendar¶
Purpose: Named working calendars used in supply route lead-time calculations.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Calendar identifier |
name |
TEXT UNIQUE | Calendar name |
type |
TEXT | working or end2end |
mode |
TEXT | available or not_available |
description |
TEXT | Description |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update |
{schema}.calendar_entry¶
Purpose: Date ranges within a calendar. Combined with the calendar's mode, defines working or non-working periods.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Entry identifier |
calendar_id |
BIGINT FK → calendar |
Parent calendar |
date_from |
DATE | Period start |
date_to |
DATE | Period end (must be ≥ date_from) |
label |
TEXT | Optional label (e.g. "Christmas shutdown") |
Import Schema (import.*)¶
The import schema is a stateless staging area for ERP and external data. Tables are flat, forgiving landing zones with no foreign key constraints. The ETL pipeline reads from import.* tables, transforms and validates the data, and writes it into scenario.* (and master.* in the tenant DB). Customers truncate and repopulate import.* tables whenever they want — there are no audit columns and no _loaded_at tracking.
Drop safety
The import schema is safe to drop and re-create. Run files/DDL/import_schema.sql to restore. No business data is lost because import tables are temporary staging copies.
Audit Tables¶
import.import_run¶
Purpose: Log of each import ETL run. Records timing, status, and summary counts.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Run identifier |
triggered_by |
TEXT | User or system that triggered the import |
started_at |
TIMESTAMPTZ NOT NULL DEFAULT NOW() | Run start time |
completed_at |
TIMESTAMPTZ | Run completion time |
status |
TEXT NOT NULL DEFAULT 'running' | running / completed / completed_with_warnings / failed |
total_rows_loaded |
INTEGER DEFAULT 0 | Total rows loaded across all tables |
total_rows_rejected |
INTEGER DEFAULT 0 | Total rows rejected |
error_message |
TEXT | Error details if failed |
notes |
TEXT | Additional notes |
import.import_table_result¶
Purpose: Per-table results within an import run. One row per staging table processed.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Result identifier |
run_id |
BIGINT FK → import_run |
Parent run |
table_name |
TEXT NOT NULL | Staging table name |
target_table |
TEXT NOT NULL | Target table in scenario.* or master.* |
load_mode |
TEXT NOT NULL | truncate_replace / upsert |
rows_staged |
INTEGER DEFAULT 0 | Rows in staging table |
rows_loaded |
INTEGER DEFAULT 0 | Rows successfully loaded |
rows_rejected |
INTEGER DEFAULT 0 | Rows rejected |
error_sample |
TEXT | Sample error messages |
started_at |
TIMESTAMPTZ | Table processing start |
completed_at |
TIMESTAMPTZ | Table processing end |
duration_ms |
INTEGER | Processing duration in milliseconds |
Unique constraint: (run_id, table_name)
Staging Tables¶
All staging tables use xuid (TEXT) as the universal external key. No foreign key constraints — FK resolution happens in the ETL SQL. Primary keys are on natural keys so bulk-reloads without TRUNCATE are safe.
| Staging Table | Target in scenario.* / master.* |
Load Mode | Natural Key |
|---|---|---|---|
import.item_type |
scenario.master_item_type |
truncate_replace | xuid |
import.site_type |
scenario.master_site_type |
truncate_replace | xuid |
import.demand_type |
scenario.master_demand_type |
truncate_replace | name |
import.on_hand_type |
scenario.master_on_hand_type |
truncate_replace | xuid |
import.route_type |
scenario.master_route_type |
truncate_replace | xuid |
import.causal_usage_type |
scenario.master_causal_usage_type |
truncate_replace | name |
import.item |
scenario.master_item |
upsert | xuid |
import.site |
scenario.master_location |
upsert | xuid |
import.customer |
scenario.master_customer |
upsert | code |
import.customer_site |
scenario.master_customer_site |
upsert | customer_code + site_xuid |
import.channel |
scenario.master_channel |
upsert | xuid |
import.item_location |
scenario.master_item_location |
upsert | item_xuid + site_xuid |
import.demand_actuals |
scenario.master_demand_actuals |
truncate_replace | composite |
import.on_hand |
scenario.master_on_hand |
upsert | item_xuid + site_xuid + type_xuid |
import.bill_of_material |
scenario.master_bill_of_material |
upsert | parent_item_xuid + site_xuid + child_item_xuid |
import.route |
scenario.master_route |
upsert | item_xuid + site_xuid |
import.item_chain |
scenario.master_item_chain |
upsert | item_xuid + site_xuid + child_item_xuid |
import.calendar |
scenario.master_calendar |
upsert | name |
import.calendar_entry |
scenario.master_calendar_entry |
upsert | calendar_name + date_from + date_to |
import.currency_conversion |
scenario.master_currency_conversion |
upsert | from_currency + to_currency + effective_date |
import.firm_supply_orders |
scenario.master_supply_orders_firm |
upsert | external_id + source_system |
import.causal_asset_type |
scenario.master_causal_asset_type |
upsert | code |
import.causal_asset |
scenario.master_causal_asset |
upsert | asset_id |
import.causal_bom |
scenario.master_causal_bom |
upsert | composite |
import.causal_failure_rate |
scenario.master_causal_failure_rate |
upsert | full waterfall key |
import.maintenance_event |
scenario.master_maintenance_event |
upsert | name |
import.maintenance_bom |
scenario.master_maintenance_bom |
upsert | composite |
master_channel location
master_channel lives in {schema} (confirmed in schema.sql / db.py) — not in the master PostgreSQL schema.
ClickHouse (PIPE_* Tables)¶
All pipeline output tables live in ClickHouse (single shared instance, default database). Tenant isolation is by pipeline_id; there is no per-tenant CH database.
The schema DDL is in files/DDL/ch_schema.sql. All tables use ReplacingMergeTree for idempotent re-runs.
Naming Convention¶
PIPE_{step}_{entity} — e.g. PIPE_supply_orders, PIPE_forecast_netting.
Table Reference¶
| Table | Populated by | Purpose |
|---|---|---|
PIPE_forecast_fingerprints |
forecasting/ step |
Pipeline-scoped forecast skip cache: combined input fingerprint MD5(data_hash\|param_hash) per (pipeline_id, scenario_id, item_id, site_id). ReplacingMergeTree(computed_at), PARTITION BY (pipeline_id, scenario_id), read with FINAL. Isolates the forecast skip per pipeline so multiple pipelines on one tenant cannot clobber each other's skip state |
PIPE_forecast_netting |
netting/forecast_netting.py |
Netted demand: stat + return + maintenance vs firm orders. Includes is_past flag, consumption_enabled, netting_direction, return_forecast_qty, return_netted_qty |
PIPE_supply_orders |
Supply step (Rust engine) | Planned BUY / TRANSFER / BUILD / REPAIR / RETURN orders |
PIPE_supply_inventory_projection |
Supply step | Week-by-week projected OH, demand, receipts, safety stock per item×site |
PIPE_supply_exceptions |
Supply step | Shortages, capacity exceedances, late orders per week. exception_type_label is the human-readable description |
PIPE_supply_capacity |
Supply step (_persist_capacity_usage) |
Resource utilisation vs capacity per week. No id column — keyed by (pipeline_id, scenario_id, resource_id, week) |
PIPE_meio_results |
MEIO step | committed_buffer, fill_rate, wait_time, marginal_value per item×site |
PIPE_series_characteristics |
Characterization step | has_seasonality, has_trend, is_intermittent, adi, zero_ratio |
PIPE_series_exceptions |
Exceptions step | Series-level exception flags per (item_id, site_id, exc_id): exc_type, severity, metric_value, threshold. Read by exceptions_runner.generate_exception_log() to populate interact_exception_log |
PIPE_preprocessed |
Segmentation step (segmentation.py:build_series_parameter_map) |
Resolved parameter IDs per series: forecasting_parameter_id, outlier_detection_parameter_id, characterization_parameter_id, supply_parameter_id, allocation_parameter_id. One row per (pipeline_id, item_id, site_id). Used by the forecast and supply steps to look up their parameter sets efficiently |
PIPE_process_log |
All steps (via ProcessLogger) |
Step execution log with started_at, ended_at, status, rows_processed, error_message, log_tail |
PIPE_demand_corrections |
Outlier detection (embedded in forecast) | Original vs corrected values, detection method, z-score, bounds |
PIPE_forecast_point_values |
forecasting/ step |
Normalised point forecast values per (series, method, forecast_week). Keyed by item_id+site_id (no unique_id column) |
PIPE_forecast_hyperparameters |
forecasting/ step |
Fitted model hyperparameters per (series, method) |
PIPE_backtest_forecast |
Evaluation step | Point forecasts paired with actuals per backtesting origin |
PIPE_backtest_fingerprints |
Backtesting step | Incremental backtest cache keyed by (pipeline_id, scenario_id, item_id, site_id) (no unique_id); fingerprint = MD5(data_hash + sorted methods + overrides + param_hash), payload defaults_v=3. ReplacingMergeTree(computed_at), read with FINAL |
PIPE_causal_forecast |
Causal forecasting step | Causal/asset-driven demand forecasts per (asset_type, item, site) |
PIPE_indirect_demand |
Indirect demand step | Dependent/indirect demand from BOM explosion |
PIPE_maintenance_forecast |
Maintenance step | Maintenance-event-driven demand forecasts |
PIPE_meio_group_results |
MEIO step | Group-level MEIO results (aggregated K-curve values) |
PIPE_segment_membership |
Segmentation step | Which series belong to which segment |
PIPE_series_hashes |
Demand hashing (tenant-global) | Demand-only data_hash per (item_id, site_id) — no pipeline_id column (the only PIPE_* table without one); forecast_hash is legacy/unused (the forecast fingerprint now lives in PIPE_forecast_fingerprints). ensure_ch_series_hashes refuses to boot if the pre-v6 unique_id-keyed schema is still present (run files/DDL/ch_migration_v6_drop_unique_id.sql); ETL _upsert_series_hashes bails loudly if the frame lacks item_id/site_id |
PIPE_abc_results |
ABC classification step | ABC/XYZ classification results |
PIPE_allocation_results |
Allocation step | Demand-to-supply allocation assignments with scores |
PIPE_allocation_explanations |
Allocation step | Explanation texts for allocation decisions |
PIPE_allocation_unfulfilled |
Allocation step | Demands that could not be fully allocated |
PIPE_on_hand_snapshot |
Freeze step | On-hand inventory snapshot at freeze time |
PIPE_service_actuals |
ETL step | Service/maintenance actual demand records |
PIPE_lead_time_actuals |
ETL step | Actual lead time performance data |
PIPE_order_arrival_actuals |
ETL step | Actual order arrival data for forecast accuracy |
PIPE_version_delta_cache |
Historization | Pre-computed delta between two archives for fast comparison |
PIPE_pipeline_version |
Historization | Pipeline version registry (legacy, replaced by master.pipeline_version_index) |
Key Design Decisions¶
is_pastflag in netting:PIPE_forecast_nettingcontains both historical (is_past=1) and future (is_past=0) rows. The supply engine always filtersAND is_past = 0to avoid using past demand as future demand.- Partition drop on re-run: Each step calls
ALTER TABLE PIPE_xxx DROP PARTITION (pipeline_id, [scenario_id,] archive_date)before inserting, ensuring a re-run is always a clean replace, not an append. - No
idcolumn in capacity:PIPE_supply_capacityhas no auto-incrementid. Queries must use the composite key.
Historization Tables¶
master.pipeline_version_index¶
Purpose: Registry of all frozen pipeline archives. One row per (pipeline_id, archive_date) for un-pinned archives; multiple rows allowed for pinned archives on the same date.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Surrogate row identifier |
pipeline_id |
BIGINT | Parent pipeline |
scenario_id |
BIGINT | Optional scenario filter (NULL = all scenarios) |
archive_date |
DATE | Monday of the freeze week — primary identifier |
snapshot_ts |
TIMESTAMPTZ | Exact freeze timestamp |
created_by |
TEXT | User or system that triggered the freeze |
comment |
TEXT | Free-text annotation |
frozen |
BOOLEAN | True = immutable; False = in-progress or failed |
pinned |
BOOLEAN | Pinned archives skip auto-replacement and retention |
parent_archive_date |
DATE | Optional lineage link to prior archive |
replaced_archive_date |
DATE | Date of the archive this one replaced in the same week |
row_count_total |
BIGINT | Total rows captured across all PIPE_* tables |
table_counts |
JSONB | Per-table row counts: {"PIPE_forecast_point_values": 12500, ...} |
kpi_revenue |
DOUBLE PRECISION | Total projected revenue |
kpi_inventory_value |
DOUBLE PRECISION | Total inventory investment |
kpi_fill_rate |
DOUBLE PRECISION | Average service fill rate |
kpi_shortage_qty |
DOUBLE PRECISION | Total shortage units |
kpi_forecast_mase |
DOUBLE PRECISION | Mean Absolute Scaled Error |
kpi_forecast_bias |
DOUBLE PRECISION | Mean forecast bias |
kpi_nervousness |
DOUBLE PRECISION | Plan change magnitude vs prior archive |
kpi_supply_churn_pct |
DOUBLE PRECISION | Percentage of orders changed |
kpi_capacity_util |
DOUBLE PRECISION | Average capacity utilisation |
Unique index: (pipeline_id, archive_date) WHERE frozen = TRUE AND pinned = FALSE
archive_date model
The archive_date always equals toMonday(snapshot_ts). The live working partition has archive_date = toMonday(today()). See Historization — Archive Date Model for full details.
ClickHouse: archive_date partition column¶
Every PIPE_* table with historization support includes:
Partition keys:
- Arity-3: PARTITION BY (pipeline_id, scenario_id, archive_date)
- Arity-2: PARTITION BY (pipeline_id, archive_date)
- PIPE_on_hand_snapshot: PARTITION BY archive_date
The live partition (archive_date = toMonday(today())) is dropped and re-inserted on every pipeline run. Frozen partitions are never touched after creation.
See Historization — Archive Date Model for the full technical reference.
Replay Engine Tables¶
scenario.replay_run¶
Purpose: One row per replay execution. Each replay run creates its own pipeline (is_replay = TRUE) and iterates through a date range, running supply + allocation at each step.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Run identifier |
pipeline_id |
BIGINT NOT NULL | The replay pipeline (cloned from source) |
name |
TEXT NOT NULL | User-provided name |
description |
TEXT | Optional description |
source_pipeline_id |
BIGINT NOT NULL | The pipeline being replayed |
start_date |
DATE NOT NULL | First iteration planning date |
end_date |
DATE NOT NULL | Last iteration planning date |
current_date |
DATE | Currently executing iteration date |
granularity |
VARCHAR(10) | 'daily' or 'weekly' (default) |
status |
VARCHAR(20) | pending, running, completed, failed, cancelled |
iteration_count |
INTEGER | Total iterations (computed from date range) |
current_iteration |
INTEGER | Current iteration number (0 = not started) |
config |
JSONB | Replay parameters (return_rate overrides, etc.) |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update timestamp |
completed_at |
TIMESTAMPTZ | Completion timestamp |
error_msg |
TEXT | Error message if failed |
Indexes: idx_replay_run_pipeline (pipeline_id), idx_replay_run_source_pipeline (source_pipeline_id), idx_replay_run_status (status)
scenario.replay_iteration¶
Purpose: One row per iteration within a replay run. Tracks the planning date, execution status, and per-iteration metrics.
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL PK | Iteration identifier |
replay_run_id |
BIGINT NOT NULL FK | → replay_run.id (CASCADE) |
iteration_num |
INTEGER NOT NULL | 1-based iteration index |
planning_date |
DATE NOT NULL | The date this iteration simulates |
status |
VARCHAR(20) | pending, running, completed, failed, skipped |
archive_date |
DATE | Written to CH as archive_date (= planning_date) |
supply_run_id |
BIGINT | FK to pipe_supply_run |
allocation_run_id |
BIGINT | FK to allocation_run |
started_at |
TIMESTAMPTZ | Iteration start timestamp |
completed_at |
TIMESTAMPTZ | Iteration end timestamp |
error_msg |
TEXT | Error details if failed |
metrics |
JSONB | {fill_rate, total_stock, total_shortage, ...} |
Unique constraint: (replay_run_id, iteration_num)
config_scenario_pipeline.is_replay¶
A boolean column (DEFAULT FALSE) added to the pipeline registry. Replay pipelines are marked is_replay = TRUE so they can be filtered from normal pipeline lists and cleaned up on deletion.
llocation_run.archive_date¶
A DATE column added to allocation_run. For standard runs, it defaults to planning_today(). For replay iterations, it is set to the iteration's planning_date. This enables time-series queries across both replay iterations and standard historized runs.
See Replay Engine Internals for the full technical reference.