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 pub offers: Vec<OfferBrief>,
48}
49
50impl UnderstandingInput {
51 #[must_use]
53 pub fn new(message: &str, locale: impl Into<Locale>, today: NaiveDate) -> Self {
54 Self {
55 message: Words::split(message),
56 locale: locale.into(),
57 today,
58 transcript: Vec::new(),
59 workflows: Vec::new(),
60 card: None,
61 expectation: None,
62 waiting: Vec::new(),
63 done: Vec::new(),
64 receipts: Vec::new(),
65 last_subjects: Vec::new(),
66 settings: None,
67 knowledge: true,
68 offers: Vec::new(),
69 }
70 }
71
72 #[must_use]
74 pub fn with_offer(mut self, offer: OfferBrief) -> Self {
75 self.offers.push(offer);
76 self
77 }
78
79 #[must_use]
82 pub const fn with_knowledge(mut self, knowledge: bool) -> Self {
83 self.knowledge = knowledge;
84 self
85 }
86
87 #[must_use]
89 pub const fn with_settings(mut self, settings: crate::Settings) -> Self {
90 self.settings = Some(settings);
91 self
92 }
93
94 #[must_use]
96 pub fn with_workflow(mut self, workflow: WorkflowBrief) -> Self {
97 self.workflows.push(workflow);
98 self
99 }
100
101 #[must_use]
103 pub fn with_earlier(mut self, speaker: Speaker, text: &str) -> Self {
104 self.transcript.push(TranscriptMessage {
105 speaker,
106 words: Words::split(text),
107 });
108 self
109 }
110
111 #[must_use]
113 pub fn with_card(mut self, card: OpenCard) -> Self {
114 self.card = Some(card);
115 self
116 }
117
118 #[must_use]
120 pub fn with_expectation(mut self, expectation: Expectation) -> Self {
121 self.expectation = Some(expectation);
122 self
123 }
124
125 #[must_use]
127 pub fn with_waiting(mut self, act: PendingAct) -> Self {
128 self.waiting.push(act);
129 self
130 }
131
132 #[must_use]
134 pub fn with_done(mut self, act: PendingAct) -> Self {
135 self.done.push(act);
136 self
137 }
138
139 #[must_use]
141 pub fn with_receipt(mut self, receipt: PreviousReceipt) -> Self {
142 self.receipts.push(receipt);
143 self
144 }
145
146 #[must_use]
148 pub fn with_last_subject(mut self, record: TargetToken) -> Self {
149 if !self.last_subjects.contains(&record) {
150 self.last_subjects.push(record);
151 }
152 self
153 }
154
155 #[must_use]
157 pub fn workflow(&self, key: &WorkflowKey) -> Option<&WorkflowBrief> {
158 self.workflows.iter().find(|workflow| &workflow.key == key)
159 }
160
161 #[must_use]
163 pub fn operation(&self, key: &OperationKey) -> Option<(&WorkflowBrief, &OperationSpec)> {
164 self.workflows.iter().find_map(|workflow| {
165 workflow
166 .operations
167 .iter()
168 .find(|spec| &spec.key == key)
169 .map(|spec| (workflow, spec))
170 })
171 }
172
173 #[must_use]
175 pub fn record(&self, token: &TargetToken) -> Option<(&WorkflowBrief, &RecordBrief)> {
176 self.workflows.iter().find_map(|workflow| {
177 workflow
178 .records
179 .iter()
180 .find(|record| &record.token == token)
181 .map(|record| (workflow, record))
182 })
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188pub enum Speaker {
189 User,
191 Assistant,
193}
194
195#[derive(Debug, Clone)]
197pub struct TranscriptMessage {
198 pub speaker: Speaker,
200 pub words: Words,
202}
203
204#[derive(Debug, Clone)]
206#[non_exhaustive]
207pub struct WorkflowBrief {
208 pub key: WorkflowKey,
210 pub summary: Option<String>,
212 pub glossary: Vec<GlossaryTerm>,
214 pub startable: bool,
216 pub operations: Vec<OperationSpec>,
218 pub records: Vec<RecordBrief>,
220 pub new_case: Vec<OperationKey>,
222 pub subjects: Vec<String>,
224}
225
226impl WorkflowBrief {
227 #[must_use]
229 pub fn new(key: impl Into<WorkflowKey>) -> Self {
230 Self {
231 key: key.into(),
232 summary: None,
233 glossary: Vec::new(),
234 startable: false,
235 operations: Vec::new(),
236 records: Vec::new(),
237 new_case: Vec::new(),
238 subjects: Vec::new(),
239 }
240 }
241
242 #[must_use]
244 pub fn summary(mut self, summary: impl Into<String>) -> Self {
245 self.summary = Some(summary.into());
246 self
247 }
248
249 #[must_use]
251 pub fn term(mut self, term: GlossaryTerm) -> Self {
252 self.glossary.push(term);
253 self
254 }
255
256 #[must_use]
258 pub const fn startable(mut self) -> Self {
259 self.startable = true;
260 self
261 }
262
263 #[must_use]
265 pub fn operation(mut self, mut spec: OperationSpec) -> Self {
266 spec.workflow = self.key.clone();
267 self.operations.push(spec);
268 self
269 }
270
271 #[must_use]
273 pub fn record(mut self, record: RecordBrief) -> Self {
274 self.records.push(record);
275 self
276 }
277
278 #[must_use]
280 pub fn on_new_case(mut self, operation: impl Into<OperationKey>) -> Self {
281 self.new_case.push(operation.into());
282 self
283 }
284
285 #[must_use]
287 pub fn subject(mut self, subject: impl Into<String>) -> Self {
288 self.subjects.push(subject.into());
289 self
290 }
291
292 #[must_use]
294 pub fn spec(&self, key: &OperationKey) -> Option<&OperationSpec> {
295 self.operations.iter().find(|spec| &spec.key == key)
296 }
297}
298
299#[derive(Debug, Clone)]
301#[non_exhaustive]
302pub struct RecordBrief {
303 pub token: TargetToken,
305 pub label: String,
307 pub phase: String,
309 pub fields: Vec<StateField>,
311 pub obligations: Vec<String>,
313 pub briefing: Option<String>,
315 pub operations: Vec<OperationKey>,
317}
318
319impl RecordBrief {
320 #[must_use]
322 pub fn new(
323 token: impl Into<TargetToken>,
324 label: impl Into<String>,
325 phase: impl Into<String>,
326 ) -> Self {
327 Self {
328 token: token.into(),
329 label: label.into(),
330 phase: phase.into(),
331 fields: Vec::new(),
332 obligations: Vec::new(),
333 briefing: None,
334 operations: Vec::new(),
335 }
336 }
337
338 #[must_use]
340 pub fn field(mut self, field: StateField) -> Self {
341 self.fields.push(field);
342 self
343 }
344
345 #[must_use]
347 pub fn obligation(mut self, sentence: impl Into<String>) -> Self {
348 self.obligations.push(sentence.into());
349 self
350 }
351
352 #[must_use]
354 pub fn briefing(mut self, briefing: impl Into<String>) -> Self {
355 self.briefing = Some(briefing.into());
356 self
357 }
358
359 #[must_use]
361 pub fn offering<I, K>(mut self, operations: I) -> Self
362 where
363 I: IntoIterator<Item = K>,
364 K: Into<OperationKey>,
365 {
366 self.operations
367 .extend(operations.into_iter().map(Into::into));
368 self
369 }
370
371 #[must_use]
373 pub fn offers(&self, operation: &OperationKey) -> bool {
374 self.operations.contains(operation)
375 }
376}
377
378#[derive(Debug, Clone)]
380#[non_exhaustive]
381pub struct OpenCard {
382 pub workflow: WorkflowKey,
384 pub record: Option<TargetToken>,
386 pub question: String,
388 pub options: Vec<CardOption>,
390 pub accepts_typed_answer: bool,
392}
393
394impl OpenCard {
395 #[must_use]
397 pub fn new(workflow: impl Into<WorkflowKey>, question: impl Into<String>) -> Self {
398 Self {
399 workflow: workflow.into(),
400 record: None,
401 question: question.into(),
402 options: Vec::new(),
403 accepts_typed_answer: true,
404 }
405 }
406
407 #[must_use]
409 pub fn about(mut self, record: impl Into<TargetToken>) -> Self {
410 self.record = Some(record.into());
411 self
412 }
413
414 #[must_use]
416 pub fn option(mut self, id: impl Into<OptionId>, label: impl Into<String>) -> Self {
417 self.options.push(CardOption {
418 id: id.into(),
419 label: label.into(),
420 });
421 self
422 }
423
424 #[must_use]
426 pub const fn click_only(mut self) -> Self {
427 self.accepts_typed_answer = false;
428 self
429 }
430}
431
432#[derive(Debug, Clone)]
434pub struct CardOption {
435 pub id: OptionId,
437 pub label: String,
439}
440
441#[derive(Debug, Clone)]
443#[non_exhaustive]
444pub enum Expectation {
445 Values(PendingAct),
447 Obligation {
449 record: TargetToken,
451 sentence: String,
453 },
454}
455
456impl Expectation {
457 #[must_use]
459 pub const fn record(&self) -> Option<&TargetToken> {
460 match self {
461 Self::Values(pending) => pending.record.as_ref(),
462 Self::Obligation { record, .. } => Some(record),
463 }
464 }
465}
466
467#[derive(Debug, Clone)]
470pub struct OfferBrief {
471 pub words: String,
473 pub act: PendingAct,
475}
476
477impl OfferBrief {
478 #[must_use]
480 pub fn new(words: impl Into<String>, act: PendingAct) -> Self {
481 Self {
482 words: words.into(),
483 act,
484 }
485 }
486}
487
488#[derive(Debug, Clone)]
490pub struct PendingAct {
491 pub operation: OperationKey,
493 pub record: Option<TargetToken>,
495 pub given: BTreeMap<String, UnderstoodArgument>,
497 pub missing: Vec<String>,
499}
500
501#[derive(Debug, Clone)]
503pub struct PreviousReceipt {
504 pub key: String,
506 pub text: String,
508}
509
510impl PreviousReceipt {
511 #[must_use]
513 pub fn new(key: impl Into<String>, text: impl Into<String>) -> Self {
514 Self {
515 key: key.into(),
516 text: text.into(),
517 }
518 }
519}