Skip to content

Commit 1ed2a5b

Browse files
authored
Add a formula generator to coalesce metrics from multiple components (#8)
2 parents 94c1372 + bcc192c commit 1ed2a5b

File tree

5 files changed

+96
-1
lines changed

5 files changed

+96
-1
lines changed

src/error.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ ErrorKind!(
5252
(Internal, internal),
5353
(InvalidComponent, invalid_component),
5454
(InvalidConnection, invalid_connection),
55-
(InvalidGraph, invalid_graph)
55+
(InvalidGraph, invalid_graph),
56+
(MissingParameters, missing_parameters)
5657
);
5758

5859
/// An error that can occur during the creation or traversal of a

src/graph/formulas.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ where
5151
generators::pv::PVFormulaBuilder::try_new(self, pv_inverter_ids)?.build()
5252
}
5353

54+
/// Returns a string with the coalesce formula for the given component IDs.
55+
///
56+
/// This formula uses the `COALESCE` function to return the first non-null
57+
/// value from the components with the provided IDs.
58+
pub fn coalesce(&self, component_ids: BTreeSet<u64>) -> Result<String, Error> {
59+
generators::generic::CoalesceFormulaBuilder::try_new(self, component_ids)?.build()
60+
}
61+
5462
/// Returns a string representing the EV charger formula for the graph.
5563
pub fn ev_charger_formula(
5664
&self,

src/graph/formulas/generators.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub(super) mod battery;
77
pub(super) mod chp;
88
pub(super) mod consumer;
99
pub(super) mod ev_charger;
10+
pub(super) mod generic;
1011
pub(super) mod grid;
1112
pub(super) mod producer;
1213
pub(super) mod pv;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// License: MIT
2+
// Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
//! This module contains the methods for generating generic formulas.
5+
6+
mod coalesce;
7+
8+
pub(crate) use coalesce::CoalesceFormulaBuilder;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// License: MIT
2+
// Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
//! This module contains the methods for generating `COALESCE` formulas for
5+
//! measuring metrics from individual components, with fallback to other
6+
//! components.
7+
8+
use std::collections::BTreeSet;
9+
10+
use crate::{graph::formulas::expr::Expr, ComponentGraph, Edge, Error, Node};
11+
12+
pub(crate) struct CoalesceFormulaBuilder {
13+
component_ids: BTreeSet<u64>,
14+
}
15+
16+
impl CoalesceFormulaBuilder {
17+
pub fn try_new(
18+
graph: &ComponentGraph<impl Node, impl Edge>,
19+
component_ids: BTreeSet<u64>,
20+
) -> Result<Self, Error> {
21+
if component_ids.is_empty() {
22+
return Err(Error::missing_parameters("No component IDs specified."));
23+
}
24+
for component_id in &component_ids {
25+
if graph.component(*component_id).is_err() {
26+
return Err(Error::component_not_found(format!(
27+
"Component with ID {} not found in the graph.",
28+
component_id
29+
)));
30+
}
31+
}
32+
Ok(Self { component_ids })
33+
}
34+
35+
/// Generates a formula that uses the `COALESCE` function to return the first
36+
/// non-null value from the provided component IDs.
37+
pub fn build(self) -> Result<String, Error> {
38+
let expr = Expr::coalesce(
39+
self.component_ids
40+
.into_iter()
41+
.map(|component_id| Expr::Component { component_id })
42+
.collect(),
43+
);
44+
Ok(expr.to_string())
45+
}
46+
}
47+
48+
#[cfg(test)]
49+
mod tests {
50+
use super::*;
51+
use crate::graph::test_utils::ComponentGraphBuilder;
52+
53+
#[test]
54+
fn test_coalesce_formula() -> Result<(), Error> {
55+
let mut builder = ComponentGraphBuilder::new();
56+
let grid = builder.grid();
57+
58+
// Add a grid meter and a battery chain behind it.
59+
let grid_meter_1 = builder.meter();
60+
builder.connect(grid, grid_meter_1);
61+
let grid_meter_2 = builder.meter();
62+
builder.connect(grid, grid_meter_2);
63+
64+
let graph = builder.build(None)?;
65+
let formula = graph.coalesce(BTreeSet::from([1, 2]))?;
66+
assert_eq!(formula, "COALESCE(#1, #2)");
67+
let formula = graph.coalesce(BTreeSet::from([1]))?;
68+
assert_eq!(formula, "COALESCE(#1)");
69+
let formula = graph.coalesce(BTreeSet::from([]));
70+
assert_eq!(
71+
formula,
72+
Err(Error::missing_parameters("No component IDs specified."))
73+
);
74+
75+
Ok(())
76+
}
77+
}

0 commit comments

Comments
 (0)