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