use crate::entities::FillingConfig;
#[inline]
#[must_use]
pub fn commissioning_active(entry: Option<i32>, exit: Option<i32>, stage_id: i32) -> bool {
entry.is_none_or(|e| e <= stage_id) && exit.is_none_or(|e| stage_id < e)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Phase {
PreFilling,
Filling,
Operating,
}
#[inline]
#[must_use]
pub fn filling_phase(
filling: Option<&FillingConfig>,
entry: Option<i32>,
exit: Option<i32>,
stage_id: i32,
) -> Phase {
let Some(config) = filling else {
return if commissioning_active(entry, exit, stage_id) {
Phase::Operating
} else {
Phase::PreFilling
};
};
if config.start_stage_id > 0 && stage_id < config.start_stage_id {
return Phase::PreFilling;
}
match entry {
Some(e) if stage_id < e => Phase::Filling,
_ => Phase::Operating,
}
}
#[inline]
#[must_use]
pub fn hydro_operating_active(
filling: Option<&FillingConfig>,
entry: Option<i32>,
exit: Option<i32>,
stage_id: i32,
) -> bool {
matches!(
filling_phase(filling, entry, exit, stage_id),
Phase::Operating
)
}
#[cfg(test)]
mod tests {
use super::{FillingConfig, Phase, filling_phase, hydro_operating_active};
fn config(start_stage_id: i32) -> FillingConfig {
FillingConfig {
start_stage_id,
filling_min_rate_m3s: 0.0,
}
}
#[test]
fn none_filling_none_window_is_operating_at_every_stage() {
for stage_id in [-1, 0, 1, 4, 100] {
assert_eq!(
filling_phase(None, None, None, stage_id),
Phase::Operating,
"none filling, no window, stage_id={stage_id}"
);
}
}
#[test]
fn none_filling_with_window_is_prefilling_outside_entry_exit() {
let entry = Some(2);
let exit = Some(5);
assert_eq!(filling_phase(None, entry, exit, 0), Phase::PreFilling);
assert_eq!(filling_phase(None, entry, exit, 1), Phase::PreFilling);
assert_eq!(filling_phase(None, entry, exit, 2), Phase::Operating);
assert_eq!(filling_phase(None, entry, exit, 4), Phase::Operating);
assert_eq!(filling_phase(None, entry, exit, 5), Phase::PreFilling);
assert_eq!(filling_phase(None, entry, exit, 9), Phase::PreFilling);
}
#[test]
fn none_filling_entry_beyond_horizon_is_prefilling_everywhere() {
let entry = Some(1000);
for stage_id in [0, 1, 4, 100] {
assert_eq!(
filling_phase(None, entry, None, stage_id),
Phase::PreFilling,
"always-dormant non-filling at stage_id={stage_id}"
);
}
}
#[test]
fn three_phases_at_exact_boundaries() {
let f = config(2);
let entry = Some(4);
assert_eq!(filling_phase(Some(&f), entry, None, 0), Phase::PreFilling);
assert_eq!(filling_phase(Some(&f), entry, None, 1), Phase::PreFilling);
assert_eq!(filling_phase(Some(&f), entry, None, 2), Phase::Filling);
assert_eq!(filling_phase(Some(&f), entry, None, 3), Phase::Filling);
assert_eq!(filling_phase(Some(&f), entry, None, 4), Phase::Operating);
assert_eq!(filling_phase(Some(&f), entry, None, 5), Phase::Operating);
}
#[test]
fn start_zero_is_filling_at_stage_zero() {
let f = config(0);
let entry = Some(4);
assert_eq!(filling_phase(Some(&f), entry, None, 0), Phase::Filling);
assert_eq!(filling_phase(Some(&f), entry, None, 3), Phase::Filling);
assert_eq!(filling_phase(Some(&f), entry, None, 4), Phase::Operating);
}
#[test]
fn filling_with_none_entry_falls_through_to_operating() {
let f = config(2);
assert_eq!(filling_phase(Some(&f), None, None, 1), Phase::PreFilling);
assert_eq!(filling_phase(Some(&f), None, None, 2), Phase::Operating);
assert_eq!(filling_phase(Some(&f), None, None, 5), Phase::Operating);
}
#[test]
fn hydro_operating_active_truth_table() {
assert!(hydro_operating_active(None, None, None, 0));
assert!(hydro_operating_active(None, None, None, 100));
assert!(!hydro_operating_active(None, Some(2), Some(5), 1));
assert!(hydro_operating_active(None, Some(2), Some(5), 2));
assert!(hydro_operating_active(None, Some(2), Some(5), 4));
assert!(!hydro_operating_active(None, Some(2), Some(5), 5));
let f = config(2);
assert!(!hydro_operating_active(Some(&f), Some(4), None, 0));
assert!(!hydro_operating_active(Some(&f), Some(4), None, 3));
assert!(hydro_operating_active(Some(&f), Some(4), None, 4));
}
}