Skip to content

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:

  1. Global defaultsscenario.config_parameters row with parameter_type = 'meio', is_default = TRUE. Edited in Settings → Business Config → MEIO. These are the baseline for every MEIO run in the tenant.
  2. Scenario overridesscenario.meio_scenarios.param_overrides JSONB. Edited in the MEIO Scenario editor (Basic Info + Global Overrides panels). These override the global defaults for one scenario.
  3. Per-segment parameter setsscenario.config_meio_parameter_set + config_meio_parameter_set_segment link 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)

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:

self.enabled = self.meio_config.get('enabled', True)
If False, 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.

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_levels list = 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 in config_meio_parameter_setfill_rate_target and in group_targets passed 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.

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:

raw_pw = meio_cfg.get("meio_parallel_workers", meio_cfg.get("parallel_workers", 0))


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.
  • 2N: 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.

fill_rate = 1 - (expected_units_short / expected_demand)
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:

repair_tat_mean = return_transit_time + in_shop_repair_time

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:

repair_tat_mean = return_lt + repair_tat_shop

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_mean weeks (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:

entry.get("wip_qty", d.get("default_wip_qty", 0.0))

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:

  1. Basic Info — scenario identity + optimizer settings
  2. Global Overrides — scenario-wide config overrides
  3. SKU Overrides — scenario-wide SKU-level overrides
  4. 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:

s["leg_lead_time"]   = s["leg_lead_time"]   * ltm
s["total_lead_time"] = s["total_lead_time"] * ltm

  • 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:

rf["return_rate"] = float(rr)

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:

rf["repair_yield"] = float(ry)

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 frt is not None:
    s["sku_tgt_fillrate"]  = float(frt)
    s["sku_min_fill_rate"] = float(frt)

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:

item_target = asset_availability_target ** (1 / n_distinct_lrus_in_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:

demand_rate = Σ_aircraft (avg_failure_rate[item] × qty_in_bom × coverage_pct)

This demand rate, combined with repair_tat_mean, determines the offered load (ρ):

rho = demand_rate × repair_tat_mean

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):

repair_tat_mean = float(overrides.get("repair_tat_mean", 4.0))

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):

repair_tat_mean = return_lt + repair_tat_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