use ifc_model::{EntityId, Model};
use crate::item::CostItem;
use crate::relation::{descendants_of, CostRelationError};
use crate::view::CostView;
#[must_use]
pub fn direct_total(view: &CostView<'_>, item: &CostItem<'_>) -> f64 {
view.values_of(item).iter().filter_map(|v| v.amount()).sum()
}
pub fn rolled_up_total(view: &CostView<'_>, item: &CostItem<'_>) -> Result<f64, CostRelationError> {
let model = view.model();
let mut total = 0.0;
for id in descendants_of(model, item.id())? {
if let Some(entity) = model.get(id) {
total += direct_total(view, &CostItem::new(id, entity));
}
}
Ok(total)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Consistency {
pub direct: f64,
pub rolled_up: f64,
pub states_own_value: bool,
pub has_children: bool,
}
impl Consistency {
#[must_use]
pub fn agrees(&self, tolerance: f64) -> bool {
if !self.has_children || !self.states_own_value {
return true;
}
(self.direct - self.rolled_up).abs() <= tolerance
}
}
pub fn consistency(
view: &CostView<'_>,
item: &CostItem<'_>,
) -> Result<Consistency, CostRelationError> {
let direct = direct_total(view, item);
let rolled_up = rolled_up_total(view, item)?;
Ok(Consistency {
direct,
rolled_up,
states_own_value: !view.values_of(item).is_empty(),
has_children: !crate::relation::children_of(view.model(), item.id()).is_empty(),
})
}
#[must_use]
pub fn grand_total(view: &CostView<'_>) -> f64 {
view.items().map(|item| direct_total(view, &item)).sum()
}
#[must_use]
pub fn roots(view: &CostView<'_>) -> Vec<EntityId> {
let model: &Model = view.model();
view.items()
.map(|item| item.id())
.filter(|id| crate::relation::parent_of(model, *id).is_none())
.collect()
}