use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaintenanceInput {
#[serde(default)]
pub dirty_regions: u64,
#[serde(default)]
pub total_regions: u64,
#[serde(default)]
pub localized_cost_per_region: f64,
#[serde(default)]
pub global_cost_per_region: f64,
#[serde(default)]
pub global_structural_gain: f64,
#[serde(default)]
pub gain_value: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MaintenanceStrategy {
NoOp,
Localized,
Global,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MaintenanceDecision {
pub strategy: MaintenanceStrategy,
pub localized_cost: f64,
pub global_cost: f64,
pub global_extra_value: f64,
pub global_net_advantage: f64,
pub rationale: String,
}
pub fn decide_maintenance(input: &MaintenanceInput) -> MaintenanceDecision {
let localized_cost = input.dirty_regions as f64 * input.localized_cost_per_region.max(0.0);
let global_cost = input.total_regions as f64 * input.global_cost_per_region.max(0.0);
let global_extra_value =
input.global_structural_gain.clamp(0.0, 1.0) * input.gain_value.max(0.0);
let global_net_advantage = global_extra_value - (global_cost - localized_cost);
let (strategy, rationale) = if input.dirty_regions == 0 {
(
MaintenanceStrategy::NoOp,
"No dirty regions — maintenance is unnecessary.".to_string(),
)
} else if global_net_advantage > 0.0 {
(
MaintenanceStrategy::Global,
format!(
"Global reorganization: its store-wide structural gain (value {global_extra_value:.3}) \
clears the extra cost of touching all {} regions (net +{global_net_advantage:.3}).",
input.total_regions
),
)
} else {
(
MaintenanceStrategy::Localized,
format!(
"Localized maintenance: patching {} dirty region(s) is more cost-efficient than a \
global reorg (global net {global_net_advantage:.3} ≤ 0).",
input.dirty_regions
),
)
};
MaintenanceDecision {
strategy,
localized_cost,
global_cost,
global_extra_value,
global_net_advantage,
rationale,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn base() -> MaintenanceInput {
MaintenanceInput {
dirty_regions: 5,
total_regions: 1000,
localized_cost_per_region: 1.0,
global_cost_per_region: 1.0,
global_structural_gain: 0.1,
gain_value: 10.0,
}
}
#[test]
fn no_dirty_is_noop() {
let d = decide_maintenance(&MaintenanceInput {
dirty_regions: 0,
..base()
});
assert_eq!(d.strategy, MaintenanceStrategy::NoOp);
}
#[test]
fn large_store_small_dirty_prefers_localized() {
let d = decide_maintenance(&base());
assert_eq!(d.strategy, MaintenanceStrategy::Localized);
assert_eq!(d.localized_cost, 5.0);
assert_eq!(d.global_cost, 1000.0);
assert!(d.global_net_advantage < 0.0);
}
#[test]
fn high_structural_value_justifies_global() {
let d = decide_maintenance(&MaintenanceInput {
dirty_regions: 5,
total_regions: 100,
localized_cost_per_region: 1.0,
global_cost_per_region: 1.0,
global_structural_gain: 1.0,
gain_value: 1000.0, });
assert_eq!(d.strategy, MaintenanceStrategy::Global);
assert!(d.global_net_advantage > 0.0);
}
#[test]
fn cheap_global_dominates() {
let d = decide_maintenance(&MaintenanceInput {
dirty_regions: 10,
total_regions: 5,
localized_cost_per_region: 5.0, global_cost_per_region: 1.0, global_structural_gain: 0.5,
gain_value: 2.0, });
assert_eq!(d.strategy, MaintenanceStrategy::Global);
}
#[test]
fn no_structural_gain_keeps_localized() {
let d = decide_maintenance(&MaintenanceInput {
global_structural_gain: 0.0,
gain_value: 1000.0,
..base()
});
assert_eq!(d.strategy, MaintenanceStrategy::Localized);
assert_eq!(d.global_extra_value, 0.0);
}
}