1use crate::computation::{OperationResult, VetoType};
2use crate::evaluation::explanations::Explanation;
3
4use crate::error::Error;
5use crate::evaluation::evaluate_data::EvaluateDataValue;
6use crate::evaluation::rows::{row_result, take_row_context};
7use crate::evaluation::tree::ensure_rule_values;
8use crate::evaluation::EvaluationContext;
9use crate::parsing::ast::DateTimeValue;
10use crate::planning::execution_plan::ExecutionPlan;
11use crate::planning::semantics::{
12 DataDefinition, DataPath, InstanceKey, LemmaType, LiteralValue, ReferenceEnd, RulePath, Source,
13 SpecValue, ValueKind,
14};
15use crate::planning::unit_family::FamilyUnitCatalog;
16use crate::result_value::{
17 rule_result_value_failure_message, rule_result_value_from_literal, RowReference, RowResult,
18 RuleResultValue, RuleResultValueFailure, SpecResult, SpecRuleResult,
19};
20use indexmap::IndexMap;
21use std::collections::HashMap;
22use std::sync::Arc;
23
24#[derive(Debug, Clone)]
27pub struct EvaluatedRule {
28 pub name: String,
29 pub path: RulePath,
30 pub source_location: Source,
31 pub rule_type: Arc<LemmaType>,
32}
33
34#[derive(Debug, Clone)]
36pub struct Response {
37 pub spec_name: String,
38 pub effective: String,
39 pub spec_effective_from: Option<DateTimeValue>,
43 pub spec_effective_to: Option<DateTimeValue>,
44 pub results: IndexMap<String, RuleResult>,
45 pub rows: Vec<RowResult>,
47}
48
49#[derive(Debug, Clone)]
51pub struct RuleResult {
52 pub rule: EvaluatedRule,
53 pub veto_detail: Option<VetoType>,
54
55 pub vetoed: bool,
56 pub veto_reason: Option<String>,
57 pub rule_type: String,
58
59 pub result: Option<RuleResultValue>,
61 pub explanation: Option<Explanation>,
62 missing_data: Vec<String>,
67 missing_data_options: IndexMap<String, Vec<EvaluateDataValue>>,
71}
72
73impl RuleResult {
74 #[must_use]
76 pub fn result(&self) -> Option<&str> {
77 self.result
78 .as_ref()
79 .and_then(|value| value.result.as_deref())
80 }
81
82 #[must_use]
87 pub fn is_missing_data(&self) -> bool {
88 matches!(
89 self.veto_detail.as_ref(),
90 Some(VetoType::MissingData { .. } | VetoType::MissingRowData { .. })
91 )
92 }
93
94 #[must_use]
97 pub fn missing_data(&self) -> &[String] {
98 &self.missing_data
99 }
100
101 #[must_use]
103 pub fn missing_data_options(&self) -> &IndexMap<String, Vec<EvaluateDataValue>> {
104 &self.missing_data_options
105 }
106
107 pub(crate) fn with_missing_data_options(
108 mut self,
109 missing_data_options: IndexMap<String, Vec<EvaluateDataValue>>,
110 ) -> Self {
111 for key in missing_data_options.keys() {
112 if !self.missing_data.iter().any(|listed| listed == key) {
113 panic!(
114 "BUG: missing_data_options key '{key}' is not in missing_data {:?}",
115 self.missing_data
116 );
117 }
118 }
119 self.missing_data_options = missing_data_options;
120 self
121 }
122
123 pub(crate) fn from_operation_result(
127 rule: EvaluatedRule,
128 operation_result: &OperationResult,
129 rule_type: &LemmaType,
130 family_units: &FamilyUnitCatalog,
131 explanation: Option<Explanation>,
132 missing_data: Vec<String>,
133 ) -> Self {
134 Self::from_evaluated(
135 rule,
136 operation_result,
137 rule_type,
138 family_units,
139 explanation,
140 missing_data,
141 None,
142 )
143 }
144
145 pub(crate) fn from_evaluated(
146 rule: EvaluatedRule,
147 operation_result: &OperationResult,
148 rule_type: &LemmaType,
149 family_units: &FamilyUnitCatalog,
150 explanation: Option<Explanation>,
151 missing_data: Vec<String>,
152 runtime_scales: Option<
153 &std::collections::HashMap<String, crate::computation::rational::RationalInteger>,
154 >,
155 ) -> Self {
156 match operation_result {
157 OperationResult::Veto(VetoType::MissingData { data, .. }) => {
158 let key = data.input_key();
159 if !missing_data.iter().any(|listed| listed == &key) {
160 panic!("BUG: MissingData path {key} not in missing_data {missing_data:?}");
161 }
162 }
163 OperationResult::Veto(VetoType::MissingRowData { key }) => {
164 if !missing_data.contains(key) {
165 panic!("BUG: missing row field {key} not in missing_data {missing_data:?}");
166 }
167 }
168 _ => {
169 if !missing_data.is_empty() {
170 panic!(
171 "BUG: missing_data must be empty when result is not MissingData: {missing_data:?}"
172 );
173 }
174 }
175 }
176 let rule_type_name = rule_type.name().to_string();
177 match operation_result {
178 OperationResult::Veto(veto) => Self::vetoed_with(
179 rule,
180 veto.clone(),
181 rule_type_name,
182 explanation,
183 missing_data,
184 IndexMap::new(),
185 ),
186 OperationResult::Value(bound) => {
187 if (rule_type.is_measure() || rule_type.is_measure_range())
188 && !crate::planning::semantics::measure_family_has_expressible_unit(
189 rule_type,
190 runtime_scales,
191 )
192 {
193 return Self::vetoed_with(
194 rule,
195 VetoType::computation(format!(
196 "no conversion factor is known for any unit of {}",
197 rule_type.name()
198 )),
199 rule_type_name,
200 explanation,
201 missing_data,
202 IndexMap::new(),
203 );
204 }
205 match rule_result_value_from_literal(
206 &bound.to_literal(),
207 rule_type,
208 family_units,
209 runtime_scales,
210 ) {
211 Ok(value) => Self {
212 rule,
213 veto_detail: None,
214 vetoed: false,
215 veto_reason: None,
216 rule_type: rule_type_name,
217 result: Some(value),
218 explanation,
219 missing_data,
220 missing_data_options: IndexMap::new(),
221 },
222 Err(failure) => vetoed_rule_result_for_rule_result_value_failure(
223 rule,
224 rule_type,
225 explanation,
226 failure,
227 missing_data,
228 ),
229 }
230 }
231 }
232 }
233
234 fn vetoed_with(
235 rule: EvaluatedRule,
236 veto: VetoType,
237 rule_type: String,
238 explanation: Option<Explanation>,
239 missing_data: Vec<String>,
240 missing_data_options: IndexMap<String, Vec<EvaluateDataValue>>,
241 ) -> Self {
242 Self {
243 rule,
244 veto_reason: match &veto {
245 VetoType::UserDefined { message: None } => None,
246 _ => Some(veto.to_string()),
247 },
248 veto_detail: Some(veto),
249 vetoed: true,
250 rule_type,
251 result: None,
252 explanation,
253 missing_data,
254 missing_data_options,
255 }
256 }
257
258 pub fn to_literal(&self) -> LiteralValue {
262 assert!(
263 !self.vetoed,
264 "BUG: to_literal called on vetoed rule '{}'",
265 self.rule.name
266 );
267 let value = self
268 .result
269 .as_ref()
270 .unwrap_or_else(|| panic!("BUG: non-vetoed rule '{}' missing result", self.rule.name));
271 value.to_literal(&self.rule.rule_type)
272 }
273}
274
275fn vetoed_rule_result_for_rule_result_value_failure(
276 rule: EvaluatedRule,
277 rule_type: &LemmaType,
278 explanation: Option<Explanation>,
279 failure: RuleResultValueFailure,
280 missing_data: Vec<String>,
281) -> RuleResult {
282 RuleResult::vetoed_with(
283 rule,
284 VetoType::computation(rule_result_value_failure_message(failure).to_string()),
285 rule_type.name().to_string(),
286 explanation,
287 missing_data,
288 IndexMap::new(),
289 )
290}
291
292impl Response {
293 pub fn get(&self, rule_name: &str) -> Result<&RuleResult, Error> {
297 self.results
298 .get(rule_name)
299 .ok_or_else(|| crate::error::Error::rule_not_found(rule_name, None::<String>))
300 }
301
302 pub fn add_result(&mut self, result: RuleResult) {
303 self.results.insert(result.rule.name.clone(), result);
304 }
305}
306
307#[derive(Default)]
311pub(crate) struct RowTable {
312 index: HashMap<InstanceKey, usize>,
313 rows: Vec<Option<RowResult>>,
314}
315
316impl RowTable {
317 pub(crate) fn into_rows(self) -> Vec<RowResult> {
318 self.rows
319 .into_iter()
320 .enumerate()
321 .map(|(index, row)| row.unwrap_or_else(|| panic!("BUG: row {index} never filled")))
322 .collect()
323 }
324}
325
326pub(crate) fn fill_spec_values(
330 literal: &ValueKind,
331 value: &mut RuleResultValue,
332 plan: &ExecutionPlan,
333 ctx: &mut EvaluationContext,
334 table: &mut RowTable,
335) {
336 if matches!(
337 literal,
338 ValueKind::Number(_)
339 | ValueKind::Measure(_)
340 | ValueKind::Ratio(_)
341 | ValueKind::Text(_)
342 | ValueKind::Date(_)
343 | ValueKind::Time(_)
344 | ValueKind::Boolean(_)
345 | ValueKind::Range(_, _)
346 ) {
347 return;
348 }
349 let mut walk = Walk {
350 root: plan,
351 base: ctx,
352 table,
353 scopes: Vec::new(),
354 ancestors: Vec::new(),
355 };
356 let root = Frame::fill(literal.clone(), std::mem::take(value));
357 *value = walk.run(root).into_value();
358}
359
360struct Scope<'a> {
362 plan: &'a ExecutionPlan,
363 ctx: EvaluationContext,
364}
365
366struct Walk<'a, 'b> {
370 root: &'a ExecutionPlan,
371 base: &'b mut EvaluationContext,
372 table: &'b mut RowTable,
373 scopes: Vec<Scope<'a>>,
374 ancestors: Vec<InstanceKey>,
375}
376
377impl<'a> Walk<'a, '_> {
378 fn ctx(&mut self) -> &mut EvaluationContext {
379 match self.scopes.last_mut() {
380 Some(scope) => &mut scope.ctx,
381 None => &mut *self.base,
382 }
383 }
384
385 fn owner_scope(&mut self, key: &InstanceKey) -> (&'a ExecutionPlan, &mut EvaluationContext) {
389 let position = key.owner().and_then(|owner| {
390 self.scopes
391 .iter()
392 .rposition(|scope| scope.ctx.owner_row.as_ref() == Some(owner))
393 });
394 match position {
395 Some(position) => {
396 let scope = &mut self.scopes[position];
397 (scope.plan, &mut scope.ctx)
398 }
399 None => (self.root, &mut *self.base),
400 }
401 }
402
403 fn run(&mut self, root: Frame<'a>) -> Output {
404 let mut frames = vec![root];
405 let mut input = None;
406 loop {
407 let frame = frames.last_mut().expect("BUG: fill walk frame stack empty");
408 match frame.resume(self, input.take()) {
409 Step::Call(child) => frames.push(child),
410 Step::Return(output) => {
411 frames.pop();
412 if frames.is_empty() {
413 return output;
414 }
415 input = Some(output);
416 }
417 }
418 }
419 }
420}
421
422enum Frame<'a> {
423 Fill(Box<FillFrame>),
424 Instance(InstanceFrame),
425 Members(Box<MembersFrame<'a>>),
426}
427
428impl<'a> Frame<'a> {
429 fn fill(literal: ValueKind, value: RuleResultValue) -> Self {
430 Frame::Fill(Box::new(FillFrame::new(literal, value)))
431 }
432
433 fn resume(&mut self, walk: &mut Walk<'a, '_>, input: Option<Output>) -> Step<'a> {
434 match self {
435 Frame::Fill(frame) => frame.resume(walk, input),
436 Frame::Instance(frame) => frame.resume(walk, input),
437 Frame::Members(frame) => frame.resume(walk, input),
438 }
439 }
440}
441
442enum Step<'a> {
443 Call(Frame<'a>),
444 Return(Output),
445}
446
447enum Output {
448 Value(Box<RuleResultValue>),
449 Spec(Box<SpecResult>),
450 Row(RowReference),
451 Members(FilledMembers),
452}
453
454impl Output {
455 fn into_value(self) -> RuleResultValue {
456 match self {
457 Output::Value(value) => *value,
458 _ => panic!("BUG: fill walk expected a value output"),
459 }
460 }
461
462 fn into_spec(self) -> SpecResult {
463 match self {
464 Output::Spec(spec) => *spec,
465 _ => panic!("BUG: fill walk expected a spec output"),
466 }
467 }
468
469 fn into_row(self) -> RowReference {
470 match self {
471 Output::Row(row) => row,
472 _ => panic!("BUG: fill walk expected a row output"),
473 }
474 }
475
476 fn into_members(self) -> FilledMembers {
477 match self {
478 Output::Members(members) => members,
479 _ => panic!("BUG: fill walk expected a members output"),
480 }
481 }
482}
483
484fn child_output(input: Option<Output>) -> Output {
485 input.expect("BUG: fill walk frame resumed without child output")
486}
487
488enum FillState {
489 Start(ValueKind),
490 AwaitSpec,
491 AwaitRow(Option<String>),
493 Items(
494 std::vec::IntoIter<(ValueKind, RuleResultValue)>,
495 Vec<RuleResultValue>,
496 ),
497}
498
499struct FillFrame {
502 value: RuleResultValue,
503 state: Option<FillState>,
504}
505
506impl FillFrame {
507 fn new(literal: ValueKind, value: RuleResultValue) -> Self {
508 Self {
509 value,
510 state: Some(FillState::Start(literal)),
511 }
512 }
513
514 fn resume<'a>(&mut self, walk: &mut Walk<'a, '_>, input: Option<Output>) -> Step<'a> {
515 match self
516 .state
517 .take()
518 .expect("BUG: fill frame resumed after completion")
519 {
520 FillState::Start(literal) => {
521 assert!(
522 input.is_none(),
523 "BUG: fill frame started with a child output"
524 );
525 self.start(walk, literal)
526 }
527 FillState::AwaitSpec => {
528 self.value.spec = Some(Box::new(child_output(input).into_spec()));
529 self.finish()
530 }
531 FillState::AwaitRow(instance) => {
532 let mut reference = child_output(input).into_row();
533 reference.instance = instance;
534 self.value.row = Some(reference);
535 self.finish()
536 }
537 FillState::Items(items, mut filled) => {
538 filled.push(child_output(input).into_value());
539 self.next_item(items, filled)
540 }
541 }
542 }
543
544 fn start<'a>(&mut self, walk: &mut Walk<'a, '_>, literal: ValueKind) -> Step<'a> {
545 match literal {
546 ValueKind::Spec(SpecValue::Row(key)) => {
547 if key.is_planned() {
548 assert!(
549 self.value.spec.is_some(),
550 "BUG: planned instance '{key}' without spec arm"
551 );
552 if walk.ancestors.contains(&key) {
553 panic!("BUG: spec result walked instance '{key}' twice");
554 }
555 self.state = Some(FillState::AwaitSpec);
556 return Step::Call(Frame::Instance(InstanceFrame::new(key, Publication::Spec)));
557 }
558 assert!(self.value.row.is_none(), "BUG: row '{key}' filled twice");
559 let (table, instance) = key.table_row();
560 let table = table.clone();
561 self.state = Some(FillState::AwaitRow(instance));
562 Step::Call(Frame::Instance(InstanceFrame::new(
563 table,
564 Publication::Table,
565 )))
566 }
567 ValueKind::List(rows) => {
568 let entries = self
569 .value
570 .list
571 .take()
572 .expect("BUG: list value without list arm");
573 assert_eq!(
574 entries.len(),
575 rows.len(),
576 "BUG: list arm row count differs from the list value"
577 );
578 let filled = Vec::with_capacity(entries.len());
579 let items: Vec<(ValueKind, RuleResultValue)> = rows
580 .iter()
581 .map(|row| row.value.clone())
582 .zip(entries)
583 .collect();
584 self.next_item(items.into_iter(), filled)
585 }
586 ValueKind::Number(_)
587 | ValueKind::Measure(_)
588 | ValueKind::UnscaledMeasure { .. }
589 | ValueKind::Ratio(_)
590 | ValueKind::Text(_)
591 | ValueKind::Date(_)
592 | ValueKind::Time(_)
593 | ValueKind::Boolean(_)
594 | ValueKind::Range(_, _) => self.finish(),
595 ValueKind::Spec(SpecValue::Fixed(_)) => {
596 unreachable!("BUG: evaluation produces only row spec values")
597 }
598 }
599 }
600
601 fn next_item<'a>(
602 &mut self,
603 mut items: std::vec::IntoIter<(ValueKind, RuleResultValue)>,
604 filled: Vec<RuleResultValue>,
605 ) -> Step<'a> {
606 match items.next() {
607 Some((literal, entry)) => {
608 self.state = Some(FillState::Items(items, filled));
609 Step::Call(Frame::fill(literal, entry))
610 }
611 None => {
612 self.value.list = Some(filled);
613 self.finish()
614 }
615 }
616 }
617
618 fn finish<'a>(&mut self) -> Step<'a> {
619 Step::Return(Output::Value(Box::new(std::mem::take(&mut self.value))))
620 }
621}
622
623#[derive(Clone, Copy)]
625enum Publication {
626 Table,
629 Spec,
631 Rules,
633}
634
635struct InstanceFrame {
638 key: InstanceKey,
639 publication: Publication,
640 index: usize,
641}
642
643impl InstanceFrame {
644 fn new(key: InstanceKey, publication: Publication) -> Self {
645 Self {
646 key,
647 publication,
648 index: 0,
649 }
650 }
651
652 fn reference(&self, index: usize) -> RowReference {
653 RowReference {
654 repository: self.key.row.identity.repository.clone(),
655 spec: self.key.row.identity.spec.clone(),
656 effective: self.key.row.identity.effective.to_string(),
657 instance: self.key.path(),
658 index,
659 }
660 }
661
662 fn resume<'a>(&mut self, walk: &mut Walk<'a, '_>, input: Option<Output>) -> Step<'a> {
663 match input {
664 None => {
665 match self.publication {
666 Publication::Table => {
667 if let Some(index) = walk.table.index.get(&self.key).copied() {
668 return Step::Return(Output::Row(self.reference(index)));
669 }
670 let index = walk.table.rows.len();
671 walk.table.index.insert(self.key.clone(), index);
672 walk.table.rows.push(None);
673 self.index = index;
674 }
675 Publication::Spec => walk.ancestors.push(self.key.clone()),
676 Publication::Rules => {}
677 }
678 let template = walk.root.row_template(&self.key.row.identity);
679 let (owner_plan, owner_ctx) = walk.owner_scope(&self.key);
680 let ctx = take_row_context(&self.key, owner_plan, template, owner_ctx);
681 walk.scopes.push(Scope {
682 plan: template,
683 ctx,
684 });
685 Step::Call(Frame::Members(Box::new(MembersFrame::new(
686 template,
687 self.key.clone(),
688 matches!(self.publication, Publication::Rules),
689 ))))
690 }
691 Some(output) => {
692 let (data, rules) = output.into_members();
693 let scope = walk
694 .scopes
695 .pop()
696 .expect("BUG: instance scope ended before it was entered");
697 walk.ctx()
698 .caches
699 .row_contexts
700 .borrow_mut()
701 .insert(self.key.clone(), scope.ctx);
702 match self.publication {
703 Publication::Table => {
704 walk.table.rows[self.index] = Some(RowResult {
705 repository: self.key.row.identity.repository.clone(),
706 spec: self.key.row.identity.spec.clone(),
707 effective: self.key.row.identity.effective.to_string(),
708 data,
709 rules,
710 });
711 Step::Return(Output::Row(self.reference(self.index)))
712 }
713 Publication::Spec => {
714 let popped = walk.ancestors.pop();
715 assert!(
716 popped.as_ref() == Some(&self.key),
717 "BUG: spec result of '{}' ended on another instance",
718 self.key
719 );
720 let path = self.key.segments().unwrap_or_else(|| {
721 panic!("BUG: planned instance '{}' has no hops", self.key)
722 });
723 Step::Return(Output::Spec(Box::new(SpecResult {
724 repository: self.key.row.identity.repository.clone(),
725 spec: self.key.row.identity.spec.clone(),
726 effective: self.key.row.identity.effective.to_string(),
727 instance: self.key.to_string(),
728 path: path.iter().map(crate::api::PathSegment::from).collect(),
729 data,
730 rules,
731 })))
732 }
733 Publication::Rules => Step::Return(Output::Members((data, rules))),
734 }
735 }
736 }
737 }
738}
739
740enum Awaiting {
743 Data(String, SpecRuleResult),
744 Rule(String, SpecRuleResult),
745 Import(String),
746}
747
748struct MembersFrame<'a> {
752 plan: &'a ExecutionPlan,
753 row: InstanceKey,
754 data_keys: std::vec::IntoIter<(&'a DataPath, &'a DataDefinition)>,
755 rule_paths: std::vec::IntoIter<&'a RulePath>,
756 imports: std::vec::IntoIter<&'a DataPath>,
757 data: IndexMap<String, SpecRuleResult>,
758 rules: IndexMap<String, SpecRuleResult>,
759 awaiting: Option<Awaiting>,
760}
761
762impl<'a> MembersFrame<'a> {
763 fn new(plan: &'a ExecutionPlan, row: InstanceKey, rules_only: bool) -> Self {
764 let data_keys: Vec<(&'a DataPath, &'a DataDefinition)> = if rules_only {
765 Vec::new()
766 } else {
767 plan.input_slots().collect()
768 };
769 let imports: Vec<&'a DataPath> = plan
770 .data
771 .iter()
772 .filter(|(_, definition)| matches!(definition, DataDefinition::Import { .. }))
773 .map(|(path, _)| path)
774 .collect();
775 Self {
776 plan,
777 row,
778 data_keys: data_keys.into_iter(),
779 rule_paths: plan.rules.keys().collect::<Vec<_>>().into_iter(),
780 imports: imports.into_iter(),
781 data: IndexMap::new(),
782 rules: IndexMap::new(),
783 awaiting: None,
784 }
785 }
786
787 fn resume(&mut self, walk: &mut Walk<'a, '_>, input: Option<Output>) -> Step<'a> {
788 match (self.awaiting.take(), input) {
789 (None, None) => {}
790 (Some(Awaiting::Data(key, mut entry)), Some(output)) => {
791 entry.result = Some(output.into_value());
792 self.data.insert(key, entry);
793 }
794 (Some(Awaiting::Rule(key, mut entry)), Some(output)) => {
795 entry.result = Some(output.into_value());
796 self.rules.insert(key, entry);
797 }
798 (Some(Awaiting::Import(alias)), Some(output)) => {
799 let (data, rules) = output.into_members();
800 assert!(
801 data.is_empty(),
802 "BUG: rules of `uses {alias}` were filled with data"
803 );
804 for (key, entry) in rules {
805 self.rules.insert(format!("{alias}.{key}"), entry);
806 }
807 }
808 _ => panic!("BUG: members frame resumed with an unexpected child output"),
809 }
810 while let Some((path, definition)) = self.data_keys.next() {
811 if let Some(step) = self.data_item(walk, path, definition) {
812 return step;
813 }
814 }
815 while let Some(path) = self.rule_paths.next() {
816 if let Some(step) = self.rule_item(walk, path) {
817 return step;
818 }
819 }
820 if let Some(slot) = self.imports.next() {
821 let nested = InstanceKey::new(
822 Arc::clone(self.plan.import_row(Some(&self.row.row), slot, None)),
823 Some(self.row.clone()),
824 );
825 self.awaiting = Some(Awaiting::Import(slot.input_key()));
826 return Step::Call(Frame::Instance(InstanceFrame::new(
827 nested,
828 Publication::Rules,
829 )));
830 }
831 Step::Return(Output::Members((
832 std::mem::take(&mut self.data),
833 std::mem::take(&mut self.rules),
834 )))
835 }
836
837 fn data_item(
838 &mut self,
839 walk: &mut Walk<'a, '_>,
840 path: &'a DataPath,
841 definition: &'a DataDefinition,
842 ) -> Option<Step<'a>> {
843 let plan = self.plan;
844 definition.schema_type()?;
845 let key = path.input_key();
846 let slot = match walk.ctx().data_slot(plan, path).cloned() {
847 Some(slot) => slot,
848 None => match plan.reference_ends.get(path) {
849 Some(ReferenceEnd::Rule(rule)) => {
850 ensure_rule_values(plan.rule_index(rule), plan, walk.ctx());
851 walk.ctx().rule_value(plan, rule).clone()
852 }
853 Some(ReferenceEnd::Promptable(_)) | None => return None,
854 },
855 };
856 if matches!(slot, OperationResult::Veto(VetoType::MissingData { .. })) {
857 return None;
858 }
859 let display_type =
860 crate::evaluation::EvaluationContext::data_display_type(plan, path, &slot);
861 let mut entry = spec_entry(
862 &slot,
863 key.clone(),
864 RulePath {
865 segments: path.segments.clone(),
866 rule: path.data.clone(),
867 },
868 definition.source().clone(),
869 display_type.as_ref(),
870 &plan.family_units,
871 Vec::new(),
872 );
873 let OperationResult::Value(bound) = slot else {
874 self.data.insert(key, entry);
875 return None;
876 };
877 let Some(value) = entry.result.take() else {
878 self.data.insert(key, entry);
879 return None;
880 };
881 self.awaiting = Some(Awaiting::Data(key, entry));
882 Some(Step::Call(Frame::fill(bound.value, value)))
883 }
884
885 fn rule_item(&mut self, walk: &mut Walk<'a, '_>, path: &'a RulePath) -> Option<Step<'a>> {
886 let plan = self.plan;
887 let exec = plan
888 .rules
889 .get(path)
890 .expect("BUG: rule path missing from plan");
891 ensure_rule_values(plan.rule_index(path), plan, walk.ctx());
892 let local = walk.ctx().rule_value(plan, path).clone();
893 let missing_data = if local.is_missing_data() {
894 walk.ctx()
895 .missing_data_for_rule(plan, exec.normal_form, Some(&self.row))
896 } else {
897 Vec::new()
898 };
899 let operation = row_result(local, &self.row, plan);
900 let key = path.input_key();
901 let mut entry = spec_entry(
902 &operation,
903 key.clone(),
904 path.clone(),
905 exec.source.clone(),
906 exec.rule_type.as_ref(),
907 &plan.family_units,
908 missing_data,
909 );
910 match operation {
911 OperationResult::Value(bound) => match entry.result.take() {
912 Some(value) => {
913 self.awaiting = Some(Awaiting::Rule(key, entry));
914 Some(Step::Call(Frame::fill(bound.value, value)))
915 }
916 None => {
917 self.rules.insert(key, entry);
918 None
919 }
920 },
921 OperationResult::Veto(_) => {
922 self.rules.insert(key, entry);
923 None
924 }
925 }
926 }
927}
928
929type FilledMembers = (
930 IndexMap<String, SpecRuleResult>,
931 IndexMap<String, SpecRuleResult>,
932);
933
934fn spec_entry(
935 operation: &OperationResult,
936 name: String,
937 path: RulePath,
938 source: Source,
939 display_type: &LemmaType,
940 family_units: &FamilyUnitCatalog,
941 missing_data: Vec<String>,
942) -> SpecRuleResult {
943 let built = RuleResult::from_operation_result(
944 EvaluatedRule {
945 name,
946 path,
947 source_location: source,
948 rule_type: Arc::new(display_type.clone()),
949 },
950 operation,
951 display_type,
952 family_units,
953 None,
954 missing_data,
955 );
956 SpecRuleResult {
957 vetoed: built.vetoed,
958 veto_reason: built.veto_reason.clone(),
959 rule_type: built.rule_type.clone(),
960 result: built.result.clone(),
961 }
962}
963
964#[cfg(test)]
965mod tests {
966 use super::*;
967 use crate::literals::DateGranularity;
968 use crate::parsing::ast::Span;
969 use crate::planning::semantics::{
970 primitive_number_arc, BaseMeasureVector, DataPath, LemmaType, LiteralValue, MeasureUnit,
971 MeasureUnits, RatioUnit, RatioUnits, RulePath, TypeExtends, TypeSpecification, ValueKind,
972 };
973 use crate::planning::unit_family::FamilyUnitCatalog;
974 use crate::planning::unit_index::{UnitIndex, UnitOwner};
975 use rust_decimal::Decimal;
976 use std::str::FromStr;
977 use std::sync::Arc;
978
979 fn dec(s: &str) -> Decimal {
980 Decimal::from_str(s).expect("BUG: test decimal literal")
981 }
982
983 fn empty_family_catalog() -> FamilyUnitCatalog {
984 FamilyUnitCatalog::default()
985 }
986
987 fn family_catalog_for_index(index: &UnitIndex) -> FamilyUnitCatalog {
988 FamilyUnitCatalog::build(index)
989 }
990
991 fn family_catalog_for_money(money: &LemmaType) -> FamilyUnitCatalog {
992 family_catalog_for_index(&unit_index_for_money(money))
993 }
994
995 fn family_catalog_for_ratio(ratio_type: &LemmaType) -> FamilyUnitCatalog {
996 family_catalog_for_index(&unit_index_for_ratio(ratio_type))
997 }
998
999 fn unit_index_for_money(money: &LemmaType) -> UnitIndex {
1000 let mut index = UnitIndex::new();
1001 let arc = Arc::new(money.clone());
1002 for unit_name in money.measure_unit_names().into_iter().flatten() {
1003 index.insert_owner(
1004 unit_name.to_string(),
1005 UnitOwner::Named {
1006 import_path: Vec::new(),
1007 type_name: "money".into(),
1008 lemma_type: Arc::clone(&arc),
1009 },
1010 );
1011 }
1012 index
1013 }
1014
1015 fn unit_index_for_ratio(ratio_type: &LemmaType) -> UnitIndex {
1016 let mut index = UnitIndex::new();
1017 let arc = Arc::new(ratio_type.clone());
1018 let type_name = ratio_type.name();
1019 for unit_name in ratio_type.ratio_unit_names().into_iter().flatten() {
1020 index.insert_owner(
1021 unit_name.to_string(),
1022 UnitOwner::Named {
1023 import_path: Vec::new(),
1024 type_name: type_name.clone(),
1025 lemma_type: Arc::clone(&arc),
1026 },
1027 );
1028 }
1029 index
1030 }
1031
1032 fn dummy_source() -> Source {
1033 Source::new(
1034 crate::parsing::source::SourceType::Volatile,
1035 Span {
1036 start: 0,
1037 end: 0,
1038 line: 1,
1039 col: 1,
1040 },
1041 )
1042 }
1043
1044 fn dummy_evaluated_rule(name: &str, rule_type: &LemmaType) -> EvaluatedRule {
1045 EvaluatedRule {
1046 name: name.to_string(),
1047 path: RulePath::new(vec![], name.to_string()),
1048 source_location: dummy_source(),
1049 rule_type: Arc::new(rule_type.clone()),
1050 }
1051 }
1052
1053 #[test]
1054 fn test_response_serialization() {
1055 let mut results = IndexMap::new();
1056 results.insert(
1057 "test_rule".to_string(),
1058 RuleResult::from_operation_result(
1059 dummy_evaluated_rule("test_rule", primitive_number_arc().as_ref()),
1060 &OperationResult::from_literal(LiteralValue::number_from_decimal(Decimal::from(
1061 42,
1062 ))),
1063 primitive_number_arc().as_ref(),
1064 &empty_family_catalog(),
1065 None,
1066 Vec::new(),
1067 ),
1068 );
1069 let response = Response {
1070 spec_name: "test_spec".to_string(),
1071 effective: "2026-01-01".to_string(),
1072 spec_effective_from: None,
1073 spec_effective_to: None,
1074 results,
1075 rows: Vec::new(),
1076 };
1077
1078 let json = serde_json::to_string(&crate::api::Response::from(&response)).unwrap();
1079 assert!(json.contains("test_spec"));
1080 assert!(json.contains("test_rule"));
1081 assert!(json.contains("\"number\":\"42\""));
1082 assert!(!json.contains("lemma_type"));
1083 }
1084
1085 #[test]
1086 fn response_number_json_never_uses_fraction_notation() {
1087 use crate::computation::rational::decimal_to_rational;
1088
1089 let rational = decimal_to_rational(Decimal::new(1, 1) / Decimal::new(3, 1)).unwrap();
1090 let decimal_string = rational.try_to_decimal().unwrap().to_string();
1091 let mut results = IndexMap::new();
1092 results.insert(
1093 "third".to_string(),
1094 RuleResult::from_operation_result(
1095 dummy_evaluated_rule("third", primitive_number_arc().as_ref()),
1096 &OperationResult::from_literal(LiteralValue::number_from_decimal(
1097 rational.try_to_decimal().unwrap(),
1098 )),
1099 primitive_number_arc().as_ref(),
1100 &empty_family_catalog(),
1101 None,
1102 Vec::new(),
1103 ),
1104 );
1105 if let Some(rule) = results.get_mut("third") {
1107 let decimal = rational.try_to_decimal().unwrap();
1108 rule.result = Some(crate::result_value::RuleResultValue {
1109 result: Some(decimal_string.clone()),
1110 number: Some(decimal),
1111 ..Default::default()
1112 });
1113 }
1114
1115 let response = Response {
1116 spec_name: "test".to_string(),
1117 effective: "test".to_string(),
1118 spec_effective_from: None,
1119 spec_effective_to: None,
1120 results,
1121 rows: Vec::new(),
1122 };
1123
1124 let json: serde_json::Value = serde_json::from_str(
1125 &serde_json::to_string(&crate::api::Response::from(&response)).unwrap(),
1126 )
1127 .unwrap();
1128 let number = json["results"]["third"]["number"]
1129 .as_str()
1130 .expect("number must be a JSON string");
1131 assert!(
1132 !number.contains('/'),
1133 "API decimal string must not use fraction notation, got {number}"
1134 );
1135 }
1136
1137 #[test]
1138 fn test_rule_result_veto() {
1139 let missing = RuleResult::from_operation_result(
1140 dummy_evaluated_rule("rule3", &LemmaType::veto_type()),
1141 &OperationResult::Veto(VetoType::missing_data(
1142 DataPath::new(vec![], "data1".to_string()),
1143 None,
1144 )),
1145 &LemmaType::veto_type(),
1146 &empty_family_catalog(),
1147 None,
1148 vec!["data1".to_string()],
1149 );
1150 assert!(missing.vetoed);
1151 assert!(missing.veto_reason.as_ref().unwrap().contains("data1"));
1152
1153 let veto = RuleResult::from_operation_result(
1154 dummy_evaluated_rule("rule4", &LemmaType::veto_type()),
1155 &OperationResult::Veto(VetoType::UserDefined {
1156 message: Some("Vetoed".to_string()),
1157 }),
1158 &LemmaType::veto_type(),
1159 &empty_family_catalog(),
1160 None,
1161 Vec::new(),
1162 );
1163 assert_eq!(veto.veto_reason.as_deref(), Some("Vetoed"));
1164 }
1165
1166 #[test]
1168 #[should_panic(expected = "BUG: MissingData")]
1169 fn missing_data_veto_must_not_attach_empty_missing_data_list() {
1170 RuleResult::from_operation_result(
1171 dummy_evaluated_rule("rule3", &LemmaType::veto_type()),
1172 &OperationResult::Veto(VetoType::missing_data(
1173 DataPath::new(vec![], "data1".to_string()),
1174 None,
1175 )),
1176 &LemmaType::veto_type(),
1177 &empty_family_catalog(),
1178 None,
1179 Vec::new(),
1180 );
1181 }
1182
1183 #[test]
1184 #[should_panic(expected = "BUG: MissingData")]
1185 fn missing_data_veto_must_include_veto_path_in_list() {
1186 RuleResult::from_operation_result(
1187 dummy_evaluated_rule("rule3", &LemmaType::veto_type()),
1188 &OperationResult::Veto(VetoType::missing_data(
1189 DataPath::new(vec![], "data1".to_string()),
1190 None,
1191 )),
1192 &LemmaType::veto_type(),
1193 &empty_family_catalog(),
1194 None,
1195 vec!["other".to_string()],
1196 );
1197 }
1198
1199 #[test]
1200 #[should_panic(expected = "BUG: missing_data must be empty")]
1201 fn non_missing_data_result_must_not_attach_leftover_missing_data() {
1202 RuleResult::from_operation_result(
1203 dummy_evaluated_rule("rule4", &LemmaType::veto_type()),
1204 &OperationResult::Veto(VetoType::UserDefined {
1205 message: Some("Vetoed".to_string()),
1206 }),
1207 &LemmaType::veto_type(),
1208 &empty_family_catalog(),
1209 None,
1210 vec!["leftover".to_string()],
1211 );
1212 }
1213
1214 #[test]
1215 fn rule_result_value_out_of_memory_is_not_decimal_limit_veto() {
1216 let result = vetoed_rule_result_for_rule_result_value_failure(
1217 dummy_evaluated_rule("rule", primitive_number_arc().as_ref()),
1218 primitive_number_arc().as_ref(),
1219 None,
1220 RuleResultValueFailure::OutOfMemory,
1221 Vec::new(),
1222 );
1223 assert_eq!(
1224 result.veto_reason.as_deref(),
1225 Some("The calculation ran out of memory.")
1226 );
1227 assert_ne!(
1228 result.veto_reason.as_deref(),
1229 Some("The calculated result exceeds the decimal value limit.")
1230 );
1231 }
1232
1233 #[test]
1234 fn rule_result_value_decimal_limit_uses_commit_message() {
1235 let result = vetoed_rule_result_for_rule_result_value_failure(
1236 dummy_evaluated_rule("rule", primitive_number_arc().as_ref()),
1237 primitive_number_arc().as_ref(),
1238 None,
1239 RuleResultValueFailure::DecimalLimit,
1240 Vec::new(),
1241 );
1242 assert_eq!(
1243 result.veto_reason.as_deref(),
1244 Some("The calculated result exceeds the decimal value limit.")
1245 );
1246 }
1247
1248 fn test_money_type() -> LemmaType {
1249 LemmaType::new(
1250 "money".to_string(),
1251 TypeSpecification::Measure {
1252 minimum: None,
1253 maximum: None,
1254 units: MeasureUnits::from(vec![
1255 MeasureUnit {
1256 name: "eur".to_string(),
1257 scale: crate::literals::UnitScale::Literal(
1258 crate::computation::rational::rational_one(),
1259 ),
1260 derived_measure_factors: Vec::new(),
1261 decomposition: BaseMeasureVector::new(),
1262 minimum: None,
1263 maximum: None,
1264 suggestion_magnitude: None,
1265 },
1266 MeasureUnit {
1267 name: "usd".to_string(),
1268 scale: crate::literals::UnitScale::Literal(
1269 crate::computation::rational::decimal_to_rational(Decimal::new(91, 2))
1270 .expect("factor"),
1271 ),
1272 derived_measure_factors: Vec::new(),
1273 decomposition: BaseMeasureVector::new(),
1274 minimum: None,
1275 maximum: None,
1276 suggestion_magnitude: None,
1277 },
1278 ]),
1279 traits: Vec::new(),
1280 decomposition: Some(BaseMeasureVector::new()),
1281 unit: None,
1282 help: String::new(),
1283 },
1284 TypeExtends::Primitive,
1285 )
1286 }
1287
1288 #[test]
1289 fn measure_rule_result_value_uses_rule_type_when_expression_index_empty() {
1290 let money = test_money_type();
1291 let family_units = family_catalog_for_money(&money);
1292 let ten_usd = LiteralValue {
1293 value: ValueKind::Measure(
1294 crate::computation::rational::checked_mul(
1295 &crate::computation::rational::decimal_to_rational(Decimal::from(10))
1296 .expect("ten"),
1297 &crate::computation::rational::decimal_to_rational(Decimal::new(91, 2))
1298 .expect("usd factor"),
1299 )
1300 .expect("canonical usd"),
1301 ),
1302 };
1303 let result = RuleResult::from_operation_result(
1304 dummy_evaluated_rule("total", &money),
1305 &OperationResult::from_literal(ten_usd),
1306 &money,
1307 &family_units,
1308 None,
1309 Vec::new(),
1310 );
1311 let measure = result
1312 .result
1313 .as_ref()
1314 .expect("value")
1315 .measure
1316 .clone()
1317 .expect("measure map");
1318 assert_eq!(measure.get("usd"), Some(&dec("10.00")));
1319 assert!(measure.contains_key("eur"));
1320 }
1321
1322 #[test]
1323 fn test_measure_rule_result_value_multi_unit() {
1324 let money = test_money_type();
1325 let family_units = family_catalog_for_money(&money);
1326 let ten_eur = LiteralValue {
1327 value: ValueKind::Measure(
1328 crate::computation::rational::decimal_to_rational(Decimal::from(10)).expect("ten"),
1329 ),
1330 };
1331 let result = RuleResult::from_operation_result(
1332 dummy_evaluated_rule("total", &money),
1333 &OperationResult::from_literal(ten_eur),
1334 &money,
1335 &family_units,
1336 None,
1337 Vec::new(),
1338 );
1339 let measure = result
1340 .result
1341 .as_ref()
1342 .expect("value")
1343 .measure
1344 .clone()
1345 .expect("measure map");
1346 assert_eq!(measure.get("eur"), Some(&dec("10.00")));
1347 assert_eq!(
1348 measure.get("usd"),
1349 Some(&dec("10.98901098901098901098901098"))
1350 );
1351 }
1352
1353 #[test]
1354 fn measure_rule_result_value_keeps_unit_conversion_unrounded() {
1355 let money = LemmaType::new(
1356 "money".to_string(),
1357 TypeSpecification::Measure {
1358 minimum: None,
1359 maximum: None,
1360 units: MeasureUnits::from(vec![
1361 MeasureUnit {
1362 name: "eur".to_string(),
1363 scale: crate::literals::UnitScale::Literal(
1364 crate::computation::rational::rational_one(),
1365 ),
1366 derived_measure_factors: Vec::new(),
1367 decomposition: BaseMeasureVector::new(),
1368 minimum: None,
1369 maximum: None,
1370 suggestion_magnitude: None,
1371 },
1372 MeasureUnit {
1373 name: "usd".to_string(),
1374 scale: crate::literals::UnitScale::Literal(
1375 crate::computation::rational::decimal_to_rational(Decimal::new(84, 2))
1376 .expect("usd factor"),
1377 ),
1378 derived_measure_factors: Vec::new(),
1379 decomposition: BaseMeasureVector::new(),
1380 minimum: None,
1381 maximum: None,
1382 suggestion_magnitude: None,
1383 },
1384 ]),
1385 traits: Vec::new(),
1386 decomposition: Some(BaseMeasureVector::new()),
1387 unit: None,
1388 help: String::new(),
1389 },
1390 TypeExtends::Primitive,
1391 );
1392 let family_units = family_catalog_for_money(&money);
1393 let three_twelve_eur = LiteralValue {
1394 value: ValueKind::Measure(
1395 crate::computation::rational::decimal_to_rational(Decimal::new(312, 2))
1396 .expect("3.12 eur canonical"),
1397 ),
1398 };
1399 let result = RuleResult::from_operation_result(
1400 dummy_evaluated_rule("delivery_cost", &money),
1401 &OperationResult::from_literal(three_twelve_eur),
1402 &money,
1403 &family_units,
1404 None,
1405 Vec::new(),
1406 );
1407 let measure = result
1408 .result
1409 .as_ref()
1410 .expect("value")
1411 .measure
1412 .clone()
1413 .expect("measure map");
1414 assert_eq!(measure.get("eur"), Some(&dec("3.12")));
1415 assert_eq!(
1416 measure.get("usd"),
1417 Some(&dec("3.714285714285714285714285714"))
1418 );
1419 }
1420
1421 #[test]
1422 fn test_ratio_rule_result_value_multi_unit() {
1423 let ratio_type = LemmaType::new(
1424 "rate".to_string(),
1425 TypeSpecification::Ratio {
1426 minimum: None,
1427 maximum: None,
1428 units: RatioUnits::from(vec![
1429 RatioUnit {
1430 name: "percent".to_string(),
1431 value: crate::computation::rational::decimal_to_rational(Decimal::from(
1432 100,
1433 ))
1434 .expect("percent"),
1435 minimum: None,
1436 maximum: None,
1437 suggestion_magnitude: None,
1438 },
1439 RatioUnit {
1440 name: "basis_points".to_string(),
1441 value: crate::computation::rational::decimal_to_rational(Decimal::from(
1442 10_000,
1443 ))
1444 .expect("bp"),
1445 minimum: None,
1446 maximum: None,
1447 suggestion_magnitude: None,
1448 },
1449 ]),
1450 unit: None,
1451 help: String::new(),
1452 },
1453 TypeExtends::Primitive,
1454 );
1455 let family_units = family_catalog_for_ratio(&ratio_type);
1456 let half = crate::computation::rational::rational_new(1, 2);
1457 let lit = LiteralValue {
1458 value: ValueKind::Ratio(half),
1459 };
1460 let result = RuleResult::from_operation_result(
1461 dummy_evaluated_rule("rate_out", &ratio_type),
1462 &OperationResult::from_literal(lit),
1463 &ratio_type,
1464 &family_units,
1465 None,
1466 Vec::new(),
1467 );
1468 let ratio = result
1469 .result
1470 .as_ref()
1471 .expect("value")
1472 .ratio
1473 .clone()
1474 .expect("ratio map");
1475 assert_eq!(ratio.get("percent"), Some(&dec("50")));
1476 assert_eq!(ratio.get("basis_points"), Some(&dec("5000")));
1477 }
1478
1479 #[test]
1480 fn test_measure_rule_result_value_cross_spec_import() {
1481 use crate::parsing::source::SourceType;
1482 use crate::Engine;
1483
1484 let mut engine = Engine::new();
1485 engine
1486 .load([(
1487 SourceType::Path(std::sync::Arc::new(std::path::PathBuf::from("t.lemma"))),
1488 r#"
1489spec consumer 2025-01-01
1490uses d: dep 2025-10-01
1491rule out: d.doubled
1492
1493spec dep 2025-01-01
1494uses c: child 2025-06-01
1495data money: c.money
1496data p: 5 usd
1497rule doubled: p * 2
1498
1499spec child 2025-01-01
1500data money: measure
1501 -> unit eur: 1.00
1502
1503spec child 2025-06-01
1504data money: measure
1505 -> unit eur: 1.00
1506 -> unit usd: 0.91
1507"#
1508 .to_string(),
1509 )])
1510 .expect("load");
1511 let effective = crate::literals::DateTimeValue {
1512 year: 2025,
1513 month: 3,
1514 day: 1,
1515 hour: 0,
1516 minute: 0,
1517 second: 0,
1518 microsecond: 0,
1519 timezone: None,
1520
1521 granularity: DateGranularity::Full,
1522 };
1523 let response = engine
1524 .evaluate(
1525 None,
1526 "consumer",
1527 Some(&effective),
1528 IndexMap::new(),
1529 None,
1530 false,
1531 )
1532 .expect("evaluate");
1533 let out = response.results.get("out").expect("out rule");
1534 assert!(!out.vetoed);
1535 let measure = out
1536 .result
1537 .as_ref()
1538 .expect("value")
1539 .measure
1540 .as_ref()
1541 .expect("measure map");
1542 assert!(measure.contains_key("usd"));
1543 assert!(measure.contains_key("eur"));
1544 }
1545
1546 #[test]
1547 fn to_literal_roundtrips_number() {
1548 let literal = LiteralValue::number_from_decimal(Decimal::from(42));
1549 let rule_result = RuleResult::from_operation_result(
1550 dummy_evaluated_rule("answer", primitive_number_arc().as_ref()),
1551 &OperationResult::from_literal(literal.clone()),
1552 primitive_number_arc().as_ref(),
1553 &empty_family_catalog(),
1554 None,
1555 Vec::new(),
1556 );
1557 assert_eq!(rule_result.to_literal(), literal);
1558 }
1559
1560 #[test]
1561 fn to_literal_roundtrips_measure() {
1562 let money = test_money_type();
1563 let family_units = family_catalog_for_money(&money);
1564 let literal = LiteralValue::measure(crate::computation::rational::rational_new(60, 1));
1565 let rule_result = RuleResult::from_operation_result(
1566 dummy_evaluated_rule("pay", &money),
1567 &OperationResult::from_literal(literal.clone()),
1568 &money,
1569 &family_units,
1570 None,
1571 Vec::new(),
1572 );
1573 assert_eq!(rule_result.to_literal(), literal);
1574 }
1575}