diff --git a/src/persistence/mod.rs b/src/persistence/mod.rs index 9add417..c590703 100644 --- a/src/persistence/mod.rs +++ b/src/persistence/mod.rs @@ -205,16 +205,38 @@ pub fn format_md(model: &Model) -> String { if !model.formulas().is_empty() { w!(out, "\n## Formulas"); for f in model.formulas() { - w!(out, "- {} [{}]", f.raw, f.target_category); + if f.target_category == "_Measure" { + w!(out, "- {}", f.raw); + } else { + w!(out, "- {} [{}]", f.raw, f.target_category); + } } } // ── Categories (items comma-separated on one line) ─────────────── + // Collect formula targets so we can exclude them from _Measure items + let formula_targets: std::collections::HashSet<&str> = model + .formulas() + .iter() + .filter(|f| f.target_category == "_Measure") + .map(|f| f.target.as_str()) + .collect(); + for cat in model.categories.values() { + use crate::model::category::CategoryKind; + // Skip _Index and _Dim — they are fully virtual, never persisted + if matches!(cat.kind, CategoryKind::VirtualIndex | CategoryKind::VirtualDim) { + continue; + } w!(out, "\n## Category: {}", cat.name); let mut bare: Vec = Vec::new(); let mut grouped: Vec = Vec::new(); for item in cat.items.values() { + // For _Measure, skip items that are formula targets + // (they'll be recreated from the ## Formulas section) + if cat.kind == CategoryKind::VirtualMeasure && formula_targets.contains(item.name.as_str()) { + continue; + } match &item.group { Some(g) => grouped.push(format!("{}[{}]", quote_name(&item.name), quote_name(g))), None => bare.push(quote_name(&item.name)), @@ -372,8 +394,13 @@ pub fn parse_md(text: &str) -> Result { raw[..i].to_string(), raw[i + 2..raw.len() - 1].to_string(), )); + continue; } } + // No [Category] suffix — default to _Measure + if !raw.is_empty() && raw.contains('=') { + formulas.push((raw, "_Measure".to_string())); + } } } }