1use crate::computation::rational::{
14 checked_div, checked_mul, decimal_to_display_str, NumericFailure,
15};
16use crate::literals::rational_from_parsed_decimal;
17use crate::planning::semantics::{
18 format_decimal_for_api, range_element_type_specification, ratio_element_type_for_api,
19 semantic_calendar_unit_from_measure_type, LemmaType, LiteralUnitMapFailure, LiteralValue,
20 SemanticDateTime, SemanticTime, SpecInstance, SpecValue, TypeSpecification, TypedLiteral,
21 UnitFactorSource, ValueKind,
22};
23use crate::planning::unit_family::{declared_bare_names_only, FamilyUnitCatalog, FamilyUnitEntry};
24use indexmap::IndexMap;
25use rust_decimal::Decimal;
26use serde::{Deserialize, Serialize};
27use std::borrow::Cow;
28use std::collections::BTreeMap;
29use std::fmt;
30use std::sync::Arc;
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct CalendarResult {
35 pub value: Decimal,
36 pub unit: String,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46pub struct RangeResult {
47 pub from: RuleResultValue,
48 pub to: RuleResultValue,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
61pub struct RuleResultValue {
62 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub result: Option<String>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub measure: Option<BTreeMap<String, Decimal>>,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub ratio: Option<BTreeMap<String, Decimal>>,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub number: Option<Decimal>,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub boolean: Option<bool>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub text: Option<String>,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub date: Option<SemanticDateTime>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub time: Option<SemanticTime>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub calendar: Option<CalendarResult>,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
84 pub unit: Option<String>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub range: Option<Box<RangeResult>>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub spec: Option<Box<SpecResult>>,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub row: Option<RowReference>,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub list: Option<Vec<RuleResultValue>>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub fields: Option<IndexMap<String, RuleResultValue>>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub reference: Option<String>,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
108pub struct SpecResult {
109 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub repository: Option<String>,
111 pub spec: String,
112 pub effective: String,
114 pub instance: String,
116 pub path: Vec<crate::api::PathSegment>,
118 pub data: IndexMap<String, SpecRuleResult>,
122 pub rules: IndexMap<String, SpecRuleResult>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
128pub struct RowReference {
129 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub repository: Option<String>,
131 pub spec: String,
132 pub effective: String,
134 pub instance: String,
136 pub index: usize,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
141pub struct RowResult {
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub repository: Option<String>,
144 pub spec: String,
145 pub effective: String,
147 pub data: IndexMap<String, SpecRuleResult>,
150 pub rules: IndexMap<String, SpecRuleResult>,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
158pub struct SpecRuleResult {
159 pub vetoed: bool,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub veto_reason: Option<String>,
162 pub rule_type: String,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub result: Option<RuleResultValue>,
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum RuleResultValueFailure {
170 DecimalLimit,
171 NumericOverflow,
172 OutOfMemory,
173}
174
175pub(crate) enum UnitExpansion<'a> {
177 Declared,
179 Family(&'a FamilyUnitEntry),
181}
182
183pub fn rule_result_value_failure_message(failure: RuleResultValueFailure) -> &'static str {
185 match failure {
186 RuleResultValueFailure::DecimalLimit => "Calculated result exceeds decimal value limit",
187 RuleResultValueFailure::NumericOverflow => "numeric overflow",
188 RuleResultValueFailure::OutOfMemory => "out of memory",
189 }
190}
191
192fn map_numeric_to_rule_result_value_failure(failure: NumericFailure) -> RuleResultValueFailure {
193 match failure {
194 NumericFailure::Overflow => RuleResultValueFailure::DecimalLimit,
195 NumericFailure::OutOfMemory => RuleResultValueFailure::OutOfMemory,
196 NumericFailure::DivisionByZero => {
197 panic!(
198 "BUG: decimal commit encountered division by zero while building RuleResultValue"
199 )
200 }
201 NumericFailure::Irrational => {
202 panic!(
203 "BUG: decimal commit encountered irrational result while building RuleResultValue"
204 )
205 }
206 NumericFailure::MagnitudeLimit => {
207 panic!("BUG: decimal commit does not apply the arithmetic magnitude limit")
208 }
209 }
210}
211
212fn map_unit_conversion_failure(failure: NumericFailure) -> RuleResultValueFailure {
213 match failure {
214 NumericFailure::Overflow => RuleResultValueFailure::NumericOverflow,
215 NumericFailure::OutOfMemory => RuleResultValueFailure::OutOfMemory,
216 NumericFailure::DivisionByZero => {
217 panic!("BUG: unit conversion division by zero while building RuleResultValue")
218 }
219 NumericFailure::Irrational => {
220 panic!("BUG: unit conversion irrational while building RuleResultValue")
221 }
222 NumericFailure::MagnitudeLimit => {
223 panic!("BUG: unit conversion does not apply the arithmetic magnitude limit")
224 }
225 }
226}
227
228fn map_literal_unit_map_failure(failure: LiteralUnitMapFailure) -> RuleResultValueFailure {
229 match failure {
230 LiteralUnitMapFailure::Commit(failure) => map_numeric_to_rule_result_value_failure(failure),
231 LiteralUnitMapFailure::UnitConversion(failure) => map_unit_conversion_failure(failure),
232 }
233}
234
235fn expansion_for_type<'a>(
236 rule_type: &LemmaType,
237 catalog: Option<&'a FamilyUnitCatalog>,
238) -> UnitExpansion<'a> {
239 let Some(catalog) = catalog else {
240 return UnitExpansion::Declared;
241 };
242 match catalog.entry_for_type(rule_type) {
243 Some(entry) => UnitExpansion::Family(entry),
244 None => UnitExpansion::Declared,
245 }
246}
247
248fn unit_names_for_expansion(
249 result_type: &LemmaType,
250 expansion: &UnitExpansion<'_>,
251 catalog: Option<&FamilyUnitCatalog>,
252) -> Arc<[String]> {
253 match expansion {
254 UnitExpansion::Declared => Arc::from(declared_bare_names_only(result_type)),
255 UnitExpansion::Family(_) => catalog
256 .expect("BUG: family expansion requires FamilyUnitCatalog")
257 .ordered_bare_names_for_type(result_type),
258 }
259}
260
261fn measure_factor_source<'a>(
262 result_type: &'a LemmaType,
263 expansion: &'a UnitExpansion<'a>,
264) -> UnitFactorSource<'a> {
265 match expansion {
266 UnitExpansion::Declared => UnitFactorSource::DeclaredOn(result_type),
267 UnitExpansion::Family(entry) => UnitFactorSource::Merged {
268 measure: entry.merged_measure_units.as_ref(),
269 ratio: entry.merged_ratio_units.as_ref(),
270 },
271 }
272}
273
274fn ratio_factor_source<'a>(
275 result_type: &'a LemmaType,
276 expansion: &'a UnitExpansion<'a>,
277) -> UnitFactorSource<'a> {
278 measure_factor_source(result_type, expansion)
279}
280
281enum NodeType<'a> {
284 Borrowed(&'a LemmaType),
285 Shared(Arc<LemmaType>),
286}
287
288impl NodeType<'_> {
289 fn get(&self) -> &LemmaType {
290 match self {
291 Self::Borrowed(lemma_type) => lemma_type,
292 Self::Shared(lemma_type) => lemma_type.as_ref(),
293 }
294 }
295}
296
297enum ResultValueTask<'a> {
298 Visit {
299 literal: Cow<'a, LiteralValue>,
300 lemma_type: NodeType<'a>,
301 },
302 AssembleList {
303 display: String,
304 rows: usize,
305 },
306}
307
308fn result_value_from_literal(
309 literal: &LiteralValue,
310 lemma_type: &LemmaType,
311 expansion: &UnitExpansion<'_>,
312 catalog: Option<&FamilyUnitCatalog>,
313) -> Result<RuleResultValue, RuleResultValueFailure> {
314 let mut tasks = vec![ResultValueTask::Visit {
315 literal: Cow::Borrowed(literal),
316 lemma_type: NodeType::Borrowed(lemma_type),
317 }];
318 let mut converted: Vec<RuleResultValue> = Vec::new();
319 while let Some(task) = tasks.pop() {
320 match task {
321 ResultValueTask::Visit {
322 literal,
323 lemma_type,
324 } => {
325 let lemma_type = lemma_type.get();
326 match &literal.value {
327 ValueKind::Range(from, to) => {
328 let from_value = result_value_from_range_endpoint(
329 &from.to_literal(),
330 from.lemma_type.as_ref(),
331 expansion,
332 catalog,
333 )?;
334 let to_value = result_value_from_range_endpoint(
335 &to.to_literal(),
336 to.lemma_type.as_ref(),
337 expansion,
338 catalog,
339 )?;
340 converted.push(RuleResultValue {
341 result: Some(literal.display_value_with_type(lemma_type)),
342 range: Some(Box::new(RangeResult {
343 from: from_value,
344 to: to_value,
345 })),
346 ..RuleResultValue::default()
347 });
348 }
349 ValueKind::List(rows) => {
350 tasks.push(ResultValueTask::AssembleList {
351 display: literal.display_value_with_type(lemma_type),
352 rows: rows.len(),
353 });
354 for row in rows.iter().rev() {
355 tasks.push(ResultValueTask::Visit {
356 literal: Cow::Owned(row.to_literal()),
357 lemma_type: NodeType::Shared(Arc::clone(&row.lemma_type)),
358 });
359 }
360 }
361 _ => converted.push(result_value_from_non_range_literal(
362 &literal, lemma_type, expansion, catalog,
363 )?),
364 }
365 }
366 ResultValueTask::AssembleList { display, rows } => {
367 let list = converted.split_off(converted.len() - rows);
368 converted.push(RuleResultValue {
369 result: Some(display),
370 list: Some(list),
371 ..RuleResultValue::default()
372 });
373 }
374 }
375 }
376 Ok(converted
377 .pop()
378 .expect("BUG: literal conversion yields exactly one RuleResultValue"))
379}
380
381pub(crate) fn rule_result_value_from_literal(
385 literal: &LiteralValue,
386 rule_type: &LemmaType,
387 catalog: &FamilyUnitCatalog,
388) -> Result<RuleResultValue, RuleResultValueFailure> {
389 let expansion = expansion_for_type(rule_type, Some(catalog));
390 result_value_from_literal(literal, rule_type, &expansion, Some(catalog))
391}
392
393pub fn type_scoped_result_value_from_literal(
395 literal: &LiteralValue,
396 lemma_type: &LemmaType,
397) -> Result<RuleResultValue, RuleResultValueFailure> {
398 let expansion = UnitExpansion::Declared;
399 result_value_from_literal(literal, lemma_type, &expansion, None)
400}
401
402fn result_value_from_range_endpoint(
403 endpoint: &LiteralValue,
404 endpoint_type: &LemmaType,
405 expansion: &UnitExpansion<'_>,
406 catalog: Option<&FamilyUnitCatalog>,
407) -> Result<RuleResultValue, RuleResultValueFailure> {
408 if matches!(&endpoint.value, ValueKind::Range(_, _)) {
409 panic!("BUG: range endpoint must not itself be a range");
410 }
411 result_value_from_non_range_literal(endpoint, endpoint_type, expansion, catalog)
412}
413
414fn result_value_from_non_range_literal(
415 literal: &LiteralValue,
416 result_type: &LemmaType,
417 expansion: &UnitExpansion<'_>,
418 catalog: Option<&FamilyUnitCatalog>,
419) -> Result<RuleResultValue, RuleResultValueFailure> {
420 match &literal.value {
421 ValueKind::Measure(rational) if result_type.is_calendar_like() => {
422 let unit = semantic_calendar_unit_from_measure_type(result_type);
423 let value = result_type
424 .try_rational_as_decimal(rational)
425 .map_err(map_numeric_to_rule_result_value_failure)?;
426 let result = Some(literal.display_value_with_type(result_type));
427 Ok(RuleResultValue {
428 result,
429 calendar: Some(CalendarResult {
430 value,
431 unit: unit.to_string(),
432 }),
433 ..RuleResultValue::default()
434 })
435 }
436 ValueKind::Measure(_) => {
437 let result = Some(literal.display_value_with_type(result_type));
438 let unit_names = unit_names_for_expansion(result_type, expansion, catalog);
439 let unit_name_refs: Vec<&str> = unit_names.iter().map(String::as_str).collect();
440 Ok(RuleResultValue {
441 result,
442 measure: Some(
443 result_type
444 .measure_literal_unit_map(
445 literal,
446 &unit_name_refs,
447 measure_factor_source(result_type, expansion),
448 )
449 .map_err(map_literal_unit_map_failure)?,
450 ),
451 unit: result_type.unit().map(str::to_string),
452 ..RuleResultValue::default()
453 })
454 }
455 ValueKind::Ratio(_) => {
456 let result = Some(literal.display_value_with_type(result_type));
457 let unit_names = unit_names_for_expansion(result_type, expansion, catalog);
458 let unit_name_refs: Vec<&str> = unit_names.iter().map(String::as_str).collect();
459 Ok(RuleResultValue {
460 result,
461 ratio: Some(
462 result_type
463 .ratio_literal_unit_map(
464 literal,
465 &unit_name_refs,
466 ratio_factor_source(result_type, expansion),
467 )
468 .map_err(map_literal_unit_map_failure)?,
469 ),
470 unit: result_type.unit().map(str::to_string),
471 ..RuleResultValue::default()
472 })
473 }
474 other => scalar_result_value(result_type, other),
475 }
476}
477
478fn scalar_result_value(
479 result_type: &LemmaType,
480 value: &ValueKind,
481) -> Result<RuleResultValue, RuleResultValueFailure> {
482 match value {
483 ValueKind::Number(rational) => {
484 let decimal = rational
485 .try_to_decimal()
486 .map_err(map_numeric_to_rule_result_value_failure)?;
487 let api_decimal = format_decimal_for_api(decimal, result_type.decimal_places());
488 let display_string = decimal_to_display_str(&decimal);
489 Ok(RuleResultValue {
490 result: Some(display_string),
491 number: Some(api_decimal),
492 ..RuleResultValue::default()
493 })
494 }
495 ValueKind::Boolean(b) => {
496 let result = Some(value.to_string());
497 Ok(RuleResultValue {
498 result,
499 boolean: Some(*b),
500 ..RuleResultValue::default()
501 })
502 }
503 ValueKind::Text(_) | ValueKind::Date(_) | ValueKind::Time(_) => {
504 let result = Some(value.to_string());
505 Ok(scalar_result_value_non_numeric(result, value))
506 }
507 ValueKind::Measure(_) | ValueKind::Ratio(_) => {
508 unreachable!("BUG: measure and ratio must be handled by caller")
509 }
510 ValueKind::Range(_, _) => {
511 unreachable!("BUG: range must be handled by result_value_from_literal")
512 }
513 ValueKind::List(_) => {
514 unreachable!("BUG: list must be handled by result_value_from_literal")
515 }
516 ValueKind::Spec(SpecValue::Fixed(instance)) => {
517 let identity = result_type.spec_identity().unwrap_or_else(|| {
518 panic!(
519 "BUG: spec value whose type is not a spec ({})",
520 result_type.name()
521 )
522 });
523 Ok(RuleResultValue {
524 result: Some(instance.to_string()),
525 spec: Some(Box::new(SpecResult {
526 repository: identity.repository.clone(),
527 spec: identity.spec.clone(),
528 effective: identity.effective.to_string(),
529 instance: instance.to_string(),
530 path: instance
531 .prefix
532 .iter()
533 .map(crate::api::PathSegment::from)
534 .collect(),
535 data: IndexMap::new(),
536 rules: IndexMap::new(),
537 })),
538 ..RuleResultValue::default()
539 })
540 }
541 ValueKind::Spec(SpecValue::Row(row)) => Ok(RuleResultValue {
542 result: Some(row.origin.to_string()),
543 ..RuleResultValue::default()
544 }),
545 }
546}
547
548fn scalar_result_value_non_numeric(result: Option<String>, value: &ValueKind) -> RuleResultValue {
549 match value {
550 ValueKind::Text(s) => RuleResultValue {
551 result,
552 text: Some(s.clone()),
553 ..RuleResultValue::default()
554 },
555 ValueKind::Date(d) => RuleResultValue {
556 result,
557 date: Some(d.clone()),
558 ..RuleResultValue::default()
559 },
560 ValueKind::Time(t) => RuleResultValue {
561 result,
562 time: Some(t.clone()),
563 ..RuleResultValue::default()
564 },
565 _ => unreachable!("BUG: scalar_result_value_non_numeric called with non-scalar type"),
566 }
567}
568
569fn literal_from_measure_map(
570 measure: &BTreeMap<String, Decimal>,
571 rule_type: &LemmaType,
572) -> LiteralValue {
573 let unit_names = rule_type
574 .measure_unit_names()
575 .expect("BUG: measure rule result must have declared units");
576 let unit_name = rule_type
577 .unit()
578 .map(str::to_string)
579 .or_else(|| unit_names.first().map(|name| (*name).to_string()))
580 .expect("BUG: measure rule result type must declare at least one unit");
581 let magnitude = measure
582 .get(&unit_name)
583 .unwrap_or_else(|| panic!("BUG: measure map missing unit '{unit_name}'"));
584 let rational = rational_from_parsed_decimal(*magnitude)
585 .expect("BUG: measure rule result value must lift to rational");
586 let factor = rule_type.measure_unit_factor(&unit_name);
587 let canonical = checked_mul(&rational, factor).unwrap_or_else(|failure| {
588 panic!("BUG: measure canonicalization from RuleResultValue fields failed: {failure}")
589 });
590 LiteralValue::measure(canonical)
591}
592
593fn literal_from_ratio_map(
594 ratio: &BTreeMap<String, Decimal>,
595 rule_type: &LemmaType,
596) -> LiteralValue {
597 let ratio_type = ratio_element_type_for_api(rule_type);
598 let units = match &ratio_type.specifications {
599 TypeSpecification::Ratio { units, .. } => units,
600 _ => panic!(
601 "BUG: ratio rule result type must be Ratio, got {}",
602 rule_type.name()
603 ),
604 };
605 let unit = rule_type
606 .unit()
607 .and_then(|name| units.get(name).ok())
608 .or_else(|| units.iter().next())
609 .expect("BUG: ratio rule result type must declare at least one unit");
610 let magnitude = ratio
611 .get(&unit.name)
612 .unwrap_or_else(|| panic!("BUG: ratio map missing unit '{}'", unit.name));
613 let display_rational = rational_from_parsed_decimal(*magnitude)
614 .expect("BUG: ratio rule result value must lift to rational");
615 let canonical = checked_div(&display_rational, &unit.value).unwrap_or_else(|failure| {
616 panic!("BUG: ratio canonicalization from RuleResultValue fields failed: {failure}")
617 });
618 LiteralValue::ratio(canonical)
619}
620
621impl RuleResultValue {
622 pub fn to_literal(&self, rule_type: &LemmaType) -> LiteralValue {
629 if let Some(row) = &self.row {
630 panic!(
631 "BUG: list row '{}' cannot be rebuilt from its API value",
632 row.instance
633 );
634 }
635 if let Some(spec) = &self.spec {
636 assert!(
637 !spec.path.is_empty(),
638 "BUG: spec result '{}' has no uses hop",
639 spec.instance
640 );
641 return LiteralValue::new(ValueKind::Spec(SpecValue::Fixed(SpecInstance {
642 prefix: spec
643 .path
644 .iter()
645 .map(|segment| crate::planning::semantics::PathSegment {
646 uses: segment.uses.clone(),
647 repository: segment.repository.clone(),
648 spec: segment.spec.clone(),
649 })
650 .collect(),
651 })));
652 }
653 if let Some(rows) = &self.list {
654 let TypeSpecification::List { element, .. } = &rule_type.specifications else {
655 panic!(
656 "BUG: list result whose rule type is not a list ({})",
657 rule_type.name()
658 );
659 };
660 return LiteralValue::new(ValueKind::List(
661 rows.iter()
662 .map(|row| {
663 let row_type =
664 endpoint_type_with_written_unit(element, row.unit.as_deref());
665 TypedLiteral {
666 value: row.to_literal(&row_type).value,
667 lemma_type: Arc::new(row_type),
668 }
669 })
670 .collect(),
671 ));
672 }
673 if let Some(range) = &self.range {
674 if range.from.range.is_some() || range.to.range.is_some() {
675 panic!("BUG: range endpoint must not itself be a range");
676 }
677 let endpoint_schema =
678 element_type_from_range_rule(rule_type).unwrap_or_else(|| rule_type.clone());
679 let left_type =
680 endpoint_type_with_written_unit(&endpoint_schema, range.from.unit.as_deref());
681 let right_type =
682 endpoint_type_with_written_unit(&endpoint_schema, range.to.unit.as_deref());
683 let left = range.from.to_literal(&left_type);
684 let right = range.to.to_literal(&right_type);
685 return LiteralValue::range(
686 crate::planning::semantics::TypedLiteral {
687 value: left.value,
688 lemma_type: Arc::new(left_type),
689 },
690 crate::planning::semantics::TypedLiteral {
691 value: right.value,
692 lemma_type: Arc::new(right_type),
693 },
694 );
695 }
696
697 if let Some(b) = self.boolean {
698 return LiteralValue::from_bool(b);
699 }
700 let owned_rule_type = match self.unit.as_deref() {
701 Some(unit)
702 if matches!(
703 &rule_type.specifications,
704 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. }
705 ) && rule_type.unit() != Some(unit) =>
706 {
707 Arc::new(rule_type.clone().with_unit(unit))
708 }
709 _ => Arc::new(rule_type.clone()),
710 };
711 if let Some(number) = self.number {
712 return LiteralValue::number_from_decimal(number);
713 }
714 if let Some(calendar) = &self.calendar {
715 let rational = rational_from_parsed_decimal(calendar.value)
716 .expect("BUG: calendar rule result value must lift to rational");
717 return LiteralValue::measure(rational);
718 }
719 if let Some(measure) = &self.measure {
720 return literal_from_measure_map(measure, owned_rule_type.as_ref());
721 }
722 if let Some(ratio) = &self.ratio {
723 return literal_from_ratio_map(ratio, owned_rule_type.as_ref());
724 }
725 if let Some(date) = &self.date {
726 return LiteralValue::date(date.clone());
727 }
728 if let Some(time) = &self.time {
729 return LiteralValue::time(time.clone());
730 }
731 if let Some(text) = &self.text {
732 return LiteralValue::text(text.clone());
733 }
734 panic!("BUG: rule result value fields cannot reconstruct literal");
735 }
736}
737
738fn endpoint_type_with_written_unit(endpoint_schema: &LemmaType, unit: Option<&str>) -> LemmaType {
739 match unit {
740 Some(unit)
741 if matches!(
742 &endpoint_schema.specifications,
743 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. }
744 ) && endpoint_schema.unit() != Some(unit) =>
745 {
746 endpoint_schema.clone().with_unit(unit)
747 }
748 _ => endpoint_schema.clone(),
749 }
750}
751
752fn element_type_from_range_rule(rule_type: &LemmaType) -> Option<LemmaType> {
753 range_element_type_specification(&rule_type.specifications).map(LemmaType::primitive)
754}
755
756fn format_unit_map(map: &BTreeMap<String, Decimal>) -> String {
757 map.iter()
758 .map(|(unit, value)| format!("{value} {unit}"))
759 .collect::<Vec<_>>()
760 .join(", ")
761}
762
763impl fmt::Display for RuleResultValue {
764 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
765 if let Some(range) = &self.range {
766 return write!(f, "{}...{}", range.from, range.to);
767 }
768 if let Some(measure) = &self.measure {
769 return write!(f, "{}", format_unit_map(measure));
770 }
771 if let Some(ratio) = &self.ratio {
772 return write!(f, "{}", format_unit_map(ratio));
773 }
774 if let Some(number) = &self.number {
775 return write!(f, "{number}");
776 }
777 if let Some(b) = self.boolean {
778 return write!(f, "{b}");
779 }
780 if let Some(text) = &self.text {
781 return write!(f, "{text}");
782 }
783 if let Some(date) = &self.date {
784 return write!(f, "{date}");
785 }
786 if let Some(time) = &self.time {
787 return write!(f, "{time}");
788 }
789 if let Some(calendar) = &self.calendar {
790 return write!(f, "{} {}", calendar.value, calendar.unit);
791 }
792 if let Some(display) = &self.result {
793 return write!(f, "{display}");
794 }
795 if let Some(reference) = &self.reference {
796 return write!(f, "{reference}");
797 }
798 write!(f, "")
799 }
800}