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 help: String,
234 },
235 Number {
236 #[serde(default)]
237 minimum: Option<Decimal>,
238 #[serde(default)]
239 maximum: Option<Decimal>,
240 decimals: Option<u8>,
241 help: String,
242 },
243 NumberRange {
244 #[serde(default)]
245 lower: Option<Decimal>,
246 #[serde(default)]
247 upper: Option<Decimal>,
248 #[serde(default)]
249 minimum: Option<Decimal>,
250 #[serde(default)]
251 maximum: Option<Decimal>,
252 help: String,
253 },
254 Ratio {
255 #[serde(default)]
256 minimum: Option<Decimal>,
257 #[serde(default)]
258 maximum: Option<Decimal>,
259 decimals: Option<u8>,
260 units: Vec<RatioUnit>,
261 help: String,
262 },
263 RatioRange {
264 #[serde(default)]
265 lower: Option<Decimal>,
266 #[serde(default)]
267 upper: Option<Decimal>,
268 #[serde(default)]
269 minimum: Option<Decimal>,
270 #[serde(default)]
271 maximum: Option<Decimal>,
272 units: Vec<RatioUnit>,
273 help: String,
274 },
275 Text {
276 length: Option<usize>,
277 options: Vec<String>,
278 help: String,
279 },
280 Date {
281 minimum: Option<DateTimeValue>,
282 maximum: Option<DateTimeValue>,
283 help: String,
284 },
285 DateRange {
286 lower: Option<DateTimeValue>,
287 upper: Option<DateTimeValue>,
288 #[serde(default)]
289 minimum: Option<NamedBound>,
290 #[serde(default)]
291 maximum: Option<NamedBound>,
292 help: String,
293 },
294 Time {
295 minimum: Option<TimeValue>,
296 maximum: Option<TimeValue>,
297 help: String,
298 },
299 TimeRange {
300 lower: Option<TimeValue>,
301 upper: Option<TimeValue>,
302 #[serde(default)]
303 minimum: Option<NamedBound>,
304 #[serde(default)]
305 maximum: Option<NamedBound>,
306 help: String,
307 },
308 MeasureRange {
309 #[serde(default)]
310 lower: Option<NamedBound>,
311 #[serde(default)]
312 upper: Option<NamedBound>,
313 #[serde(default)]
314 minimum: Option<NamedBound>,
315 #[serde(default)]
316 maximum: Option<NamedBound>,
317 units: Vec<MeasureUnit>,
318 #[serde(default)]
319 decomposition: Option<BaseMeasureVector>,
320 help: String,
321 },
322 Veto {
323 message: Option<String>,
324 },
325 Undetermined,
326}
327
328impl From<&DomainTypeSpecification> for TypeSpecification {
329 fn from(spec: &DomainTypeSpecification) -> Self {
330 match spec {
331 DomainTypeSpecification::Boolean { help } => Self::Boolean { help: help.clone() },
332 DomainTypeSpecification::Measure {
333 minimum,
334 maximum,
335 decimals,
336 units,
337 traits,
338 decomposition,
339 help,
340 } => Self::Measure {
341 minimum: optional_named_bound(minimum),
342 maximum: optional_named_bound(maximum),
343 decimals: *decimals,
344 units: measure_units_from(units),
345 traits: traits.iter().copied().map(MeasureTrait::from).collect(),
346 decomposition: decomposition.clone(),
347 help: help.clone(),
348 },
349 DomainTypeSpecification::Number {
350 minimum,
351 maximum,
352 decimals,
353 help,
354 } => Self::Number {
355 minimum: optional_decimal_bound(minimum),
356 maximum: optional_decimal_bound(maximum),
357 decimals: *decimals,
358 help: help.clone(),
359 },
360 DomainTypeSpecification::NumberRange {
361 lower,
362 upper,
363 minimum,
364 maximum,
365 help,
366 } => Self::NumberRange {
367 lower: optional_decimal_bound(lower),
368 upper: optional_decimal_bound(upper),
369 minimum: optional_decimal_bound(minimum),
370 maximum: optional_decimal_bound(maximum),
371 help: help.clone(),
372 },
373 DomainTypeSpecification::Ratio {
374 minimum,
375 maximum,
376 decimals,
377 units,
378 help,
379 } => Self::Ratio {
380 minimum: optional_decimal_bound(minimum),
381 maximum: optional_decimal_bound(maximum),
382 decimals: *decimals,
383 units: ratio_units_from(units),
384 help: help.clone(),
385 },
386 DomainTypeSpecification::RatioRange {
387 lower,
388 upper,
389 minimum,
390 maximum,
391 units,
392 help,
393 } => Self::RatioRange {
394 lower: optional_decimal_bound(lower),
395 upper: optional_decimal_bound(upper),
396 minimum: optional_decimal_bound(minimum),
397 maximum: optional_decimal_bound(maximum),
398 units: ratio_units_from(units),
399 help: help.clone(),
400 },
401 DomainTypeSpecification::Text {
402 length,
403 options,
404 help,
405 } => Self::Text {
406 length: *length,
407 options: options.clone(),
408 help: help.clone(),
409 },
410 DomainTypeSpecification::Date {
411 minimum,
412 maximum,
413 help,
414 } => Self::Date {
415 minimum: minimum.clone(),
416 maximum: maximum.clone(),
417 help: help.clone(),
418 },
419 DomainTypeSpecification::DateRange {
420 lower,
421 upper,
422 minimum,
423 maximum,
424 help,
425 } => Self::DateRange {
426 lower: lower.clone(),
427 upper: upper.clone(),
428 minimum: optional_named_bound(minimum),
429 maximum: optional_named_bound(maximum),
430 help: help.clone(),
431 },
432 DomainTypeSpecification::Time {
433 minimum,
434 maximum,
435 help,
436 } => Self::Time {
437 minimum: minimum.clone(),
438 maximum: maximum.clone(),
439 help: help.clone(),
440 },
441 DomainTypeSpecification::TimeRange {
442 lower,
443 upper,
444 minimum,
445 maximum,
446 help,
447 } => Self::TimeRange {
448 lower: lower.clone(),
449 upper: upper.clone(),
450 minimum: optional_named_bound(minimum),
451 maximum: optional_named_bound(maximum),
452 help: help.clone(),
453 },
454 DomainTypeSpecification::MeasureRange {
455 lower,
456 upper,
457 minimum,
458 maximum,
459 units,
460 decomposition,
461 help,
462 } => Self::MeasureRange {
463 lower: optional_named_bound(lower),
464 upper: optional_named_bound(upper),
465 minimum: optional_named_bound(minimum),
466 maximum: optional_named_bound(maximum),
467 units: measure_units_from(units),
468 decomposition: decomposition.clone(),
469 help: help.clone(),
470 },
471 DomainTypeSpecification::Veto { message } => Self::Veto {
472 message: message.clone(),
473 },
474 DomainTypeSpecification::Undetermined => Self::Undetermined,
475 }
476 }
477}
478
479#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
480pub struct LemmaType {
481 pub name: Option<String>,
482 #[serde(flatten)]
483 pub specifications: TypeSpecification,
484 pub extends: TypeExtends,
485 #[serde(default, skip_serializing_if = "Option::is_none")]
486 pub measure_binding_unit: Option<String>,
487}
488
489impl From<&DomainLemmaType> for LemmaType {
490 fn from(lemma_type: &DomainLemmaType) -> Self {
491 Self {
492 name: lemma_type.name.clone(),
493 specifications: TypeSpecification::from(&lemma_type.specifications),
494 extends: TypeExtends::from(&lemma_type.extends),
495 measure_binding_unit: lemma_type.measure_binding_unit.clone(),
496 }
497 }
498}