domain/daycare/assignment.rs
1//! Daycare playgroup assignment decisions after eligibility and coverage are known.
2//!
3//! ```
4//! use domain::{daycare, entities};
5//! use uuid::Uuid;
6//!
7//! let request = daycare::assignment::Request::builder()
8//! .pet_id(entities::PetId(Uuid::nil()))
9//! .service(daycare::ServiceVariant::AllDayPlay)
10//! .eligibility(daycare::eligibility::GroupPlayDecision::Eligible {
11//! basis: daycare::eligibility::EligibleBasis::CurrentEvidence,
12//! })
13//! .coverage(daycare::coverage::Decision::Sufficient)
14//! .playgroup(daycare::assignment::PlaygroupId::try_new("small-dogs-am").unwrap())
15//! .build();
16//!
17//! assert!(matches!(
18//! daycare::assignment::Service.assign(request),
19//! daycare::assignment::Decision::Assigned { .. }
20//! ));
21//! ```
22
23use super::*;
24use crate::policy;
25
26pub use playgroup_id::Id as PlaygroupId;
27
28/// Playgroup identifier chosen from scheduling/source data for daycare assignment review.
29pub mod playgroup_id {
30 use super::*;
31
32 #[nutype(
33 sanitize(trim),
34 validate(not_empty, len_char_max = 120),
35 derive(
36 Debug,
37 Clone,
38 PartialEq,
39 Eq,
40 PartialOrd,
41 Ord,
42 Hash,
43 Serialize,
44 Deserialize
45 )
46 )]
47 pub struct Id(String);
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
51/// Assignment request that joins pet, service, eligibility, coverage, and target playgroup evidence.
52pub struct Request {
53 /// Pet being considered for playgroup assignment.
54 pub pet_id: PetId,
55 /// Requested service that drives scheduling and labor estimates.
56 pub service: ServiceVariant,
57 /// Group-play eligibility decision that must be clear before assignment.
58 pub eligibility: eligibility::GroupPlayDecision,
59 /// Staffing coverage decision that must be sufficient before assignment.
60 pub coverage: coverage::Decision,
61 /// Candidate playgroup selected from resort operations data.
62 pub playgroup: PlaygroupId,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66/// Playgroup assignment outcome staff can act on or review.
67pub enum Decision {
68 /// Pet may be placed in the requested playgroup.
69 Assigned {
70 /// Pet being considered for playgroup assignment.
71 pet_id: PetId,
72 /// Candidate playgroup selected from resort operations data.
73 playgroup: PlaygroupId,
74 },
75 /// Pet is eligible, but staffing coverage prevents immediate assignment.
76 Waitlist {
77 /// Operational reason the assignment is not automatically clear.
78 reason: WaitlistReason,
79 /// Human review gate required before staff override this assignment outcome.
80 gate: policy::ReviewGate,
81 },
82 /// Pet cannot be assigned until eligibility or safety review clears.
83 Blocked {
84 /// Operational reason the assignment is not automatically clear.
85 reason: BlockReason,
86 /// Human review gate required before staff override this assignment outcome.
87 gate: policy::ReviewGate,
88 },
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
92/// Reasons a daycare assignment should waitlist instead of placing the pet.
93pub enum WaitlistReason {
94 /// Staffing ratio or roster evidence does not support another playgroup assignment.
95 StaffCoverageInsufficient,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
99/// Reasons assignment is blocked rather than merely waitlisted.
100pub enum BlockReason {
101 /// Group-play eligibility is not clear enough for playgroup assignment.
102 EligibilityNotCleared,
103}
104
105#[derive(Debug, Clone, Default)]
106/// Deterministic daycare assignment service for playgroup placement decisions.
107pub struct Service;
108
109impl Service {
110 /// Assigns, waitlists, or blocks a pet from playgroup placement using eligibility and coverage evidence.
111 pub fn assign(&self, request: Request) -> Decision {
112 match (&request.eligibility, &request.coverage) {
113 (eligibility::GroupPlayDecision::Eligible { .. }, coverage::Decision::Sufficient) => {
114 Decision::Assigned {
115 pet_id: request.pet_id,
116 playgroup: request.playgroup,
117 }
118 }
119 (
120 eligibility::GroupPlayDecision::Eligible { .. },
121 coverage::Decision::Insufficient { gate, .. },
122 ) => Decision::Waitlist {
123 reason: WaitlistReason::StaffCoverageInsufficient,
124 gate: gate.clone(),
125 },
126 (
127 eligibility::GroupPlayDecision::Eligible { .. },
128 coverage::Decision::Unknown { gate },
129 ) => Decision::Waitlist {
130 reason: WaitlistReason::StaffCoverageInsufficient,
131 gate: gate.clone(),
132 },
133 _ => Decision::Blocked {
134 reason: BlockReason::EligibilityNotCleared,
135 gate: policy::ReviewGate::BehaviorReview,
136 },
137 }
138 }
139}