from __future__ import annotations import json from collections.abc import Mapping, Sequence from dataclasses import dataclass from datetime import UTC, datetime, timedelta from importlib.resources import files from typing import Any from kings_guard.contracts import CadenceForm, EvidenceClass def parse_timestamp(value: str) -> datetime: text = value.strip() if text.endswith("Z"): text = text[:-1] + "+00:00" parsed = datetime.fromisoformat(text) if parsed.tzinfo is None: return parsed.replace(tzinfo=UTC) return parsed.astimezone(UTC) def parse_interval(value: str | int) -> timedelta: if isinstance(value, int): return timedelta(seconds=value) text = str(value).strip().lower() if text.endswith("s") and text[:-1].isdigit(): return timedelta(seconds=int(text[:-1])) if text.endswith("m") and text[:-1].isdigit(): return timedelta(minutes=int(text[:-1])) if text.endswith("h") and text[:-1].isdigit(): return timedelta(hours=int(text[:-1])) if text.endswith("d") and text[:-1].isdigit(): return timedelta(days=int(text[:-1])) if text.startswith("pt"): # Minimal ISO-8601 duration: PT24H, PT1H, PT30M. amount = text[2:] if amount.endswith("h") and amount[:-1].isdigit(): return timedelta(hours=int(amount[:-1])) if amount.endswith("m") and amount[:-1].isdigit(): return timedelta(minutes=int(amount[:-1])) if amount.endswith("s") and amount[:-1].isdigit(): return timedelta(seconds=int(amount[:-1])) raise ValueError(f"unsupported interval: {value!r}") @dataclass(frozen=True, slots=True) class RateCadence: event_class: str evidence_class: EvidenceClass window: timedelta expected_min: int @dataclass(frozen=True, slots=True) class HeartbeatCadence: event_class: str covered_event_class: str evidence_class: EvidenceClass interval: timedelta assertion: str @dataclass(frozen=True, slots=True) class ReconciliationCadence: covered_event_class: str evidence_class: EvidenceClass local_field: str observed_field: str @dataclass(frozen=True, slots=True) class EmissionCadence: """Runtime view of the Taxonomy draft, loaded from the local worked example. This is a consumer of the draft in `specs/EmissionCadenceDeclaration.md`, not a competing schema. Ownership stays with Taxonomy. """ schema_version: str status: str drafter: str owner: str source_system: str reference_instance: str rates: tuple[RateCadence, ...] heartbeats: tuple[HeartbeatCadence, ...] reconciliations: tuple[ReconciliationCadence, ...] def forms(self) -> frozenset[CadenceForm]: forms: set[CadenceForm] = set() if self.rates: forms.add("expected-rate") if self.heartbeats or self.reconciliations: forms.add("heartbeat-or-reconciliation") return frozenset(forms) def load_qonto_assistant_cadence() -> EmissionCadence: payload = json.loads( files("kings_guard") .joinpath("fixtures") .joinpath("qonto_assistant_cadence.json") .read_text(encoding="utf-8") ) return emission_cadence_from_dict(payload) def emission_cadence_from_dict(data: Mapping[str, Any]) -> EmissionCadence: rates: list[RateCadence] = [] heartbeats: list[HeartbeatCadence] = [] reconciliations: list[ReconciliationCadence] = [] for item in data.get("sources", ()): evidence_class = EvidenceClass(str(item["evidence_class"])) form = str(item["form"]) if form == "expected-rate": rates.append( RateCadence( event_class=str(item["event_class"]), evidence_class=evidence_class, window=parse_interval(item.get("window_seconds", item.get("window"))), expected_min=int(item["expected_min"]), ) ) continue if form != "heartbeat-or-reconciliation": raise ValueError(f"unknown cadence form: {form}") heartbeat = item.get("heartbeat") or {} if heartbeat: heartbeats.append( HeartbeatCadence( event_class=str(heartbeat["event_class"]), covered_event_class=str(item["event_class"]), evidence_class=evidence_class, interval=parse_interval( heartbeat.get("interval_seconds", heartbeat.get("interval")) ), assertion=str(heartbeat.get("assertion", "nothing-to-report")), ) ) reconciliation = item.get("reconciliation") or {} if reconciliation: reconciliations.append( ReconciliationCadence( covered_event_class=str(item["event_class"]), evidence_class=evidence_class, local_field=str(reconciliation.get("compare_local", "source_counts")), observed_field=str(reconciliation.get("compare_observed", "evidence_counts")), ) ) return EmissionCadence( schema_version=str(data.get("schema_version", "0.1")), status=str(data.get("status", "taxonomy-draft")), drafter=str(data.get("drafter", "kings-guard")), owner=str(data.get("owner", "Taxonomy")), source_system=str(data.get("source", data.get("source_system", "unknown"))), reference_instance=str(data.get("reference_instance", "GH-WP-0002-T04")), rates=tuple(rates), heartbeats=tuple(heartbeats), reconciliations=tuple(reconciliations), ) def count_event_classes(event_classes: Sequence[str]) -> dict[str, int]: counts: dict[str, int] = {} for event_class in event_classes: counts[event_class] = counts.get(event_class, 0) + 1 return counts