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:
+14
-5
@@ -239,6 +239,15 @@ impl App {
|
||||
/// Rebuild the grid layout from current model, view, and drill state.
|
||||
/// Note: `with_frozen_records` already handles pruning internally.
|
||||
pub fn rebuild_layout(&mut self) {
|
||||
// Gather none_cats before mutable borrow for formula recomputation
|
||||
let none_cats: Vec<String> = self
|
||||
.model
|
||||
.active_view()
|
||||
.categories_on(crate::view::Axis::None)
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect();
|
||||
self.model.recompute_formulas(&none_cats);
|
||||
let view = self.model.active_view();
|
||||
let frozen = self.drill_state.as_ref().map(|s| Rc::clone(&s.records));
|
||||
self.layout = GridLayout::with_frozen_records(&self.model, view, frozen);
|
||||
@@ -314,13 +323,13 @@ impl App {
|
||||
}
|
||||
|
||||
/// True when the model has no user-defined categories (show welcome/help).
|
||||
/// Virtual categories (_Index, _Dim) are always present and don't count.
|
||||
/// Virtual categories (_Index, _Dim, _Measure) are always present and don't count.
|
||||
pub fn is_empty_model(&self) -> bool {
|
||||
use crate::model::category::CategoryKind;
|
||||
self.model.categories.values().all(|c| {
|
||||
matches!(
|
||||
c.kind,
|
||||
CategoryKind::VirtualIndex | CategoryKind::VirtualDim
|
||||
CategoryKind::VirtualIndex | CategoryKind::VirtualDim | CategoryKind::VirtualMeasure
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -412,8 +421,8 @@ mod tests {
|
||||
app.apply_effects(effects);
|
||||
}
|
||||
|
||||
fn enter_advance_cmd(app: &App) -> crate::command::cmd::EnterAdvance {
|
||||
use crate::command::cmd::CursorState;
|
||||
fn enter_advance_cmd(app: &App) -> crate::command::cmd::navigation::EnterAdvance {
|
||||
use crate::command::cmd::navigation::CursorState;
|
||||
let view = app.model.active_view();
|
||||
let cursor = CursorState {
|
||||
row: view.selected.0,
|
||||
@@ -425,7 +434,7 @@ mod tests {
|
||||
visible_rows: 20,
|
||||
visible_cols: 8,
|
||||
};
|
||||
crate::command::cmd::EnterAdvance { cursor }
|
||||
crate::command::cmd::navigation::EnterAdvance { cursor }
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user