fix(incremental): a new aggregation reaches the stems that carry its measure; ordered evaluation re-runs the passes over a zero (improvise-shbc)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Claude (dbsp-backend)
2026-10-10 16:06:22 -07:00
committed by Edward Langley
parent c11666ff7a
commit 28e9836759
6 changed files with 191 additions and 26 deletions
@@ -292,3 +292,109 @@ fn a_new_record_adds_its_stem_and_a_cleared_one_takes_it_away() {
];
same_model(&wb.model, &none, &edits, Evaluation::Ordered).unwrap();
}
/// Region {East, West}, a Time category folded away, Revenue by quarter: `R0 = East + West`
/// targets Region, so its stems carry the measure but no formula names it.
fn region_target_model() -> Model {
let mut model = Model::new("regions");
for (cat, items) in [("Region", vec!["East", "West"]), ("Time", vec!["Q1", "Q2"])] {
model.add_category(cat).unwrap();
for item in items {
model.category_mut(cat).unwrap().add_item(item);
}
}
model.category_mut("_Measure").unwrap().add_item("Revenue");
for (region, quarter, v) in [
("East", "Q1", 1.0),
("East", "Q2", 3.0),
("West", "Q1", 10.0),
] {
let key = CellKey::new(vec![
("Region".into(), region.into()),
("Time".into(), quarter.into()),
("_Measure".into(), "Revenue".into()),
]);
model.set_cell(key, CellValue::Number(v));
}
model.add_formula(parse_formula("R0 = East + West", "Region").unwrap());
model
}
#[test]
fn a_new_aggregation_reaches_the_stems_of_a_formula_that_targets_another_category() {
let model = region_target_model();
let none = vec!["Time".to_string()];
let edits = [Edit::SetMeasureAgg {
measure: "Revenue".into(),
func: improvise_core::formula::ast::AggFunc::Max,
}];
for by in [Evaluation::Ordered, Evaluation::Plain] {
same_model(&model, &none, &edits, by).unwrap();
}
let mut backend = Incremental::new(model, none);
backend.apply(&edits[0]);
let at = CellKey::new(vec![
("Region".into(), "R0".into()),
("_Measure".into(), "Revenue".into()),
]);
let want = Some(CellValue::Number(13.0));
assert_eq!(
backend.value(&at, Aggregation::OverHidden),
want,
"Max 3 + Max 10"
);
}
#[test]
fn a_new_aggregation_reaches_a_product_targeted_filtered_sum() {
let mut model = Model::new("products");
for (cat, items) in [("Region", vec!["East", "West"]), ("Product", vec!["F0"])] {
model.add_category(cat).unwrap();
for item in items {
model.category_mut(cat).unwrap().add_item(item);
}
}
model.category_mut("_Measure").unwrap().add_item("Cost");
for (region, v) in [("East", 4.0), ("West", 9.0)] {
let key = CellKey::new(vec![
("Region".into(), region.into()),
("Product".into(), "F0".into()),
("_Measure".into(), "Cost".into()),
]);
model.set_cell(key, CellValue::Number(v));
}
model.add_formula(parse_formula("P0 = SUM(F0 WHERE Region = East)", "Product").unwrap());
let none = vec![];
let edits = [Edit::SetMeasureAgg {
measure: "Cost".into(),
func: improvise_core::formula::ast::AggFunc::Min,
}];
for by in [Evaluation::Ordered, Evaluation::Plain] {
same_model(&model, &none, &edits, by).unwrap();
}
}
/// A stored `0.0` at `F1`, then `F3 = F1`, `F4 = F3 ^ -1` and last `F1 = -0`: eager's first
/// pass reads the stored zero, and its later passes call `-0.0` equal to it and stop.
#[test]
fn ordered_evaluation_keeps_the_zero_eager_settles_on() {
let mut model = Model::new("zeros");
model.add_category("Region").unwrap();
model.category_mut("Region").unwrap().add_item("East");
let key = CellKey::new(vec![
("Region".into(), "East".into()),
("_Measure".into(), "F1".into()),
]);
model.set_cell(key, CellValue::Number(0.0));
let edits: Vec<Edit> = [
("F3 = F1", "_Measure"),
("F4 = F3 ^ -1", "_Measure"),
("F1 = -0", "_Measure"),
]
.iter()
.map(|(raw, cat)| Edit::AddFormula(parse_formula(raw, cat).unwrap()))
.collect();
for by in [Evaluation::Ordered, Evaluation::Plain] {
same_model(&model, &[], &edits, by).unwrap_or_else(|e| panic!("{by:?}: {e}"));
}
}