Data Quality & Schema Contracts
Every wrong price eventually traces back to a bad record that nobody stopped at the door. Data quality and schema contracts are the discipline of refusing malformed OTA and competitor payloads at the ingestion boundary, before they can poison forecasts and pricing. This component sits inside the Data Ingestion & OTA API Integration Workflows pillar as the gatekeeper between raw external feeds and the internal model, working alongside the transport concerns of Async Polling & Pagination Handling and the resilience of Rate Limiting & Retry Strategies. Without it, a renamed field or a currency-format change propagates silently until a revenue manager notices rates are wrong.
Taxonomy and schema governance
A schema contract is a versioned, machine-checkable definition of what a valid record looks like: field names, types, ranges, and cross-field invariants. For a rate record that means a positive base rate, an ISO-4217 currency, an ISO-8601 date, a check-out strictly after check-in, and a rate plan that maps to the internal taxonomy from Rate Plan Structuring & Mapping. Contracts are versioned so a provider adding a field is a compatible change while a provider renaming one is a breaking change that must be handled explicitly. The contract is the single authority on validity; downstream consumers trust that anything past the gate already conforms, which is what lets the pricing and forecasting layers skip defensive re-validation on every read.
Ingestion and normalization
Validation and normalization are one step, not two: a record is coerced into canonical form and rejected if coercion is impossible. Currencies are upper-cased and checked against the ISO-4217 set, dates are parsed to ISO-8601 in a known timezone, and monetary amounts are converted to integer minor units. Upserts are idempotent, keyed on a natural identity (property, room type, stay date, source), so re-ingesting the same payload updates in place rather than duplicating. Schema drift — a field appearing, disappearing, or changing type relative to the contract version — is detected here and routed for handling rather than silently absorbed. The staging layer keeps the raw payload alongside the normalized record so a quarantined item can be re-examined and replayed after a contract fix.
Core implementation
A Pydantic model expresses the contract; a validation function coerces and routes. Valid records return normalized; invalid ones are logged with the specific violation and handed to the quarantine sink.
from __future__ import annotations
import logging
from datetime import date
from typing import Optional
from pydantic import BaseModel, Field, ValidationError, field_validator
logger = logging.getLogger("ingestion.contract")
_ISO_4217 = {"USD", "EUR", "GBP", "JPY", "AUD", "CAD"} # extend from a reference table
class RateRecord(BaseModel):
property_id: str
room_type: str
check_in: date
check_out: date
base_rate_cents: int = Field(gt=0)
currency: str
@field_validator("currency")
@classmethod
def known_currency(cls, v: str) -> str:
up = v.upper()
if up not in _ISO_4217:
raise ValueError(f"unknown currency {v!r}")
return up
@field_validator("check_out")
@classmethod
def after_check_in(cls, v: date, info) -> date:
ci = info.data.get("check_in")
if ci is not None and v <= ci:
raise ValueError("check_out must be after check_in")
return v
def validate_record(payload: dict) -> Optional[RateRecord]:
try:
return RateRecord(**payload)
except ValidationError as exc:
logger.warning("quarantined record %s: %s",
payload.get("property_id"), exc.errors())
return None
Returning None rather than raising lets the caller batch-process a feed, quarantining the few bad records while committing the good ones — a single malformed row never blocks an entire ingestion cycle.
Cross-system dependencies
Upstream, contracts guard the raw feeds arriving from OTA integrations and from Competitor Rate Scraping Pipelines. Downstream, every consumer benefits: Occupancy Forecasting & Demand Analytics trains only on validated history, and Dynamic Pricing Rule Engines & Optimization prices only from conforming records. The contract’s currency and rate-plan rules are inherited directly from the core architecture pillar, so a change to the canonical taxonomy propagates into the contract as a version bump rather than a scattered set of ad-hoc checks.
Operational governance
Contracts are code: versioned, reviewed, and tested against a corpus of real historical payloads in CI so a tightening never silently quarantines a legitimate stream. Drift detection runs continuously, comparing incoming field sets against the active contract and alerting when an unrecognized field appears or an expected one vanishes. The quarantine sink is monitored like any other queue — depth, age of oldest item, and rejection reason distribution — because a spike in a single reason is usually a provider-side change, not random noise. Audit logs retain the raw payload and the violation for every rejection so a contract fix can be validated by replaying the quarantined backlog.
Troubleshooting and failure modes
| Symptom | Likely cause | Remediation |
|---|---|---|
| Quarantine depth spikes | Provider renamed or retyped a field | Bump contract version; add a compatibility shim |
| Valid records rejected | Contract tightened without a corpus test | Test against historical payloads before release |
| Duplicate rows downstream | Non-idempotent upsert key | Key on natural identity + source |
| Silent bad prices | Field absent from contract entirely | Add the field and a range check; replay history |
Conclusion
Production readiness here is measured by what never reaches the model: a versioned contract, coercion-as-validation, an idempotent upsert, and a monitored quarantine make bad data a visible, replayable event rather than a silent corruption. That gate is what lets every downstream pillar trust the records it reads.
Related
- Detecting schema drift in OTA payloads — catching contract-breaking changes the moment a provider ships them.
- Quarantining invalid records with a dead-letter queue — the sink pattern for records that fail the gate.
- Validating JSON payloads from Expedia Partner Central — the same contract discipline applied to a specific provider.