Skip to main content

turnframe_understand/
input.rs

1//! Everything one turn's understanding may see, built by the runtime from stores and views.
2
3use 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/// The input of one turn's understanding.
15#[derive(Debug, Clone)]
16#[non_exhaustive]
17pub struct UnderstandingInput {
18    /// The user's message.
19    pub message: Words,
20    /// The turn's language.
21    pub locale: Locale,
22    /// Today, in the user's time zone: what relative dates count from.
23    pub today: NaiveDate,
24    /// The most recent messages before this one, oldest first.
25    pub transcript: Vec<TranscriptMessage>,
26    /// The workflows in view, in registration order.
27    pub workflows: Vec<WorkflowBrief>,
28    /// The card waiting for an answer.
29    pub card: Option<OpenCard>,
30    /// What the assistant asked for last turn.
31    pub expectation: Option<Expectation>,
32    /// Acts of earlier turns still waiting for a record the user named that did not exist.
33    pub waiting: Vec<PendingAct>,
34    /// Acts the last turn did, with the values they were given: a correction changes what
35    /// it says of one and keeps the rest.
36    pub done: Vec<PendingAct>,
37    /// What the assistant reported doing last turn, so a dispute can name it.
38    pub receipts: Vec<PreviousReceipt>,
39    /// The records the assistant's last message was about.
40    pub last_subjects: Vec<TargetToken>,
41    /// How this turn is run, over the understander's own settings.
42    pub settings: Option<crate::Settings>,
43    /// Whether a knowledge source can answer a question about the domain in general.
44    pub knowledge: bool,
45}
46
47impl UnderstandingInput {
48    /// A turn saying `message` on `today`, with nothing else in view.
49    #[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    /// Says whether a knowledge source can answer a question about the domain in general;
69    /// without one, no question is read as asking it.
70    #[must_use]
71    pub const fn with_knowledge(mut self, knowledge: bool) -> Self {
72        self.knowledge = knowledge;
73        self
74    }
75
76    /// Runs this turn under `settings` instead of the understander's.
77    #[must_use]
78    pub const fn with_settings(mut self, settings: crate::Settings) -> Self {
79        self.settings = Some(settings);
80        self
81    }
82
83    /// Adds a workflow.
84    #[must_use]
85    pub fn with_workflow(mut self, workflow: WorkflowBrief) -> Self {
86        self.workflows.push(workflow);
87        self
88    }
89
90    /// Adds an earlier message.
91    #[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    /// Sets the card on screen.
101    #[must_use]
102    pub fn with_card(mut self, card: OpenCard) -> Self {
103        self.card = Some(card);
104        self
105    }
106
107    /// Sets what the assistant asked for.
108    #[must_use]
109    pub fn with_expectation(mut self, expectation: Expectation) -> Self {
110        self.expectation = Some(expectation);
111        self
112    }
113
114    /// Adds an act of an earlier turn still waiting for a record the user named.
115    #[must_use]
116    pub fn with_waiting(mut self, act: PendingAct) -> Self {
117        self.waiting.push(act);
118        self
119    }
120
121    /// Adds an act the last turn did.
122    #[must_use]
123    pub fn with_done(mut self, act: PendingAct) -> Self {
124        self.done.push(act);
125        self
126    }
127
128    /// Adds a receipt of the last turn.
129    #[must_use]
130    pub fn with_receipt(mut self, receipt: PreviousReceipt) -> Self {
131        self.receipts.push(receipt);
132        self
133    }
134
135    /// Adds a record the assistant's last message was about.
136    #[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    /// The workflow with this key.
145    #[must_use]
146    pub fn workflow(&self, key: &WorkflowKey) -> Option<&WorkflowBrief> {
147        self.workflows.iter().find(|workflow| &workflow.key == key)
148    }
149
150    /// The operation with this key, and its workflow.
151    #[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    /// The record with this token, and its workflow.
163    #[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/// Who wrote an earlier message.
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum Speaker {
178    /// The user.
179    User,
180    /// The assistant.
181    Assistant,
182}
183
184/// An earlier message, split into words so a value from it can be pointed at.
185#[derive(Debug, Clone)]
186pub struct TranscriptMessage {
187    /// Who wrote it.
188    pub speaker: Speaker,
189    /// What it said.
190    pub words: Words,
191}
192
193/// A workflow as understanding sees it.
194#[derive(Debug, Clone)]
195#[non_exhaustive]
196pub struct WorkflowBrief {
197    /// Its key.
198    pub key: WorkflowKey,
199    /// One line on what it is for.
200    pub summary: Option<String>,
201    /// What its words mean.
202    pub glossary: Vec<GlossaryTerm>,
203    /// Whether a new case of it may be started.
204    pub startable: bool,
205    /// Every operation offered on a record in view or on a new case, each once.
206    pub operations: Vec<OperationSpec>,
207    /// The records in view.
208    pub records: Vec<RecordBrief>,
209    /// The operations offered on a record that does not exist yet.
210    pub new_case: Vec<OperationKey>,
211    /// What a question about it may be about: its enumerations and stated fields.
212    pub subjects: Vec<String>,
213}
214
215impl WorkflowBrief {
216    /// A workflow with nothing in view.
217    #[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    /// Sets its summary.
232    #[must_use]
233    pub fn summary(mut self, summary: impl Into<String>) -> Self {
234        self.summary = Some(summary.into());
235        self
236    }
237
238    /// Adds a glossary term.
239    #[must_use]
240    pub fn term(mut self, term: GlossaryTerm) -> Self {
241        self.glossary.push(term);
242        self
243    }
244
245    /// Lets a new case be started.
246    #[must_use]
247    pub const fn startable(mut self) -> Self {
248        self.startable = true;
249        self
250    }
251
252    /// Adds an operation, stamped with this workflow.
253    #[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    /// Adds a record.
261    #[must_use]
262    pub fn record(mut self, record: RecordBrief) -> Self {
263        self.records.push(record);
264        self
265    }
266
267    /// Offers an operation on a new case.
268    #[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    /// Adds a subject questions may be about.
275    #[must_use]
276    pub fn subject(mut self, subject: impl Into<String>) -> Self {
277        self.subjects.push(subject.into());
278        self
279    }
280
281    /// The operation with this key.
282    #[must_use]
283    pub fn spec(&self, key: &OperationKey) -> Option<&OperationSpec> {
284        self.operations.iter().find(|spec| &spec.key == key)
285    }
286}
287
288/// A record in view.
289#[derive(Debug, Clone)]
290#[non_exhaustive]
291pub struct RecordBrief {
292    /// The token it is named by.
293    pub token: TargetToken,
294    /// Server-authored label.
295    pub label: String,
296    /// Its phase, as the workflow names it.
297    pub phase: String,
298    /// What it holds, as the workflow says it may be stated.
299    pub fields: Vec<StateField>,
300    /// What it still needs, as sentences.
301    pub obligations: Vec<String>,
302    /// The workflow's guidance for a turn about a record in this phase.
303    pub briefing: Option<String>,
304    /// The operations offered on it now.
305    pub operations: Vec<OperationKey>,
306}
307
308impl RecordBrief {
309    /// A record with its token, label and phase.
310    #[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    /// Adds a field.
328    #[must_use]
329    pub fn field(mut self, field: StateField) -> Self {
330        self.fields.push(field);
331        self
332    }
333
334    /// Adds an open obligation.
335    #[must_use]
336    pub fn obligation(mut self, sentence: impl Into<String>) -> Self {
337        self.obligations.push(sentence.into());
338        self
339    }
340
341    /// Sets the phase guidance.
342    #[must_use]
343    pub fn briefing(mut self, briefing: impl Into<String>) -> Self {
344        self.briefing = Some(briefing.into());
345        self
346    }
347
348    /// Offers operations on it.
349    #[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    /// Whether `operation` is offered on it.
361    #[must_use]
362    pub fn offers(&self, operation: &OperationKey) -> bool {
363        self.operations.contains(operation)
364    }
365}
366
367/// The card on screen, as its question and option labels.
368#[derive(Debug, Clone)]
369#[non_exhaustive]
370pub struct OpenCard {
371    /// The workflow it belongs to.
372    pub workflow: WorkflowKey,
373    /// The record it is about.
374    pub record: Option<TargetToken>,
375    /// Its question.
376    pub question: String,
377    /// Its options.
378    pub options: Vec<CardOption>,
379    /// Whether typed text may answer it at all.
380    pub accepts_typed_answer: bool,
381}
382
383impl OpenCard {
384    /// A card asking `question` about a record of `workflow`.
385    #[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    /// Sets the record it is about.
397    #[must_use]
398    pub fn about(mut self, record: impl Into<TargetToken>) -> Self {
399        self.record = Some(record.into());
400        self
401    }
402
403    /// Adds an option.
404    #[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    /// Refuses typed answers: only a click resolves it.
414    #[must_use]
415    pub const fn click_only(mut self) -> Self {
416        self.accepts_typed_answer = false;
417        self
418    }
419}
420
421/// One option of a card.
422#[derive(Debug, Clone)]
423pub struct CardOption {
424    /// Its identifier.
425    pub id: OptionId,
426    /// Its label.
427    pub label: String,
428}
429
430/// What the assistant asked for last turn.
431#[derive(Debug, Clone)]
432#[non_exhaustive]
433pub enum Expectation {
434    /// Values an act needs before it can run.
435    Values(PendingAct),
436    /// An open obligation of a record.
437    Obligation {
438        /// The record.
439        record: TargetToken,
440        /// The obligation, as a sentence.
441        sentence: String,
442    },
443}
444
445impl Expectation {
446    /// The record the assistant asked about, when it named one.
447    #[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/// An act of an earlier turn with the values it was given: one waiting for more, or one done.
457#[derive(Debug, Clone)]
458pub struct PendingAct {
459    /// The operation.
460    pub operation: OperationKey,
461    /// The record it applies to.
462    pub record: Option<TargetToken>,
463    /// The arguments already given.
464    pub given: BTreeMap<String, UnderstoodArgument>,
465    /// The arguments asked for.
466    pub missing: Vec<String>,
467}
468
469/// A change the assistant reported last turn.
470#[derive(Debug, Clone)]
471pub struct PreviousReceipt {
472    /// The key it is shown under, such as `r1`.
473    pub key: String,
474    /// What it said, as shown.
475    pub text: String,
476}
477
478impl PreviousReceipt {
479    /// A receipt shown under `key`.
480    #[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}