domain/data_quality.rs
1//! Data-quality findings that guard the source-fact → domain → workflow chain.
2//!
3//! These contracts 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 data-quality finding for cleanup or review.
18 Stay,
19 /// Source data-quality finding for cleanup or review.
20 Source,
21 /// Customer record id data-quality finding for cleanup or review.
22 CustomerRecordId,
23 /// Pet record id data-quality finding for cleanup or review.
24 PetRecordId,
25 /// Location record id data-quality finding for cleanup or review.
26 LocationRecordId,
27 /// Service type record id data-quality finding for cleanup or review.
28 ServiceTypeRecordId,
29 /// Status data-quality finding for cleanup or review.
30 Status,
31 /// Owner pet relationship data-quality finding for cleanup or review.
32 OwnerPetRelationship,
33 /// Record id data-quality finding for cleanup or review.
34 RecordId,
35 /// Endpoint data-quality finding for cleanup or review.
36 Endpoint,
37 /// Payload hash data-quality finding for cleanup or review.
38 PayloadHash,
39 /// Raw payload ref data-quality finding for cleanup or review.
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 data-quality finding for cleanup or review.
47 CustomerRecordId,
48 /// Pet record id data-quality finding for cleanup or review.
49 PetRecordId,
50 /// Location record id data-quality finding for cleanup or review.
51 LocationRecordId,
52 /// Service type record id data-quality finding for cleanup or review.
53 ServiceTypeRecordId,
54 /// Status data-quality finding for cleanup or review.
55 Status,
56 /// Owner pet relationship data-quality finding for cleanup or review.
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 /// Id data-quality finding for cleanup or review.
64 Id,
65 /// Pet record id data-quality finding for cleanup or review.
66 PetRecordId,
67 /// Location record id data-quality finding for cleanup or review.
68 LocationRecordId,
69 /// Status data-quality finding for cleanup or review.
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 /// Record id data-quality finding for cleanup or review.
77 RecordId,
78 /// Endpoint data-quality finding for cleanup or review.
79 Endpoint,
80 /// Payload hash data-quality finding for cleanup or review.
81 PayloadHash,
82 /// Raw payload ref data-quality finding for cleanup or review.
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 data-quality finding for cleanup or review.
92 Stay(StayField),
93 /// Source data-quality finding for cleanup or review.
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 /// Missing required field data-quality finding for cleanup or review.
157 MissingRequiredField {
158 /// Field fact promoted into this data quality contract.
159 field: FieldPath,
160 },
161 /// Assumption in force data-quality finding for cleanup or review.
162 AssumptionInForce {
163 /// Assumption fact promoted into this data quality contract.
164 assumption: source::reservation::Assumption,
165 },
166 /// Unknown source status data-quality finding for cleanup or review.
167 UnknownSourceStatus {
168 /// Provider status text retained before normalization.
169 observed: source::ObservedStatus,
170 },
171 /// Conflicting timestamps data-quality finding for cleanup or review.
172 ConflictingTimestamps,
173 /// Duplicate source record data-quality finding for cleanup or review.
174 DuplicateSourceRecord,
175 /// Ambiguous owner pet relationship data-quality finding for cleanup or review.
176 AmbiguousOwnerPetRelationship,
177 /// Unmapped service type data-quality finding for cleanup or review.
178 UnmappedServiceType,
179 /// Location scope ambiguity data-quality finding for cleanup or review.
180 LocationScopeAmbiguity,
181 /// Payment state conflict data-quality finding for cleanup or review.
182 PaymentStateConflict,
183 /// Checkout evidence missing data-quality finding for cleanup or review.
184 CheckoutEvidenceMissing,
185 /// Unclosed reservation data-quality finding for cleanup or review.
186 UnclosedReservation,
187 /// Incomplete pet profile data-quality finding for cleanup or review.
188 IncompletePetProfile,
189 /// Missing vaccination record data-quality finding for cleanup or review.
190 MissingVaccinationRecord,
191 /// Sensitive payload quarantined data-quality finding for cleanup or review.
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 data-quality finding for cleanup or review.
199 Informational,
200 /// Warning data-quality finding for cleanup or review.
201 Warning,
202 /// Blocking data-quality finding for cleanup or review.
203 Blocking,
204 /// Critical data-quality finding for cleanup or review.
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 data-quality finding for cleanup or review.
212 Open,
213 /// Acknowledged data-quality finding for cleanup or review.
214 Acknowledged,
215 /// Ignored data-quality finding for cleanup or review.
216 Ignored,
217 /// Repaired data-quality finding for cleanup or review.
218 Repaired,
219 /// Superseded data-quality finding for cleanup or review.
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 kind for this data quality value.
259 pub fn kind(&self) -> Kind {
260 self.kind.clone()
261 }
262
263 /// Returns this data quality value's severity.
264 pub const fn severity(&self) -> Severity {
265 self.severity
266 }
267
268 /// Returns this data quality value's source system.
269 pub const fn source_system(&self) -> source::System {
270 self.source_record_ref.system()
271 }
272
273 /// Returns this data quality value's source record ref.
274 pub const fn source_record_ref(&self) -> &source::RecordRef {
275 &self.source_record_ref
276 }
277
278 /// Returns this data quality value's provenance.
279 pub const fn provenance(&self) -> &source::Provenance {
280 &self.provenance
281 }
282
283 /// Returns this data quality value's detected at.
284 pub const fn detected_at(&self) -> &source::Timestamp {
285 &self.detected_at
286 }
287
288 /// Returns this data quality value's resolution status.
289 pub const fn resolution_status(&self) -> ResolutionStatus {
290 self.resolution_status
291 }
292
293 /// Returns this data quality value's visible to bi.
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}