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}