aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-17 12:28:03 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-17 12:28:03 +0200
commit8d1eb25a311e16ddb7bf89d2b21fb1ca0aa058a7 (patch)
tree9c72d1c30f0f37ef7138325abb3ca26ebe68ff14 /tools
parent9947a28f3a40bd8ac36892dcf5aad59d9e6f847c (diff)
downloadcolitur-8d1eb25a311e16ddb7bf89d2b21fb1ca0aa058a7.tar.gz
colitur-8d1eb25a311e16ddb7bf89d2b21fb1ca0aa058a7.zip
test(oracle): compare reading citations against missalemeum
Layer 4 (test_oracle.ml) now extracts and compares the Epistle/Gospel citation for every one of the 730 days in the 2026-2027 fixture, the same axis Task 8 already added to layer 3. tools/ extract_missalemeum_oracle.py gained a structural citation-span detector (the first "*...*"-wrapped span under 40 chars containing a chapter:verse-shaped digit pair), verified exhaustively over all 1 458 Lectio/Evangelium sections. 728 of 730 days are comparable; the 2 that are not (Good Friday, both years) have no such section in missalemeum's own JSON at all -- a structural limit on the oracle's own side (M23), not a colitur gap. 451 of 730 days differ and are all explained across 19 cited entries; 0 unexplained. Chain step 4 (the Commons) has no layer-4 witness: confirmed empirically (a scratch sweep, not merely re-asserted from Task 6's own report) that none of the 15 Commons/proper saints is ever the observed office in this window. The comparison surfaced three genuine colitur bugs in data/ef/lectionary.sexp, all fixed at the source in tools/bootstrap_lectionary.ml: 1. Holy Name of Jesus (ef-holy-name-sunday/ef-holy-name) carried a mis-derived citation, borrowed by analogy from an unrelated ini section ("ef-christmas-sunday-0", the Sunday-within-the-Octave's own Mass) on the wrong assumption the two share a Mass. Disproved by missalemeum's own extraction for both real Holy Name Sundays in its window, confirmed against the Missal itself, twice, both scans (Acts 4:8-12/Luke 2:21, not Gal 4:1-7/Luke 2:33-40). 2. RG 17(a)'s own "diebus ferialibus" rubric (immediately following the Holy Name heading, both scans) was never implemented: 2-5 January ferias repeat 1 January's Mass, 7-12 January ferias before Holy Family Sunday repeat Epiphany's, and Holy Family's own week has a named formula ("Missa Dominicae Primae post Epiphaniam"). All three fell to step 3's undifferentiated Sunday walkback instead. Fixed for three 5-weekday families, deliberately excluding Saturday from all three -- RG 78's BVM Saturday Office wins there unconditionally, confirmed against missalemeum's own title on this window's own such Saturdays. 3. Corpus Christi and Sacred Heart (both real, never-impeded I-class movable feasts) had no lectionary entry at all. Both engines independently fell back to Trinity Sunday's own citation and silently agreed on the wrong answer -- the "Holy Thursday violet in both" shape CLAUDE.md names for exactly this reason. Because lectionary.sexp is shared with layer 3 (the lectio differential, 2005-2050), these fixes changed colitur's output for the whole domain, not only 2026-2027. data/ef/expected-divergences.sexp and test_differential.ml needed matching updates: C1 narrowed 361->173 and C25 narrowed 57->13 (rows that now resolve correctly moved to new entries), C16's note corrected (count unchanged), and six new entries added (C29-C34, 397 rows total, all verdict colitur). test_lectionary.ml and test_lectionary_ef.ml's own pins were updated to match (entry count 131->148; one step-3 regression test re-pointed to a date that still genuinely exercises that step). One entry, M26 (30 rows, verdict open), is a genuine unfixed gap: colitur's step-3 "resume the preceding Sunday" fallback is its only ferial-Mass mechanism, and missalemeum's own selection is more granular in three confirmed ways (a rotating votive Mass of the BVM, a named weekly formula for at least one week, and later Time-after-Pentecost weeks this task's own investigation did not narrow to a single rule) -- the same "needs a season-keyed selection mechanism, new behaviour not new data" shape data/ef/expected-divergences.sexp's own C6/C25 and Task 6's own residual concern already name, out of this task's own scope to build. dune test: 358 tests green. COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force: 359 tests green, 89.5s.
Diffstat (limited to 'tools')
-rw-r--r--tools/bootstrap_lectionary.ml248
-rw-r--r--tools/extract_missalemeum_oracle.py58
2 files changed, 255 insertions, 51 deletions
diff --git a/tools/bootstrap_lectionary.ml b/tools/bootstrap_lectionary.ml
index 670130d..357cb81 100644
--- a/tools/bootstrap_lectionary.ml
+++ b/tools/bootstrap_lectionary.ml
@@ -91,25 +91,27 @@ let weekdays = [ "monday"; "tuesday"; "wednesday"; "thursday"; "friday"; "saturd
let colitur_keys name =
if String.equal name "ef-christmas-sunday-0" then
- (* WIDEN, not rename -- fix round (found by test_lectionary_ef.ml's own
- pre-existing test_step3_christmas_feria_resumes_sunday going from
- PASS to FAIL): "ef-christmas-sunday-0" is not only lectio's own
- calendar-level slug for RG 17(a)'s Holy Name Sunday
- (test_differential.ml's own [norm_slug] comment) -- it is ALSO
- colitur's own GENUINE, DIRECTLY-COMPUTED temporal slug (Rite_ef.
- Temporal_ef.temporal, "if m = 12 && dd >= 26 then Some
- \"ef-christmas-sunday-0\"") for an ordinary Sunday landing on 26-28
- December, the historical "Sunday within the Octave of the Nativity"
- -- a DIFFERENT civil window than Holy Name Sunday (2-5 January), and
- Missal-confirmed (docs/research/scan1.txt, "Dominica infra octavam
- Nativitatis Domini, II classis") to be the IDENTICAL Mass (Gal.
- 4,1-7 / Luc. 2,33-40), not a coincidence. The first version of this
- translation REPLACED the key outright and broke that pre-existing,
- working case -- caught by the existing test suite, not by this
- generator's own reasoning; keeping the original key alongside the
- Holy-Name-Sunday alias fixes it, following the same "widen, never
- narrow" rule this table's own general design already intends. *)
- [ "ef-christmas-sunday-0"; "ef-holy-name-sunday" ]
+ (* Task 9 (branch ef-lectionary, layer-4 oracle): CORRECTED -- this
+ branch used to ALSO widen into "ef-holy-name-sunday" (see git
+ history for the removed paragraph's own reasoning), on the claim
+ that RG 17(a)'s Holy Name Sunday shares the IDENTICAL Mass with the
+ historical "Sunday within the Octave of the Nativity" this ini
+ section actually carries (Gal. 4,1-7 / Luc. 2,33-40). WRONG,
+ DISPROVED by an INDEPENDENT primary source once this task built a
+ real oracle comparison against it: missalemeum's own extraction for
+ 2026-01-04/2027-01-03 (both real Holy Name Sundays) shows Acts
+ 4:8-12 / Luke 2:21 instead, and docs/research/scan1.txt AND scan2.txt
+ both confirm it, twice over, word for word ("SANCTISSIMI NOMINIS
+ IESU, II classis... Lectio Actuum Apostolorum. Act. 4, 8-12...
+ Sequentia sancti Evangelii secundum Lucam. Luc. 2, 21",
+ scan1.txt:6579-6608, scan2.txt:7225-7297) -- a DIFFERENT, DISTINCT
+ Mass from the Sunday-within-the-Octave's own (still correctly
+ carried by THIS key, "ef-christmas-sunday-0", unaffected by this
+ correction). The widen is removed; Holy Name Sunday's own citation
+ is now hand-authored directly, [holy_name_entries] below, sourced
+ from the Missal rather than mis-derived from an unrelated ini
+ section by analogy. *)
+ [ "ef-christmas-sunday-0" ]
else if String.equal name "ef-easter-8-wednesday" then [ "ef-pentecost-ember-wed" ]
else if String.equal name "ef-easter-8-friday" then [ "ef-pentecost-ember-fri" ]
else if String.equal name "ef-easter-8-saturday" then [ "ef-pentecost-ember-sat" ]
@@ -440,36 +442,180 @@ let nativity_octave_entries =
let cs = pair ~first:"Titus 3:4-7" ~gospel:"Luke 2:15-20" in
List.map (fun n -> (slug_or_die (Printf.sprintf "ef-nativity-octave-day-%d" n), cs)) [ 5; 6; 7 ]
-(* One more colitur-only slug, DERIVED from [ef-holy-name-sunday]'s own
- citations (just translated above) rather than a second hand-typed copy
- of the same text: [ef-holy-name] is RG 17(a)'s own fallback ("secus die
- 2 ianuarii", 2 January in a year with no Sunday 2-5 January). The
- calendarium's own table (data/ef/expected-divergences.sexp's own C16
- note quotes it) gives ONE heading for both the Sunday and the fallback
- shape -- the identical Mass, not two -- so this is the same citation
- pair, not an independent lookup. Safe to hard-wire (unlike a step-3
- fallback, which would walk back into a DIFFERENT, unrelated Sunday of
- the OLD liturgical year): RG 17(a)'s whole point is that this Mass is
- said in place of, not alongside, whatever ferial reading a bare
- fallback would otherwise find.
+(* Task 9 (branch ef-lectionary, layer-4 oracle): [ef-holy-name-sunday] and
+ [ef-holy-name] (RG 17(a)'s own 2-January fallback, "secus die 2
+ ianuarii") HAND-AUTHORED directly from the Missal, replacing the
+ PREVIOUS mechanism -- deriving [ef-holy-name] from
+ [ef-holy-name-sunday]'s own (then wrongly-widened) ini-translated
+ citation, itself borrowed from an unrelated ini section by analogy (see
+ [colitur_keys]'s own "ef-christmas-sunday-0" branch, CORRECTED note,
+ for the full account of the error this replaces). Both slugs get the
+ SAME citation pair -- the calendarium's own table (scan1.txt/scan2.txt,
+ same headings cited above) gives ONE Mass heading covering both the
+ Sunday and the 2-January fallback shape, not two -- so this is still
+ one hand-typed value, not a second independent lookup, the same
+ discipline the removed [derived_entries] intended, just sourced
+ correctly this time.
[ef-nativity-octave-day-{5,6,7}] does NOT reuse this value -- a first
attempt tried exactly that and was measured wrong and reverted; see
[nativity_octave_entries] above for the correct, DIFFERENT, directly
Missal-sourced formulary and the full account of why the two headings
differ. *)
-let derived_entries entries =
- let holy_name_sunday_citations =
- match
- List.find_opt (fun (s, _) -> String.equal (Slug.to_string s) "ef-holy-name-sunday") entries
- with
- | Some (_, cs) -> cs
- | None ->
- die "internal: ef-holy-name-sunday missing after translation -- cannot derive its dependants"
- in
- match Slug.of_string "ef-holy-name" with
- | Ok s -> [ (s, holy_name_sunday_citations) ]
- | Error e -> die "%s" e
+let holy_name_citations = pair ~first:"Acts 4:8-12" ~gospel:"Luke 2:21"
+
+let holy_name_entries =
+ [ (slug_or_die "ef-holy-name-sunday", holy_name_citations); (slug_or_die "ef-holy-name", holy_name_citations) ]
+
+(* Task 9 (branch ef-lectionary, layer-4 oracle): three more colitur-only
+ slug families, all in the same 2-13 January stretch as
+ [holy_name_entries] just above and found the same way -- missalemeum's
+ own oracle disagreeing with colitur's step-3 fallback answer on every
+ civil day these slugs cover, traced back to an EXPLICIT primary-source
+ rubric each time, not a guess. All three ranges and their own slug
+ families are documented, independently, in
+ {!Rite_ef.Temporal_ef.christmastide_feria_slug}'s own comment (that
+ function's docstring gives the exact civil-date boundaries this
+ generator relies on here; not re-derived, read directly from the code
+ that computes them).
+
+ - [ef-christmas-1-{monday,tuesday,wednesday,thursday,friday}] (2-5
+ January, EXCLUDING Saturday -- see below): RG 17(a)'s OWN rubric,
+ immediately following the Holy-Name-Sunday heading, both scans, word
+ for word: "Diebus ferialibus a 2 ad 5 ianuarii Missa dicitur ut die 1
+ ianuarii" (scan1.txt:6522-6526) / "Diebus ferialibus a 2 ad 6
+ ianuarii Missa dicitur ut die 1 ianuarii" (scan2.txt, the "6" a
+ one-digit OCR variant of scan1's "5" -- immaterial either way, since
+ 6 January is always Epiphany, a fixed I-class feast outranking any
+ ferial rubric outright) -- ON FERIAL DAYS from 2 to 5 January, THE
+ SAME MASS AS 1 JANUARY (the Circumcision) IS SAID.
+ - [ef-christmas-2-{monday,tuesday,wednesday,thursday,friday}] (7
+ January through the day before Holy Family Sunday's own civil date,
+ EXCLUDING Saturday -- see below): the SAME rubric's own next
+ sentence, both scans, word for word: "Diebus ferialibus a 7 ad 12
+ ianuarii ANTE dominicam I post Epiphaniam occurrentibus, dicitur
+ Missa de festo Epiphaniae" (scan1.txt:6753-6757, scan2.txt
+ corroborates) -- ON FERIAL DAYS from 7 to 12 January occurring BEFORE
+ the first Sunday after Epiphany (Holy Family), THE MASS OF THE FEAST
+ OF EPIPHANY IS SAID.
+ - [ef-time-after-epiphany-1-{monday,tuesday,wednesday,thursday,friday}]
+ (Holy Family Sunday's own week, EXCLUDING Saturday -- see below,
+ straddling the Christmastide/Time-after-Epiphany season boundary on
+ 14 January without changing slug -- {!christmastide_feria_slug}'s
+ own comment: "it is the SAME COMPUTATION the ordinary
+ Time-after-Epiphany ferial fallback... will give the rest of that
+ same Sunday-to-Saturday week", so one hand-authored value covers the
+ whole week regardless of which code path computed the slug for a
+ given civil day): the rubric's own THIRD sentence, same citation, a
+ named formula rather than a second "repeat the Sunday" instruction:
+ "post eandem dominicam vero Missa de eadem dominica In excelso
+ throno, ut infra... Haec Missa dicenda est diebus ferialibus huius
+ hebdomadae" (scan1.txt:6758-6851, the formula's own heading "MISSA
+ DOMINICAE PRIMAE post Epiphaniam" immediately below) -- Epistle
+ "Lectio Epistolae beati Pauli Apostoli ad Romanos. Rom. 12, 1-5"
+ (scan1.txt:6875-6877), Gospel the SAME pericope as Holy Family's own
+ Sunday Mass, Luke 2:42-52 (printed out in full immediately below the
+ Epistle, scan1.txt:6885-6905) -- a DIFFERENT Epistle from Holy Family
+ Sunday's own (Col 3:12-17), but the SAME Gospel, exactly as
+ missalemeum's own fixture rows show (2026-01-12/2027-01-11/
+ 2027-01-12: Gospel already agreed before this fix, only the Epistle
+ diverged).
+ - 13 January's OWN separate case, checked and found to need NO entry
+ here: when Holy Family does not fall there, 13 January carries a
+ II-class sanctoral commemoration of its own ("Commemoration of the
+ Baptism of the Lord", data/ef/sanctoral.sexp's own
+ [commemoration-of-the-baptism-of-the-lord], already carrying its own
+ citations, Isa 60:1-6 / John 1:29-34, matching missalemeum exactly)
+ -- {!Rite_ef.Lectionary_ef.readings}'s own step 1 (the OBSERVED
+ celebration's own proper) resolves it before step 2 (this
+ generator's own data) is ever consulted, verified directly against
+ the real resolver (tools/dump_dates.ml, a scratch diagnostic, not
+ committed): 2026-01-13/2027-01-13 both already show the correct
+ citation with none of the entries below in play.
+
+ SATURDAY IS EXCLUDED FROM ALL THREE FAMILIES, deliberately, not an
+ oversight, and NOT (as an earlier draft of this comment argued for the
+ latter two) safe to include: RG 78 ("In sabbatis, in quibus occurrit
+ Officium de feria IV classis, fit de sancta Maria in sabbato", already
+ built, Rite_ef.Temporal_ef's own [bvm_saturday_names]) makes ANY
+ otherwise-unoccupied IV-class Saturday the Saturday Office of the BVM
+ instead of a plain feria -- UNCONDITIONALLY, not merely when nothing
+ else happens to compete that day (Precedence_ef's own [band] gives it
+ this ranking whenever the day's own office would otherwise be a plain
+ Class4 feria, checked in {!Rite_ef.Temporal_ef}'s own [bvm_saturday_names]
+ comment and confirmed against the real resolver here, not assumed).
+ This window's own five otherwise-unoccupied Saturdays inside these
+ three families (2026-01-03, 2026-01-10, 2027-01-09; 2026-01-17 and
+ 2027-01-16 are won outright by a saint instead, Anthony and Marcellus I
+ respectively, whose own citations are unaffected either way) genuinely
+ ARE the BVM Office, missalemeum's own title confirming it on every one
+ that reaches the office at all ("II Mass of the B. V. M. -- Vultum
+ Tuum", never "Feria") -- so none of the three "repeat X" rubrics above
+ ever actually apply on a Saturday. The three [-saturday] slugs are
+ DELIBERATELY left unentered, all three: the BVM Saturday Office's own
+ citation is a genuine, ALREADY-DOCUMENTED open item (register, Task 6's
+ own residual concern 4, "the BVM Saturday Office still emits its
+ feria's Mass") this task does not build, out of scope here the same
+ way it was there. (CORRECTED from this comment's own first draft,
+ which excluded only [ef-christmas-1-saturday] and reasoned -- without
+ checking -- that the other two families' own Saturdays were safe to
+ include: measured directly against the real resolver and missalemeum's
+ own fixture, they were not; the same RG 78 mechanism governs all
+ three windows identically.) *)
+let non_saturday_weekdays = [ "monday"; "tuesday"; "wednesday"; "thursday"; "friday" ]
+
+let epiphanytide_weekday_citations =
+ [ (`Christmas1, non_saturday_weekdays, pair ~first:"Titus 2:11-15" ~gospel:"Luke 2:21");
+ (`Christmas2, non_saturday_weekdays, pair ~first:"Isa 60:1-6" ~gospel:"Matt 2:1-12");
+ (`TimeAfterEpiphany1, non_saturday_weekdays, pair ~first:"Rom 12:1-5" ~gospel:"Luke 2:42-52")
+ ]
+
+let epiphanytide_opening_entries =
+ List.concat_map
+ (fun (family, wds, cs) ->
+ let prefix =
+ match family with
+ | `Christmas1 -> "ef-christmas-1-"
+ | `Christmas2 -> "ef-christmas-2-"
+ | `TimeAfterEpiphany1 -> "ef-time-after-epiphany-1-"
+ in
+ List.map (fun wd -> (slug_or_die (prefix ^ wd), cs)) wds)
+ epiphanytide_weekday_citations
+
+(* Task 9 (branch ef-lectionary, layer-4 oracle): two major, permanently-
+ named I-class movable feasts of the Lord that had NO lectionary entry
+ at all until now -- not a renamed/mis-widened value like
+ [holy_name_entries] above, a genuine ABSENCE. Both slugs are real,
+ directly-computed [Temporal_ef] offices (temporal_ef.ml's own [named]
+ function, "ef-corpus-christi"/"ef-sacred-heart"), so every occurrence
+ fell through to step 3's ferial resumption (Trinity Sunday's own
+ citation, repeated) instead of the feast's own Mass -- invisible before
+ this task made citation CONTENT comparable at all, the identical shape
+ Task 8's own [colitur_keys] fixes already found for renamed slug
+ families, here for a slug that was never translated from anywhere
+ because lectio's own EF ini has no equivalent section for either feast.
+ Both citations verified against BOTH scans, word for word:
+
+ - [ef-corpus-christi] ("IN FESTO SSMI CORPORIS CHRISTI, I classis"):
+ 1 Cor 11:23-29 / John 6:56-59 -- scan1.txt:21867-21917 ("Lectio
+ Epistolae beati Pauli Apostoli ad Corinthios. 1 Cor. 11, 23-29" /
+ "...secundum Ioannem. Io. 6, 56-59"), scan2.txt:24084-24172
+ corroborates both citations.
+ - [ef-sacred-heart] ("IN FESTO SACRATISSIMI CORDIS IESU, I classis"):
+ Eph 3:8-12, 14-19 / John 19:31-37 -- scan1.txt:22030-22095 ("...ad
+ Ephesios. Ephes. 3, 8-12 et 14-19" / "...secundum Ioannem. Io. 19,
+ 31-37"), scan2.txt:24284-24336 corroborates the heading and both
+ citations (the Epistle's own "et 14-19" clause is OCR-truncated to
+ "3, 8-19" on this second scan alone, its printed body text otherwise
+ matching word for word; scan1's own clean transcription is
+ unambiguous, so this is a scan-quality gap, not a genuine
+ disagreement between the two printings). Both citations independently
+ corroborated by missalemeum's own extraction too (test/fixtures/
+ missalemeum-ef-2026-2027.txt, 2026-06-04 and 2026-06-12/2027-06-04). *)
+let movable_feast_entries =
+ [ (slug_or_die "ef-corpus-christi", pair ~first:"1 Cor 11:23-29" ~gospel:"John 6:56-59");
+ (slug_or_die "ef-sacred-heart", pair ~first:"Eph 3:8-12, 14-19" ~gospel:"John 19:31-37")
+ ]
let sha256 path =
let ic = Unix.open_process_in (Printf.sprintf "sha256sum %s" (Filename.quote path)) in
@@ -574,7 +720,8 @@ let () =
let translated = List.concat_map convert secs in
let entries =
translated @ vigil_entries @ holy_week_entries @ passion_tuesday_entry
- @ ember_saturday_corrections @ nativity_octave_entries @ derived_entries translated
+ @ ember_saturday_corrections @ nativity_octave_entries @ holy_name_entries
+ @ epiphanytide_opening_entries @ movable_feast_entries
in
assert_reachable entries;
let lect =
@@ -603,16 +750,17 @@ let () =
; Missal, one entry [passion_tuesday_entry]; %d hand-authored directly\n\
; from the Missal, two entries [ember_saturday_corrections]; %d\n\
; hand-authored directly from the Missal [nativity_octave_entries];\n\
- ; %d derived from an already-translated entry above rather than\n\
- ; re-typed [derived_entries] -- see this generator's own comments on\n\
- ; all seven). Regenerate with:\n\
+ ; %d hand-authored directly from the Missal, two entries\n\
+ ; [holy_name_entries]; %d hand-authored directly from the Missal\n\
+ ; [epiphanytide_opening_entries]; %d hand-authored directly from the\n\
+ ; Missal, two entries [movable_feast_entries] -- see this generator's\n\
+ ; own comments on all nine). Regenerate with:\n\
; eval $(opam env) && dune exec tools/bootstrap_lectionary.exe -- %s %s\n"
src (sha256 src) (List.length entries) (List.length secs) (List.length vigil_entries)
(List.length holy_week_entries) (List.length passion_tuesday_entry)
(List.length ember_saturday_corrections) (List.length nativity_octave_entries)
- (List.length entries - List.length translated - List.length vigil_entries
- - List.length holy_week_entries - List.length passion_tuesday_entry
- - List.length ember_saturday_corrections - List.length nativity_octave_entries)
+ (List.length holy_name_entries) (List.length epiphanytide_opening_entries)
+ (List.length movable_feast_entries)
src dst;
Sexplib.Sexp.output_hum oc (Lectionary.sexp_of_t lect);
output_char oc '\n';
diff --git a/tools/extract_missalemeum_oracle.py b/tools/extract_missalemeum_oracle.py
index a010b52..4b010e2 100644
--- a/tools/extract_missalemeum_oracle.py
+++ b/tools/extract_missalemeum_oracle.py
@@ -18,7 +18,7 @@
# exists for every day.
#
# One line per day, pipe-separated:
-# date|rank|colors|title|tempora|commemorations|displaced|n_masses|commemoration_ids
+# date|rank|colors|title|tempora|commemorations|displaced|n_masses|commemoration_ids|first|gospel
#
# - rank/colors/title/tempora/commemorations/displaced are info.rank,
# info.colors (sorted, concatenated, e.g. "pv"), info.title, info.tempora
@@ -42,6 +42,35 @@
# is ever commemorated there). Never parsed for its date component by this
# fixture or the comparator (info.rank/colors already give the day's own
# values); kept opaque and compared as a plain string.
+# - first/gospel (Task 9) are the day's own Epistle/Lesson and Gospel
+# citations, extracted from entry[0]["sections"] -- the section with
+# id "Lectio" ("Epistle") and "Evangelium" ("Gospel") respectively. Each
+# section's own body is a [[english, latin]] pair; the ENGLISH text
+# (body[0][0]) embeds the citation as one of possibly several
+# "*...*"-wrapped (markdown-italic) spans -- e.g. "Lesson from the letter
+# of St. Paul... \n*Titus 2:11-15*\nBeloved: ...". It is NOT always the
+# FIRST such span: Holy Saturday's own Gospel section opens with a rubric
+# note, itself "*"-wrapped ("*While singing the Gospel candles are not
+# being hold.*"), before the real citation "*Matt 28:1-7*" -- checked
+# directly against the raw JSON, not assumed. The citation span is
+# identified structurally instead of positionally: the first "*...*" span
+# under 40 characters that contains a chapter:verse-shaped digit pair
+# (`\d+\s*[:,.]\s*\d+`, matching all three separators actually used in
+# this source -- "Titus 2:11-15", "4 Kings, 5:1-15", "John 20. 19-31").
+# Verified exhaustively over the whole fixture (all 1 458 Lectio/
+# Evangelium sections across the 728 days that carry one): exactly one
+# candidate per section, zero ambiguous, zero false positives from a
+# longer rubric sentence. TWO days (Good Friday, both years -- the "Missa
+# Praesanctificatorum" liturgy) have no "Lectio"/"Evangelium" section at
+# all (a multi-lesson structure instead, "Lectiones"/"Passio", with no
+# single reading occupying the Epistle/Gospel slot this schema assumes,
+# the SAME shape colitur's own test_lectionary.ml records for its own
+# hand-authored Holy Week data) -- "-" for both fields on those two rows,
+# a WARNING on stderr naming the date, never a silent guess. Extracted
+# VERBATIM (only .strip()ped of surrounding whitespace) -- not normalized,
+# not re-punctuated: the comparator's job, not this extractor's, matching
+# the same "verbatim, not translated or slugified" discipline the fields
+# above already state.
# - 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 '|',
@@ -50,8 +79,27 @@
# pass-through display, is space-collapsed for a cheap column-count check).
import json
import os
+import re
import sys
+STAR_RE = re.compile(r"\*([^*]+)\*")
+VERSE_RE = re.compile(r"\d+\s*[:,.]\s*\d+")
+
+
+def extract_citation(sections, section_id, date, label):
+ for sec in sections:
+ if sec["id"] != section_id:
+ continue
+ text = sec["body"][0][0]
+ candidates = [s.strip() for s in STAR_RE.findall(text) if VERSE_RE.search(s) and len(s) < 40]
+ if candidates:
+ return candidates[0]
+ print(f"WARNING: {date}: {section_id!r} section found but no citation-shaped span in it ({label})",
+ file=sys.stderr)
+ return "-"
+ print(f"WARNING: {date}: no {section_id!r} section ({label})", file=sys.stderr)
+ return "-"
+
def main():
if len(sys.argv) != 3:
@@ -84,8 +132,16 @@ def main():
comms = ";".join(comm_titles) or "-"
disp = ";".join(disp_titles) or "-"
ids = ";".join(comm_ids) or "-"
+ sections = data[0]["sections"]
+ cit_first = extract_citation(sections, "Lectio", date, "Epistle")
+ cit_gospel = extract_citation(sections, "Evangelium", date, "Gospel")
+ for t in (cit_first, cit_gospel):
+ if "|" in t:
+ print(f"ERROR: {date}: citation {t!r} contains a delimiter this fixture uses", file=sys.stderr)
+ return 1
rows.append(
f"{date}|{first['rank']}|{colors}|{first['title']}|{tempora}|{comms}|{disp}|{len(data)}|{ids}"
+ f"|{cit_first}|{cit_gospel}"
)
if len(rows) != 730: