Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ ErrorKind!(
(Internal, internal),
(InvalidComponent, invalid_component),
(InvalidConnection, invalid_connection),
(InvalidGraph, invalid_graph)
(InvalidGraph, invalid_graph),
(MissingParameters, missing_parameters)
);

/// An error that can occur during the creation or traversal of a
Expand Down
8 changes: 8 additions & 0 deletions src/graph/formulas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ where
generators::pv::PVFormulaBuilder::try_new(self, pv_inverter_ids)?.build()
}

/// Returns a string with the coalesce formula for the given component IDs.
///
/// This formula uses the `COALESCE` function to return the first non-null
/// value from the components with the provided IDs.
pub fn coalesce(&self, component_ids: BTreeSet<u64>) -> Result<String, Error> {
generators::generic::CoalesceFormulaBuilder::try_new(self, component_ids)?.build()
}

/// Returns a string representing the EV charger formula for the graph.
pub fn ev_charger_formula(
&self,
Expand Down
1 change: 1 addition & 0 deletions src/graph/formulas/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub(super) mod battery;
pub(super) mod chp;
pub(super) mod consumer;
pub(super) mod ev_charger;
pub(super) mod generic;
pub(super) mod grid;
pub(super) mod producer;
pub(super) mod pv;
8 changes: 8 additions & 0 deletions src/graph/formulas/generators/generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// License: MIT
// Copyright © 2025 Frequenz Energy-as-a-Service GmbH

//! This module contains the methods for generating generic formulas.

mod coalesce;

pub(crate) use coalesce::CoalesceFormulaBuilder;
77 changes: 77 additions & 0 deletions src/graph/formulas/generators/generic/coalesce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// License: MIT
// Copyright © 2025 Frequenz Energy-as-a-Service GmbH

//! This module contains the methods for generating `COALESCE` formulas for
//! measuring metrics from individual components, with fallback to other
//! components.

use std::collections::BTreeSet;

use crate::{graph::formulas::expr::Expr, ComponentGraph, Edge, Error, Node};

pub(crate) struct CoalesceFormulaBuilder {
component_ids: BTreeSet<u64>,
}

impl CoalesceFormulaBuilder {
pub fn try_new(
graph: &ComponentGraph<impl Node, impl Edge>,
component_ids: BTreeSet<u64>,
) -> Result<Self, Error> {
if component_ids.is_empty() {
return Err(Error::missing_parameters("No component IDs specified."));
}
for component_id in &component_ids {
if graph.component(*component_id).is_err() {
return Err(Error::component_not_found(format!(
"Component with ID {} not found in the graph.",
component_id
)));
}
}
Ok(Self { component_ids })
}

/// Generates a formula that uses the `COALESCE` function to return the first
/// non-null value from the provided component IDs.
pub fn build(self) -> Result<String, Error> {
let expr = Expr::coalesce(
self.component_ids
.into_iter()
.map(|component_id| Expr::Component { component_id })
.collect(),
);
Ok(expr.to_string())
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::graph::test_utils::ComponentGraphBuilder;

#[test]
fn test_coalesce_formula() -> Result<(), Error> {
let mut builder = ComponentGraphBuilder::new();
let grid = builder.grid();

// Add a grid meter and a battery chain behind it.
let grid_meter_1 = builder.meter();
builder.connect(grid, grid_meter_1);
let grid_meter_2 = builder.meter();
builder.connect(grid, grid_meter_2);

let graph = builder.build(None)?;
let formula = graph.coalesce(BTreeSet::from([1, 2]))?;
assert_eq!(formula, "COALESCE(#1, #2)");
let formula = graph.coalesce(BTreeSet::from([1]))?;
assert_eq!(formula, "COALESCE(#1)");
let formula = graph.coalesce(BTreeSet::from([]));
assert_eq!(
formula,
Err(Error::missing_parameters("No component IDs specified."))
);

Ok(())
}
}
Loading