underdog-lab / scripts /verify_world_cup_bracket.py
Moftah
Complete forecasting release gates and official bracket
6cc8e1a
Raw
History Blame Contribute Delete
2.92 kB
from __future__ import annotations
import argparse
import json
from itertools import combinations
from pathlib import Path
from underdog_lab.world_cup.bracket import (
KNOCKOUT_PATH,
ROUND_OF_32_SLOTS,
THIRD_PLACE_MATCH,
assign_third_place_groups,
load_annex_c,
)
def verify(root: Path) -> dict:
annex = load_annex_c(root / "data/world_cup_2026/annex_c_third_place.json")
third_slots = {
slot.match: slot.second_groups
for slot in ROUND_OF_32_SLOTS
if slot.second_kind == "third"
}
failures = []
checked = 0
for groups in combinations("ABCDEFGHIJKL", 8):
assignment = assign_third_place_groups(groups, annex)
if set(assignment) != set(third_slots):
failures.append({"groups": "".join(groups), "reason": "slot mismatch"})
elif set(assignment.values()) != set(groups):
failures.append({"groups": "".join(groups), "reason": "group mismatch"})
elif any(
group not in third_slots[match]
for match, group in assignment.items()
):
failures.append({"groups": "".join(groups), "reason": "illegal opponent"})
checked += 1
downstream_matches = [
match
for matches in KNOCKOUT_PATH.values()
for match, _, _ in matches
] + [THIRD_PLACE_MATCH[0]]
report = {
"status": "verified" if not failures and checked == 495 else "failed",
"release_blocking": bool(failures) or checked != 495,
"round_of_32_slots": len(ROUND_OF_32_SLOTS),
"annex_c_combinations": checked,
"annex_c_failures": failures,
"downstream_matches_verified": downstream_matches,
"sources": [
"https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/fifa-world-cup-2026-match-schedule-fixtures-results-teams-stadiums",
"https://en.wikipedia.org/wiki/2026_FIFA_World_Cup_knockout_stage",
],
"scope": (
"Published matches 73-104, including the third-place match, and "
"all 495 Annex C third-place "
"assignments. Fair-play data is not available in the snapshot."
),
}
return report
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--root", type=Path, default=Path("."))
parser.add_argument(
"--output",
type=Path,
default=Path("results/bracket_verification.json"),
)
args = parser.parse_args()
report = verify(args.root.resolve())
args.output.resolve().write_text(
json.dumps(report, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
print(
f"Bracket verification: {report['status']} "
f"({report['annex_c_combinations']} Annex C combinations)"
)
return 0 if report["status"] == "verified" else 1
if __name__ == "__main__":
raise SystemExit(main())