Skip to main content

made_core/events/
deliberation_completed.rs

1//! [`DeliberationCompletedEvent`] — a deliberation finished with a winner.
2
3use serde::{Deserialize, Serialize};
4
5use crate::events::envelope::EventEnvelope;
6use crate::value_objects::{DurationMs, ProposalId, Score, Specialty, TaskId};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct DeliberationCompletedEvent {
10    #[serde(flatten)]
11    envelope: EventEnvelope,
12    task_id: TaskId,
13    specialty: Specialty,
14    winner_proposal_id: ProposalId,
15    winner_score: Score,
16    num_candidates: u32,
17    duration: DurationMs,
18    /// `bundle_id` of the [`ExternalContextBundle`] the task carried,
19    /// when present. Lets a downstream consumer correlate this
20    /// completion back to the context payload it (or its context source)
21    /// fed in. Omitted from the serialized envelope when `None`.
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    external_context_bundle_id: Option<String>,
24}
25
26impl DeliberationCompletedEvent {
27    #[must_use]
28    pub fn new(
29        envelope: EventEnvelope,
30        task_id: TaskId,
31        specialty: Specialty,
32        winner_proposal_id: ProposalId,
33        winner_score: Score,
34        num_candidates: u32,
35        duration: DurationMs,
36    ) -> Self {
37        Self::new_with_context(
38            envelope,
39            task_id,
40            specialty,
41            winner_proposal_id,
42            winner_score,
43            num_candidates,
44            duration,
45            None,
46        )
47    }
48
49    /// Variant that captures the `bundle_id` of the task's
50    /// [`ExternalContextBundle`] (when present). Use this from
51    /// `DeliberateUseCase` so consumers can correlate the completion
52    /// envelope back to the bundle that fed it.
53    #[must_use]
54    pub fn new_with_context(
55        envelope: EventEnvelope,
56        task_id: TaskId,
57        specialty: Specialty,
58        winner_proposal_id: ProposalId,
59        winner_score: Score,
60        num_candidates: u32,
61        duration: DurationMs,
62        external_context_bundle_id: Option<String>,
63    ) -> Self {
64        Self {
65            envelope,
66            task_id,
67            specialty,
68            winner_proposal_id,
69            winner_score,
70            num_candidates,
71            duration,
72            external_context_bundle_id,
73        }
74    }
75
76    #[must_use]
77    pub fn envelope(&self) -> &EventEnvelope {
78        &self.envelope
79    }
80    #[must_use]
81    pub fn task_id(&self) -> &TaskId {
82        &self.task_id
83    }
84    #[must_use]
85    pub fn specialty(&self) -> &Specialty {
86        &self.specialty
87    }
88    #[must_use]
89    pub fn winner_proposal_id(&self) -> &ProposalId {
90        &self.winner_proposal_id
91    }
92    #[must_use]
93    pub fn winner_score(&self) -> Score {
94        self.winner_score
95    }
96    #[must_use]
97    pub fn num_candidates(&self) -> u32 {
98        self.num_candidates
99    }
100    #[must_use]
101    pub fn duration(&self) -> DurationMs {
102        self.duration
103    }
104    #[must_use]
105    pub fn external_context_bundle_id(&self) -> Option<&str> {
106        self.external_context_bundle_id.as_deref()
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use crate::value_objects::EventId;
114    use time::macros::datetime;
115
116    #[test]
117    fn accessors_return_fields() {
118        let env = EventEnvelope::new(
119            EventId::new("e").unwrap(),
120            datetime!(2026-04-15 12:00:00 UTC),
121            "s",
122            None,
123        )
124        .unwrap();
125        let ev = DeliberationCompletedEvent::new(
126            env,
127            TaskId::new("t").unwrap(),
128            Specialty::new("triage").unwrap(),
129            ProposalId::new("p").unwrap(),
130            Score::new(0.87).unwrap(),
131            3,
132            DurationMs::from_millis(900),
133        );
134        assert_eq!(ev.num_candidates(), 3);
135        assert_eq!(ev.winner_score().get(), 0.87);
136        assert_eq!(ev.duration().get(), 900);
137    }
138}