domain/daycare/front_desk.rs
1//! Daycare front-desk readiness decisions for faster, safer check-in lanes.
2//!
3//! ```
4//! use domain::{daycare, entities};
5//! use uuid::Uuid;
6//!
7//! let context = daycare::front_desk::ReadinessContext::builder()
8//! .reservation_id(entities::reservation::Id(Uuid::nil()))
9//! .service(daycare::ServiceVariant::DayBoarding)
10//! .eligibility(daycare::front_desk::EligibilityReadiness::IndividualCareReady)
11//! .coverage(daycare::coverage::Decision::Sufficient)
12//! .care(daycare::front_desk::CareReadiness::Ready)
13//! .package(daycare::front_desk::PackageReadiness::NeedsFrontDeskCollection)
14//! .customer_message(daycare::front_desk::CustomerMessageReadiness::NoMessageNeeded)
15//! .build();
16//! let decision = daycare::front_desk::ThroughputPolicy.evaluate(&context);
17//! let ticket = daycare::front_desk::QueueTicket::new(
18//! daycare::front_desk::QueuePosition::try_new(1).unwrap(),
19//! decision,
20//! );
21//!
22//! assert_eq!(ticket.lane(), daycare::front_desk::QueueLane::CollectionLane);
23//! ```
24
25use super::*;
26use crate::{entities, policy};
27
28positive_scalar!(
29 QueuePosition,
30 u16,
31 QueuePositionError,
32 "front-desk queue position requires at least one ticket position"
33);
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
36/// Combined daycare check-in evidence used to route a front-desk queue ticket.
37pub struct ReadinessContext {
38 /// Reservation being prepared for check-in or staff review.
39 pub reservation_id: entities::reservation::Id,
40 /// Requested service that drives scheduling and labor estimates.
41 pub service: ServiceVariant,
42 /// Eligibility readiness for the requested daycare care mode.
43 pub eligibility: EligibilityReadiness,
44 /// Staffing coverage state that may block or route check-in.
45 pub coverage: coverage::Decision,
46 /// Care-team readiness for special handling, medical, or behavior review.
47 pub care: CareReadiness,
48 /// Package/payment readiness controlling collection or manager review at check-in.
49 pub package: PackageReadiness,
50 /// Customer-message approval state for any drafted daycare communication.
51 pub customer_message: CustomerMessageReadiness,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55/// Eligibility readiness category used by front-desk routing.
56pub enum EligibilityReadiness {
57 /// Group-play add-on or accommodation feature.
58 GroupPlay(eligibility::GroupPlayDecision),
59 /// Individual-care service does not need group-play clearance for check-in.
60 IndividualCareReady,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64/// Care-team readiness state for daycare check-in.
65pub enum CareReadiness {
66 /// Care-team medical, behavior, and handling evidence has no unresolved check-in blocker.
67 ///
68 /// Package/payment and manager policy readiness are represented by `PackageReadiness`.
69 Ready,
70 /// Care team must clear medical, behavior, or handling evidence before check-in advances.
71 ///
72 /// This gate is not the package/payment or manager policy readiness gate.
73 NeedsCareTeamReview {
74 /// Specific care-team gate that must clear before daycare check-in advances.
75 gate: policy::ReviewGate,
76 },
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80/// Package/payment readiness state used to route daycare front-desk work.
81pub enum PackageReadiness {
82 /// Package and payment state is clear enough for front desk to skip collection.
83 Ready,
84 /// Front desk must collect payment, package visits, or missing account information.
85 NeedsFrontDeskCollection,
86 /// Manager must review package, payment, or policy ambiguity before check-in advances.
87 NeedsManagerReview {
88 /// Specific gate the responsible team must clear before daycare check-in advances.
89 gate: policy::ReviewGate,
90 },
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
94/// Approval status for daycare customer-message drafts.
95pub enum CustomerMessageReadiness {
96 /// No customer-facing message is required for this check-in path.
97 NoMessageNeeded,
98 /// A drafted customer message must be approved before it is sent or used.
99 DraftNeedsApproval,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103/// Front-desk routing outcome for a daycare check-in ticket.
104pub enum ReadinessDecision {
105 /// Ticket can move through the fast lane without extra collection or review.
106 ReadyToCheckIn,
107 /// Front desk must collect payment, package visits, or missing account information.
108 NeedsFrontDeskCollection,
109 /// Care team must clear medical, behavior, or handling evidence before check-in advances.
110 NeedsCareTeamReview {
111 /// Specific gate the responsible team must clear before daycare check-in advances.
112 gate: policy::ReviewGate,
113 },
114 /// Manager must clear operational or package/payment policy before check-in advances.
115 NeedsManagerReview {
116 /// Specific gate the responsible team must clear before daycare check-in advances.
117 gate: policy::ReviewGate,
118 },
119 /// Safety or policy block prevents check-in until the named gate is resolved.
120 BlockedForSafetyOrPolicy {
121 /// Specific gate the responsible team must clear before daycare check-in advances.
122 gate: policy::ReviewGate,
123 },
124}
125
126impl ReadinessDecision {
127 /// Returns any approval gate tied specifically to customer-message handling for this decision.
128 pub fn customer_message_gate(&self) -> Option<policy::ReviewGate> {
129 match self {
130 Self::ReadyToCheckIn
131 | Self::NeedsFrontDeskCollection
132 | Self::NeedsCareTeamReview { .. }
133 | Self::NeedsManagerReview { .. }
134 | Self::BlockedForSafetyOrPolicy { .. } => None,
135 }
136 }
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
140/// Physical front-desk lane that routes arrivals, pickups, incidents, billing, and package help.
141pub enum QueueLane {
142 /// Queue lane for check-ins with all readiness gates clear.
143 FastLane,
144 /// Queue lane for tickets needing payment or package collection.
145 CollectionLane,
146 /// Queue lane for tickets needing care-team or behavior review.
147 CareTeamReviewLane,
148 /// Queue lane for manager approval before check-in proceeds.
149 ManagerReviewLane,
150 /// Queue lane for blocked, ineligible, or policy-limited attendance paths.
151 WaitlistOrPolicyLane,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155/// Front-desk queue ticket pairing a position with the readiness decision that determines lane routing.
156pub struct QueueTicket {
157 position: QueuePosition,
158 decision: ReadinessDecision,
159}
160
161impl QueueTicket {
162 /// Creates a queue ticket from a validated position and readiness decision.
163 pub const fn new(position: QueuePosition, decision: ReadinessDecision) -> Self {
164 Self { position, decision }
165 }
166
167 /// Maps the readiness decision to the operational queue lane staff should use.
168 pub const fn lane(&self) -> QueueLane {
169 match self.decision {
170 ReadinessDecision::ReadyToCheckIn => QueueLane::FastLane,
171 ReadinessDecision::NeedsFrontDeskCollection => QueueLane::CollectionLane,
172 ReadinessDecision::NeedsCareTeamReview { .. } => QueueLane::CareTeamReviewLane,
173 ReadinessDecision::NeedsManagerReview { .. } => QueueLane::ManagerReviewLane,
174 ReadinessDecision::BlockedForSafetyOrPolicy { .. } => QueueLane::WaitlistOrPolicyLane,
175 }
176 }
177}
178
179#[derive(Debug, Clone, Default)]
180/// Deterministic policy for turning daycare readiness evidence into front-desk routing.
181pub struct ThroughputPolicy;
182
183impl ThroughputPolicy {
184 /// Evaluates message, package, care, coverage, and eligibility gates in front-desk priority order.
185 pub fn evaluate(&self, context: &ReadinessContext) -> ReadinessDecision {
186 if let CustomerMessageReadiness::DraftNeedsApproval = context.customer_message {
187 return ReadinessDecision::NeedsManagerReview {
188 gate: policy::ReviewGate::CustomerMessageApproval,
189 };
190 }
191 if let PackageReadiness::NeedsManagerReview { gate } = &context.package {
192 return ReadinessDecision::NeedsManagerReview { gate: gate.clone() };
193 }
194 if let PackageReadiness::NeedsFrontDeskCollection = context.package {
195 return ReadinessDecision::NeedsFrontDeskCollection;
196 }
197 if let CareReadiness::NeedsCareTeamReview { gate } = &context.care {
198 return ReadinessDecision::NeedsCareTeamReview { gate: gate.clone() };
199 }
200 if let coverage::Decision::Insufficient { gate, .. }
201 | coverage::Decision::Unknown { gate } = &context.coverage
202 {
203 return ReadinessDecision::NeedsManagerReview { gate: gate.clone() };
204 }
205 match &context.eligibility {
206 EligibilityReadiness::IndividualCareReady => ReadinessDecision::ReadyToCheckIn,
207 EligibilityReadiness::GroupPlay(eligibility::GroupPlayDecision::Eligible {
208 ..
209 }) => ReadinessDecision::ReadyToCheckIn,
210 EligibilityReadiness::GroupPlay(eligibility::GroupPlayDecision::NeedsStaffReview {
211 gate,
212 ..
213 }) => ReadinessDecision::NeedsCareTeamReview { gate: gate.clone() },
214 EligibilityReadiness::GroupPlay(
215 eligibility::GroupPlayDecision::TemporarilySuspended { gate, .. },
216 ) => ReadinessDecision::NeedsManagerReview { gate: gate.clone() },
217 EligibilityReadiness::GroupPlay(eligibility::GroupPlayDecision::Ineligible {
218 ..
219 }) => ReadinessDecision::BlockedForSafetyOrPolicy {
220 gate: policy::ReviewGate::ManagerApproval,
221 },
222 }
223 }
224}