1use crate::computation::rational::RationalInteger;
4use crate::literals::{
5 BaseMeasureVector, DateTimeValue, MeasureUnit as DomainMeasureUnit, MeasureUnits,
6 RatioUnit as DomainRatioUnit, RatioUnits, TimeValue,
7};
8use crate::planning::semantics::{
9 LemmaType as DomainLemmaType, MeasureTrait as DomainMeasureTrait,
10 TypeDefiningSpec as DomainTypeDefiningSpec, TypeExtends as DomainTypeExtends,
11 TypeSpecification as DomainTypeSpecification,
12};
13use rust_decimal::Decimal;
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub struct RationalFactor {
19 pub numer: String,
20 pub denom: String,
21}
22
23impl RationalFactor {
24 fn from_ratio(value: &RationalInteger) -> Self {
25 let reduced = value
26 .clone()
27 .try_reduce()
28 .expect("BUG: stored measure unit factor must reduce");
29 Self {
30 numer: reduced.numer_to_string(),
31 denom: reduced.denom_to_string(),
32 }
33 }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct NamedBound {
39 pub value: Decimal,
40 pub unit: String,
41}
42
43fn planned_bound_decimal(rational: &RationalInteger) -> Decimal {
44 rational
45 .try_to_decimal()
46 .expect("BUG: planned bound must convert to decimal")
47}
48
49fn named_bound_from(bound: &(RationalInteger, String)) -> NamedBound {
50 NamedBound {
51 value: planned_bound_decimal(&bound.0),
52 unit: bound.1.clone(),
53 }
54}
55
56fn optional_named_bound(bound: &Option<(RationalInteger, String)>) -> Option<NamedBound> {
57 bound.as_ref().map(named_bound_from)
58}
59
60fn optional_decimal_bound(bound: &Option<RationalInteger>) -> Option<Decimal> {
61 bound.as_ref().map(planned_bound_decimal)
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum MeasureTrait {
67 Duration,
68 Calendar,
69}
70
71impl From<DomainMeasureTrait> for MeasureTrait {
72 fn from(value: DomainMeasureTrait) -> Self {
73 match value {
74 DomainMeasureTrait::Duration => Self::Duration,
75 DomainMeasureTrait::Calendar => Self::Calendar,
76 }
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81pub struct MeasureUnit {
82 pub name: String,
83 pub factor: RationalFactor,
84 pub derived_measure_factors: Vec<(String, i32)>,
85 pub decomposition: BaseMeasureVector,
86 #[serde(skip_serializing_if = "Option::is_none", default)]
87 pub minimum: Option<Decimal>,
88 #[serde(skip_serializing_if = "Option::is_none", default)]
89 pub maximum: Option<Decimal>,
90 #[serde(
91 rename = "suggestion",
92 skip_serializing_if = "Option::is_none",
93 default
94 )]
95 pub suggestion_magnitude: Option<Decimal>,
96}
97
98impl From<&DomainMeasureUnit> for MeasureUnit {
99 fn from(unit: &DomainMeasureUnit) -> Self {
100 Self {
101 name: unit.name.clone(),
102 factor: RationalFactor::from_ratio(&unit.factor),
103 derived_measure_factors: unit.derived_measure_factors.clone(),
104 decomposition: unit.decomposition.clone(),
105 minimum: unit.minimum.as_ref().map(|minimum| {
106 minimum
107 .try_to_decimal()
108 .expect("BUG: planned measure unit minimum must convert to decimal")
109 }),
110 maximum: unit.maximum.as_ref().map(|maximum| {
111 maximum
112 .try_to_decimal()
113 .expect("BUG: planned measure unit maximum must convert to decimal")
114 }),
115 suggestion_magnitude: unit.suggestion_magnitude.as_ref().map(|suggestion| {
116 suggestion
117 .try_to_decimal()
118 .expect("BUG: planned measure unit suggestion must convert to decimal")
119 }),
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub struct RatioUnit {
126 pub name: String,
127 pub value: RationalFactor,
128 #[serde(skip_serializing_if = "Option::is_none", default)]
129 pub minimum: Option<Decimal>,
130 #[serde(skip_serializing_if = "Option::is_none", default)]
131 pub maximum: Option<Decimal>,
132 #[serde(
133 rename = "suggestion",
134 skip_serializing_if = "Option::is_none",
135 default
136 )]
137 pub suggestion_magnitude: Option<Decimal>,
138}
139
140impl From<&DomainRatioUnit> for RatioUnit {
141 fn from(unit: &DomainRatioUnit) -> Self {
142 Self {
143 name: unit.name.clone(),
144 value: RationalFactor::from_ratio(&unit.value),
145 minimum: unit.minimum.as_ref().map(|minimum| {
146 minimum
147 .try_to_decimal()
148 .expect("BUG: planned ratio unit minimum must convert to decimal")
149 }),
150 maximum: unit.maximum.as_ref().map(|maximum| {
151 maximum
152 .try_to_decimal()
153 .expect("BUG: planned ratio unit maximum must convert to decimal")
154 }),
155 suggestion_magnitude: unit.suggestion_magnitude.as_ref().map(|suggestion| {
156 suggestion
157 .try_to_decimal()
158 .expect("BUG: planned ratio unit suggestion must convert to decimal")
159 }),
160 }
161 }
162}
163
164fn measure_units_from(units: &MeasureUnits) -> Vec<MeasureUnit> {
165 units.iter().map(MeasureUnit::from).collect()
166}
167
168fn ratio_units_from(units: &RatioUnits) -> Vec<RatioUnit> {
169 units.iter().map(RatioUnit::from).collect()
170}
171
172#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
173#[serde(tag = "kind", rename_all = "snake_case")]
174pub enum TypeDefiningSpec {
175 Local,
176 Import,
177}
178
179impl From<&DomainTypeDefiningSpec> for TypeDefiningSpec {
180 fn from(value: &DomainTypeDefiningSpec) -> Self {
181 match value {
182 DomainTypeDefiningSpec::Local => Self::Local,
183 DomainTypeDefiningSpec::Import => Self::Import,
184 }
185 }
186}
187
188#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
189#[serde(tag = "kind", rename_all = "snake_case")]
190pub enum TypeExtends {
191 Primitive,
192 Custom {
193 parent: String,
194 family: String,
195 defining_spec: TypeDefiningSpec,
196 },
197}
198
199impl From<&DomainTypeExtends> for TypeExtends {
200 fn from(value: &DomainTypeExtends) -> Self {
201 match value {
202 DomainTypeExtends::Primitive => Self::Primitive,
203 DomainTypeExtends::Custom {
204 parent,
205 family,
206 defining_spec,
207 } => Self::Custom {
208 parent: parent.clone(),
209 family: family.clone(),
210 defining_spec: TypeDefiningSpec::from(defining_spec),
211 },
212 }
213 }
214}
215
216#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
217#[serde(tag = "kind", rename_all = "lowercase")]
218pub enum TypeSpecification {
219 Boolean {
220 help: String,
221 },
222 Measure {
223 #[serde(default)]
224 minimum: Option<NamedBound>,
225 #[serde(default)]
226 maximum: Option<NamedBound>,
227 decimals: Option<u8>,
228 units: Vec<MeasureUnit>,
229 #[serde(default)]
230 traits: Vec<MeasureTrait>,
231 #[serde(default)]
232 decomposition: Option<BaseMeasureVector>,
233 #[serde(default, skip_serializing_if = "Option::is_none")]
234 unit: Option<String>,
235 help: String,
236 },
237 Number {
238 #[serde(default)]
239 minimum: Option<Decimal>,
240 #[serde(default)]
241 maximum: Option<Decimal>,
242 decimals: Option<u8>,
243 help: String,
244 },
245 NumberRange {
246 #[serde(default)]
247 lower: Option<Decimal>,
248 #[serde(default)]
249 upper: Option<Decimal>,
250 #[serde(default)]
251 minimum: Option<Decimal>,
252 #[serde(default)]
253 maximum: Option<Decimal>,
254 help: String,
255 },
256 Ratio {
257 #[serde(default)]
258 minimum: Option<Decimal>,
259 #[serde(default)]
260 maximum: Option<Decimal>,
261 decimals: Option<u8>,
262 units: Vec<RatioUnit>,
263 #[serde(default, skip_serializing_if = "Option::is_none")]
264 unit: Option<String>,
265 help: String,
266 },
267 RatioRange {
268 #[serde(default)]
269 lower: Option<Decimal>,
270 #[serde(default)]
271 upper: Option<Decimal>,
272 #[serde(default)]
273 minimum: Option<Decimal>,
274 #[serde(default)]
275 maximum: Option<Decimal>,
276 units: Vec<RatioUnit>,
277 help: String,
278 },
279 Text {
280 length: Option<usize>,
281 options: Vec<String>,
282 help: String,
283 },
284 Date {
285 minimum: Option<DateTimeValue>,
286 maximum: Option<DateTimeValue>,
287 help: String,
288 },
289 DateRange {
290 lower: Option<DateTimeValue>,
291 upper: Option<DateTimeValue>,
292 #[serde(default)]
293 minimum: Option<NamedBound>,
294 #[serde(default)]
295 maximum: Option<NamedBound>,
296 help: String,
297 },
298 Time {
299 minimum: Option<TimeValue>,
300 maximum: Option<TimeValue>,
301 help: String,
302 },
303 TimeRange {
304 lower: Option<TimeValue>,
305 upper: Option<TimeValue>,
306 #[serde(default)]
307 minimum: Option<NamedBound>,
308 #[serde(default)]
309 maximum: Option<NamedBound>,
310 help: String,
311 },
312 MeasureRange {
313 #[serde(default)]
314 lower: Option<NamedBound>,
315 #[serde(default)]
316 upper: Option<NamedBound>,
317 #[serde(default)]
318 minimum: Option<NamedBound>,
319 #[serde(default)]
320 maximum: Option<NamedBound>,
321 units: Vec<MeasureUnit>,
322 #[serde(default)]
323 decomposition: Option<BaseMeasureVector>,
324 help: String,
325 },
326 Veto {
327 message: Option<String>,
328 },
329 Undetermined,
330 Spec {
331 repository: Option<String>,
332 spec: String,
333 effective: String,
334 },
335}
336
337impl From<&DomainTypeSpecification> for TypeSpecification {
338 fn from(spec: &DomainTypeSpecification) -> Self {
339 match spec {
340 DomainTypeSpecification::Boolean { help } => Self::Boolean { help: help.clone() },
341 DomainTypeSpecification::Measure {
342 minimum,
343 maximum,
344 decimals,
345 units,
346 traits,
347 decomposition,
348 unit,
349 help,
350 } => Self::Measure {
351 minimum: optional_named_bound(minimum),
352 maximum: optional_named_bound(maximum),
353 decimals: *decimals,
354 units: measure_units_from(units),
355 traits: traits.iter().copied().map(MeasureTrait::from).collect(),
356 decomposition: decomposition.clone(),
357 unit: unit.clone(),
358 help: help.clone(),
359 },
360 DomainTypeSpecification::Number {
361 minimum,
362 maximum,
363 decimals,
364 help,
365 } => Self::Number {
366 minimum: optional_decimal_bound(minimum),
367 maximum: optional_decimal_bound(maximum),
368 decimals: *decimals,
369 help: help.clone(),
370 },
371 DomainTypeSpecification::NumberRange {
372 lower,
373 upper,
374 minimum,
375 maximum,
376 help,
377 } => Self::NumberRange {
378 lower: optional_decimal_bound(lower),
379 upper: optional_decimal_bound(upper),
380 minimum: optional_decimal_bound(minimum),
381 maximum: optional_decimal_bound(maximum),
382 help: help.clone(),
383 },
384 DomainTypeSpecification::Ratio {
385 minimum,
386 maximum,
387 decimals,
388 units,
389 unit,
390 help,
391 } => Self::Ratio {
392 minimum: optional_decimal_bound(minimum),
393 maximum: optional_decimal_bound(maximum),
394 decimals: *decimals,
395 units: ratio_units_from(units),
396 unit: unit.clone(),
397 help: help.clone(),
398 },
399 DomainTypeSpecification::RatioRange {
400 lower,
401 upper,
402 minimum,
403 maximum,
404 units,
405 help,
406 } => Self::RatioRange {
407 lower: optional_decimal_bound(lower),
408 upper: optional_decimal_bound(upper),
409 minimum: optional_decimal_bound(minimum),
410 maximum: optional_decimal_bound(maximum),
411 units: ratio_units_from(units),
412 help: help.clone(),
413 },
414 DomainTypeSpecification::Text {
415 length,
416 options,
417 help,
418 } => Self::Text {
419 length: *length,
420 options: options.clone(),
421 help: help.clone(),
422 },
423 DomainTypeSpecification::Date {
424 minimum,
425 maximum,
426 help,
427 } => Self::Date {
428 minimum: minimum.clone(),
429 maximum: maximum.clone(),
430 help: help.clone(),
431 },
432 DomainTypeSpecification::DateRange {
433 lower,
434 upper,
435 minimum,
436 maximum,
437 help,
438 } => Self::DateRange {
439 lower: lower.clone(),
440 upper: upper.clone(),
441 minimum: optional_named_bound(minimum),
442 maximum: optional_named_bound(maximum),
443 help: help.clone(),
444 },
445 DomainTypeSpecification::Time {
446 minimum,
447 maximum,
448 help,
449 } => Self::Time {
450 minimum: minimum.clone(),
451 maximum: maximum.clone(),
452 help: help.clone(),
453 },
454 DomainTypeSpecification::TimeRange {
455 lower,
456 upper,
457 minimum,
458 maximum,
459 help,
460 } => Self::TimeRange {
461 lower: lower.clone(),
462 upper: upper.clone(),
463 minimum: optional_named_bound(minimum),
464 maximum: optional_named_bound(maximum),
465 help: help.clone(),
466 },
467 DomainTypeSpecification::MeasureRange {
468 lower,
469 upper,
470 minimum,
471 maximum,
472 units,
473 decomposition,
474 help,
475 } => Self::MeasureRange {
476 lower: optional_named_bound(lower),
477 upper: optional_named_bound(upper),
478 minimum: optional_named_bound(minimum),
479 maximum: optional_named_bound(maximum),
480 units: measure_units_from(units),
481 decomposition: decomposition.clone(),
482 help: help.clone(),
483 },
484 DomainTypeSpecification::Veto { message } => Self::Veto {
485 message: message.clone(),
486 },
487 DomainTypeSpecification::Undetermined => Self::Undetermined,
488 DomainTypeSpecification::Spec { spec } => Self::Spec {
489 repository: spec.repository.clone(),
490 spec: spec.spec.clone(),
491 effective: spec.effective.to_string(),
492 },
493 }
494 }
495}
496
497#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
498pub struct LemmaType {
499 pub name: Option<String>,
500 #[serde(flatten)]
501 pub specifications: TypeSpecification,
502 pub extends: TypeExtends,
503}
504
505impl From<&DomainLemmaType> for LemmaType {
506 fn from(lemma_type: &DomainLemmaType) -> Self {
507 Self {
508 name: lemma_type.name.clone(),
509 specifications: TypeSpecification::from(&lemma_type.specifications),
510 extends: TypeExtends::from(&lemma_type.extends),
511 }
512 }
513}