Routes — Complete Reference¶
Status: Implemented (USE-route rollup updated 2026-08; coverage matrix rollout in progress). Primary sources:
files/indirect/indirect_demand.py,files/bad_forecast/runner.py,files/supply_runner.py,files/supply/src/types.rs,files/DDL/schema.sql
Routes are the backbone of Mirabelle's supply-network model. Every physical or logical
flow of stock between two locations — purchasing, transfer, repair, return, deployment —
is a row in master.route, typed by master.route_type. Routes drive four distinct
subsystems:
- Demand rollup — derived (indirect) forecasts propagated along lanes
- Bad / repair forecasting — reverse-logistics projections
- Forecast netting — firm-order and sales-return consumption
- Supply planning & MEIO — order generation, sourcing, safety stock
Audience
Developers and implementation consultants who need the exact semantics of each
route type: direction, stock-type conversion, lead-time offset sign, and which
split field (quota vs coverage) applies.
Table of Contents¶
- Data Model
- Direction Convention
- Route Types & Stock Conversion
- Quota vs Coverage
- The Demand Rollup
- Pipeline Integration
- Rust Engine — OrderType & RouteOption
- Field Reference — master.route
- Key Files
Data Model¶
master.route_type¶
| Column | Description |
|---|---|
id |
PK |
name |
Display name (e.g. "Transfer", "Use (factory good → end-consumer bad)") |
planning_type |
The semantic key. One of BUY, MAKE, TRANSFER, REPAIR, RETURN, BALANCING, USE, ZONE_BALANCING, REPLENISH |
source_type |
Engine-facing category used by the supply solver |
All route behaviour is keyed off planning_type — never off location types or
hardcoded site roles. The same algorithm works for any network (distribution,
pooled-asset, MRO) because only the route types matter.
master.route¶
One row per (item, destination, type, source) lane. See the full field reference below.
master.route_quota_override¶
Time-varying quota overrides. The supply runner uses the latest override whose
effective_date <= planning_today(), falling back to master.route.quota.
Direction Convention¶
Every route is directional:
| Column | Role |
|---|---|
site_id |
Destination — the location that receives stock / bears demand |
source_site_id |
Source — the location that ships / supplies |
item_id |
Item at the destination |
source_item_id |
Item at the source (NULL = same item) |
A TRANSFER route (site_id=84, source_site_id=80) means: site 84 is replenished
by site 80. Demand at 84 pulls supply from 80.
Route Types & Stock Conversion¶
Each planning type defines a stock-type conversion between the source pool and the destination pool, and whether the route is a push or pull for rollup purposes:
| planning_type | Flow | Stock conversion | Rollup direction | Offset |
|---|---|---|---|---|
BUY |
External supplier → site | external → good | — (leaf supply) | −LT (release earlier) |
MAKE / BUILD |
Components → assembly | components → good | BOM explosion | −LT |
TRANSFER |
WH/store replenishment | good → good | pull (coverage) | −LT |
REPLENISH |
Same as TRANSFER (engine-level) | good → good | pull (coverage) | −LT |
USE |
Producer/factory → consumer site | good → bad | push (quota) | +LT |
RETURN |
Consumer → depot/repair facility | bad → bad | push (quota) | +LT |
REPAIR |
Repair shop → demand site | bad → good | push (quota) | +LT (TAT) |
BALANCING |
Intra-zone excess redistribution | good → good | engine-level | −LT |
ZONE_BALANCING |
Inter-zone excess-to-need | good → good | engine-level | −LT |
The stock_type column on the route ('good' | 'bad') declares which pool the
route draws from; it defaults to 'good' and is set to 'bad' for return/bad-balancing
lanes.
Quota vs Coverage¶
These are two different split semantics, and they must not be conflated:
| Quota (push) | Coverage (pull) | |
|---|---|---|
| Question answered | "Of what leaves this source, what fraction goes to each destination?" | "Of what this destination needs, what fraction comes from each source?" |
| Normalisation group | All routes from the same (item_id, source_site_id) |
All routes into the same (item_id, site_id) |
| Sums to | ≤ 1.0 across destinations (residual stays at source) | ≤ 1.0 across sources (residual = uncovered demand) |
| Used by | USE, RETURN, REPAIR, BALANCING |
TRANSFER / REPLENISH (demand rollup) |
| Column | master.route.quota |
master.route.coverage (planned — see status note) |
Example (GPS pooled-asset network):
- Producer Hannover (84) has USE routes to Michelin (82) and Bridgestone (83).
quotasplits Hannover's good outflow: e.g. 0.6 / 0.4. Normalised per source. - Hannover itself is replenished by Rotterdam DC (80) and Frankfurt DC (81).
coveragesplits Hannover's inbound need: e.g. 0.5 / 0.5. Normalised per destination.
Implementation status
- USE routes use
quota, normalised per source — implemented. - TRANSFER rollup currently reads
quotaand keeps only the best-priority route per destination. Migration to a dedicatedcoveragecolumn with per-destination normalisation (all sources participate) is in progress.
When all split values in a normalisation group are NULL, the group falls back to
an equal split (1/n per route).
The Demand Rollup¶
The rollup answers: which locations get a real statistical forecast, and which get a derived one?
Rule: a real time-series forecast exists only where actuals are recorded
(master.demand_actuals). Every other node's forecast is derived by walking the
route network from those roots. In a pooled-asset network the actuals live at the
producer (the real client); in a classic distribution network they live at the
stores/depots. The algorithm is identical — it is driven purely by where actuals
exist and which route types connect the nodes.
flowchart LR
subgraph actuals["Real forecast (from actuals)"]
P["Producer / demand leaf<br/><b>GOOD forecast</b><br/>netted with firm orders"]
end
subgraph upstream["Upstream supply nodes"]
D["Depot / warehouse<br/><b>GOOD indirect forecast</b><br/>fcst × coverage, offset −LT"]
end
subgraph consumer["Consumer sites"]
M["Manufacturer / consumer<br/><b>BAD forecast</b><br/>fcst × quota, offset +LT<br/>netted with sales returns"]
end
D -- "TRANSFER (good→good)<br/>coverage, −LT" --> P
P -- "USE (good→bad)<br/>quota, +LT" --> M
M -- "RETURN (bad→bad)<br/>quota × (1−wash), +LT" --> D
D -- "REPAIR (bad→good)<br/>quota × (1−wash) × yield, +TAT" --> P
1. Transport / Replenishment rollup (good → good)¶
The destination's good forecast is rolled up to each source so the source can size its safety stock:
- Offset is negative — the source must release stock
LTweeks before the destination needs it. - Implemented as
demand_type='good_replenishment'inPIPE_indirect_demand(multi-level: rolls up through successive Transfer hops until no further source). - Purpose: the resulting indirect forecast at the supply node sizes its service level / safety stock.
2. USE push (good → bad)¶
A USE route is a push lane: the destination (consumer/manufacturer) receives the source's good forecast as a bad forecast:
- Offset is positive — bad units appear at the consumer only after deployment
and consumption (
lead_time + detention_time + transit_time). - The bad item at the destination is resolved via the RETURN routes leaving that site (codebase convention for "the dirty item at a site").
- Implemented as
demand_type='use_return'inPIPE_indirect_demand.return_rateis not part of this formula — it is a RETURN-route concept.
3. RETURN (bad → bad)¶
RETURN routes bring bad material back to the depot/repair facility:
- Wash rate = fraction of returned units lost in transit.
- Quotas from the same source are normalised to sum to 1.
- Implemented in
files/bad_forecast/runner.py→PIPE_supply_forecast(demand_type='bad'). Sources include stat + causal + maintenance + indirect demand, so the bad forecast accounts for all demand streams.
4. REPAIR (bad → good)¶
Repair routes convert bad stock back into good supply at the demand site:
- Also in
PIPE_supply_forecast(demand_type='repair'). - Consumed by the supply engine as incoming supply (
repair_returns) and by netting as an audit column (not subtracted — the engine already credits it).
Netting integration¶
- Firm (good) demand orders are registered where the actuals live
(
is_forecast=FALSErows) and consume the good forecast there. - Sales returns (
master_sales_return, statusconfirmed) are registered at the consumer site (USE destination) and consume the bad forecast (return_consumed_qty/return_netted_qtyinPIPE_forecast_netting).
Pipeline Integration¶
| Stage | Route types consumed | How |
|---|---|---|
indirect-demand step |
TRANSFER, USE | network (historical rollup, no LT shift), good_replenishment (forecast rollup, −LT × split), use_return (good→bad push, +LT × quota), kit_make (BOM) |
bad-forecast step (7c) |
RETURN, REPAIR | PIPE_supply_forecast rows (bad, repair) |
netting step |
— (reads the above) | Firm orders consume good forecast; sales returns consume bad forecast; repair credited as supply |
supply step |
ALL | Routes embedded into SKU plans as RouteOptions; Rust engine picks sources, respects LT, lot sizing, calendars, capacity |
meio step |
RETURN, REPAIR | return_rate, repair_yield, TAT mean/CV, wip_qty loaded via _load_repair_params_from_routes (NOT from segment params) |
| MEIO/supply guardrails | USE, REPAIR | Proactive repair-balance pre-pass boosts wash-depot SS floors |
Lead-time composition¶
| Context | Formula |
|---|---|
| Forward supply (engine) | lead_time + transit_time + pick_pack_time |
| USE bad-forecast offset | lead_time + detention_time + transit_time |
| RETURN bad-forecast offset | lead_time + transit_time + pick_pack_time |
| REPAIR offset (TAT) | lead_time + transit_time + pick_pack_time |
All day values are converted to ceiling weeks (ceil(days/7)).
Rust Engine — OrderType & RouteOption¶
The supply engine (files/supply/) maps planning_type to a fixed OrderType
enum. The index order is contractual — Python _ORDER_TYPE_IDX must match:
| Index | OrderType | planning_type |
|---|---|---|
| 0 | Buy |
BUY |
| 1 | Build |
MAKE |
| 2 | Repair |
REPAIR |
| 3 | Transfer |
TRANSFER |
| 4 | Balancing |
BALANCING |
| 5 | Replenish |
REPLENISH |
| 6 | Return |
RETURN |
| 7 | Use |
USE |
| 8 | ZoneBalancing |
ZONE_BALANCING |
These indices key the per-SKU arrays latest_firm_by_type: [u8; 9] and
min_lt_by_type: [u8; 9] (firm-order block horizon / min lead-time gating in
sourcing.rs). Changing the enum length without updating the Python serializers
breaks msgpack decoding (invalid length 8, expected an array of length 9).
Each route is serialised into a RouteOption (files/supply/src/types.rs):
| Field | From route column | Notes |
|---|---|---|
route_id |
id |
|
source_type |
route_type.source_type |
engine category |
source_location_id |
source_site_id |
|
lead_time |
lead_time + transit_time + pick_pack_time |
weeks |
unit_cost / order_cost |
unit_cost / order_cost |
|
min_qty / mult_qty / max_qty |
lot sizing | |
capacity_id |
capacity_id |
links to pipe_supply_capacity resource |
shipping_calendar / receiving_calendar |
ship_calendar_id / lead_time_calendar_id |
52-week bitmasks |
repair_yield |
repair_yield |
0 = not repairable (None-safe) |
return_rate |
return_rate |
REPAIR/RETURN demand fraction |
repair_turnaround |
lead_time |
REPAIR TAT |
priority |
priority |
lower wins |
quota |
quota (+ route_quota_override) |
engine order-splitting |
stock_type |
stock_type |
'good' / 'bad' pool |
detention_time |
detention_time |
USE-route detention |
setup_cost / expedite_cost / transport_mode |
same | costing |
Field Reference — master.route¶
| Column | Type | Meaning |
|---|---|---|
id |
BIGINT PK | |
item_id, site_id |
BIGINT | Item + destination |
source_item_id, source_site_id |
BIGINT | Item + source (NULL = same item / external) |
supplier_id |
BIGINT | External supplier (BUY) |
type_id |
BIGINT FK | → route_type (carries planning_type) |
tag |
TEXT | Free label |
quota |
DOUBLE | Push split from source (USE/RETURN/REPAIR/BALANCING); engine order split |
coverage |
DOUBLE | (planned) Pull split into destination (TRANSFER rollup) |
priority |
SMALLINT | Route selection order (lower wins) |
lead_time |
SMALLINT | Base lead time (days) |
pick_pack_time, transit_time, inspection_time |
SMALLINT | LT components (days) |
safety_lead_time, ptf |
SMALLINT | Planning time fence / safety LT |
*_calendar_id (8 FKs) |
BIGINT | Per-component working calendars → 52-week bitmasks |
yield |
DOUBLE | Process yield (MAKE) |
unit_cost, order_cost |
DOUBLE | Costing |
min_qty, mult_qty, max_qty |
DOUBLE | Lot sizing |
end_date |
DATE | Route expiry (NULL = open) |
return_rate |
DOUBLE | RETURN/REPAIR: fraction of demand that returns |
repair_yield |
DOUBLE | REPAIR: fraction repaired successfully (0 = not repairable) |
return_wash_rate |
DOUBLE | RETURN transit loss (0–1) |
repair_wash_rate |
DOUBLE | REPAIR process loss (0–1) |
lead_time_cv |
DOUBLE | LT coefficient of variation (MEIO bootstrap) |
wip_qty |
DOUBLE | Repair shop work-in-progress |
capacity_id |
BIGINT | → pipe_supply_capacity.resource_id |
detention_time |
SMALLINT | USE-route detention component |
stock_type |
TEXT | 'good' / 'bad' pool drawn |
transport_mode |
TEXT | Air / Sea / Road / Rail label |
is_expedite, expedite_cost |
— | Expedite premium |
setup_cost, scrap_cost, diagnostic_cost |
DOUBLE | Additional cost terms |
Key Files¶
| File | Role |
|---|---|
files/indirect/indirect_demand.py |
Demand rollup: network, good_replenishment, kit_make, use_return |
files/bad_forecast/runner.py |
RETURN → bad forecast, REPAIR → repair forecast (PIPE_supply_forecast) |
files/netting/forecast_netting.py |
Firm-order / sales-return consumption of good & bad forecasts |
files/supply_runner.py |
Route loading, RouteOption embedding, _ORDER_TYPE_IDX, min_lt_by_type, latest_firm_by_type, proactive repair-balance pre-pass |
files/supply/src/types.rs |
OrderType (9 variants), RouteOption, SkuPlan |
files/supply/src/sourcing.rs |
Route selection, LT/calendar gating, firm-block horizon |
files/meio_runner.py |
_load_repair_params_from_routes — MEIO repair params from RETURN/REPAIR routes |
files/DDL/schema.sql |
master_route, master_route_type, route_quota_override DDL |
files/api/master_data_router.py |
Route grid (Settings → Master Data → Routes) |
Related documentation¶
- Resource Modeling, Constraints & Calendars — capacity, calendars, and the route constraint algebra
- Indirect Demand — user-facing rollup guide
- Forecast of Bad — Computation Reference — reverse-logistics parameters in depth
- Supply Planning Engine — Full Reference — the Rust engine