Skip to main content

domain/
data_quality.rs

1//! Data-quality findings that guard the source-fact → domain → workflow chain.
2//!
3//! These types keep messy provider facts visible instead of silently normalizing them:
4//! blocking issues stop unsafe automation, reviewable issues remain attached to analytics
5//! facts and manager briefs, and BI-visible status lets labor-cost reports explain when
6//! data hygiene—not actual demand or staffing—is driving an apparent exception.
7
8use serde::{Deserialize, Serialize};
9
10use crate::source;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13/// Path segment vocabulary for source fields that can fail validation.
14pub enum FieldSegment {
15    /// Reservation record participating in the workflow.
16    Reservation,
17    /// Stay projection path used when read-model evidence is incomplete or inconsistent.
18    Stay,
19    /// Source metadata path used when provenance or payload evidence is missing.
20    Source,
21    /// Customer record id is missing or ambiguous, blocking safe owner communication and merge decisions.
22    CustomerRecordId,
23    /// Pet record id is missing or ambiguous, blocking care, vaccine, and temperament confidence.
24    PetRecordId,
25    /// Location record id is missing or ambiguous, blocking resort-specific labor and capacity reporting.
26    LocationRecordId,
27    /// Service type record id is missing or unmapped, blocking correct service-line grouping.
28    ServiceTypeRecordId,
29    /// Status value is missing, conflicting, or unmapped, so workflow state must be reviewed.
30    Status,
31    /// Owner-pet relationship is ambiguous, blocking customer communication and profile cleanup automation.
32    OwnerPetRelationship,
33    /// Source record id is missing, so the issue cannot be traced back for repair.
34    RecordId,
35    /// Source endpoint is missing, so adapter evidence cannot be audited confidently.
36    Endpoint,
37    /// Payload hash is missing, weakening replay and tamper-evidence for source review.
38    PayloadHash,
39    /// Raw payload reference is missing or quarantined, limiting audit and repair evidence.
40    RawPayloadRef,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
44/// Reservation fields whose absence or ambiguity can block workflow projections.
45pub enum ReservationField {
46    /// Customer record id is missing or ambiguous, blocking safe owner communication and merge decisions.
47    CustomerRecordId,
48    /// Pet record id is missing or ambiguous, blocking care, vaccine, and temperament confidence.
49    PetRecordId,
50    /// Location record id is missing or ambiguous, blocking resort-specific labor and capacity reporting.
51    LocationRecordId,
52    /// Service type record id is missing or unmapped, blocking correct service-line grouping.
53    ServiceTypeRecordId,
54    /// Status value is missing, conflicting, or unmapped, so workflow state must be reviewed.
55    Status,
56    /// Owner-pet relationship is ambiguous, blocking customer communication and profile cleanup automation.
57    OwnerPetRelationship,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61/// Stay/read-model fields checked before analytics and labor views trust a record.
62pub enum StayField {
63    /// Stay id is missing, so analytics cannot safely join or deduplicate the projected fact.
64    Id,
65    /// Pet record id is missing or ambiguous, blocking care, vaccine, and temperament confidence.
66    PetRecordId,
67    /// Location record id is missing or ambiguous, blocking resort-specific labor and capacity reporting.
68    LocationRecordId,
69    /// Status value is missing, conflicting, or unmapped, so workflow state must be reviewed.
70    Status,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
74/// Source-ingestion metadata fields needed to preserve provenance and auditability.
75pub enum SourceField {
76    /// Source record id is missing, so the issue cannot be traced back for repair.
77    RecordId,
78    /// Source endpoint is missing, so adapter evidence cannot be audited confidently.
79    Endpoint,
80    /// Payload hash is missing, weakening replay and tamper-evidence for source review.
81    PayloadHash,
82    /// Raw payload reference is missing or quarantined, limiting audit and repair evidence.
83    RawPayloadRef,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
87/// Semantic path to the source field that produced a data-quality issue.
88pub enum FieldPath {
89    /// Reservation record participating in the workflow.
90    Reservation(ReservationField),
91    /// Stay projection path used when read-model evidence is incomplete or inconsistent.
92    Stay(StayField),
93    /// Source metadata path used when provenance or payload evidence is missing.
94    Source(SourceField),
95}
96
97impl FieldPath {
98    /// Builds a reservation-field path for a source-data-quality issue.
99    pub const fn reservation(field: ReservationField) -> Self {
100        Self::Reservation(field)
101    }
102
103    /// Builds a stay/read-model-field path for a source-data-quality issue.
104    pub const fn stay(field: StayField) -> Self {
105        Self::Stay(field)
106    }
107
108    /// Builds a source-metadata-field path for a source-data-quality issue.
109    pub const fn source(field: SourceField) -> Self {
110        Self::Source(field)
111    }
112
113    /// Returns stable path segments for repair queues, BI dimensions, and manager review.
114    pub const fn segments(&self) -> &'static [FieldSegment] {
115        match self {
116            Self::Reservation(ReservationField::CustomerRecordId) => {
117                &[FieldSegment::Reservation, FieldSegment::CustomerRecordId]
118            }
119            Self::Reservation(ReservationField::PetRecordId) => {
120                &[FieldSegment::Reservation, FieldSegment::PetRecordId]
121            }
122            Self::Reservation(ReservationField::LocationRecordId) => {
123                &[FieldSegment::Reservation, FieldSegment::LocationRecordId]
124            }
125            Self::Reservation(ReservationField::ServiceTypeRecordId) => {
126                &[FieldSegment::Reservation, FieldSegment::ServiceTypeRecordId]
127            }
128            Self::Reservation(ReservationField::Status) => {
129                &[FieldSegment::Reservation, FieldSegment::Status]
130            }
131            Self::Reservation(ReservationField::OwnerPetRelationship) => &[
132                FieldSegment::Reservation,
133                FieldSegment::OwnerPetRelationship,
134            ],
135            Self::Stay(StayField::Id) => &[FieldSegment::Stay, FieldSegment::RecordId],
136            Self::Stay(StayField::PetRecordId) => &[FieldSegment::Stay, FieldSegment::PetRecordId],
137            Self::Stay(StayField::LocationRecordId) => {
138                &[FieldSegment::Stay, FieldSegment::LocationRecordId]
139            }
140            Self::Stay(StayField::Status) => &[FieldSegment::Stay, FieldSegment::Status],
141            Self::Source(SourceField::RecordId) => &[FieldSegment::Source, FieldSegment::RecordId],
142            Self::Source(SourceField::Endpoint) => &[FieldSegment::Source, FieldSegment::Endpoint],
143            Self::Source(SourceField::PayloadHash) => {
144                &[FieldSegment::Source, FieldSegment::PayloadHash]
145            }
146            Self::Source(SourceField::RawPayloadRef) => {
147                &[FieldSegment::Source, FieldSegment::RawPayloadRef]
148            }
149        }
150    }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154/// Kind of hygiene defect found while validating source facts.
155pub enum Kind {
156    /// Required source field is absent, so projection or automation must stop until staff/source repair happens.
157    MissingRequiredField {
158        /// Source field whose missing value or conflict created the data-quality issue.
159        field: FieldPath,
160    },
161    /// Source mapper used an assumption that must remain visible before managers trust derived workflow data.
162    AssumptionInForce {
163        /// Assumption staff or managers must review before the workflow trusts derived data.
164        assumption: source::reservation::Assumption,
165    },
166    /// Provider status was not mapped to known workflow vocabulary and needs review before automation acts.
167    UnknownSourceStatus {
168        /// Provider status text retained before normalization.
169        observed: source::ObservedStatus,
170    },
171    /// Source timestamps conflict, so ordering-sensitive stay or checkout decisions need human review.
172    ConflictingTimestamps,
173    /// Duplicate source record may inflate demand, revenue, or workload until a human verifies the duplicate.
174    DuplicateSourceRecord,
175    /// Owner-pet relationship ambiguity blocks safe customer messaging and profile merge work.
176    AmbiguousOwnerPetRelationship,
177    /// Service type cannot be mapped to boarding, daycare, grooming, training, or retail without review.
178    UnmappedServiceType,
179    /// Location scope is unclear, so regional or local labor reporting may point at the wrong resort.
180    LocationScopeAmbiguity,
181    /// Payment state conflicts must be reviewed before checkout, invoice, refund, or discount work proceeds.
182    PaymentStateConflict,
183    /// Checkout evidence is missing, so stay completion and billing summaries cannot be trusted automatically.
184    CheckoutEvidenceMissing,
185    /// Unclosed reservation may distort occupancy, labor, and checkout queues until staff closes or explains it.
186    UnclosedReservation,
187    /// Incomplete pet profile blocks reliable care, eligibility, and customer-update drafts.
188    IncompletePetProfile,
189    /// Missing vaccination record blocks safety-sensitive eligibility claims until documentation is reviewed.
190    MissingVaccinationRecord,
191    /// Sensitive payload was quarantined, so automation may preserve audit evidence but must not expose secrets or PII.
192    SensitivePayloadQuarantined,
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
196/// Severity of a hygiene defect and its effect on read-model or workflow safety.
197pub enum Severity {
198    /// Informational issue stays visible for audit and trend reporting without blocking workflow use.
199    Informational,
200    /// Warning issue allows read-model use only with manager-visible context.
201    Warning,
202    /// Blocking issue stops projection or automation until source evidence is repaired or reviewed.
203    Blocking,
204    /// Critical issue signals source safety or trust failure that must be escalated before workflow use.
205    Critical,
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
209/// Lifecycle state for a data-quality issue as staff acknowledge or repair it.
210pub enum ResolutionStatus {
211    /// Open issue still needs staff or source-system action.
212    Open,
213    /// Acknowledged issue has been seen by staff but remains part of evidence shown to workflows.
214    Acknowledged,
215    /// Ignored issue is intentionally bypassed for a workflow, preserving audit context for why.
216    Ignored,
217    /// Repaired issue records that source or profile cleanup resolved the defect.
218    Repaired,
219    /// Superseded issue was replaced by fresher evidence or a more precise finding.
220    Superseded,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
224/// Evidence-backed data-quality issue attached to source records and derived facts.
225pub struct Issue {
226    kind: Kind,
227    severity: Severity,
228    provenance: source::Provenance,
229    source_record_ref: source::RecordRef,
230    detected_at: source::Timestamp,
231    resolution_status: ResolutionStatus,
232    visible_to_bi: bool,
233    workflow_blocking: bool,
234}
235
236impl Issue {
237    /// Creates an open data-quality issue from provenance and validated issue metadata.
238    pub fn new(
239        kind: Kind,
240        severity: Severity,
241        provenance: source::Provenance,
242        detected_at: source::Timestamp,
243        workflow_blocking: bool,
244    ) -> Self {
245        let source_record_ref = source::RecordRef::from_provenance(&provenance);
246        Self {
247            kind,
248            severity,
249            provenance,
250            source_record_ref,
251            detected_at,
252            resolution_status: ResolutionStatus::Open,
253            visible_to_bi: true,
254            workflow_blocking,
255        }
256    }
257
258    /// Returns the concrete hygiene defect that explains why a source fact is blocked or reviewable.
259    pub fn kind(&self) -> Kind {
260        self.kind.clone()
261    }
262
263    /// Returns how strongly the issue should affect projection, manager review, or escalation.
264    pub const fn severity(&self) -> Severity {
265        self.severity
266    }
267
268    /// Returns the provider system that produced the record needing cleanup or review.
269    pub const fn source_system(&self) -> source::System {
270        self.source_record_ref.system()
271    }
272
273    /// Returns the auditable source record reference staff can use to repair the defect.
274    pub const fn source_record_ref(&self) -> &source::RecordRef {
275        &self.source_record_ref
276    }
277
278    /// Returns the provenance chain that keeps cleanup evidence tied to provider ingestion.
279    pub const fn provenance(&self) -> &source::Provenance {
280        &self.provenance
281    }
282
283    /// Returns when the issue was detected so stale hygiene can be prioritized.
284    pub const fn detected_at(&self) -> &source::Timestamp {
285        &self.detected_at
286    }
287
288    /// Returns whether the hygiene issue is open, acknowledged, ignored, repaired, or superseded.
289    pub const fn resolution_status(&self) -> ResolutionStatus {
290        self.resolution_status
291    }
292
293    /// Returns whether BI/labor reports should show the issue as context for apparent exceptions.
294    pub const fn visible_to_bi(&self) -> bool {
295        self.visible_to_bi
296    }
297
298    /// Returns whether this issue must stop automation or projection into labor workflows.
299    pub const fn workflow_blocking(&self) -> bool {
300        self.workflow_blocking
301    }
302}