MEIO Configuration Parameters — Complete Reference¶
This document covers every parameter that controls the Multi-Echelon Inventory Optimization (MEIO) engine: global defaults, scenario-level overrides, per-segment parameter sets, per-SKU constraints, and reverse-logistics defaults.
MEIO parameters live in three layers:
- Global defaults —
scenario.config_parametersrow withparameter_type = 'meio',is_default = TRUE. Edited in Settings → Business Config → MEIO. These are the baseline for every MEIO run in the tenant. - Scenario overrides —
scenario.meio_scenarios.param_overridesJSONB. Edited in the MEIO Scenario editor (Basic Info + Global Overrides panels). These override the global defaults for one scenario. - Per-segment parameter sets —
scenario.config_meio_parameter_set+config_meio_parameter_set_segmentlink table. Edited in the MEIO Scenario editor → Parameter Sets panel. These override both the global defaults and the scenario-level overrides for the specific segments they are linked to.
Later layers win over earlier ones. Per-SKU columns on master.item (min_fill_rate, max_fill_rate, min_sl_qty, etc.) provide per-item floors and caps that sit underneath all three layers.
Table of Contents¶
- Global MEIO Parameters (Settings → Business Config → MEIO)
- Distribution Fitting Group
- Optimizer Group
- Service Level Group
- Reverse Logistics Group
- Advanced / Hidden Parameters
- MEIO Scenario Window Parameters
- Basic Info Panel
- Global Overrides Panel
- SKU Overrides Panel
- Parameter Sets Panel (Per-Segment)
- Availability Scenario Extras
- Per-SKU Columns on
master.item - Per-Route Columns on Supply Routes
- Parameter Override Hierarchy (Full)
- API Reference
Global MEIO Parameters (Settings → Business Config → MEIO)¶
These are stored as a JSONB parameters_set in the config_parameters row with parameter_type = 'meio' and is_default = TRUE. They are loaded by _load_meio_config() in files/meio_runner.py and by the distribution fitting engine in files/fitting.py / files/distribution/fitting.py.
The panel in the UI groups them under three headings: Distribution Fitting, MEIO Optimizer, and Reverse Logistics. A few additional parameters exist in the DB seed and code but are not surfaced in the Settings UI — they are documented in the Advanced / Hidden Parameters section.
Distribution Fitting Group¶
These control the demand distribution fitting step that runs before the MEIO optimizer. For every SKU with enough history, the fitter produces a parametric distribution (normal, gamma, etc.) from the forecast quantiles. The MEIO optimizer then reads the fitted quantile at the target service level to compute safety stock.
enabled¶
| Type | Boolean |
| Default | true |
| UI group | Distribution Fitting |
| Scope | Pipeline-level (distribution fitting + MEIO optimizer) |
| Scenario override? | No |
| Segment override? | No |
When true, the distribution fitting step runs during the pipeline and the MEIO optimizer can be invoked. When false, the entire MEIO module is skipped — no demand distributions are fitted, no safety stocks are computed, and the PIPE_meio_results ClickHouse table is not populated for this run.
This flag is checked early in the fitting engine:
IfFalse, the fitting loop returns immediately and downstream steps (MEIO optimizer, safety stock) see no fitted distributions.
When to toggle off: - A forecast-only pipeline where you do not need inventory optimization. - Initial data validation when demand history is too sparse for distribution fitting. - A pipeline that only computes forecasts for a different module (e.g. supply LP solver with its own safety stock method).
distributions¶
| Type | List of strings |
| Default | ["normal", "gamma", "negative_binomial", "lognormal", "weibull", "poisson"] |
| UI group | Distribution Fitting |
| Scope | Pipeline-level (distribution fitting) |
| Scenario override? | No |
| Segment override? | No |
Defines which parametric distributions the fitting engine attempts to fit to each SKU's forecast quantiles. The engine fits all listed candidates, then selects the one with the best fit score (lowest average absolute quantile error via _score_fit()).
| Distribution | Best for | Notes |
|---|---|---|
normal |
High-volume, steady demand | Symmetric tails — can produce negative quantiles for low-demand SKUs |
gamma |
Right-skewed, positive demand | Always positive; good general-purpose choice |
negative_binomial |
Intermittent / lumpy demand | Discrete; handles over-dispersion (variance > mean) |
lognormal |
Right-skewed with heavy tail | Always positive; good for spare parts with occasional spikes |
weibull |
Varying skewness | Shape parameter controls tail heaviness; flexible |
poisson |
Low-rate, count-based demand | Discrete; variance = mean (equidispersion assumption) |
How it works in code: The fitter iterates self.distributions in fit_from_quantiles(). For each candidate, it calls _fit_distribution(quantiles, dist_type), scores the fit, and keeps the best. Reducing this list (e.g. removing poisson for high-volume retail) speeds up fitting; adding distributions improves coverage at the cost of CPU time.
Tuning advice:
- For retail/consumer goods: ["normal", "gamma", "negative_binomial"] covers most SKUs.
- For spare parts / MRO: ["gamma", "lognormal", "weibull", "negative_binomial"] — heavier tails.
- For very slow-moving items: add ["poisson"] for low-rate count modeling.
fitting_method¶
| Type | String (select) |
| Options | quantile_matching, mle, mom |
| Default | quantile_matching |
| UI group | Distribution Fitting |
| Scope | Pipeline-level (distribution fitting) |
| Scenario override? | No |
| Segment override? | No |
Determines how distribution parameters are estimated from the data.
quantile_matching (recommended, default)¶
Finds the distribution parameters whose theoretical quantiles best match the forecast quantiles (P10, P50, P90, etc.). Minimizes the mean absolute error between observed and predicted quantile values:
for q_level, q_value in quantiles.items():
predicted = fit.get_quantile(q_level)
error = abs(predicted - q_value)
errors.append(error)
return np.mean(errors)
This is the best method when the input is forecast quantiles (which is how the pipeline feeds the fitter), because it directly matches the uncertainty information that matters for safety stock. The fitted distribution's quantile at the target service level becomes the safety stock level.
mle — Maximum Likelihood Estimation¶
Finds the parameters that maximize the probability of observing the actual demand history. Requires raw demand observations, not just quantiles. Generally more statistically efficient but computationally heavier and can be unstable with small samples.
mom — Method of Moments¶
Matches the sample mean and variance to the distribution's theoretical moments. Fastest method but least accurate for skewed distributions. Useful as a quick sanity check or for very large catalogs where speed matters more than precision.
service_levels¶
| Type | List of floats |
| Default | [0.90, 0.95, 0.99] |
| UI group | Distribution Fitting |
| Scope | Pipeline-level (distribution fitting) |
| Scenario override? | No |
| Segment override? | No |
This parameter tells the distribution fitting engine which quantiles to pre-compute from each fitted distribution. After fitting, the engine calls calculate_service_level_quantiles() which evaluates fit.get_quantile(service_level) for each value in this list, producing the service_level_quantiles JSONB column stored in ClickHouse (PIPE_fitted_distributions).
Critical distinction from scenario-level targets:
- This
service_levelslist = which quantile breakpoints to store in the fitting results (for reporting, the EOQ/K-Curve tab, and the IO detail view). It does not set optimization targets. - Per-segment
fill_rate_target(set in the MEIO scenario editor / parameter sets) = the actual optimization target the Rust optimizer uses to compute safety stock. This is stored inconfig_meio_parameter_set→fill_rate_targetand ingroup_targetspassed to the optimizer.
For example, if service_levels = [0.90, 0.95, 0.99] and a segment's fill_rate_target = 0.95, the optimizer uses the 0.95 quantile of the fitted distribution as the safety stock level. The 0.90 and 0.99 quantiles are stored for comparison/reporting but don't drive the optimization.
When to modify: If your segments target fill rates not covered by the default list (e.g. 0.975 or 0.98), add them here so the pre-computed quantiles are available. The optimizer itself can interpolate any quantile at runtime, but having them pre-computed avoids recomputation and makes them visible in the UI's IO detail tab and EOQ tab.
Optimizer Group¶
These control the Rust MEIO optimizer — the greedy multi-echelon buffer allocation engine that computes safety stock per SKU.
meio_optimizer_version¶
| Type | String |
| Options | v1, v2, v3 |
| Default | v3 |
| UI group | Optimizer |
| Scope | Pipeline-level |
| Scenario override? | Yes — via param_overrides.meio_optimizer_version |
| Segment override? | No |
Selects which Rust optimizer binary to use.
v3 (default, recommended)¶
The current optimizer. Adds empirical/bootstrap demand-over-lead-time distributions, lead-time marginal distributions, the tgt_max (Max Safety Qty / Max Cover) continuous ceiling, group max_budget completion with member freeze, and the empirical-mean cycle-stock baseline override (see MEIO V3 empirical distributions). When use_empirical_distributions=true, confidence levels are bypassed for safety-stock computation (V3 builds demand-over-lead-time distributions from master.demand_actuals).
v2 (explicit fallback)¶
The previous optimizer. Supports:
- service_level_type: fill_rate, line_fill_rate, stockout, availability
- concurrent_candidates: parallel group optimization
- direct_demand_only = true: customer-facing-only fill-rate reporting
- Asset availability groups (Erlang-B for repairable spares)
- Per-scenario overrides for service_level_type and concurrent_candidates
- achievement_logging: detailed fill-rate achievement breakdown in logs
v1 (legacy)¶
The original optimizer. Simpler feature set: - Only fill-rate metric - No concurrent candidates control - Warehouse nodes included in fill-rate calculation - No asset availability mode
Fallback behavior: If meio_optimizer_version = "v2" but the V2 binary (meio_optimizer_v2.pyd) is not available, the runner logs a warning and falls back to V1:
if _opt_ver == "v2" and not _OPTIMIZER_V2_AVAILABLE:
logger.warning("V2 configured but not available — falling back to V1")
meio_parallel_workers¶
| Type | Integer |
| Default | 0 (auto) |
| UI group | Optimizer |
| Scope | Pipeline-level (Rust engine thread pool) |
| Scenario override? | No |
| Segment override? | No |
Sets the Rayon thread pool size for the Rust optimizer. Controls how many OS threads the optimizer uses for parallel processing of SKU groups and greedy iterations.
0(auto, default): Uses all available CPU cores (Rayon's default behavior — spawns one thread per logical core).N> 0: Caps the thread pool to exactly N threads.
When to modify:
- On a shared server where the MEIO run must not consume all CPU, set to a lower number (e.g. 4 on an 8-core machine).
- On a dedicated planning server, leave at 0 for maximum throughput.
- The legacy key parallel_workers is accepted as a fallback for backward compatibility.
This parameter is resolved with:
greedy_candidates¶
| Type | Integer |
| Default | 3 (DB seed) |
| UI group | Optimizer |
| Scope | Pipeline-level (Rust V2 engine) |
| Scenario override? | No |
| Segment override? | No |
Controls how many candidate buffer increments the Rust optimizer evaluates at each greedy iteration before committing to the best one.
How the greedy optimizer works:
1. For each group of SKUs, the optimizer starts from zero buffer.
2. At each iteration, it generates greedy_candidates candidate increments (each adding buffer to a different SKU or a different amount).
3. It evaluates the marginal improvement (fill-rate gain per dollar of inventory) for each candidate.
4. It commits to the best candidate and repeats.
5. The loop stops when the group's fill_rate_target is reached or max_budget is exhausted.
Trade-off: - Low values (3–5): Faster runtime, but the optimizer may get stuck in local optima — it might miss a slightly better allocation because it didn't evaluate enough candidates. - High values (16–32): Better solution quality (closer to optimal), at the cost of more CPU time per iteration. Diminishing returns above ~20 for most catalogs. - 0: Falls back to a simple single-candidate heuristic (fastest, lowest quality).
concurrent_candidates¶
| Type | Integer |
| Default | 0 |
| UI group | Optimizer |
| Scope | Pipeline-level global default; scenario-level override available |
| Scenario override? | Yes — via param_overrides.concurrent_candidates in the scenario editor |
| Segment override? | No |
Controls how many SKU-groups are optimized concurrently in the V2 Rust optimizer. This is a thread-level parallelism knob for the optimizer's internal work distribution.
0(auto): The optimizer uses all available CPU cores (via Rayon's default thread pool). Recommended for most deployments.1: Sequential — one group at a time. Useful for debugging or when the optimizer must share the machine with other heavy processes.2–N: Explicit cap on concurrent group optimizations.
Note: This is separate from meio_parallel_workers which controls the total Rayon thread pool size. concurrent_candidates controls how many groups are in-flight at once within that pool. If meio_parallel_workers = 4 and concurrent_candidates = 8, each group optimization gets ~0.5 threads on average (they time-share). A good rule of thumb: set concurrent_candidates ≤ meio_parallel_workers for best throughput.
Override chain: Global default → scenario-level param_overrides.concurrent_candidates.
Service Level Group¶
service_level_type¶
| Type | String (select) |
| Options | fill_rate, line_fill_rate, stockout, availability |
| Default | fill_rate |
| UI group | Service Level |
| Scope | Pipeline-level global default; scenario-level override available |
| Scenario override? | Yes — via param_overrides.service_level_type in the scenario editor Basic Info panel |
| Segment override? | No |
Defines which service-level metric the optimizer targets when computing safety stock.
fill_rate (β — Type 2 service level, default)¶
The fraction of demand fulfilled from on-hand stock. If 95 out of 100 units requested are available immediately, fill rate = 0.95. This is the most common metric for inventory optimization because it directly measures how much demand is satisfied, not just how often a stockout occurs.
line_fill_rate (β_line)¶
Same as fill rate, but demand is divided by average order line size before computing the buffer. This accounts for the fact that a customer order line typically contains multiple units — being out of stock on one line has a different business impact than being short a few units within a line.
When the companion flag line_fill_rate = true is set, the optimizer divides total_demand_rate and direct_demand_rate by avgsize (average line size from the item master) before optimization. The safety stock is computed for line-based demand, then the resulting buffer is in units of lines, which is converted back to units.
stockout (α — Type 1 service level / cycle service level)¶
The probability of no stockout during a replenishment cycle. If α = 0.95, there is a 95% chance that demand during the lead time does not exceed the reorder point. This is a more conservative metric than fill rate: for the same target value, stockout requires more safety stock because it treats any stockout event (even of 1 unit) as a failure, regardless of how much demand was actually unsatisfied.
availability (Erlang-B / asset availability)¶
Specialized for repairable spare parts (LRUs in aerospace, defense, heavy industry). Uses the Erlang-B formula to compute the pool size needed to achieve a target asset availability (e.g. 99.5% probability that a spare is available when needed). This is the METRIC-style model where: - Demand follows a Poisson process (failure rate × fleet size) - Failed units go through a repair loop with mean turnaround time - The question is: how many spares do we need in the pool to achieve the target availability?
When this mode is selected, the MeioScenarios UI shows asset-availability-specific inputs (see Availability Scenario Extras).
Override chain: Global default → scenario-level param_overrides.service_level_type (set in the scenario editor's Basic Info panel).
Reverse Logistics Group¶
These provide fallback values for the reverse-logistics (repair loop) calculation. The MEIO runner reads per-route values from the supply route configuration via _load_repair_params_from_routes(). If a route's column is NULL, the corresponding default_* value here is used instead.
default_return_rate¶
| Type | Float |
| Default | 0.0 |
| Range | 0.0 – 1.0 |
| UI group | Reverse Logistics |
| Scope | Pipeline-level global default; scenario-level override available |
| Scenario override? | Yes — via param_overrides.default_return_rate |
| Segment override? | No — sourced from master.route RETURN/REPAIR rows only |
The fraction of issued units that are returned for repair or refurbishment. Fallback when a supply route's return_rate column is NULL.
Used in the reverse logistics / repair loop calculation. When return_rate > 0, the optimizer models a repair pipeline:
1. A fraction return_rate of demand comes back as returns.
2. Those returns enter repair (with yield repair_yield and TAT repair_tat_mean).
3. Successfully repaired units re-enter the available pool, reducing the net demand that safety stock must cover.
Effect on safety stock: Higher return_rate → more units coming back → lower net demand → lower safety stock requirement. Setting this to 0 (default) means no reverse logistics are modeled unless explicitly defined per route.
default_repair_yield¶
| Type | Float |
| Default | 1.0 (100%) |
| Range | 0.0 – 1.0 |
| UI group | Reverse Logistics |
| Scope | Pipeline-level global default; scenario-level override available |
| Scenario override? | Yes — via param_overrides.default_repair_yield |
| Segment override? | No — sourced from master.route RETURN/REPAIR rows only |
The fraction of returned units that are successfully repaired and re-enter the available inventory pool. Fallback when a route's repair_yield is NULL.
1.0= every returned unit is successfully repaired (optimistic, common for simple items).0.8= 80% of returns are repaired; 20% are condemned/scrapped.0.0= no units are repaired (effectively disables the repair loop).
Effect on safety stock: Lower repair_yield → fewer repaired units returning to stock → higher net demand → higher safety stock. This interacts with return_rate: effective repaired supply = demand × return_rate × repair_yield.
default_repair_tat_mean¶
| Type | Float (weeks) |
| Default | 4.0 |
| UI group | Reverse Logistics |
| Scope | Pipeline-level global default; scenario-level override available |
| Scenario override? | Yes — via param_overrides.default_repair_tat_mean |
| Segment override? | No — sourced from master.route RETURN/REPAIR rows only |
The mean repair turnaround time in weeks — how long it takes from when a unit arrives at the repair shop to when it is back in serviceable condition. Fallback when a route's tat_weeks is NULL.
Important nuance: The actual repair_tat_mean used by the optimizer is:
The default_repair_tat_mean applies to the in-shop portion. The physical return transit time (customer → repair facility) comes from the RETURN route's tat_weeks. The MEIO runner merges these:
So if a RETURN route specifies 1 week transit and this default is 4 weeks, the total repair cycle = 5 weeks.
Effect on safety stock: Longer repair TAT → more time before repaired units return → more units "in the pipeline" → higher safety stock needed to cover demand during the repair cycle. In the Erlang-B availability model, repair_tat_mean directly determines the offered load (ρ = demand_rate × repair_tat_mean), which drives pool size.
default_repair_tat_cv¶
| Type | Float |
| Default | 0.3 |
| Range | ≥ 0.0 |
| UI group | Reverse Logistics |
| Scope | Pipeline-level global default; scenario-level override available |
| Scenario override? | Yes — via param_overrides.default_repair_tat_cv |
| Segment override? | No — sourced from master.route RETURN/REPAIR rows only |
The coefficient of variation (CV) of the repair turnaround time — a measure of how variable the repair duration is. Fallback when a route's lead_time_cv is NULL.
- CV = 0: Repair always takes exactly
repair_tat_meanweeks (deterministic). - CV = 0.3 (default): Standard deviation = 30% of the mean. If mean TAT is 4 weeks, σ = 1.2 weeks.
- CV > 0.5: Highly variable repair times — the optimizer adds extra safety stock to account for the uncertainty.
Effect on safety stock: Higher TAT CV → more uncertainty in when repaired units become available → larger safety stock buffer needed. This is the repair-loop equivalent of lead-time variability in forward logistics.
default_wip_qty¶
| Type | Float |
| Default | 0.0 |
| UI group | Reverse Logistics |
| Scope | Pipeline-level global default; scenario-level override available |
| Scenario override? | Yes — via param_overrides.default_wip_qty |
| Segment override? | No — sourced from master.route RETURN/REPAIR rows only |
Work-In-Process quantity — the number of units currently undergoing repair at the shop. Fallback value used when a supply route's wip_qty column is NULL.
When the MEIO runner builds SKU records, it reads route data via _load_repair_params_from_routes(). For each (item_id, site_id) pair that has a RETURN route:
WIP units are treated as pipeline inventory — they are already in the repair loop and will become available after the repair TAT elapses. Setting this > 0 reduces the recommended safety stock because the optimizer sees those units as future supply.
When to modify: Set to a positive value if you know a typical baseline of items always in repair (e.g. "we always have ~5 units of this LRU at the repair shop"). For accurate results, prefer setting wip_qty per route in the supply route configuration rather than relying on this global default.
Advanced / Hidden Parameters¶
These exist in the DB seed (files/db/db.py) and the MEIO runner code but are not surfaced in the Settings UI panel. They can be edited directly in the config_parameters.parameters_set JSONB via the API or psql.
distribution_threshold¶
| Type | Integer |
| Default | 25 |
| Scope | Pipeline-level (distribution fitting) |
Minimum number of historical observations required before distribution fitting is attempted. SKUs with fewer data points than this threshold are skipped — no distribution is fitted, no safety stock is computed by the MEIO optimizer for them. They may still receive a default safety stock from the supply planning engine if that runs.
Lowering this (e.g. to 12) allows newer SKUs to get fitted distributions sooner, at the cost of less reliable fits. Raising it (e.g. to 52 = one full year) ensures only well-established SKUs are optimized.
consider_eoq¶
| Type | Boolean |
| Default | true |
| Scope | Pipeline-level global default; scenario-level override available |
Whether the optimizer factors in Economic Order Quantity when computing batch sizes. When true, the eoq from the item master is used as the order quantity increment; when false, the optimizer uses 1 (any quantity allowed).
This affects how the optimizer rounds the recommended buffer to a multiple of the EOQ. With consider_eoq = true, a recommended buffer of 73 units with EOQ = 24 would be rounded to 72 or 96, not 73.
Scenario override: Yes — via param_overrides.consider_eoq (also exposed in the scenario editor Basic Info panel as a toggle).
line_fill_rate¶
| Type | Boolean |
| Default | true |
| Scope | Pipeline-level global default; scenario-level override available |
Companion flag to service_level_type = line_fill_rate. When true, the optimizer divides total_demand_rate and direct_demand_rate by avgsize (average line size from the item master) before optimization. The safety stock is computed for line-based demand, then the resulting buffer is in units of lines, which is converted back to units.
When false, demand is measured in units (standard fill rate, regardless of how many units per order line).
Scenario override: Yes — via param_overrides.line_fill_rate (also exposed in the scenario editor Basic Info panel as a toggle).
precision_jump¶
| Type | Float |
| Default | 0.0 |
| Scope | Pipeline-level (Rust optimizer) |
Early-stopping threshold: if the fill-rate improvement per iteration drops below this value, the optimizer skips ahead to the next SKU rather than continuing to fine-tune the current one. 0.0 = disabled (the optimizer always evaluates every candidate fully).
Useful for very large catalogs where the last few percentage points of fill rate have diminishing returns and you want to prioritize coverage over precision.
big_jump_threshold¶
| Type | Float |
| Default | 0.95 |
| Scope | Pipeline-level (Rust optimizer) |
When the achieved fill rate for a group exceeds this value, the optimizer switches to a finer-grained search to precision-navigate the last few percentage points. Below this threshold, it uses larger jumps (faster convergence); above, it uses smaller jumps (more precise).
Default of 0.95 means: once a group hits 95% fill rate, the optimizer becomes more careful to avoid overshooting the target.
default_fill_rate_target¶
| Type | Float |
| Default | 0.95 |
| Scope | Pipeline-level (fallback for segments without an explicit target) |
Fallback fill-rate target for segments that don't have an explicit meio_target parameter linked. Used by _load_group_targets_from_segments() when a segment's meio_target parameter set has no fill_rate_target key.
Note: The preferred path is to always link a meio_target parameter set to every active segment with an explicit fill_rate_target. This fallback exists as a safety net only.
default_max_budget¶
| Type | Float |
| Default | 1000000000000 (1e12) |
| Scope | Pipeline-level (fallback for segments without an explicit budget cap) |
Maximum inventory investment per group (in currency units). Fallback when a segment's meio_target parameter set has no max_budget key. Effectively unlimited by default.
Each segment can have its own max_budget via the meio_target parameter set. When the optimizer hits the budget cap for a group, it stops allocating buffer to that group even if the fill_rate_target has not been reached.
Cap enforcement (V3): every buffer step (greedy main loop, endgame, AND Phase-1 ASL) is clamped at commit time in commit_result so its investment never exceeds the binding group's remaining budget headroom — achieved_budget lands exactly at max_budget and the group completes via completion_reason="budget". The clamp runs at COMMIT time (not evaluation time) because the greedy loop evaluates candidates in parallel against a stale budget snapshot. When a group completes via budget exhaustion, ALL its member SKUs are frozen — they cannot receive more buffer through any other overlapping group (matching V1's reset_items_in_just_completed_group, but freezing ALL members). Groups that couldn't reach their fill_rate target because their SKUs were frozen are surfaced in the UI with an amber banner.
achievement_logging¶
| Type | Boolean |
| Default | true |
| Scope | Pipeline-level (MEIO runner logging) |
When enabled, the MEIO runner logs a detailed fill-rate achievement breakdown after each optimization run. This includes per-group completion reasons (fill_rate vs budget exhaustion), demand-weighted FR validation, and top-20 SKU contributors by demand weight.
Disable to reduce log volume on very large catalogs where the achievement log is too verbose.
MEIO Scenario Window Parameters¶
The MEIO Scenario editor (MeioScenarios.jsx) is the per-scenario configuration UI. It has four panels:
- Basic Info — scenario identity + optimizer settings
- Global Overrides — scenario-wide config overrides
- SKU Overrides — scenario-wide SKU-level overrides
- Parameter Sets — per-segment parameter sets
Each MEIO scenario is a row in scenario.meio_scenarios with a param_overrides JSONB column that stores the scenario-level overrides. The parameter sets are separate rows in scenario.config_meio_parameter_set linked to segments via config_meio_parameter_set_segment.
Basic Info Panel¶
These fields are stored directly on the meio_scenarios row or in the param_overrides JSONB.
name¶
| Type | String |
| Stored in | meio_scenarios.name |
| Required | Yes |
Display name for the scenario. Shown in the scenario list and the scenario dropdown. Must be unique within the tenant (enforced by a unique constraint).
description¶
| Type | String |
| Stored in | meio_scenarios.description |
| Required | No |
Optional free-text notes about the scenario's purpose. Displayed in the scenario list and the editor. Useful for documenting what the scenario is testing (e.g. "Stress test: +20% demand on Spain retail segment").
is_base¶
| Type | Boolean |
| Stored in | meio_scenarios.is_base |
| Default | false |
If true, this is the baseline scenario run by default. Exactly one scenario per tenant should be marked as base. The base scenario is the one run automatically when a pipeline completes (if the pipeline has run_meio = true). Base scenarios cannot be deleted from the UI (the delete button is disabled).
service_level_type (scenario override)¶
| Type | String (select) |
| Options | fill_rate, line_fill_rate, stockout, availability |
| Stored in | param_overrides.service_level_type |
| Default | Inherits from global service_level_type |
Overrides the global service_level_type for this scenario. See the global service_level_type section for the meaning of each option.
When set to availability, the scenario uses the Erlang-B availability path instead of the standard Rust optimizer. The UI shows a "✈ Asset Avail." badge on the scenario row, and additional availability-specific inputs appear (see Availability Scenario Extras).
concurrent_candidates (scenario override)¶
| Type | Integer |
| Stored in | param_overrides.concurrent_candidates |
| Default | Inherits from global concurrent_candidates |
Overrides the global concurrent_candidates for this scenario. See the global concurrent_candidates section for details.
The UI shows a hint "(0 = auto)" next to the input. A value of 0 means the optimizer uses all available CPU cores.
consider_eoq (scenario override)¶
| Type | Boolean (toggle) |
| Stored in | param_overrides.consider_eoq |
| Default | Inherits from global consider_eoq (true) |
Overrides the global consider_eoq for this scenario. When true, the optimizer respects the EOQ from the item master when computing batch sizes. When false, the optimizer uses 1 (any quantity allowed).
The UI shows a toggle with the hint "Respect lot-sizing in optimization".
line_fill_rate (scenario override)¶
| Type | Boolean (toggle) |
| Stored in | param_overrides.line_fill_rate |
| Default | Inherits from global line_fill_rate (true) |
Overrides the global line_fill_rate for this scenario. When true, the optimizer measures fill rate at the order-line level (demand divided by average line size). When false, demand is measured in units.
The UI shows a toggle with the hint "Measure fill rate at order-line level".
Global Overrides Panel¶
These are scenario-wide config overrides applied on top of the global defaults. They are stored in param_overrides as top-level keys. Blank = no override (use the global default).
holding_cost_pct¶
| Type | Float (percent) |
| Range | 0.0 – 1.0 |
| Stored in | param_overrides.holding_cost_pct |
| Default | Inherits from global (typically 0.25 = 25%) |
The annual holding cost as a fraction of inventory value. Used by the optimizer to convert inventory investment into an annual cost for the cost-vs-service-level trade-off.
For example, 0.25 means holding one unit of inventory for one year costs 25% of its unit cost. If a unit costs €100, the annual holding cost is €25.
This value flows through to:
- The MEIO optimizer's marginal value calculation (€ fill-rate gain per € inventory investment)
- The K-Curve / EOQ calculation in kcurve/frequency.py
- The supply LP solver's holding cost term in supply_solver/model.py
Effect on optimization: Higher holding_cost_pct → inventory is more expensive → the optimizer favors smaller buffers (lower service level for the same budget). Lower holding_cost_pct → inventory is cheap → the optimizer favors larger buffers (higher service level).
SKU Overrides Panel¶
These are scenario-wide SKU-level overrides applied to every SKU after parameter-set resolution. They are stored in param_overrides.sku_overrides as a flat dict. Blank = no override.
The override application is in _apply_sku_overrides() (meio_runner.py:1715). Per-segment overrides are applied first, then global SKU overrides win.
demand_multiplier (SKU override)¶
| Type | Float |
| Default | 1.0 (no change) |
| Stored in | param_overrides.sku_overrides.demand_multiplier |
Scales all demand rates for every SKU in the scenario:
s["total_demand_rate"] = s["total_demand_rate"] * dm
s["direct_demand_rate"] = s["direct_demand_rate"] * dm
s["indirect_demand_rate"] = s["indirect_demand_rate"] * dm
1.0= no change (default).1.2= +20% demand across the board.0.8= -20% demand.
Use case: Stress testing. "What if demand is 20% higher than forecasted?" Set demand_multiplier = 1.2 and re-run the scenario. The optimizer will recommend larger safety stocks.
This is a scenario-wide override — it applies to every SKU in the scenario. For per-segment demand scaling, use the per-segment parameter set's demand_multiplier instead.
lead_time_multiplier (SKU override)¶
| Type | Float |
| Default | 1.0 (no change) |
| Stored in | param_overrides.sku_overrides.lead_time_multiplier |
Scales lead times for every SKU in the scenario:
1.0= no change.1.5= +50% lead time (modeling supplier delays).0.9= -10% lead time (modeling supplier improvements).
Use case: Lead-time stress testing. "What if our supplier is 50% slower?" Set lead_time_multiplier = 1.5 and re-run. The optimizer will recommend larger safety stocks to cover the longer exposure window.
return_rate (SKU override)¶
| Type | Float (percent) |
| Range | 0.0 – 1.0 |
| Stored in | param_overrides.sku_overrides.return_rate |
Overrides the return_rate in the repair_flow dict for every SKU in the scenario:
This is the scenario-wide reverse-logistics override. It overrides both the global default_return_rate and the per-route return_rate. For per-segment reverse-logistics overrides, use the per-segment parameter set's return_rate instead.
See the global default_return_rate section for the meaning.
repair_yield (SKU override)¶
| Type | Float (percent) |
| Range | 0.0 – 1.0 |
| Stored in | param_overrides.sku_overrides.repair_yield |
Overrides the repair_yield in the repair_flow dict for every SKU in the scenario:
Scenario-wide reverse-logistics override. See the global default_repair_yield section for the meaning.
Parameter Sets Panel (Per-Segment)¶
A parameter set is a named collection of per-segment overrides. Each scenario can have multiple parameter sets. Each parameter set can be linked to one or more segments (or to "All Segments" via segment_id = NULL).
The parameter set model is stored in two tables:
- config_meio_parameter_set — the parameter set itself (name + params JSONB)
- config_meio_parameter_set_segment — link table between parameter sets and segments
When the MEIO runner builds SKU records, it resolves per-segment overrides via _load_scenario_segment_params():
1. Reads all parameter sets for the scenario, ordered by sort_order (lowest wins per segment).
2. Builds a dict[segment_id | None, params] map.
3. For each SKU, looks up its segment's overrides and applies them.
The parameter set editor shows a flat table with one row per parameter set and inline-editable columns for each parameter. The columns are grouped:
Service Level columns¶
fill_rate_target (per-segment)¶
| Type | Float (percent) |
| Range | 0.0 – 1.0 |
| Stored in | config_meio_parameter_set.params.fill_rate_target |
| Required | Yes — every active segment must have an explicit target |
The target fill rate for the segment. This is the actual optimization target the Rust optimizer uses to compute safety stock for SKUs in this segment. The optimizer stops allocating buffer to a group when this target is reached or max_budget is exhausted.
This is the most important per-segment parameter. It's the lever you pull to set service-level policy by segment. For example:
- Critical spare parts: fill_rate_target = 0.99 (99%)
- Standard retail: fill_rate_target = 0.95 (95%)
- Slow-movers: fill_rate_target = 0.90 (90%)
When fill_rate_target is set via a parameter set, it overrides the per-SKU sku_tgt_fillrate and sku_min_fill_rate for SKUs in that segment:
If no parameter set is linked to a segment, the fallback is the global default_fill_rate_target (typically 0.95). The preferred path is to always link a parameter set with an explicit fill_rate_target to every active segment.
max_budget (per-segment)¶
| Type | Float (currency units) |
| Stored in | config_meio_parameter_set.params.max_budget |
| Default | 1e12 (effectively unlimited) |
The maximum total inventory investment for the segment's group, in the tenant's default currency. When the optimizer hits this cap, it stops allocating buffer to the group even if fill_rate_target has not been reached.
This is the budget lever. For example:
- "I have €500k to spend on this segment" → set max_budget = 500000.
- The optimizer will allocate the €500k across the segment's SKUs to maximize fill rate, but will not exceed the cap.
When max_budget is blank in the UI, the fallback is the global default_max_budget (1e12 = effectively unlimited).
sku_min_fill_rate (per-segment)¶
| Type | Float (percent) |
| Range | 0.0 – 1.0 |
| Stored in | config_meio_parameter_set.params.sku_min_fill_rate |
The minimum allowed fill rate per SKU within the segment. The optimizer will not let any individual SKU's fill rate fall below this value, even if the group's fill_rate_target has been met.
This is a per-SKU floor. For example, if sku_min_fill_rate = 0.85 and the group target is 0.95, the optimizer will ensure every SKU reaches at least 85%, then allocate the remaining budget to push the group average toward 95%.
Without this, a few SKUs could have very low fill rates while the group average meets target — this prevents that.
Per-SKU column on master.item (min_fill_rate) provides the same floor but at the item-master level. The parameter set's sku_min_fill_rate overrides the item-master value for SKUs in that segment.
sku_max_fill_rate (per-segment)¶
| Type | Float (percent) |
| Range | 0.0 – 1.0 (typically 1.0) |
| Stored in | config_meio_parameter_set.params.sku_max_fill_rate |
The maximum allowed fill rate per SKU within the segment. The optimizer will not push any individual SKU's fill rate above this value, even if budget remains.
This is a per-SKU cap. For example, if sku_max_fill_rate = 0.99, the optimizer will not over-invest in any single SKU beyond 99% fill rate, even if the group target hasn't been met. This prevents the optimizer from "wasting" budget on SKUs that are already well-stocked.
Per-SKU column on master.item (max_fill_rate) provides the same cap but at the item-master level. The parameter set's sku_max_fill_rate overrides the item-master value for SKUs in that segment.
Demand & LT columns¶
demand_multiplier (per-segment)¶
| Type | Float |
| Default | 1.0 (no change) |
| Stored in | config_meio_parameter_set.params.demand_multiplier |
Scales all demand rates for SKUs in this segment. Same semantics as the scenario-wide demand_multiplier, but scoped to the segment only.
Use case: Per-segment stress testing. "What if Spain retail demand is +30% but Germany is unchanged?" Create a parameter set linked to the Spain segment with demand_multiplier = 1.3.
lead_time_multiplier (per-segment)¶
| Type | Float |
| Default | 1.0 (no change) |
| Stored in | config_meio_parameter_set.params.lead_time_multiplier |
Scales lead times for SKUs in this segment. Same semantics as the scenario-wide lead_time_multiplier, but scoped to the segment only.
SKU Bounds columns¶
sku_min_sl_qty (per-segment)¶
| Type | Float (units) |
| Default | 0.0 |
| Stored in | config_meio_parameter_set.params.sku_min_sl_qty |
The minimum safety level in units per SKU within the segment. The optimizer will not let any SKU's safety stock fall below this quantity, regardless of the fill-rate calculation.
This is a hard floor in units. For example, if sku_min_sl_qty = 5, every SKU in the segment gets at least 5 units of safety stock, even if the fill-rate calculation says 3 is enough.
Useful for critical items where you want a minimum physical buffer regardless of statistical optimization.
Per-SKU column on master.item (min_sl_qty) provides the same floor at the item-master level. The parameter set's sku_min_sl_qty overrides the item-master value for SKUs in that segment.
sku_max_sl_qty (per-segment)¶
| Type | Float (units) |
| Default | 99999999 (effectively unlimited) |
| Stored in | config_meio_parameter_set.params.sku_max_sl_qty |
The maximum safety level in units per SKU within the segment. The optimizer will not push any SKU's safety stock above this quantity, even if budget remains and the fill-rate target would justify more.
This is a hard cap in units. For example, if sku_max_sl_qty = 100, no SKU in the segment gets more than 100 units of safety stock, even if the fill-rate calculation says 120 is optimal.
Useful for space-constrained locations or items with high obsolescence risk.
Per-SKU column on master.item (max_sl_qty) provides the same cap at the item-master level. The parameter set's sku_max_sl_qty overrides the item-master value for SKUs in that segment.
sku_min_sl_slices (per-segment)¶
| Type | Float (months of cover) |
| Default | 0.0 |
| Stored in | config_meio_parameter_set.params.sku_min_sl_slices |
The minimum months of demand cover per SKU within the segment. The optimizer will not let any SKU's safety stock represent less than this many months of demand, regardless of the fill-rate calculation.
This is a time-based floor. For example, if sku_min_sl_slices = 2, every SKU in the segment gets at least 2 months of demand cover as safety stock, even if the fill-rate calculation says 1 month is enough.
Useful when you want to express the floor in time rather than units (e.g. "I want at least 3 months of cover on every critical spare").
sku_max_sl_slices (per-segment)¶
| Type | Float (months of cover) |
| Default | 99999999 (effectively unlimited) |
| Stored in | config_meio_parameter_set.params.sku_max_sl_slices |
The maximum months of demand cover per SKU within the segment. The optimizer will not push any SKU's safety stock beyond this many months of demand cover.
This is a time-based cap. For example, if sku_max_sl_slices = 12, no SKU in the segment gets more than 12 months of cover, even if the fill-rate calculation would justify 18 months.
Useful to prevent over-stocking slow-movers (where 12 months of cover might already be years of demand).
Config columns¶
holding_cost_pct (per-segment)¶
| Type | Float (percent) |
| Range | 0.0 – 1.0 |
| Stored in | config_meio_parameter_set.params.holding_cost_pct |
Per-segment override of the annual holding cost as a fraction of inventory value. See the scenario-wide holding_cost_pct for the meaning.
Use case: Different holding costs by segment. For example, cold-chain items might have holding_cost_pct = 0.35 (refrigeration is expensive) while dry goods have 0.20.
use_existing_inventory (per-segment)¶
| Type | Boolean |
| Default | false |
| Stored in | config_meio_parameter_set.params.use_existing_inventory |
When true, the optimizer credits on-hand stock against the recommended buffer. The starting point for the optimizer is the current on-hand inventory, not zero.
onhand_min_qty = on_hand if use_existing_inventory else 0
min_sl_qty = max(sku_min_sl_qty, onhand_min_qty, sku_min_sl_slices_qty)
When false, the optimizer starts from zero and recommends a total buffer. The planner then compares the recommendation to current on-hand to decide the order quantity.
When true, the optimizer starts from current on-hand and recommends the incremental buffer needed. If on-hand already exceeds the recommendation, the optimizer says "0 additional needed."
Per-SKU column on master.item (use_existing_inventory) provides the same flag at the item-master level. The parameter set's use_existing_inventory overrides the item-master value for SKUs in that segment.
Reverse-logistics parameters — NOT per-segment¶
Reverse-logistics parameters (return_rate, repair_yield, repair_tat_mean, repair_tat_cv, wip_qty) are no longer editable per segment. They are sourced exclusively from master.route RETURN/REPAIR rows via _load_repair_params_from_routes in meio_runner.py, with the global default_* values above used as fallbacks when a route column is NULL.
Any persisted values in config_meio_parameter_set.params JSONB are stripped by files/DDL/migrations/strip_meio_rl_segment_params.sql (run once per tenant). Per-series overrides for these keys were also removed from the Hyperparameters UI.
Availability Scenario Extras¶
When a scenario's service_level_type is set to availability, the MEIO runner bypasses the standard Rust optimizer and uses the Erlang-B pool-sizing model (_run_causal_asset_scenario() in meio_runner.py:403).
These parameters are read from param_overrides (not from the parameter sets):
asset_availability_target¶
| Type | Float |
| Default | 0.995 (99.5%) |
| Stored in | param_overrides.asset_availability_target |
The target asset availability — the probability that a spare is available when needed. For example, 0.995 means there is a 99.5% chance that when an asset fails, the required spare is in stock.
The target is allocated geometrically across the LRUs in the asset type:
So if the asset has 10 distinct LRUs and the target is 0.995, each LRU gets a per-item target of 0.995^(1/10) ≈ 0.9995 (99.95%). This is because all LRUs must be available simultaneously for the asset to be available — the probabilities multiply.
causal_scenario_id¶
| Type | Integer |
| Default | 1 (first causal scenario) |
| Stored in | param_overrides.causal_scenario_id |
The causal scenario that defines the fleet plan and failure rates. The availability model reads:
- The LRU BOM from master.causal_bom (which items are LRUs, how many per asset, repair yield)
- The fleet plan and failure rates from the causal scenario (asset quantities, failure rates, coverage)
The causal scenario determines the demand rate for each LRU at each site:
This demand rate, combined with repair_tat_mean, determines the offered load (ρ):
The Erlang-B formula then computes the pool size needed to achieve asset_availability_target given the offered load.
repair_tat_mean (availability scenario)¶
| Type | Float (weeks) |
| Default | 4.0 |
| Stored in | param_overrides.repair_tat_mean |
The repair turnaround time mean used in the Erlang-B calculation. In the availability path, this is read directly from param_overrides (not from the route table or global default):
This is the total repair cycle time (transit back + in-shop repair) for the availability model. It directly determines the offered load (ρ = demand_rate × repair_tat_mean), which is the primary driver of pool size.
Per-SKU Columns on master.item¶
These are stored on the item master and provide per-item floors and caps that sit underneath all three configuration layers. They are read by the MEIO runner's SKU loading SQL:
COALESCE(i.max_fill_rate, 1.0) AS sku_max_fill_rate,
COALESCE(i.min_fill_rate, 0.0) AS sku_min_fill_rate,
COALESCE(i.max_sl_qty, 99999999.0) AS sku_max_sl_qty,
COALESCE(i.min_sl_qty, 0.0) AS sku_min_sl_qty,
COALESCE(i.use_existing_inventory, false) AS use_existing_inventory
These are the lowest-priority layer — they are the defaults that parameter sets and scenario overrides replace. But they are also the only way to set per-item constraints without creating a segment for each item.
| Column | Type | Default | Meaning |
|---|---|---|---|
min_fill_rate |
Float | 0.0 |
Minimum allowed fill rate for this SKU (per-item floor) |
max_fill_rate |
Float | 1.0 |
Maximum allowed fill rate for this SKU (per-item cap) |
min_sl_qty |
Float | 0.0 |
Minimum safety level in units (per-item floor) |
max_sl_qty |
Float | 99999999 |
Maximum safety level in units (per-item cap) |
min_sl_slices |
Float | 0.0 |
Minimum months of cover (per-item floor) |
max_sl_slices |
Float | 99999999 |
Maximum months of cover (per-item cap) |
use_existing_inventory |
Boolean | false |
Credit on-hand stock against recommended buffer |
eoq |
Float | 1 |
Economic Order Quantity (used when consider_eoq = true) |
avgsize |
Float | 1 |
Average order line size (used when line_fill_rate = true) |
unit_cost |
Float | 0.0 |
Unit cost (used for inventory value and holding cost) |
Override rule: Parameter set values for the SKU's segment override these item-master values. Scenario-level SKU overrides (param_overrides.sku_overrides) override both.
Per-Route Columns on Supply Routes¶
These are stored on the supply route configuration and provide per-route values for the reverse-logistics parameters. They are read by _load_repair_params_from_routes() in meio_runner.py.
| Column | Type | Default | Meaning |
|---|---|---|---|
return_rate |
Float | default_return_rate |
Fraction of units returned for repair |
repair_yield |
Float | default_repair_yield |
Fraction of returned units successfully repaired |
tat_weeks (RETURN) |
Float | 0.0 |
Physical return transit time (customer → repair facility) in weeks |
tat_weeks (REPAIR) |
Float | default_repair_tat_mean |
In-shop repair time in weeks |
lead_time_cv |
Float | default_repair_tat_cv |
Repair TAT coefficient of variation |
wip_qty |
Float | default_wip_qty |
Work-in-process quantity |
Override rule: Parameter set values for the SKU's segment override these route values. Scenario-level SKU overrides (param_overrides.sku_overrides) override both. Global default_* values are the fallback when a route column is NULL.
The final repair_tat_mean used by the optimizer is the sum of the RETURN route's tat_weeks (transit) and the REPAIR route's tat_weeks (in-shop):
Parameter Override Hierarchy (Full)¶
Understanding where each parameter can be overridden is crucial. Later sources win over earlier ones.
┌────────────────────────────────────────────────────────────────────────────┐
│ 1. Per-SKU Columns on master.item │
│ min_fill_rate, max_fill_rate, min_sl_qty, max_sl_qty, │
│ min_sl_slices, max_sl_slices, use_existing_inventory, eoq, avgsize, │
│ unit_cost │
│ (lowest priority — the item-master defaults) │
├────────────────────────────────────────────────────────────────────────────┤
│ 2. DB Default Parameter Set (config_parameters, is_default = TRUE) │
│ All global MEIO parameters in this document start here. │
│ (edited in Settings → Business Config → MEIO) │
├────────────────────────────────────────────────────────────────────────────┤
│ 3. Per-Route Supply Route Configuration │
│ return_rate, repair_yield, tat_weeks, lead_time_cv, wip_qty │
│ (override the default_* fallbacks for specific (item, site) pairs) │
├────────────────────────────────────────────────────────────────────────────┤
│ 4. Scenario-Level param_overrides (meio_scenarios.param_overrides) │
│ service_level_type, concurrent_candidates, │
│ holding_cost_pct, line_fill_rate, consider_eoq, │
│ default_return_rate, default_repair_yield, │
│ default_repair_tat_mean, default_repair_tat_cv, default_wip_qty, │
│ sku_overrides.demand_multiplier, sku_overrides.lead_time_multiplier, │
│ sku_overrides.return_rate, sku_overrides.repair_yield, │
│ asset_availability_target, causal_scenario_id, repair_tat_mean (avail)│
│ (edited in the MEIO scenario editor Basic Info + Global/SKU Overrides)│
├────────────────────────────────────────────────────────────────────────────┤
│ 5. Per-Segment Parameter Sets (config_meio_parameter_set) │
│ fill_rate_target, max_budget, │
│ sku_min_fill_rate, sku_max_fill_rate, │
│ sku_min_sl_qty, sku_max_sl_qty, │
│ sku_min_sl_slices, sku_max_sl_slices, │
│ holding_cost_pct, use_existing_inventory, │
│ demand_multiplier, lead_time_multiplier, │
│ return_rate, repair_yield, repair_tat_mean, repair_tat_cv, wip_qty, │
│ line_fill_rate, consider_eoq │
│ (edited in the MEIO scenario editor Parameter Sets panel) │
│ (highest priority — wins over all above for SKUs in the segment) │
└────────────────────────────────────────────────────────────────────────────┘
Note on sku_overrides vs parameter sets: Within a scenario, the per-segment parameter set is applied first, then the scenario-wide sku_overrides win over the per-segment values:
merged = {}
if has_seg:
merged.update(_resolve_sku_seg_params(sku, seg_params))
merged.update(sku_ov) # global overrides win over per-segment
This means a scenario-wide demand_multiplier = 1.2 will override a per-segment demand_multiplier = 1.1, even if the segment's parameter set is linked. Use scenario-wide overrides for global stress tests; use per-segment overrides for targeted segment-level tuning.
API Reference¶
| Method | Path | Description |
|---|---|---|
| GET | /api/meio/scenarios |
List all MEIO scenarios |
| POST | /api/meio/scenarios |
Create a scenario |
| PUT | /api/meio/scenarios/{id} |
Update a scenario (name, description, is_base, param_overrides) |
| DELETE | /api/meio/scenarios/{id} |
Delete a scenario (base scenarios cannot be deleted) |
| GET | /api/meio/scenarios/{id}/parameter-sets |
List parameter sets for a scenario |
| POST | /api/meio/scenarios/{id}/parameter-sets |
Add a parameter set to a scenario |
| PUT | /api/meio/parameter-sets/{pset_id} |
Update a parameter set (name, params) |
| DELETE | /api/meio/parameter-sets/{pset_id} |
Delete a parameter set |
| PUT | /api/meio/parameter-sets/{pset_id}/segments |
Link segments to a parameter set |
| PUT | /api/meio/scenarios/{id}/parameter-sets/reorder |
Reorder parameter sets (affects resolution priority) |
| POST | /api/meio/scenarios/{id}/run?pipeline_id={pid} |
Run the optimizer for a scenario |
| GET | /api/meio/scenarios/{id}/results?pipeline_id={pid} |
Get SKU + group results |
| GET | /api/series/{unique_id}/mc-distribution?pipeline_id={pid}&meio_scenario_id={sid} |
MEIO V3 MC distribution snapshot (demand × supply-LT → compound → SS) |
| GET | /api/meio/fr-heatmap?pipeline_id={pid} |
Fill-rate heatmap |
| GET | /api/meio/diagnostic |
Diagnostic data for debugging |
Pipeline ID is required for all business-logic endpoints that read/write PIPE_meio_results. Use Optional[int] = Query(default=None) and raise HTTPException(400, "pipeline_id is required") if it's None. See utils/pipeline_guard.py.
Key Files¶
| File | Purpose |
|---|---|
files/meio_runner.py |
MEIO runner: config loading, SKU loading, override application, optimizer dispatch |
files/meio_optimizer_v2.pyd |
Rust V2 optimizer binary (greedy multi-echelon buffer allocation) |
files/meio_optimizer.pyd |
Rust V1 optimizer binary (legacy) |
files/fitting.py |
Distribution fitting engine (reads enabled, distributions, fitting_method, service_levels) |
files/distribution/fitting.py |
Distribution fitting engine (canonical implementation with OpenTelemetry spans) |
files/db/db.py |
DB seed for the meio parameter row and master.item columns |
files/api/main.py |
API endpoints for MEIO scenarios, parameter sets, results, heatmap |
files/frontend/src/components/MeioScenarios.jsx |
MEIO scenario editor UI (Basic Info, Global Overrides, SKU Overrides, Parameter Sets) |
files/frontend/src/components/DashConfigPanel.jsx |
Settings UI panel for the global meio parameter row |
files/frontend/src/components/ForecastParameterSets.jsx |
Settings UI for distribution-fitting parameter sets (distribution.service_levels, etc.) |
files/kcurve/frequency.py |
K-Curve / EOQ calculation (reads holding_cost_pct) |
files/supply_solver/model.py |
Supply LP solver holding cost term (reads holding_cost_pct) |
files/utils/pipeline_guard.py |
require_pipeline_id() helper for API endpoint guards |