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}
44
45impl UnderstandingInput {
46    /// A turn saying `message` on `today`, with nothing else in view.
47    #[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    /// Runs this turn under `settings` instead of the understander's.
66    #[must_use]
67    pub const fn with_settings(mut self, settings: crate::Settings) -> Self {
68        self.settings = Some(settings);
69        self
70    }
71
72    /// Adds a workflow.
73    #[must_use]
74    pub fn with_workflow(mut self, workflow: WorkflowBrief) -> Self {
75        self.workflows.push(workflow);
76        self
77    }
78
79    /// Adds an earlier message.
80    #[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    /// Sets the card on screen.
90    #[must_use]
91    pub fn with_card(mut self, card: OpenCard) -> Self {
92        self.card = Some(card);
93        self
94    }
95
96    /// Sets what the assistant asked for.
97    #[must_use]
98    pub fn with_expectation(mut self, expectation: Expectation) -> Self {
99        self.expectation = Some(expectation);
100        self
101    }
102
103    /// Adds an act of an earlier turn still waiting for a record the user named.
104    #[must_use]
105    pub fn with_waiting(mut self, act: PendingAct) -> Self {
106        self.waiting.push(act);
107        self
108    }
109
110    /// Adds an act the last turn did.
111    #[must_use]
112    pub fn with_done(mut self, act: PendingAct) -> Self {
113        self.done.push(act);
114        self
115    }
116
117    /// Adds a receipt of the last turn.
118    #[must_use]
119    pub fn with_receipt(mut self, receipt: PreviousReceipt) -> Self {
120        self.receipts.push(receipt);
121        self
122    }
123
124    /// Adds a record the assistant's last message was about.
125    #[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    /// The workflow with this key.
134    #[must_use]
135    pub fn workflow(&self, key: &WorkflowKey) -> Option<&WorkflowBrief> {
136        self.workflows.iter().find(|workflow| &workflow.key == key)
137    }
138
139    /// The operation with this key, and its workflow.
140    #[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    /// The record with this token, and its workflow.
152    #[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/// Who wrote an earlier message.
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum Speaker {
167    /// The user.
168    User,
169    /// The assistant.
170    Assistant,
171}
172
173/// An earlier message, split into words so a value from it can be pointed at.
174#[derive(Debug, Clone)]
175pub struct TranscriptMessage {
176    /// Who wrote it.
177    pub speaker: Speaker,
178    /// What it said.
179    pub words: Words,
180}
181
182/// A workflow as understanding sees it.
183#[derive(Debug, Clone)]
184#[non_exhaustive]
185pub struct WorkflowBrief {
186    /// Its key.
187    pub key: WorkflowKey,
188    /// One line on what it is for.
189    pub summary: Option<String>,
190    /// What its words mean.
191    pub glossary: Vec<GlossaryTerm>,
192    /// Whether a new case of it may be started.
193    pub startable: bool,
194    /// Every operation offered on a record in view or on a new case, each once.
195    pub operations: Vec<OperationSpec>,
196    /// The records in view.
197    pub records: Vec<RecordBrief>,
198    /// The operations offered on a record that does not exist yet.
199    pub new_case: Vec<OperationKey>,
200    /// What a question about it may be about: its enumerations and stated fields.
201    pub subjects: Vec<String>,
202}
203
204impl WorkflowBrief {
205    /// A workflow with nothing in view.
206    #[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    /// Sets its summary.
221    #[must_use]
222    pub fn summary(mut self, summary: impl Into<String>) -> Self {
223        self.summary = Some(summary.into());
224        self
225    }
226
227    /// Adds a glossary term.
228    #[must_use]
229    pub fn term(mut self, term: GlossaryTerm) -> Self {
230        self.glossary.push(term);
231        self
232    }
233
234    /// Lets a new case be started.
235    #[must_use]
236    pub const fn startable(mut self) -> Self {
237        self.startable = true;
238        self
239    }
240
241    /// Adds an operation, stamped with this workflow.
242    #[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    /// Adds a record.
250    #[must_use]
251    pub fn record(mut self, record: RecordBrief) -> Self {
252        self.records.push(record);
253        self
254    }
255
256    /// Offers an operation on a new case.
257    #[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    /// Adds a subject questions may be about.
264    #[must_use]
265    pub fn subject(mut self, subject: impl Into<String>) -> Self {
266        self.subjects.push(subject.into());
267        self
268    }
269
270    /// The operation with this key.
271    #[must_use]
272    pub fn spec(&self, key: &OperationKey) -> Option<&OperationSpec> {
273        self.operations.iter().find(|spec| &spec.key == key)
274    }
275}
276
277/// A record in view.
278#[derive(Debug, Clone)]
279#[non_exhaustive]
280pub struct RecordBrief {
281    /// The token it is named by.
282    pub token: TargetToken,
283    /// Server-authored label.
284    pub label: String,
285    /// Its phase, as the workflow names it.
286    pub phase: String,
287    /// What it holds, as the workflow says it may be stated.
288    pub fields: Vec<StateField>,
289    /// What it still needs, as sentences.
290    pub obligations: Vec<String>,
291    /// The workflow's guidance for a turn about a record in this phase.
292    pub briefing: Option<String>,
293    /// The operations offered on it now.
294    pub operations: Vec<OperationKey>,
295}
296
297impl RecordBrief {
298    /// A record with its token, label and phase.
299    #[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    /// Adds a field.
317    #[must_use]
318    pub fn field(mut self, field: StateField) -> Self {
319        self.fields.push(field);
320        self
321    }
322
323    /// Adds an open obligation.
324    #[must_use]
325    pub fn obligation(mut self, sentence: impl Into<String>) -> Self {
326        self.obligations.push(sentence.into());
327        self
328    }
329
330    /// Sets the phase guidance.
331    #[must_use]
332    pub fn briefing(mut self, briefing: impl Into<String>) -> Self {
333        self.briefing = Some(briefing.into());
334        self
335    }
336
337    /// Offers operations on it.
338    #[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    /// Whether `operation` is offered on it.
350    #[must_use]
351    pub fn offers(&self, operation: &OperationKey) -> bool {
352        self.operations.contains(operation)
353    }
354}
355
356/// The card on screen, as its question and option labels.
357#[derive(Debug, Clone)]
358#[non_exhaustive]
359pub struct OpenCard {
360    /// The workflow it belongs to.
361    pub workflow: WorkflowKey,
362    /// The record it is about.
363    pub record: Option<TargetToken>,
364    /// Its question.
365    pub question: String,
366    /// Its options.
367    pub options: Vec<CardOption>,
368    /// Whether typed text may answer it at all.
369    pub accepts_typed_answer: bool,
370}
371
372impl OpenCard {
373    /// A card asking `question` about a record of `workflow`.
374    #[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    /// Sets the record it is about.
386    #[must_use]
387    pub fn about(mut self, record: impl Into<TargetToken>) -> Self {
388        self.record = Some(record.into());
389        self
390    }
391
392    /// Adds an option.
393    #[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    /// Refuses typed answers: only a click resolves it.
403    #[must_use]
404    pub const fn click_only(mut self) -> Self {
405        self.accepts_typed_answer = false;
406        self
407    }
408}
409
410/// One option of a card.
411#[derive(Debug, Clone)]
412pub struct CardOption {
413    /// Its identifier.
414    pub id: OptionId,
415    /// Its label.
416    pub label: String,
417}
418
419/// What the assistant asked for last turn.
420#[derive(Debug, Clone)]
421#[non_exhaustive]
422pub enum Expectation {
423    /// Values an act needs before it can run.
424    Values(PendingAct),
425    /// An open obligation of a record.
426    Obligation {
427        /// The record.
428        record: TargetToken,
429        /// The obligation, as a sentence.
430        sentence: String,
431    },
432}
433
434/// An act of an earlier turn with the values it was given: one waiting for more, or one done.
435#[derive(Debug, Clone)]
436pub struct PendingAct {
437    /// The operation.
438    pub operation: OperationKey,
439    /// The record it applies to.
440    pub record: Option<TargetToken>,
441    /// The arguments already given.
442    pub given: BTreeMap<String, UnderstoodArgument>,
443    /// The arguments asked for.
444    pub missing: Vec<String>,
445}
446
447/// A change the assistant reported last turn.
448#[derive(Debug, Clone)]
449pub struct PreviousReceipt {
450    /// The key it is shown under, such as `r1`.
451    pub key: String,
452    /// What it said, as shown.
453    pub text: String,
454}
455
456impl PreviousReceipt {
457    /// A receipt shown under `key`.
458    #[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}