1use std::collections::BTreeMap;
4
5use chrono::NaiveDate;
6use turnframe_core::flow::StateField;
7use turnframe_core::ids::{OperationKey, OptionId, TargetToken, WorkflowKey};
8use turnframe_core::locale::Locale;
9use turnframe_core::operation::{GlossaryTerm, OperationSpec};
10use turnframe_core::understanding::UnderstoodArgument;
11
12use crate::words::Words;
13
14#[derive(Debug, Clone)]
16#[non_exhaustive]
17pub struct UnderstandingInput {
18 pub message: Words,
20 pub locale: Locale,
22 pub today: NaiveDate,
24 pub transcript: Vec<TranscriptMessage>,
26 pub workflows: Vec<WorkflowBrief>,
28 pub card: Option<OpenCard>,
30 pub expectation: Option<Expectation>,
32 pub waiting: Vec<PendingAct>,
34 pub done: Vec<PendingAct>,
37 pub receipts: Vec<PreviousReceipt>,
39 pub last_subjects: Vec<TargetToken>,
41 pub settings: Option<crate::Settings>,
43 pub knowledge: bool,
45}
46
47impl UnderstandingInput {
48 #[must_use]
50 pub fn new(message: &str, locale: impl Into<Locale>, today: NaiveDate) -> Self {
51 Self {
52 message: Words::split(message),
53 locale: locale.into(),
54 today,
55 transcript: Vec::new(),
56 workflows: Vec::new(),
57 card: None,
58 expectation: None,
59 waiting: Vec::new(),
60 done: Vec::new(),
61 receipts: Vec::new(),
62 last_subjects: Vec::new(),
63 settings: None,
64 knowledge: true,
65 }
66 }
67
68 #[must_use]
71 pub const fn with_knowledge(mut self, knowledge: bool) -> Self {
72 self.knowledge = knowledge;
73 self
74 }
75
76 #[must_use]
78 pub const fn with_settings(mut self, settings: crate::Settings) -> Self {
79 self.settings = Some(settings);
80 self
81 }
82
83 #[must_use]
85 pub fn with_workflow(mut self, workflow: WorkflowBrief) -> Self {
86 self.workflows.push(workflow);
87 self
88 }
89
90 #[must_use]
92 pub fn with_earlier(mut self, speaker: Speaker, text: &str) -> Self {
93 self.transcript.push(TranscriptMessage {
94 speaker,
95 words: Words::split(text),
96 });
97 self
98 }
99
100 #[must_use]
102 pub fn with_card(mut self, card: OpenCard) -> Self {
103 self.card = Some(card);
104 self
105 }
106
107 #[must_use]
109 pub fn with_expectation(mut self, expectation: Expectation) -> Self {
110 self.expectation = Some(expectation);
111 self
112 }
113
114 #[must_use]
116 pub fn with_waiting(mut self, act: PendingAct) -> Self {
117 self.waiting.push(act);
118 self
119 }
120
121 #[must_use]
123 pub fn with_done(mut self, act: PendingAct) -> Self {
124 self.done.push(act);
125 self
126 }
127
128 #[must_use]
130 pub fn with_receipt(mut self, receipt: PreviousReceipt) -> Self {
131 self.receipts.push(receipt);
132 self
133 }
134
135 #[must_use]
137 pub fn with_last_subject(mut self, record: TargetToken) -> Self {
138 if !self.last_subjects.contains(&record) {
139 self.last_subjects.push(record);
140 }
141 self
142 }
143
144 #[must_use]
146 pub fn workflow(&self, key: &WorkflowKey) -> Option<&WorkflowBrief> {
147 self.workflows.iter().find(|workflow| &workflow.key == key)
148 }
149
150 #[must_use]
152 pub fn operation(&self, key: &OperationKey) -> Option<(&WorkflowBrief, &OperationSpec)> {
153 self.workflows.iter().find_map(|workflow| {
154 workflow
155 .operations
156 .iter()
157 .find(|spec| &spec.key == key)
158 .map(|spec| (workflow, spec))
159 })
160 }
161
162 #[must_use]
164 pub fn record(&self, token: &TargetToken) -> Option<(&WorkflowBrief, &RecordBrief)> {
165 self.workflows.iter().find_map(|workflow| {
166 workflow
167 .records
168 .iter()
169 .find(|record| &record.token == token)
170 .map(|record| (workflow, record))
171 })
172 }
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum Speaker {
178 User,
180 Assistant,
182}
183
184#[derive(Debug, Clone)]
186pub struct TranscriptMessage {
187 pub speaker: Speaker,
189 pub words: Words,
191}
192
193#[derive(Debug, Clone)]
195#[non_exhaustive]
196pub struct WorkflowBrief {
197 pub key: WorkflowKey,
199 pub summary: Option<String>,
201 pub glossary: Vec<GlossaryTerm>,
203 pub startable: bool,
205 pub operations: Vec<OperationSpec>,
207 pub records: Vec<RecordBrief>,
209 pub new_case: Vec<OperationKey>,
211 pub subjects: Vec<String>,
213}
214
215impl WorkflowBrief {
216 #[must_use]
218 pub fn new(key: impl Into<WorkflowKey>) -> Self {
219 Self {
220 key: key.into(),
221 summary: None,
222 glossary: Vec::new(),
223 startable: false,
224 operations: Vec::new(),
225 records: Vec::new(),
226 new_case: Vec::new(),
227 subjects: Vec::new(),
228 }
229 }
230
231 #[must_use]
233 pub fn summary(mut self, summary: impl Into<String>) -> Self {
234 self.summary = Some(summary.into());
235 self
236 }
237
238 #[must_use]
240 pub fn term(mut self, term: GlossaryTerm) -> Self {
241 self.glossary.push(term);
242 self
243 }
244
245 #[must_use]
247 pub const fn startable(mut self) -> Self {
248 self.startable = true;
249 self
250 }
251
252 #[must_use]
254 pub fn operation(mut self, mut spec: OperationSpec) -> Self {
255 spec.workflow = self.key.clone();
256 self.operations.push(spec);
257 self
258 }
259
260 #[must_use]
262 pub fn record(mut self, record: RecordBrief) -> Self {
263 self.records.push(record);
264 self
265 }
266
267 #[must_use]
269 pub fn on_new_case(mut self, operation: impl Into<OperationKey>) -> Self {
270 self.new_case.push(operation.into());
271 self
272 }
273
274 #[must_use]
276 pub fn subject(mut self, subject: impl Into<String>) -> Self {
277 self.subjects.push(subject.into());
278 self
279 }
280
281 #[must_use]
283 pub fn spec(&self, key: &OperationKey) -> Option<&OperationSpec> {
284 self.operations.iter().find(|spec| &spec.key == key)
285 }
286}
287
288#[derive(Debug, Clone)]
290#[non_exhaustive]
291pub struct RecordBrief {
292 pub token: TargetToken,
294 pub label: String,
296 pub phase: String,
298 pub fields: Vec<StateField>,
300 pub obligations: Vec<String>,
302 pub briefing: Option<String>,
304 pub operations: Vec<OperationKey>,
306}
307
308impl RecordBrief {
309 #[must_use]
311 pub fn new(
312 token: impl Into<TargetToken>,
313 label: impl Into<String>,
314 phase: impl Into<String>,
315 ) -> Self {
316 Self {
317 token: token.into(),
318 label: label.into(),
319 phase: phase.into(),
320 fields: Vec::new(),
321 obligations: Vec::new(),
322 briefing: None,
323 operations: Vec::new(),
324 }
325 }
326
327 #[must_use]
329 pub fn field(mut self, field: StateField) -> Self {
330 self.fields.push(field);
331 self
332 }
333
334 #[must_use]
336 pub fn obligation(mut self, sentence: impl Into<String>) -> Self {
337 self.obligations.push(sentence.into());
338 self
339 }
340
341 #[must_use]
343 pub fn briefing(mut self, briefing: impl Into<String>) -> Self {
344 self.briefing = Some(briefing.into());
345 self
346 }
347
348 #[must_use]
350 pub fn offering<I, K>(mut self, operations: I) -> Self
351 where
352 I: IntoIterator<Item = K>,
353 K: Into<OperationKey>,
354 {
355 self.operations
356 .extend(operations.into_iter().map(Into::into));
357 self
358 }
359
360 #[must_use]
362 pub fn offers(&self, operation: &OperationKey) -> bool {
363 self.operations.contains(operation)
364 }
365}
366
367#[derive(Debug, Clone)]
369#[non_exhaustive]
370pub struct OpenCard {
371 pub workflow: WorkflowKey,
373 pub record: Option<TargetToken>,
375 pub question: String,
377 pub options: Vec<CardOption>,
379 pub accepts_typed_answer: bool,
381}
382
383impl OpenCard {
384 #[must_use]
386 pub fn new(workflow: impl Into<WorkflowKey>, question: impl Into<String>) -> Self {
387 Self {
388 workflow: workflow.into(),
389 record: None,
390 question: question.into(),
391 options: Vec::new(),
392 accepts_typed_answer: true,
393 }
394 }
395
396 #[must_use]
398 pub fn about(mut self, record: impl Into<TargetToken>) -> Self {
399 self.record = Some(record.into());
400 self
401 }
402
403 #[must_use]
405 pub fn option(mut self, id: impl Into<OptionId>, label: impl Into<String>) -> Self {
406 self.options.push(CardOption {
407 id: id.into(),
408 label: label.into(),
409 });
410 self
411 }
412
413 #[must_use]
415 pub const fn click_only(mut self) -> Self {
416 self.accepts_typed_answer = false;
417 self
418 }
419}
420
421#[derive(Debug, Clone)]
423pub struct CardOption {
424 pub id: OptionId,
426 pub label: String,
428}
429
430#[derive(Debug, Clone)]
432#[non_exhaustive]
433pub enum Expectation {
434 Values(PendingAct),
436 Obligation {
438 record: TargetToken,
440 sentence: String,
442 },
443}
444
445impl Expectation {
446 #[must_use]
448 pub const fn record(&self) -> Option<&TargetToken> {
449 match self {
450 Self::Values(pending) => pending.record.as_ref(),
451 Self::Obligation { record, .. } => Some(record),
452 }
453 }
454}
455
456#[derive(Debug, Clone)]
458pub struct PendingAct {
459 pub operation: OperationKey,
461 pub record: Option<TargetToken>,
463 pub given: BTreeMap<String, UnderstoodArgument>,
465 pub missing: Vec<String>,
467}
468
469#[derive(Debug, Clone)]
471pub struct PreviousReceipt {
472 pub key: String,
474 pub text: String,
476}
477
478impl PreviousReceipt {
479 #[must_use]
481 pub fn new(key: impl Into<String>, text: impl Into<String>) -> Self {
482 Self {
483 key: key.into(),
484 text: text.into(),
485 }
486 }
487}