# Data-quality rules Run rules after seed/import, after relevant commands and through an explicit scan service. Rules must be deterministic and explainable. ## DQ-01 Possible duplicate customer Signals and example weights: - exact normalized email: 60; - exact normalized phone: 50; - exact postal code: 10; - strong normalized full-name similarity: up to 30. Open an issue at score >= 70. Store individual signals; do not expose a mysterious AI-only confidence. Resolution: - merge into selected survivor; - reject as not duplicate; - defer. Merge rewires booking references, preserves the loser as a tombstone and audits before/after values. ## DQ-02 Missing required field Required for active customers: first name, last name and at least one of email or phone. Required for active vehicles: registration number, make, model and location. Resolution: `POST /provide-fields` accepts only the fields the entity type actually requires (rejects anything else), applies them, and re-runs the same missing-field check. The issue resolves only once nothing required remains missing; a partial submission updates the record and its evidence but leaves the issue open. ## DQ-03 Odometer regression Flag a newly recorded checkout, return-inspection or maintenance reading below the canonical odometer. The deterministic import scan compares booking, inspection and maintenance readings in chronological order. A return inspection's actual completion time is authoritative over the booking's planned end, so an early return cannot be mistaken for a later regression. Never lower the canonical value automatically. When another regression is recorded while the vehicle already has an open DQ-03 issue, append the new source reference and signal to that issue. Do not silently discard the new evidence and do not open parallel issues for the same vehicle. Resolution: `POST /resolve-odometer-regression` offers exactly two bounded decisions — `retain_canonical` (the submitted reading is treated as erroneous; canonical is untouched) or `correct_reading`. The correction option is offered only for an explicitly named, correctable booking reading; checkout-only and maintenance-only issues can only retain the canonical value. A corrected return updates the booking, its related return inspection and the vehicle's canonical odometer in one transaction. A `correct_reading` value below the current canonical is rejected, since it would not resolve the regression, not silently applied. When one open issue contains several source readings, correcting one removes only that booking/inspection evidence and keeps the issue open until every remaining signal has received a bounded decision. ## DQ-04 Booking overlap Flag overlapping `reserved` or `active` bookings for one vehicle. Normal write APIs reject new overlaps; the seed/import path may create one controlled legacy conflict. Resolution: `POST /resolve-overlap` blocks one of the two named overlapping bookings (minimal safe resolution, not a scheduling calendar) and re-verifies no reserved/active overlap remains among the issue's related bookings before resolving. ## DQ-05 Vehicle status conflict Examples: - status `available` while an active booking exists; - status `rented` without an active booking; - status `available` while critical open quality issue exists; - status `maintenance` with an active booking. Resolution: `POST /apply-recommended-status` computes a recommendation from one authoritative function mirroring the conditions above, applies it, and re-runs the same function to confirm the conflict is actually gone before resolving. ## Lifecycle Detection is idempotent by `(rule_type, entity_type, entity_id)` while open. Source-aware rules additionally store stable evidence identity. For DQ-03 this is the tuple `(source_type, later_ref, later_km)`: an explicit `retain_canonical` decision suppresses only that exact fact on later scans. A changed reading or a different source is actionable again. Resolved issues remain historical. Reintroduced evidence creates a new issue whose evidence carries `reopened_from` (the prior issue's reference) and `previous_decision` (its resolved status), so a repeat problem is never presented as if no decision was ever made.