diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-12 11:38:35 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-12 11:38:35 +0200 |
| commit | e19adb9c7ea369cafe76e7eff50e9248a4a954d0 (patch) | |
| tree | 57824e121606286ebdf8fff68439986dd3300eae /tools/extract_missalemeum_oracle.py | |
| parent | 0506388da160a15ceddb0cea1a697d737103d5c4 (diff) | |
| parent | 36df2bd0af9a555a09334e14b0d89118be1dbbc0 (diff) | |
| download | colitur-e19adb9c7ea369cafe76e7eff50e9248a4a954d0.tar.gz colitur-e19adb9c7ea369cafe76e7eff50e9248a4a954d0.zip | |
Merge branch 'ef-plan3': Plan 3, the EF resolution engine
Adds the rite-parameterised Precedence resolver, Liturgical_day, Rite,
and Calendar (year as the primitive, because transfers need whole-year
knowledge), the full EF precedence ruleset (RG 91's 28-entry table,
occurrence RG 92-95, commemorations RG 108-111, transfers RG 96-98),
322 bootstrapped sanctoral entries, colitur day <year>, and validation
layers 3-5.
Layer 3 diffs 16801 days against lectio; layer 4 diffs 730 days against
missalemeum; layer 5 pins ~30 dates on the known-tricky years. Both
comparison layers carry cited allow-lists that name the governing RG
paragraph and which engine is right.
The oracle layer earned its place immediately: Holy Thursday was violet
in colitur and lectio alike, because colitur's data was bootstrapped
from lectio and both carried the same error. Only an independent source
could see it. RG 128(b) and RG 122 name it white.
Diffstat (limited to 'tools/extract_missalemeum_oracle.py')
| -rw-r--r-- | tools/extract_missalemeum_oracle.py | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/tools/extract_missalemeum_oracle.py b/tools/extract_missalemeum_oracle.py new file mode 100644 index 0000000..044aa54 --- /dev/null +++ b/tools/extract_missalemeum_oracle.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# A documented one-shot, not part of the OCaml build (spec's "no hand-written +# JSON parser" rule is about colitur's OWN sexp format -- reading someone +# else's JSON needs a reader, same reasoning as tools/bootstrap_sanctoral.ml's +# own header comment on its INI reader). Task 16: extracts the missalemeum +# oracle fixture (test/fixtures/missalemeum-ef-2026-2027.txt) that +# test/test_oracle.ml compares colitur against -- there is no JSON library in +# this project's frozen deps, so the fixture is shaped here, once, outside +# the test, exactly as test/test_differential.ml's lectio fixture already is +# (see that file's own header comment). +# +# Usage: +# python3 tools/extract_missalemeum_oracle.py <snapshot-dir> <out-file> +# +# <snapshot-dir> is ~/git/projects/lectio/sources/ already unpacked (`tar xzf +# snapshot.tar.gz` in that directory -- lectio's own scripts/snapshot- +# sources.sh does this), so <snapshot-dir>/missalemeum/en/YYYY-MM-DD.json +# exists for every day. +# +# One line per day, pipe-separated: +# date|rank|colors|title|tempora|commemorations|displaced|n_masses +# +# - rank/colors/title/tempora/commemorations/displaced are info.rank, +# info.colors (sorted, concatenated, e.g. "pv"), info.title, info.tempora +# ("-" for JSON null), the ";"-joined titles of info.commemorations and +# info.displaced ("-" for an empty list), verbatim from the JSON's own +# English strings -- not translated or slugified, so the comparator (and a +# human auditor) can match colitur's own celebration names against them. +# - n_masses is len(the day's JSON array). Four days (Christmas, All Souls) +# carry more than one Mass; entry[0]'s info block is used throughout (rank/ +# colors/tempora/commemorations/displaced are IDENTICAL across every Mass +# of the same day for all 730 days -- checked below, not assumed; see the +# task report for why entry[0] loses nothing). +# - Spaces in tempora are turned to "_" (matching the "no field has an +# internal space" convention test/fixtures/lectio-ef-2005-2050.txt already +# uses, so this fixture can be read the same simple way -- split on '|', +# not on whitespace, so title/commemoration/displaced text keeps its own +# spaces; only tempora, which nothing in this project parses further than +# pass-through display, is space-collapsed for a cheap column-count check). +import json +import os +import sys + + +def main(): + if len(sys.argv) != 3: + print(f"usage: {sys.argv[0]} <snapshot-dir> <out-file>", file=sys.stderr) + return 2 + snapshot_dir, out_path = sys.argv[1], sys.argv[2] + src = os.path.join(snapshot_dir, "missalemeum", "en") + rows = [] + for fname in sorted(os.listdir(src)): + if not fname.endswith(".json"): + continue + date = fname[:-5] + with open(os.path.join(src, fname), encoding="utf-8") as f: + data = json.load(f) + first = data[0]["info"] + for other in data[1:]: + o = other["info"] + for k in ("rank", "colors", "tempora", "commemorations", "displaced"): + if first[k] != o[k]: + print(f"WARNING: {date} masses disagree on {k}: {first[k]!r} vs {o[k]!r}", file=sys.stderr) + comm_titles = [c["title"] for c in first["commemorations"]] + disp_titles = [d["title"] for d in first["displaced"]] + for t in [first["title"]] + comm_titles + disp_titles: + if "|" in t or ";" in t: + print(f"ERROR: {date}: field {t!r} contains a delimiter this fixture uses", file=sys.stderr) + return 1 + colors = "".join(sorted(first["colors"])) + tempora = (first["tempora"] or "-").replace(" ", "_") + comms = ";".join(comm_titles) or "-" + disp = ";".join(disp_titles) or "-" + rows.append(f"{date}|{first['rank']}|{colors}|{first['title']}|{tempora}|{comms}|{disp}|{len(data)}") + + if len(rows) != 730: + print(f"ERROR: expected 730 days, got {len(rows)}", file=sys.stderr) + return 1 + + with open(out_path, "w", encoding="utf-8") as f: + f.write("\n".join(rows) + "\n") + print(f"wrote {len(rows)} lines to {out_path}", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) |
