Skip to main content

domain/boarding/
upsell.rs

1//! Boarding upsell recommendations for exit baths, play, grooming, suite, and training offers.
2//!
3//! Recommendations carry care-safety gates so labor-saving offer drafting never bypasses staff
4//! review for allergies, medications, medical conditions, behavior context, or customer messaging.
5
6use super::*;
7use crate::{entities, policy};
8
9#[derive(Debug, Clone, Default)]
10/// Boarding upsell policy that classifies offer eligibility from care evidence.
11pub struct Policy;
12
13impl Policy {
14    /// Evaluates whether an exit-bath offer can be drafted or must be held for care-team review.
15    pub fn evaluate_exit_bath(
16        &self,
17        reservation_id: entities::reservation::Id,
18        pet_id: PetId,
19        care_profile: &entities::CareProfile,
20    ) -> Recommendation {
21        let eligibility = if care_profile.allergies.is_empty()
22            && care_profile.medical_conditions.is_empty()
23            && care_profile.medications.is_empty()
24        {
25            Eligibility::Eligible
26        } else {
27            Eligibility::NeedsStaffReview {
28                gate: policy::ReviewGate::MedicalDocumentReview,
29                reason: ReviewReason::CareSafetyAmbiguity,
30            }
31        };
32
33        Recommendation {
34            reservation_id,
35            pet_id,
36            opportunity: Opportunity::ExitBath,
37            eligibility,
38        }
39    }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43/// Staff-reviewable boarding upsell recommendation with source reservation, pet, and eligibility evidence.
44pub struct Recommendation {
45    /// Boarding reservation the offer would attach to.
46    pub reservation_id: entities::reservation::Id,
47    /// Pet whose stay evidence drives the upsell recommendation.
48    pub pet_id: PetId,
49    /// Upsell opportunity identified for this stay.
50    pub opportunity: Opportunity,
51    /// Safety and review state controlling whether staff may offer the upsell.
52    pub eligibility: Eligibility,
53}
54
55impl Recommendation {
56    /// Returns the approval gate required before any recommendation becomes customer-facing.
57    pub fn customer_offer_gate(&self) -> Option<policy::ReviewGate> {
58        Some(policy::ReviewGate::CustomerMessageApproval)
59    }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63/// Boarding-adjacent revenue opportunities that can be recommended from stay evidence.
64pub enum Opportunity {
65    /// Bath offered before departure from boarding.
66    ExitBath,
67    /// Additional enrichment or playtime during the boarding stay.
68    Playtime,
69    /// Grooming service line or care-note category.
70    Grooming,
71    /// Upgrade opportunity for a higher-tier boarding accommodation.
72    PremiumSuite,
73    /// Training service line or care-note category.
74    Training,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78/// Safety and review status for presenting a boarding upsell recommendation.
79pub enum Eligibility {
80    /// Source care evidence shows no safety ambiguity, so staff may review the offer normally.
81    Eligible,
82    /// Recommendation should not be shown because context makes the offer inappropriate.
83    Suppressed {
84        /// Reason this recommendation is suppressed or must be reviewed before use.
85        reason: SuppressionReason,
86    },
87    /// Care, behavior, or capacity evidence requires staff review before the offer is used.
88    NeedsStaffReview {
89        /// Review gate staff must clear before presenting this recommendation.
90        gate: policy::ReviewGate,
91        /// Reason this recommendation is suppressed or must be reviewed before use.
92        reason: ReviewReason,
93    },
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
97/// Reasons an otherwise possible upsell should be hidden from staff or customer drafts.
98pub enum SuppressionReason {
99    /// A recent similar offer exists, so repeating it would add noise instead of labor savings.
100    DuplicateRecentOffer,
101    /// Billing, refund, or complaint context makes a sales offer unsafe or inappropriate.
102    PaymentOrComplaintContext,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
106/// Reasons staff must review an upsell recommendation before use.
107pub enum ReviewReason {
108    /// Allergies, medications, or medical conditions make the offer safety-sensitive.
109    CareSafetyAmbiguity,
110    /// Behavior notes make the offer sensitive enough for staff review.
111    BehaviorContextSensitive,
112    /// Inventory or staffing evidence is needed before staff can offer the upgrade.
113    CapacityEvidenceRequired,
114}