#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", rename_all = "SCREAMING_SNAKE_CASE")
)]
pub enum AggregationRule {
Sum {
source_malo_ids: Vec<String>,
},
Residual {
total_malo_id: String,
subtract_malo_ids: Vec<String>,
},
PvSelfConsumption {
grid_malo_id: String,
generation_malo_id: String,
},
GgvConstantAllocation {
plant_melo_id: String,
tenant_melo_id: String,
fraction: rust_decimal::Decimal,
},
GgvProportionalAllocation {
plant_melo_id: String,
tenant_melo_id: String,
all_tenant_melo_ids: Vec<String>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum VirtualMeterKind {
Sum,
Residual,
PvSelfConsumption,
GgvConstantAllocation,
GgvProportionalAllocation,
}
impl VirtualMeterKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Sum => "SUM",
Self::Residual => "RESIDUAL",
Self::PvSelfConsumption => "PV_SELF_CONSUMPTION",
Self::GgvConstantAllocation => "GGV_CONSTANT_ALLOCATION",
Self::GgvProportionalAllocation => "GGV_PROPORTIONAL_ALLOCATION",
}
}
pub const ALL: [Self; 5] = [
Self::Sum,
Self::Residual,
Self::PvSelfConsumption,
Self::GgvConstantAllocation,
Self::GgvProportionalAllocation,
];
}
crate::codes::string_codes! {
VirtualMeterKind;
}
impl From<&AggregationRule> for VirtualMeterKind {
fn from(rule: &AggregationRule) -> Self {
rule.kind()
}
}
impl AggregationRule {
#[must_use]
pub const fn kind(&self) -> VirtualMeterKind {
match self {
Self::Sum { .. } => VirtualMeterKind::Sum,
Self::Residual { .. } => VirtualMeterKind::Residual,
Self::PvSelfConsumption { .. } => VirtualMeterKind::PvSelfConsumption,
Self::GgvConstantAllocation { .. } => VirtualMeterKind::GgvConstantAllocation,
Self::GgvProportionalAllocation { .. } => VirtualMeterKind::GgvProportionalAllocation,
}
}
#[must_use]
pub fn source_malo_ids(&self) -> Vec<&str> {
match self {
Self::Sum { source_malo_ids } => source_malo_ids.iter().map(String::as_str).collect(),
Self::Residual {
total_malo_id,
subtract_malo_ids,
} => {
let mut ids = vec![total_malo_id.as_str()];
ids.extend(subtract_malo_ids.iter().map(String::as_str));
ids
}
Self::PvSelfConsumption {
grid_malo_id,
generation_malo_id,
} => vec![grid_malo_id.as_str(), generation_malo_id.as_str()],
Self::GgvConstantAllocation {
plant_melo_id,
tenant_melo_id,
..
} => vec![plant_melo_id.as_str(), tenant_melo_id.as_str()],
Self::GgvProportionalAllocation {
plant_melo_id,
all_tenant_melo_ids,
..
} => {
let mut ids = vec![plant_melo_id.as_str()];
ids.extend(all_tenant_melo_ids.iter().map(String::as_str));
ids
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal::dec;
#[test]
fn sum_rule_lists_all_sources() {
let rule = AggregationRule::Sum {
source_malo_ids: vec!["MALO_A".to_owned(), "MALO_B".to_owned()],
};
let sources = rule.source_malo_ids();
assert_eq!(sources, vec!["MALO_A", "MALO_B"]);
assert_eq!(rule.kind(), VirtualMeterKind::Sum);
}
#[test]
fn residual_rule_includes_total_and_subtracts() {
let rule = AggregationRule::Residual {
total_malo_id: "TOTAL".to_owned(),
subtract_malo_ids: vec!["PV".to_owned()],
};
let sources = rule.source_malo_ids();
assert!(sources.contains(&"TOTAL"));
assert!(sources.contains(&"PV"));
assert_eq!(rule.kind(), VirtualMeterKind::Residual);
}
#[test]
fn ggv_constant_lists_plant_and_tenant() {
let rule = AggregationRule::GgvConstantAllocation {
plant_melo_id: "MELO_PLANT".to_owned(),
tenant_melo_id: "MELO_T1".to_owned(),
fraction: dec!(0.10),
};
let sources = rule.source_malo_ids();
assert_eq!(sources.len(), 2);
assert!(sources.contains(&"MELO_PLANT"));
assert!(sources.contains(&"MELO_T1"));
assert_eq!(rule.kind(), VirtualMeterKind::GgvConstantAllocation);
}
#[test]
fn ggv_proportional_lists_plant_and_all_tenants() {
let rule = AggregationRule::GgvProportionalAllocation {
plant_melo_id: "MELO_PLANT".to_owned(),
tenant_melo_id: "MELO_T1".to_owned(),
all_tenant_melo_ids: vec!["MELO_T1".to_owned(), "MELO_T2".to_owned()],
};
let sources = rule.source_malo_ids();
assert_eq!(sources.len(), 3);
assert!(sources.contains(&"MELO_PLANT"));
assert_eq!(rule.kind(), VirtualMeterKind::GgvProportionalAllocation);
}
}