feat: rename Measure to _Measure (virtual), add fixed-point formula eval

_Measure is now a virtual category (CategoryKind::VirtualMeasure),
created automatically in Model::new() alongside _Index and _Dim.
Formula targets are added as items automatically by add_formula.

Formula evaluation uses a fixed-point cache: recompute_formulas()
iterates evaluation of all formula cells until values stabilize,
resolving refs through the cache for formula values and raw data
aggregation for non-formula values. This fixes formulas that
reference other measures when hidden dimensions are present.

evaluate_aggregated now checks the formula cache instead of
recursively evaluating formulas, breaking the dependency between
formula evaluation and aggregation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Edward Langley
2026-04-09 02:39:06 -07:00
parent fd69126cdc
commit 23a876d556
6 changed files with 465 additions and 210 deletions

View File

@ -578,21 +578,21 @@ mod tests {
fn records_model() -> Model {
let mut m = Model::new("T");
m.add_category("Region").unwrap();
m.add_category("Measure").unwrap();
m.add_category("_Measure").unwrap();
m.category_mut("Region").unwrap().add_item("North");
m.category_mut("Measure").unwrap().add_item("Revenue");
m.category_mut("Measure").unwrap().add_item("Cost");
m.category_mut("_Measure").unwrap().add_item("Revenue");
m.category_mut("_Measure").unwrap().add_item("Cost");
m.set_cell(
CellKey::new(vec![
("Region".into(), "North".into()),
("Measure".into(), "Revenue".into()),
("_Measure".into(), "Revenue".into()),
]),
CellValue::Number(100.0),
);
m.set_cell(
CellKey::new(vec![
("Region".into(), "North".into()),
("Measure".into(), "Cost".into()),
("_Measure".into(), "Cost".into()),
]),
CellValue::Number(50.0),
);