use std::collections::BTreeMap;
use chrono::NaiveDate;
use turnframe_core::flow::StateField;
use turnframe_core::ids::{OperationKey, OptionId, TargetToken, WorkflowKey};
use turnframe_core::locale::Locale;
use turnframe_core::operation::{GlossaryTerm, OperationSpec};
use turnframe_core::understanding::UnderstoodArgument;
use crate::words::Words;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct UnderstandingInput {
pub message: Words,
pub locale: Locale,
pub today: NaiveDate,
pub transcript: Vec<TranscriptMessage>,
pub workflows: Vec<WorkflowBrief>,
pub card: Option<OpenCard>,
pub expectation: Option<Expectation>,
pub waiting: Vec<PendingAct>,
pub done: Vec<PendingAct>,
pub receipts: Vec<PreviousReceipt>,
pub last_subjects: Vec<TargetToken>,
pub settings: Option<crate::Settings>,
}
impl UnderstandingInput {
#[must_use]
pub fn new(message: &str, locale: impl Into<Locale>, today: NaiveDate) -> Self {
Self {
message: Words::split(message),
locale: locale.into(),
today,
transcript: Vec::new(),
workflows: Vec::new(),
card: None,
expectation: None,
waiting: Vec::new(),
done: Vec::new(),
receipts: Vec::new(),
last_subjects: Vec::new(),
settings: None,
}
}
#[must_use]
pub const fn with_settings(mut self, settings: crate::Settings) -> Self {
self.settings = Some(settings);
self
}
#[must_use]
pub fn with_workflow(mut self, workflow: WorkflowBrief) -> Self {
self.workflows.push(workflow);
self
}
#[must_use]
pub fn with_earlier(mut self, speaker: Speaker, text: &str) -> Self {
self.transcript.push(TranscriptMessage {
speaker,
words: Words::split(text),
});
self
}
#[must_use]
pub fn with_card(mut self, card: OpenCard) -> Self {
self.card = Some(card);
self
}
#[must_use]
pub fn with_expectation(mut self, expectation: Expectation) -> Self {
self.expectation = Some(expectation);
self
}
#[must_use]
pub fn with_waiting(mut self, act: PendingAct) -> Self {
self.waiting.push(act);
self
}
#[must_use]
pub fn with_done(mut self, act: PendingAct) -> Self {
self.done.push(act);
self
}
#[must_use]
pub fn with_receipt(mut self, receipt: PreviousReceipt) -> Self {
self.receipts.push(receipt);
self
}
#[must_use]
pub fn with_last_subject(mut self, record: TargetToken) -> Self {
if !self.last_subjects.contains(&record) {
self.last_subjects.push(record);
}
self
}
#[must_use]
pub fn workflow(&self, key: &WorkflowKey) -> Option<&WorkflowBrief> {
self.workflows.iter().find(|workflow| &workflow.key == key)
}
#[must_use]
pub fn operation(&self, key: &OperationKey) -> Option<(&WorkflowBrief, &OperationSpec)> {
self.workflows.iter().find_map(|workflow| {
workflow
.operations
.iter()
.find(|spec| &spec.key == key)
.map(|spec| (workflow, spec))
})
}
#[must_use]
pub fn record(&self, token: &TargetToken) -> Option<(&WorkflowBrief, &RecordBrief)> {
self.workflows.iter().find_map(|workflow| {
workflow
.records
.iter()
.find(|record| &record.token == token)
.map(|record| (workflow, record))
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Speaker {
User,
Assistant,
}
#[derive(Debug, Clone)]
pub struct TranscriptMessage {
pub speaker: Speaker,
pub words: Words,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct WorkflowBrief {
pub key: WorkflowKey,
pub summary: Option<String>,
pub glossary: Vec<GlossaryTerm>,
pub startable: bool,
pub operations: Vec<OperationSpec>,
pub records: Vec<RecordBrief>,
pub new_case: Vec<OperationKey>,
pub subjects: Vec<String>,
}
impl WorkflowBrief {
#[must_use]
pub fn new(key: impl Into<WorkflowKey>) -> Self {
Self {
key: key.into(),
summary: None,
glossary: Vec::new(),
startable: false,
operations: Vec::new(),
records: Vec::new(),
new_case: Vec::new(),
subjects: Vec::new(),
}
}
#[must_use]
pub fn summary(mut self, summary: impl Into<String>) -> Self {
self.summary = Some(summary.into());
self
}
#[must_use]
pub fn term(mut self, term: GlossaryTerm) -> Self {
self.glossary.push(term);
self
}
#[must_use]
pub const fn startable(mut self) -> Self {
self.startable = true;
self
}
#[must_use]
pub fn operation(mut self, mut spec: OperationSpec) -> Self {
spec.workflow = self.key.clone();
self.operations.push(spec);
self
}
#[must_use]
pub fn record(mut self, record: RecordBrief) -> Self {
self.records.push(record);
self
}
#[must_use]
pub fn on_new_case(mut self, operation: impl Into<OperationKey>) -> Self {
self.new_case.push(operation.into());
self
}
#[must_use]
pub fn subject(mut self, subject: impl Into<String>) -> Self {
self.subjects.push(subject.into());
self
}
#[must_use]
pub fn spec(&self, key: &OperationKey) -> Option<&OperationSpec> {
self.operations.iter().find(|spec| &spec.key == key)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct RecordBrief {
pub token: TargetToken,
pub label: String,
pub phase: String,
pub fields: Vec<StateField>,
pub obligations: Vec<String>,
pub briefing: Option<String>,
pub operations: Vec<OperationKey>,
}
impl RecordBrief {
#[must_use]
pub fn new(
token: impl Into<TargetToken>,
label: impl Into<String>,
phase: impl Into<String>,
) -> Self {
Self {
token: token.into(),
label: label.into(),
phase: phase.into(),
fields: Vec::new(),
obligations: Vec::new(),
briefing: None,
operations: Vec::new(),
}
}
#[must_use]
pub fn field(mut self, field: StateField) -> Self {
self.fields.push(field);
self
}
#[must_use]
pub fn obligation(mut self, sentence: impl Into<String>) -> Self {
self.obligations.push(sentence.into());
self
}
#[must_use]
pub fn briefing(mut self, briefing: impl Into<String>) -> Self {
self.briefing = Some(briefing.into());
self
}
#[must_use]
pub fn offering<I, K>(mut self, operations: I) -> Self
where
I: IntoIterator<Item = K>,
K: Into<OperationKey>,
{
self.operations
.extend(operations.into_iter().map(Into::into));
self
}
#[must_use]
pub fn offers(&self, operation: &OperationKey) -> bool {
self.operations.contains(operation)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct OpenCard {
pub workflow: WorkflowKey,
pub record: Option<TargetToken>,
pub question: String,
pub options: Vec<CardOption>,
pub accepts_typed_answer: bool,
}
impl OpenCard {
#[must_use]
pub fn new(workflow: impl Into<WorkflowKey>, question: impl Into<String>) -> Self {
Self {
workflow: workflow.into(),
record: None,
question: question.into(),
options: Vec::new(),
accepts_typed_answer: true,
}
}
#[must_use]
pub fn about(mut self, record: impl Into<TargetToken>) -> Self {
self.record = Some(record.into());
self
}
#[must_use]
pub fn option(mut self, id: impl Into<OptionId>, label: impl Into<String>) -> Self {
self.options.push(CardOption {
id: id.into(),
label: label.into(),
});
self
}
#[must_use]
pub const fn click_only(mut self) -> Self {
self.accepts_typed_answer = false;
self
}
}
#[derive(Debug, Clone)]
pub struct CardOption {
pub id: OptionId,
pub label: String,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Expectation {
Values(PendingAct),
Obligation {
record: TargetToken,
sentence: String,
},
}
#[derive(Debug, Clone)]
pub struct PendingAct {
pub operation: OperationKey,
pub record: Option<TargetToken>,
pub given: BTreeMap<String, UnderstoodArgument>,
pub missing: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct PreviousReceipt {
pub key: String,
pub text: String,
}
impl PreviousReceipt {
#[must_use]
pub fn new(key: impl Into<String>, text: impl Into<String>) -> Self {
Self {
key: key.into(),
text: text.into(),
}
}
}