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}
44
45impl UnderstandingInput {
46 #[must_use]
48 pub fn new(message: &str, locale: impl Into<Locale>, today: NaiveDate) -> Self {
49 Self {
50 message: Words::split(message),
51 locale: locale.into(),
52 today,
53 transcript: Vec::new(),
54 workflows: Vec::new(),
55 card: None,
56 expectation: None,
57 waiting: Vec::new(),
58 done: Vec::new(),
59 receipts: Vec::new(),
60 last_subjects: Vec::new(),
61 settings: None,
62 }
63 }
64
65 #[must_use]
67 pub const fn with_settings(mut self, settings: crate::Settings) -> Self {
68 self.settings = Some(settings);
69 self
70 }
71
72 #[must_use]
74 pub fn with_workflow(mut self, workflow: WorkflowBrief) -> Self {
75 self.workflows.push(workflow);
76 self
77 }
78
79 #[must_use]
81 pub fn with_earlier(mut self, speaker: Speaker, text: &str) -> Self {
82 self.transcript.push(TranscriptMessage {
83 speaker,
84 words: Words::split(text),
85 });
86 self
87 }
88
89 #[must_use]
91 pub fn with_card(mut self, card: OpenCard) -> Self {
92 self.card = Some(card);
93 self
94 }
95
96 #[must_use]
98 pub fn with_expectation(mut self, expectation: Expectation) -> Self {
99 self.expectation = Some(expectation);
100 self
101 }
102
103 #[must_use]
105 pub fn with_waiting(mut self, act: PendingAct) -> Self {
106 self.waiting.push(act);
107 self
108 }
109
110 #[must_use]
112 pub fn with_done(mut self, act: PendingAct) -> Self {
113 self.done.push(act);
114 self
115 }
116
117 #[must_use]
119 pub fn with_receipt(mut self, receipt: PreviousReceipt) -> Self {
120 self.receipts.push(receipt);
121 self
122 }
123
124 #[must_use]
126 pub fn with_last_subject(mut self, record: TargetToken) -> Self {
127 if !self.last_subjects.contains(&record) {
128 self.last_subjects.push(record);
129 }
130 self
131 }
132
133 #[must_use]
135 pub fn workflow(&self, key: &WorkflowKey) -> Option<&WorkflowBrief> {
136 self.workflows.iter().find(|workflow| &workflow.key == key)
137 }
138
139 #[must_use]
141 pub fn operation(&self, key: &OperationKey) -> Option<(&WorkflowBrief, &OperationSpec)> {
142 self.workflows.iter().find_map(|workflow| {
143 workflow
144 .operations
145 .iter()
146 .find(|spec| &spec.key == key)
147 .map(|spec| (workflow, spec))
148 })
149 }
150
151 #[must_use]
153 pub fn record(&self, token: &TargetToken) -> Option<(&WorkflowBrief, &RecordBrief)> {
154 self.workflows.iter().find_map(|workflow| {
155 workflow
156 .records
157 .iter()
158 .find(|record| &record.token == token)
159 .map(|record| (workflow, record))
160 })
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum Speaker {
167 User,
169 Assistant,
171}
172
173#[derive(Debug, Clone)]
175pub struct TranscriptMessage {
176 pub speaker: Speaker,
178 pub words: Words,
180}
181
182#[derive(Debug, Clone)]
184#[non_exhaustive]
185pub struct WorkflowBrief {
186 pub key: WorkflowKey,
188 pub summary: Option<String>,
190 pub glossary: Vec<GlossaryTerm>,
192 pub startable: bool,
194 pub operations: Vec<OperationSpec>,
196 pub records: Vec<RecordBrief>,
198 pub new_case: Vec<OperationKey>,
200 pub subjects: Vec<String>,
202}
203
204impl WorkflowBrief {
205 #[must_use]
207 pub fn new(key: impl Into<WorkflowKey>) -> Self {
208 Self {
209 key: key.into(),
210 summary: None,
211 glossary: Vec::new(),
212 startable: false,
213 operations: Vec::new(),
214 records: Vec::new(),
215 new_case: Vec::new(),
216 subjects: Vec::new(),
217 }
218 }
219
220 #[must_use]
222 pub fn summary(mut self, summary: impl Into<String>) -> Self {
223 self.summary = Some(summary.into());
224 self
225 }
226
227 #[must_use]
229 pub fn term(mut self, term: GlossaryTerm) -> Self {
230 self.glossary.push(term);
231 self
232 }
233
234 #[must_use]
236 pub const fn startable(mut self) -> Self {
237 self.startable = true;
238 self
239 }
240
241 #[must_use]
243 pub fn operation(mut self, mut spec: OperationSpec) -> Self {
244 spec.workflow = self.key.clone();
245 self.operations.push(spec);
246 self
247 }
248
249 #[must_use]
251 pub fn record(mut self, record: RecordBrief) -> Self {
252 self.records.push(record);
253 self
254 }
255
256 #[must_use]
258 pub fn on_new_case(mut self, operation: impl Into<OperationKey>) -> Self {
259 self.new_case.push(operation.into());
260 self
261 }
262
263 #[must_use]
265 pub fn subject(mut self, subject: impl Into<String>) -> Self {
266 self.subjects.push(subject.into());
267 self
268 }
269
270 #[must_use]
272 pub fn spec(&self, key: &OperationKey) -> Option<&OperationSpec> {
273 self.operations.iter().find(|spec| &spec.key == key)
274 }
275}
276
277#[derive(Debug, Clone)]
279#[non_exhaustive]
280pub struct RecordBrief {
281 pub token: TargetToken,
283 pub label: String,
285 pub phase: String,
287 pub fields: Vec<StateField>,
289 pub obligations: Vec<String>,
291 pub briefing: Option<String>,
293 pub operations: Vec<OperationKey>,
295}
296
297impl RecordBrief {
298 #[must_use]
300 pub fn new(
301 token: impl Into<TargetToken>,
302 label: impl Into<String>,
303 phase: impl Into<String>,
304 ) -> Self {
305 Self {
306 token: token.into(),
307 label: label.into(),
308 phase: phase.into(),
309 fields: Vec::new(),
310 obligations: Vec::new(),
311 briefing: None,
312 operations: Vec::new(),
313 }
314 }
315
316 #[must_use]
318 pub fn field(mut self, field: StateField) -> Self {
319 self.fields.push(field);
320 self
321 }
322
323 #[must_use]
325 pub fn obligation(mut self, sentence: impl Into<String>) -> Self {
326 self.obligations.push(sentence.into());
327 self
328 }
329
330 #[must_use]
332 pub fn briefing(mut self, briefing: impl Into<String>) -> Self {
333 self.briefing = Some(briefing.into());
334 self
335 }
336
337 #[must_use]
339 pub fn offering<I, K>(mut self, operations: I) -> Self
340 where
341 I: IntoIterator<Item = K>,
342 K: Into<OperationKey>,
343 {
344 self.operations
345 .extend(operations.into_iter().map(Into::into));
346 self
347 }
348
349 #[must_use]
351 pub fn offers(&self, operation: &OperationKey) -> bool {
352 self.operations.contains(operation)
353 }
354}
355
356#[derive(Debug, Clone)]
358#[non_exhaustive]
359pub struct OpenCard {
360 pub workflow: WorkflowKey,
362 pub record: Option<TargetToken>,
364 pub question: String,
366 pub options: Vec<CardOption>,
368 pub accepts_typed_answer: bool,
370}
371
372impl OpenCard {
373 #[must_use]
375 pub fn new(workflow: impl Into<WorkflowKey>, question: impl Into<String>) -> Self {
376 Self {
377 workflow: workflow.into(),
378 record: None,
379 question: question.into(),
380 options: Vec::new(),
381 accepts_typed_answer: true,
382 }
383 }
384
385 #[must_use]
387 pub fn about(mut self, record: impl Into<TargetToken>) -> Self {
388 self.record = Some(record.into());
389 self
390 }
391
392 #[must_use]
394 pub fn option(mut self, id: impl Into<OptionId>, label: impl Into<String>) -> Self {
395 self.options.push(CardOption {
396 id: id.into(),
397 label: label.into(),
398 });
399 self
400 }
401
402 #[must_use]
404 pub const fn click_only(mut self) -> Self {
405 self.accepts_typed_answer = false;
406 self
407 }
408}
409
410#[derive(Debug, Clone)]
412pub struct CardOption {
413 pub id: OptionId,
415 pub label: String,
417}
418
419#[derive(Debug, Clone)]
421#[non_exhaustive]
422pub enum Expectation {
423 Values(PendingAct),
425 Obligation {
427 record: TargetToken,
429 sentence: String,
431 },
432}
433
434#[derive(Debug, Clone)]
436pub struct PendingAct {
437 pub operation: OperationKey,
439 pub record: Option<TargetToken>,
441 pub given: BTreeMap<String, UnderstoodArgument>,
443 pub missing: Vec<String>,
445}
446
447#[derive(Debug, Clone)]
449pub struct PreviousReceipt {
450 pub key: String,
452 pub text: String,
454}
455
456impl PreviousReceipt {
457 #[must_use]
459 pub fn new(key: impl Into<String>, text: impl Into<String>) -> Self {
460 Self {
461 key: key.into(),
462 text: text.into(),
463 }
464 }
465}