"""A small, versioned data contract for model inputs.""" from __future__ import annotations from dataclasses import dataclass from datetime import datetime import math import re from typing import Any, Literal FeatureKind = Literal["string", "integer", "number", "boolean", "timestamp"] ALLOWED_FEATURE_KINDS = frozenset( {"string", "integer", "number", "boolean", "timestamp"} ) SEMVER = re.compile(r"\d+\.\d+\.\d+") @dataclass(frozen=True) class LineageSource: name: str version: str owner: str transform_ref: str @dataclass(frozen=True) class FeatureContract: name: str kind: FeatureKind meaning: str required: bool = True minimum: float | None = None maximum: float | None = None allowed_values: tuple[str, ...] = () @dataclass(frozen=True) class ModelDataContract: name: str version: str entity_key: str event_time_field: str available_at_field: str intended_uses: tuple[str, ...] prohibited_uses: tuple[str, ...] lineage: tuple[LineageSource, ...] features: tuple[FeatureContract, ...] def _parse_timestamp(value: Any, field: str) -> datetime: if not isinstance(value, str): raise ValueError(f"{field} must be an ISO 8601 timestamp") try: parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) except ValueError as error: raise ValueError(f"{field} must be an ISO 8601 timestamp") from error if parsed.tzinfo is None or parsed.utcoffset() is None: raise ValueError(f"{field} must include a UTC offset") return parsed def validate_contract(contract: ModelDataContract) -> None: if not contract.name.strip(): raise ValueError("contract name must be explicit") if SEMVER.fullmatch(contract.version) is None: raise ValueError("contract version must be semantic x.y.z") if not contract.intended_uses: raise ValueError("at least one intended use is required") overlap = set(contract.intended_uses) & set(contract.prohibited_uses) if overlap: raise ValueError(f"uses cannot be both intended and prohibited: {sorted(overlap)}") if not contract.lineage: raise ValueError("lineage must identify at least one source") for source in contract.lineage: if any( not value.strip() for value in (source.name, source.version, source.owner, source.transform_ref) ): raise ValueError("every lineage source needs name, version, owner, and transform_ref") names = [feature.name for feature in contract.features] if len(names) != len(set(names)): raise ValueError("feature names must be unique") for feature in contract.features: if feature.kind not in ALLOWED_FEATURE_KINDS: raise ValueError(f"unknown feature kind: {feature.kind}") for required_field in ( contract.entity_key, contract.event_time_field, contract.available_at_field, ): if required_field not in names: raise ValueError(f"contract field is missing from features: {required_field}") by_name = {feature.name: feature for feature in contract.features} for timestamp_field in (contract.event_time_field, contract.available_at_field): if by_name[timestamp_field].kind != "timestamp": raise ValueError(f"{timestamp_field} must have timestamp kind") for feature in contract.features: if not feature.name.strip() or not feature.meaning.strip(): raise ValueError("every feature needs a name and business meaning") for bound_name, bound in ( ("minimum", feature.minimum), ("maximum", feature.maximum), ): if bound is None: continue if isinstance(bound, bool) or not isinstance(bound, (int, float)): raise ValueError(f"{bound_name} for {feature.name} must be numeric") if isinstance(bound, float) and not math.isfinite(bound): raise ValueError(f"{bound_name} for {feature.name} must be finite") if ( feature.minimum is not None and feature.maximum is not None and feature.minimum > feature.maximum ): raise ValueError(f"invalid range for {feature.name}") if (feature.minimum is not None or feature.maximum is not None) and feature.kind not in { "integer", "number", }: raise ValueError(f"numeric bounds are invalid for {feature.name}") def _has_kind(value: Any, kind: FeatureKind) -> bool: if kind in {"string", "timestamp"}: return isinstance(value, str) if kind == "integer": return isinstance(value, int) and not isinstance(value, bool) if kind == "number": return isinstance(value, (int, float)) and not isinstance(value, bool) if kind == "boolean": return isinstance(value, bool) raise ValueError(f"unknown feature kind: {kind}") def validate_record( contract: ModelDataContract, record: dict[str, Any], *, use: str, decision_time: str, ) -> list[str]: validate_contract(contract) errors: list[str] = [] if use in contract.prohibited_uses: errors.append(f"use '{use}' is prohibited") elif use not in contract.intended_uses: errors.append(f"use '{use}' is not declared") known = {feature.name for feature in contract.features} for field in sorted(record.keys() - known): errors.append(f"unexpected field: {field}") by_name = {feature.name: feature for feature in contract.features} parsed_timestamps: dict[str, datetime] = {} for feature in contract.features: if feature.required and feature.name not in record: errors.append(f"missing required field: {feature.name}") continue if feature.name not in record: continue value = record[feature.name] if not _has_kind(value, feature.kind): errors.append(f"{feature.name} must have kind {feature.kind}") continue if feature.kind == "timestamp": try: parsed_timestamps[feature.name] = _parse_timestamp(value, feature.name) except ValueError as error: errors.append(str(error)) continue if feature.allowed_values and value not in feature.allowed_values: errors.append(f"{feature.name} is outside its allowed values") if feature.kind in {"integer", "number"}: if isinstance(value, float) and not math.isfinite(value): errors.append(f"{feature.name} must be finite") continue if feature.minimum is not None and value < feature.minimum: errors.append(f"{feature.name} is below {feature.minimum}") if feature.maximum is not None and value > feature.maximum: errors.append(f"{feature.name} is above {feature.maximum}") try: decision = _parse_timestamp(decision_time, "decision_time") except ValueError as error: errors.append(str(error)) decision = None event_time = parsed_timestamps.get(contract.event_time_field) available_at = parsed_timestamps.get(contract.available_at_field) if event_time is not None and available_at is not None and event_time > available_at: errors.append("event_time must not be after available_at") if decision is not None and available_at is not None and available_at > decision: errors.append("available_at must not be after decision_time") return errors SUPPORT_REQUEST_CONTRACT = ModelDataContract( name="support-request-model-input", version="1.0.0", entity_key="request_id", event_time_field="request_created_at", available_at_field="features_available_at", intended_uses=("refund-triage",), prohibited_uses=("automated-refund", "ads-targeting"), lineage=( LineageSource( name="support-requests", version="snapshot-001", owner="Support data platform", transform_ref="feature-pipeline:v1", ), ), features=( FeatureContract("request_id", "string", "Stable support request identifier"), FeatureContract("request_created_at", "timestamp", "Time the customer submitted the request"), FeatureContract("features_available_at", "timestamp", "Time every feature in this row was available"), FeatureContract("message_length", "integer", "Unicode code-point count after canonical cleanup", minimum=0, maximum=100_000), FeatureContract("account_age_days", "integer", "Completed days since account creation at request time", minimum=0), FeatureContract("language", "string", "BCP 47 language tag emitted by the approved detector"), ), ) VALID_RECORD = { "request_id": "req-1042", "request_created_at": "2026-08-11T09:00:00Z", "features_available_at": "2026-08-11T09:00:02Z", "message_length": 240, "account_age_days": 90, "language": "en", } if __name__ == "__main__": valid_errors = validate_record( SUPPORT_REQUEST_CONTRACT, VALID_RECORD, use="refund-triage", decision_time="2026-08-11T09:00:05Z", ) print("valid_record=PASS" if not valid_errors else "valid_record=FAIL") rejected = {**VALID_RECORD, "features_available_at": "2026-08-11T09:00:06Z"} rejected_errors = validate_record( SUPPORT_REQUEST_CONTRACT, rejected, use="automated-refund", decision_time="2026-08-11T09:00:05Z", ) print("rejected_record=FAIL" if rejected_errors else "rejected_record=PASS") for validation_error in rejected_errors: print(f"- {validation_error}")