Skip to content

Commit f0be26b

Browse files
Pigbibicodex
andcommitted
feat: add weekly source contract foundation
Co-Authored-By: Codex <noreply@openai.com>
1 parent 075dc4f commit f0be26b

2 files changed

Lines changed: 260 additions & 0 deletions

File tree

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"""Pure producer-owned contract for complete official PERT weekly inputs."""
2+
from __future__ import annotations
3+
4+
import hashlib
5+
import json
6+
import re
7+
from collections.abc import Mapping
8+
from dataclasses import dataclass
9+
from datetime import date, datetime, timedelta, timezone
10+
from pathlib import PurePosixPath
11+
12+
SCHEMA_VERSION = "1"
13+
CONTRACT_VERSION = "political_event_weekly.v1"
14+
CADENCE = "weekly"
15+
_DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
16+
_TIMESTAMP_RE = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z$")
17+
_SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
18+
_SHA1_RE = re.compile(r"^[0-9a-f]{40}$")
19+
_KEYS = frozenset({
20+
"schema_version", "contract_version", "cadence", "as_of", "period_start", "period_end_exclusive",
21+
"generated_at", "run_mode", "producer_ref", "source_provenance", "source_artifacts", "feed_status",
22+
})
23+
_FEED_KEYS = frozenset({"feed_count", "successful_feed_count", "failed_feed_count", "stale_feed_count", "missing_feed_count", "complete"})
24+
_ARTIFACT_KEYS = frozenset({"path", "sha256", "row_count"})
25+
26+
27+
class WeeklyContractError(ValueError):
28+
def __init__(self, code: str) -> None:
29+
self.code = code
30+
super().__init__(code)
31+
32+
33+
@dataclass(frozen=True, slots=True)
34+
class WeeklySourceArtifact:
35+
path: str
36+
sha256: str
37+
row_count: int
38+
39+
40+
@dataclass(frozen=True, slots=True)
41+
class WeeklyFeedStatus:
42+
feed_count: int
43+
successful_feed_count: int
44+
failed_feed_count: int
45+
stale_feed_count: int
46+
missing_feed_count: int
47+
48+
49+
@dataclass(frozen=True, slots=True)
50+
class WeeklySourceContract:
51+
as_of: date
52+
period_start: date
53+
period_end_exclusive: date
54+
generated_at: datetime
55+
run_mode: str
56+
producer_ref: str
57+
source_provenance: str
58+
source_artifacts: tuple[WeeklySourceArtifact, ...]
59+
feed_status: WeeklyFeedStatus
60+
61+
62+
def _invalid(code: str) -> WeeklyContractError:
63+
return WeeklyContractError(code)
64+
65+
66+
def _date(value: object) -> date:
67+
if type(value) is not str or not _DATE_RE.fullmatch(value):
68+
raise _invalid("date_invalid")
69+
try:
70+
return date.fromisoformat(value)
71+
except ValueError:
72+
raise _invalid("date_invalid") from None
73+
74+
75+
def _generated_at(value: object) -> datetime:
76+
if type(value) is not str or not _TIMESTAMP_RE.fullmatch(value):
77+
raise _invalid("generated_at_invalid")
78+
try:
79+
parsed = datetime.fromisoformat(value[:-1] + "+00:00")
80+
except ValueError:
81+
raise _invalid("generated_at_invalid") from None
82+
if parsed.tzinfo != timezone.utc:
83+
raise _invalid("generated_at_invalid")
84+
return parsed
85+
86+
87+
def _artifact(value: object) -> WeeklySourceArtifact:
88+
if not isinstance(value, Mapping) or set(value) != _ARTIFACT_KEYS:
89+
raise _invalid("source_artifact_invalid")
90+
path = value["path"]
91+
if type(path) is not str or not path or PurePosixPath(path).is_absolute() or ".." in PurePosixPath(path).parts or "\\" in path:
92+
raise _invalid("source_artifact_invalid")
93+
digest = value["sha256"]
94+
row_count = value["row_count"]
95+
if type(digest) is not str or not _SHA256_RE.fullmatch(digest) or type(row_count) is not int or row_count < 0:
96+
raise _invalid("source_artifact_invalid")
97+
return WeeklySourceArtifact(path, digest, row_count)
98+
99+
100+
def _feed_status(value: object) -> WeeklyFeedStatus:
101+
if not isinstance(value, Mapping) or set(value) != _FEED_KEYS or value.get("complete") is not True:
102+
raise _invalid("feed_status_invalid")
103+
values = [value[key] for key in _FEED_KEYS if key != "complete"]
104+
if any(type(item) is not int or item < 0 for item in values):
105+
raise _invalid("feed_status_invalid")
106+
status = WeeklyFeedStatus(*(value[key] for key in ("feed_count", "successful_feed_count", "failed_feed_count", "stale_feed_count", "missing_feed_count")))
107+
if status.feed_count <= 0 or status.successful_feed_count != status.feed_count or any((status.failed_feed_count, status.stale_feed_count, status.missing_feed_count)):
108+
raise _invalid("feed_status_incomplete")
109+
return status
110+
111+
112+
def parse_weekly_contract(value: Mapping[str, object]) -> WeeklySourceContract:
113+
if not isinstance(value, Mapping) or set(value) != _KEYS:
114+
raise _invalid("contract_shape_invalid")
115+
if value["schema_version"] != SCHEMA_VERSION or value["contract_version"] != CONTRACT_VERSION or value["cadence"] != CADENCE:
116+
raise _invalid("contract_version_invalid")
117+
if type(value["run_mode"]) is not str or value["run_mode"] not in {"scheduled", "manual"}:
118+
raise _invalid("run_mode_invalid")
119+
producer_ref = value["producer_ref"]
120+
provenance = value["source_provenance"]
121+
if type(producer_ref) is not str or not _SHA1_RE.fullmatch(producer_ref) or type(provenance) is not str or not provenance:
122+
raise _invalid("provenance_invalid")
123+
as_of = _date(value["as_of"])
124+
start = _date(value["period_start"])
125+
end = _date(value["period_end_exclusive"])
126+
if start.weekday() != 0 or end != start + timedelta(days=7) or as_of != end - timedelta(days=1):
127+
raise _invalid("period_invalid")
128+
generated_at = _generated_at(value["generated_at"])
129+
if generated_at < datetime.combine(end, datetime.min.time(), timezone.utc):
130+
raise _invalid("generated_at_before_period_end")
131+
artifacts_value = value["source_artifacts"]
132+
if not isinstance(artifacts_value, list) or not artifacts_value:
133+
raise _invalid("source_artifact_invalid")
134+
artifacts = tuple(sorted((_artifact(item) for item in artifacts_value), key=lambda item: item.path))
135+
if len({item.path for item in artifacts}) != len(artifacts):
136+
raise _invalid("source_artifact_duplicate")
137+
return WeeklySourceContract(as_of, start, end, generated_at, value["run_mode"], producer_ref, provenance, artifacts, _feed_status(value["feed_status"]))
138+
139+
140+
def serialize_weekly_contract(contract: WeeklySourceContract) -> bytes:
141+
if not isinstance(contract, WeeklySourceContract):
142+
raise _invalid("contract_type_invalid")
143+
try:
144+
payload = {
145+
"schema_version": SCHEMA_VERSION, "contract_version": CONTRACT_VERSION, "cadence": CADENCE,
146+
"as_of": contract.as_of.isoformat(), "period_start": contract.period_start.isoformat(),
147+
"period_end_exclusive": contract.period_end_exclusive.isoformat(),
148+
"generated_at": contract.generated_at.isoformat(timespec="microseconds").replace("+00:00", "Z"),
149+
"run_mode": contract.run_mode, "producer_ref": contract.producer_ref, "source_provenance": contract.source_provenance,
150+
"source_artifacts": [{"path": item.path, "sha256": item.sha256, "row_count": item.row_count} for item in contract.source_artifacts],
151+
"feed_status": {"feed_count": contract.feed_status.feed_count, "successful_feed_count": contract.feed_status.successful_feed_count, "failed_feed_count": 0, "stale_feed_count": 0, "missing_feed_count": 0, "complete": True},
152+
}
153+
except (AttributeError, TypeError, ValueError, OverflowError):
154+
raise _invalid("contract_invalid") from None
155+
parse_weekly_contract(payload)
156+
try:
157+
return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
158+
except (TypeError, UnicodeError, ValueError):
159+
raise _invalid("serialization_invalid") from None

tests/test_weekly_contract.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from __future__ import annotations
2+
3+
from datetime import date
4+
5+
import pytest
6+
7+
from political_event_tracking_research.weekly_contract import (
8+
WeeklyContractError,
9+
parse_weekly_contract,
10+
serialize_weekly_contract,
11+
)
12+
13+
14+
def payload(**overrides: object) -> dict[str, object]:
15+
value: dict[str, object] = {
16+
"schema_version": "1",
17+
"contract_version": "political_event_weekly.v1",
18+
"cadence": "weekly",
19+
"as_of": "2026-07-12",
20+
"period_start": "2026-07-06",
21+
"period_end_exclusive": "2026-07-13",
22+
"generated_at": "2026-07-13T00:00:00.123456Z",
23+
"run_mode": "manual",
24+
"producer_ref": "a" * 40,
25+
"source_provenance": "official_political_event_tracking_research",
26+
"source_artifacts": [
27+
{"path": "data/live/political_events.csv", "sha256": "b" * 64, "row_count": 11},
28+
],
29+
"feed_status": {
30+
"feed_count": 9,
31+
"successful_feed_count": 9,
32+
"failed_feed_count": 0,
33+
"stale_feed_count": 0,
34+
"missing_feed_count": 0,
35+
"complete": True,
36+
},
37+
}
38+
value.update(overrides)
39+
return value
40+
41+
42+
def test_valid_weekly_contract_round_trips_deterministically():
43+
contract = parse_weekly_contract(payload())
44+
encoded = serialize_weekly_contract(contract)
45+
assert encoded == serialize_weekly_contract(parse_weekly_contract(__import__("json").loads(encoded)))
46+
assert contract.as_of == date(2026, 7, 12)
47+
48+
49+
@pytest.mark.parametrize("field,value", [
50+
("schema_version", "2"),
51+
("contract_version", "political_event_weekly.v2"),
52+
("cadence", "daily"),
53+
("as_of", "2026-07-13"),
54+
("period_start", "2026-07-07"),
55+
("period_end_exclusive", "2026-07-14"),
56+
("generated_at", "2026-07-12T23:59:59Z"),
57+
("generated_at", "2026-07-13T00:00:00+08:00"),
58+
])
59+
def test_period_and_time_contract_fail_closed(field, value):
60+
with pytest.raises(WeeklyContractError):
61+
parse_weekly_contract(payload(**{field: value}))
62+
63+
64+
def test_scheduled_and_manual_are_explicit():
65+
assert parse_weekly_contract(payload(run_mode="scheduled")).run_mode == "scheduled"
66+
with pytest.raises(WeeklyContractError):
67+
parse_weekly_contract(payload(run_mode=""))
68+
with pytest.raises(WeeklyContractError):
69+
parse_weekly_contract({key: value for key, value in payload().items() if key != "as_of"})
70+
71+
72+
@pytest.mark.parametrize("status", [
73+
{"feed_count": 9, "successful_feed_count": 8, "failed_feed_count": 1, "stale_feed_count": 0, "missing_feed_count": 0, "complete": True},
74+
{"feed_count": 9, "successful_feed_count": 9, "failed_feed_count": 0, "stale_feed_count": 1, "missing_feed_count": 0, "complete": True},
75+
{"feed_count": 9, "successful_feed_count": 9, "failed_feed_count": 0, "stale_feed_count": 0, "missing_feed_count": 0, "complete": False},
76+
])
77+
def test_partial_stale_or_incomplete_feed_status_fails_closed(status):
78+
with pytest.raises(WeeklyContractError, match="feed_status"):
79+
parse_weekly_contract(payload(feed_status=status))
80+
81+
82+
def test_artifacts_are_sorted_and_duplicate_or_unsafe_inputs_fail_closed():
83+
items = [
84+
{"path": "z.csv", "sha256": "c" * 64, "row_count": 1},
85+
{"path": "a.csv", "sha256": "d" * 64, "row_count": 2},
86+
]
87+
contract = parse_weekly_contract(payload(source_artifacts=items))
88+
assert [item.path for item in contract.source_artifacts] == ["a.csv", "z.csv"]
89+
with pytest.raises(WeeklyContractError):
90+
parse_weekly_contract(payload(source_artifacts=[items[0], items[0]]))
91+
with pytest.raises(WeeklyContractError):
92+
parse_weekly_contract(payload(source_artifacts=[{"path": "../secret", "sha256": "c" * 64, "row_count": 1}]))
93+
94+
95+
def test_unknown_fields_and_wire_type_confusion_fail_closed():
96+
with pytest.raises(WeeklyContractError):
97+
parse_weekly_contract(payload(extra=True))
98+
with pytest.raises(WeeklyContractError):
99+
parse_weekly_contract(payload(schema_version=1))
100+
with pytest.raises(WeeklyContractError):
101+
parse_weekly_contract(payload(feed_status={**payload()["feed_status"], "feed_count": True}))

0 commit comments

Comments
 (0)