1use std::collections::BTreeMap;
9use std::fmt;
10use std::str::FromStr;
11
12use serde::{Deserialize, Serialize};
13
14use crate::hash::{Digest, HashError, canonical_digest};
15use crate::ids::{OperationKey, OptionId, TargetToken, WorkflowKey};
16use crate::plan::AnswerBasis;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
21#[serde(transparent)]
22pub struct UnitId(pub u16);
23
24impl fmt::Display for UnitId {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 write!(f, "u{}", self.0)
27 }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
32pub struct ActId {
33 pub unit: UnitId,
35 pub act: u16,
37}
38
39impl ActId {
40 #[must_use]
42 pub const fn new(unit: UnitId, act: u16) -> Self {
43 Self { unit, act }
44 }
45}
46
47impl fmt::Display for ActId {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 write!(f, "{}.a{}", self.unit, self.act)
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
55#[error("`{0}` is not an act identifier")]
56pub struct ActIdError(pub String);
57
58impl FromStr for ActId {
59 type Err = ActIdError;
60
61 fn from_str(text: &str) -> Result<Self, Self::Err> {
62 let invalid = || ActIdError(text.to_owned());
63 let (unit, act) = text.split_once(".a").ok_or_else(invalid)?;
64 let unit = unit.strip_prefix('u').ok_or_else(invalid)?;
65 Ok(Self {
66 unit: UnitId(unit.parse().map_err(|_| invalid())?),
67 act: act.parse().map_err(|_| invalid())?,
68 })
69 }
70}
71
72impl Serialize for ActId {
73 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
74 serializer.collect_str(self)
75 }
76}
77
78impl<'de> Deserialize<'de> for ActId {
79 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
80 let text = String::deserialize(deserializer)?;
81 text.parse().map_err(serde::de::Error::custom)
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
87#[serde(tag = "kind", rename_all = "snake_case")]
88pub enum MessageRef {
89 Current,
91 Earlier {
93 index: usize,
95 },
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
100pub struct WordRange {
101 pub first: usize,
103 pub last: usize,
105 pub start: usize,
107 pub end: usize,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
113pub struct Excerpt {
114 pub message: MessageRef,
116 pub words: WordRange,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
122#[serde(rename_all = "snake_case")]
123#[non_exhaustive]
124pub enum UnitKind {
125 Request,
127 Question,
129 Constraint,
131 Correction,
133 Cancel,
135 CardAnswer,
137 Dispute,
139 ProvidesValue,
141 Chitchat,
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
147#[serde(rename_all = "snake_case")]
148pub enum FoundBy {
149 Segment,
151 Coverage,
153 CrossCheck,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
159pub struct Unit {
160 pub id: UnitId,
162 pub kind: UnitKind,
164 pub words: WordRange,
166 pub workflow: Option<WorkflowKey>,
168 pub found_by: FoundBy,
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
174#[serde(tag = "kind", rename_all = "snake_case")]
175pub enum ActAction {
176 Apply {
178 operation: OperationKey,
180 },
181 Start {
183 workflow: WorkflowKey,
185 },
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
190#[serde(tag = "kind", rename_all = "snake_case")]
191#[non_exhaustive]
192pub enum ActTarget {
193 Record {
195 token: TargetToken,
197 },
198 New {
200 workflow: WorkflowKey,
202 },
203 SameTurn {
205 act: ActId,
207 },
208 Card,
210 NotListed {
212 workflow: WorkflowKey,
214 words: Option<WordRange>,
216 },
217 Ambiguous {
219 candidates: Vec<TargetToken>,
221 },
222 Nothing,
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
228#[serde(tag = "kind", rename_all = "snake_case")]
229pub enum RecordValue {
230 Record {
232 token: TargetToken,
234 },
235 SameTurn {
237 act: ActId,
239 },
240 Named {
242 workflow: WorkflowKey,
244 named: String,
246 },
247}
248
249#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
251#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
252pub enum ArgumentValue {
253 Json(serde_json::Value),
255 Record(RecordValue),
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
261pub struct UnderstoodArgument {
262 pub value: ArgumentValue,
264 pub excerpt: Option<Excerpt>,
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270#[serde(tag = "kind", rename_all = "snake_case")]
271#[non_exhaustive]
272pub enum ActStatus {
273 Ready,
275 NeedsValue {
277 arguments: Vec<String>,
279 reason: Option<String>,
281 },
282 Held {
284 because: UnitId,
286 },
287}
288
289#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
291pub struct UnderstoodAct {
292 pub id: ActId,
294 pub action: ActAction,
296 pub target: ActTarget,
298 pub arguments: BTreeMap<String, UnderstoodArgument>,
300 pub words: WordRange,
302 pub depends_on: Vec<ActId>,
304 pub status: ActStatus,
306}
307
308impl UnderstoodAct {
309 #[must_use]
311 pub const fn operation(&self) -> Option<&OperationKey> {
312 match &self.action {
313 ActAction::Apply { operation } => Some(operation),
314 ActAction::Start { .. } => None,
315 }
316 }
317
318 #[must_use]
320 pub const fn kind_name(&self) -> &'static str {
321 match self.action {
322 ActAction::Apply { .. } => "apply_operation",
323 ActAction::Start { .. } => "start_workflow",
324 }
325 }
326}
327
328#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
330pub struct Superseded {
331 pub act: ActId,
333 pub action: ActAction,
335 pub by: UnitId,
337}
338
339#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
341pub struct UnderstoodQuestion {
342 pub unit: UnitId,
344 pub words: WordRange,
346 pub workflow: Option<WorkflowKey>,
348 pub record: Option<TargetToken>,
350 pub subjects: Vec<String>,
352 pub basis: AnswerBasis,
354 #[serde(default)]
356 pub topic: QuestionTopic,
357 pub continues_previous: bool,
359}
360
361#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
363#[serde(rename_all = "snake_case")]
364#[non_exhaustive]
365pub enum QuestionTopic {
366 #[default]
368 RecordState,
369 AcceptedValues,
371 Capabilities,
373 Knowledge,
375}
376
377#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
379#[serde(rename_all = "snake_case")]
380#[non_exhaustive]
381pub enum ConstraintKind {
382 DoNotSubmit,
384 DoNotDelete,
386 DraftOnly,
388 AskBeforeApplying,
390 ApplyOnlyIf,
392 NoExternalEffects,
394 KeepUnchanged,
396}
397
398#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
400pub struct TurnConstraint {
401 pub unit: UnitId,
403 pub kind: ConstraintKind,
405 pub words: WordRange,
407}
408
409#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
411pub struct CardAnswer {
412 pub unit: UnitId,
414 pub option: OptionId,
416 pub words: WordRange,
418}
419
420#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
422pub struct Dispute {
423 pub unit: UnitId,
425 pub words: WordRange,
427 pub receipt: Option<String>,
429}
430
431#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
433#[serde(tag = "kind", rename_all = "snake_case")]
434#[non_exhaustive]
435pub enum NotUnderstoodReason {
436 NoOperation,
438 Unclear,
440 NotRequested,
442 KeptUnchanged {
444 constraint: UnitId,
446 },
447 TaskFailed {
449 task: String,
451 code: String,
453 },
454}
455
456#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
458pub struct NotUnderstood {
459 pub unit: UnitId,
461 pub words: WordRange,
463 pub reason: NotUnderstoodReason,
465}
466
467#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
469#[serde(tag = "kind", rename_all = "snake_case")]
470#[non_exhaustive]
471pub enum Unreadable {
472 Segmentation {
474 code: String,
476 },
477 LostConstraint,
479}
480
481#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
483pub struct Understanding {
484 pub units: Vec<Unit>,
486 pub acts: Vec<UnderstoodAct>,
488 pub superseded: Vec<Superseded>,
490 pub questions: Vec<UnderstoodQuestion>,
492 pub constraints: Vec<TurnConstraint>,
494 pub card_answer: Option<CardAnswer>,
496 pub disputes: Vec<Dispute>,
498 pub not_understood: Vec<NotUnderstood>,
500 pub unreadable: Option<Unreadable>,
502}
503
504impl Understanding {
505 #[must_use]
507 pub fn unreadable(reason: Unreadable) -> Self {
508 Self {
509 unreadable: Some(reason),
510 ..Self::default()
511 }
512 }
513
514 #[must_use]
516 pub fn act(&self, id: ActId) -> Option<&UnderstoodAct> {
517 self.acts.iter().find(|act| act.id == id)
518 }
519
520 pub fn hash(&self) -> Result<Digest, HashError> {
526 canonical_digest(self)
527 }
528}
529
530#[cfg(test)]
531mod tests {
532 use super::*;
533
534 #[test]
535 fn act_identifiers_read_and_parse_as_unit_and_position() {
536 let id = ActId::new(UnitId(2), 1);
537 assert_eq!(id.to_string(), "u2.a1");
538 assert_eq!("u2.a1".parse::<ActId>().unwrap(), id);
539 assert!("u2".parse::<ActId>().is_err());
540 assert!("x2.a1".parse::<ActId>().is_err());
541 let json = serde_json::to_value(id).unwrap();
542 assert_eq!(json, serde_json::json!("u2.a1"));
543 assert_eq!(serde_json::from_value::<ActId>(json).unwrap(), id);
544 }
545}