diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/bootstrap_lectionary_of.ml | 228 |
1 files changed, 175 insertions, 53 deletions
diff --git a/tools/bootstrap_lectionary_of.ml b/tools/bootstrap_lectionary_of.ml index f521bb1..da37bd9 100644 --- a/tools/bootstrap_lectionary_of.ml +++ b/tools/bootstrap_lectionary_of.ml @@ -459,12 +459,14 @@ let named_overrides = and Epiphany, 2-5 Jan) -> stretch 1. - "christmas-after-epiphany-<wd>" (7 Jan..Baptism) -> stretch 2. lectio ALSO carries "christmas-dec-29/30/31" and "christmas-jan- - 2..7", a DATE-keyed duplicate of the same stretches -- deliberately - UNUSED (see [excluded_bases] below): colitur's own slugs are - weekday-keyed, not date-keyed, so a fixed civil date such as - "29 December" is not any single colitur slug -- it is a DIFFERENT - weekday (hence a different of-christmas-0-<weekday>) in different - years, and Lectionary.t has no per-date lookup at all. *) + 2..7" -- content-verified (2026-08-26 review) NOT duplicates of the + weekday-keyed families above (17 December's own O-Antiphon sibling + proved the same shape first: Gen 49:2,8-10/Matt 1:1-17, found + nowhere else in the 754 emitted entries). These are OLM n. 69.3's + genuinely date-fixed readings; mapped separately below via + {!Rite_of.Lectionary_of.date_keyed_slug}, not through this table -- + see [excluded_bases]'s own comment for exactly which of the two + ranges are and are not still excluded, and why. *) ("christmas-octave-mon", [ "of-christmas-0-monday" ]); ("christmas-octave-tue", [ "of-christmas-0-tuesday" ]); ("christmas-octave-wed", [ "of-christmas-0-wednesday" ]); @@ -482,24 +484,58 @@ let named_overrides = ("christmas-after-epiphany-wed", [ "of-christmas-2-wednesday" ]); ("christmas-after-epiphany-thu", [ "of-christmas-2-thursday" ]); ("christmas-after-epiphany-fri", [ "of-christmas-2-friday" ]); - ("christmas-after-epiphany-sat", [ "of-christmas-2-saturday" ]) + ("christmas-after-epiphany-sat", [ "of-christmas-2-saturday" ]); + (* OLM n. 69.3's date-fixed windows (see the block comment above and + Rite_of.Lectionary_of.date_keyed_slug's own doc comment for the + full citation and argument -- this task's fix, 2026-08-26 review). + These slugs are NOT Temporal_of office slugs (no colitur day's + [Celebration.slug] is ever literally "of-advent-dec-17") -- they + exist purely as [Lectionary.t] lookup keys that + [Lectionary_of.readings]'s own step 3 constructs directly from the + civil date via [date_keyed_slug], bypassing this table's usual + "base name -> real colitur slug" contract. [assert_reachable] + below is widened with its own matching date-keyed reachability + sweep (calling the SAME function, not a re-implementation) so a + typo here still dies loudly rather than shipping a dead key. + 6 January is deliberately absent -- see [excluded_bases]. *) + ("advent-dec-17", [ "of-advent-dec-17" ]); + ("advent-dec-18", [ "of-advent-dec-18" ]); + ("advent-dec-19", [ "of-advent-dec-19" ]); + ("advent-dec-20", [ "of-advent-dec-20" ]); + ("advent-dec-21", [ "of-advent-dec-21" ]); + ("advent-dec-22", [ "of-advent-dec-22" ]); + ("advent-dec-23", [ "of-advent-dec-23" ]); + ("advent-dec-24", [ "of-advent-dec-24" ]); + ("christmas-dec-29", [ "of-christmas-dec-29" ]); + ("christmas-dec-30", [ "of-christmas-dec-30" ]); + ("christmas-dec-31", [ "of-christmas-dec-31" ]); + ("christmas-jan-2", [ "of-christmas-jan-2" ]); + ("christmas-jan-3", [ "of-christmas-jan-3" ]); + ("christmas-jan-4", [ "of-christmas-jan-4" ]); + ("christmas-jan-5", [ "of-christmas-jan-5" ]); + ("christmas-jan-7", [ "of-christmas-jan-7" ]) ] -(* Deliberately unmapped, with the reason named -- see [named_overrides]'s - own comment on the Christmas-season families for the date-vs-weekday - mismatch these two groups share: +(* Deliberately unmapped, with the reason named. CORRECTED 2026-08-26 + review: this list previously ALSO carried the 16 O-Antiphon/Christmas- + season date-keyed bases, under the claim that they were "date-keyed + duplicates" of the weekday-keyed families [named_overrides] already + maps. That claim was false -- diffed against lectio's own ini directly, + each carries unique per-date content (17 December: Gen 49:2,8-10/ + Matt 1:1-17, found nowhere else among the 754 emitted entries), which + OLM n. 69.3 explains: those ferias are date-fixed, not merely + non-alternating within a weekday slot. They are mapped now, via + [named_overrides]'s own date-keyed block, not here. Genuinely still + unreachable, for three DIFFERENT structural reasons, none of them + "duplicate": - - "advent-dec-17".."advent-dec-24": the O-Antiphon days. colitur's own - Temporal_of has no per-date slug for late Advent at all (Temporal_ef's - EF counterpart doesn't either); these ferias fall through to the - ordinary week-numbered "of-advent-{3,4}-<weekday>" slug, and WHICH - weekday-string a given December date is varies year to year, so no - static base-name mapping is possible. A future task wanting these - eight dates covered needs a genuinely date-keyed lookup path in - Lectionary_of.readings, not a slug rename here. - - "christmas-dec-29/30/31", "christmas-jan-2/3/4/5/6/7": the date-keyed - duplicates of the weekday-keyed families already mapped above. Same - reasoning, same remedy if ever closed. + - "christmas-jan-6": 6 January is always Epiphany in colitur's model + (Temporal_of's own [named] fixes it unconditionally, "m = 1 && dd = + 6", before the ferial dispatch ever runs) -- "of-christmas-jan-6" + would be a real lectio entry with no colitur day that could ever look + it up. See Rite_of.Lectionary_of.date_keyed_slug's own doc comment, + which deliberately excludes 6 January from its date range for this + exact reason. - "easter-6-thu": Thursday of Easter week 6 is STRUCTURALLY, always, the Ascension (Easter+39 is always a Thursday) -- Temporal_of's own [named] intercepts it before the generic ferial branch ever runs, so @@ -510,19 +546,32 @@ let named_overrides = (when 24 December is itself the Fourth Sunday of Advent, week 4 has no ferial days whatsoever) -- "of-advent-4-saturday" is not a slug Temporal_of can ever produce either. - Both confirmed, not assumed: [assert_reachable]'s own reachability - sweep flagged each as a genuine dead key this generator's own pattern - rule produced (after fixing the suffix-stripping bug that function's - own comment describes, which had masked them under 580 false - positives on this generator's first run). *) -let excluded_bases = - [ "advent-dec-17"; "advent-dec-18"; "advent-dec-19"; "advent-dec-20"; "advent-dec-21"; - "advent-dec-22"; "advent-dec-23"; "advent-dec-24"; - "christmas-dec-29"; "christmas-dec-30"; "christmas-dec-31"; - "christmas-jan-2"; "christmas-jan-3"; "christmas-jan-4"; "christmas-jan-5"; "christmas-jan-6"; - "christmas-jan-7"; "easter-6-thu"; "advent-4-sat" ] + The latter two confirmed, not assumed: [assert_reachable]'s own + reachability sweep flagged each as a genuine dead key this + generator's own pattern rule produced (after fixing the suffix- + stripping bug that function's own comment describes, which had + masked them under 580 false positives on this generator's first + run). *) +let excluded_bases = [ "christmas-jan-6"; "easter-6-thu"; "advent-4-sat" ] -type mapping_report = { mapped : int; excluded : int; unmapped : string list } +type mapping_report = { + mapped : int; + excluded : int; + unmapped : string list; + sanctoral_passthrough : int; + (* Count of the [Hashtbl.mem sanctoral_slugs base] branch below -- + how many of the real merged sanctoral layer's own slugs got a + DEDICATED lectio entry this way, out of its full total (passed in + separately at the print site, [Hashtbl.length sanctoral_slugs]). + Named explicitly, 2026-08-26 review's own Minor finding: the + REMAINDER (222 shipped sanctoral slugs total, per that review -- + 189 without a dedicated entry) is not a gap, it is OLM norms + working as designed (a saint with no proper of its own falls + through to the day's own ferial, {!Lectionary_of.readings}'s own + step 2 -> step 3), but the provenance header never said so before + this fix, which reads as an unstated 85% gap rather than the + correctly-small dedicated-entry set it actually is. *) +} (* Resolves every (base, resolved-citations) pair into zero or more (colitur-slug, resolved-citations) pairs, and separately tracks what @@ -535,6 +584,7 @@ type mapping_report = { mapped : int; excluded : int; unmapped : string list } let map_bases resolved ~sanctoral_slugs = let unmapped = ref [] in let excluded = ref 0 in + let sanctoral_passthrough = ref 0 in let out = List.filter_map (fun (base, r) -> @@ -550,14 +600,19 @@ let map_bases resolved ~sanctoral_slugs = | Some [ slug ] -> Some (slug, r) | Some _ -> die "%s: named_overrides entry must name exactly one slug" base | None -> - if Hashtbl.mem sanctoral_slugs base then Some (base, r) + if Hashtbl.mem sanctoral_slugs base then begin + incr sanctoral_passthrough; + Some (base, r) + end else begin unmapped := base :: !unmapped; None end)) resolved in - (out, { mapped = List.length out; excluded = !excluded; unmapped = List.sort compare !unmapped }) + ( out, + { mapped = List.length out; excluded = !excluded; unmapped = List.sort compare !unmapped; + sanctoral_passthrough = !sanctoral_passthrough } ) (* ---- Emit one Lectionary.t entry per (slug, resolved) pair ---------- *) @@ -661,13 +716,42 @@ let strip_emitted_suffix s = None [ "-a"; "-b"; "-c"; "-ii"; "-i" ] |> Option.value ~default:s -let assert_reachable entries ~temporal_slugs ~sanctoral_slugs = +(* THIS TASK'S fix, 2026-08-26 review: the 16 new date-keyed slugs + ([named_overrides]'s own date-keyed block) are not Temporal_of office + slugs and not sanctoral slugs, so without this they would all be + flagged DEAD by [assert_reachable] below. Calls + Rite_of.Lectionary_of.date_keyed_slug directly -- the SAME function + {!Rite_of.Lectionary_of.readings} calls at runtime -- rather than + re-deriving the date ranges here a second time, so a typo in either + [named_overrides]'s literal strings or in [date_keyed_slug]'s own + ranges still dies loudly instead of silently drifting apart. Swept over + the same 2004-2051 range as [reachable_temporal_slugs] and for the same + reason: every distinct date recurs every year, only its weekday + alignment (irrelevant here, [date_keyed_slug] itself is weekday- + independent except for its Sunday guard) and which years hit a Sunday + change. *) +let reachable_date_keyed_slugs () = + let tbl = Hashtbl.create 32 in + let mk y m d = match Date.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> die "%s" e in + for y = 2004 to 2051 do + let d = ref (mk y 1 1) in + let stop = mk y 12 31 in + while Date.compare !d stop <= 0 do + (match Rite_of.Lectionary_of.date_keyed_slug !d with + | Some s -> Hashtbl.replace tbl (Slug.to_string s) true + | None -> ()); + d := Date.add_days !d 1 + done + done; + tbl + +let assert_reachable entries ~temporal_slugs ~sanctoral_slugs ~date_keyed_slugs = let base_of s = strip_emitted_suffix (Slug.to_string s) in let dead = List.filter (fun (s, _) -> let b = base_of s in - not (Hashtbl.mem temporal_slugs b || Hashtbl.mem sanctoral_slugs b)) + not (Hashtbl.mem temporal_slugs b || Hashtbl.mem sanctoral_slugs b || Hashtbl.mem date_keyed_slugs b)) entries in (match dead with @@ -676,8 +760,8 @@ let assert_reachable entries ~temporal_slugs ~sanctoral_slugs = List.iter (fun (s, _) -> Printf.eprintf - "bootstrap_lectionary_of: DEAD KEY -- %S is not a real Temporal_of slug (2004-2051) \ - nor a real sanctoral slug\n" + "bootstrap_lectionary_of: DEAD KEY -- %S is not a real Temporal_of slug (2004-2051), \ + nor a real sanctoral slug, nor a real date_keyed_slug (2004-2051)\n" (Slug.to_string s)) dead; die "%d emitted key(s) are unreachable (see above)" (List.length dead)); @@ -715,7 +799,14 @@ let temporal_day_gap ~lectionary ~year_start = let t = Rite_of.Temporal_of.temporal date in let base = Slug.to_string t.Temporal.office.Celebration.slug in let found = - Lectionary.mem lectionary (slug_or_die base) + (* THIS TASK'S fix, 2026-08-26 review: tried first, matching + Lectionary_of.readings' own step-3 order, so the O-Antiphon/ + Christmas-season date-keyed days this fix closed no longer + misreport as a gap in the header below. *) + (match Rite_of.Lectionary_of.date_keyed_slug date with + | Some s -> Lectionary.mem lectionary s + | None -> false) + || Lectionary.mem lectionary (slug_or_die base) || Lectionary.mem lectionary (slug_or_die (base ^ "-" @@ -749,7 +840,8 @@ let () = let mapped, report = map_bases resolved ~sanctoral_slugs in let entries = List.concat_map entries_of mapped in let temporal_slugs = reachable_temporal_slugs () in - let uncovered_temporal = assert_reachable entries ~temporal_slugs ~sanctoral_slugs in + let date_keyed_slugs = reachable_date_keyed_slugs () in + let uncovered_temporal = assert_reachable entries ~temporal_slugs ~sanctoral_slugs ~date_keyed_slugs in let lect = match Lectionary.of_entries entries with Ok l -> l | Error e -> die "%s" e in let gap_count, gap_total, gap_misses = temporal_day_gap ~lectionary:lect ~year_start:Rite_of.Temporal_of.year_start @@ -783,34 +875,64 @@ let () = ; a MISSING source file is fatal, checked before any read is attempted --\n\ ; see this generator's own [parse_ini]) and commit the diff instead.\n\ ; Every emitted key is asserted, at generation time, to be a slug\n\ - ; Rite_of.Temporal_of actually computes OR a slug the real merged\n\ + ; Rite_of.Temporal_of actually computes, OR a slug the real merged\n\ ; sanctoral layer (data/of/calendar-2002.sexp + all 13 decree overlays)\n\ - ; actually carries -- see [assert_reachable].\n\ + ; actually carries, OR a date Rite_of.Lectionary_of.date_keyed_slug\n\ + ; actually reaches -- see [assert_reachable].\n\ + ;\n\ + ; CORRECTED 2026-08-26 (review): this file used to exclude 16 lectio\n\ + ; bases -- the 8 O-Antiphon days (17-24 December) and 8 further\n\ + ; Christmas-season dates (29-31 December, 2-5 and 7 January) -- as\n\ + ; \"date-keyed duplicates\" of the weekday-keyed ferial families mapped\n\ + ; above. That was false: each carries content found nowhere else among\n\ + ; the emitted entries (17 December: Gen 49:2,8-10/Matt 1:1-17), which\n\ + ; OLM n. 69.3 explains -- these ferias are fixed by CIVIL DATE, not\n\ + ; merely non-alternating within a weekday slot like every other Advent/\n\ + ; Christmastide feria. Every day in 2005-2050 previously served a\n\ + ; DRIFTING citation there (whichever weekday-keyed family that year's\n\ + ; own alignment happened to land on) instead of the Missal's fixed one.\n\ + ; Fixed via a new date-keyed lookup route, tried BEFORE the weekday-\n\ + ; keyed one (Rite_of.Lectionary_of.date_keyed_slug, readings' own step\n\ + ; 3) -- Temporal_of's slugs are UNCHANGED, only which lectionary key\n\ + ; resolves the day's citations. 3 lectio bases remain excluded, for\n\ + ; three genuinely different structural-unreachability reasons, none of\n\ + ; them \"duplicate\" -- see [excluded_bases]'s own comment.\n\ ;\n\ ; Source: %s\n\ ; SHA-256: %s\n\ ; %d ini sections (988 expected) -> %d resolved (base, cycle-shape) \ -pairs -> %d colitur slugs mapped, %d ini bases deliberately excluded (date-\n\ - ; keyed duplicates/late-Advent, no colitur date-keyed lookup exists --\n\ - ; see [excluded_bases]), %d ini bases genuinely unmapped (no pattern, no\n\ - ; override, no matching sanctoral slug) -> %d emitted lectionary entries.\n\ +pairs -> %d colitur slugs mapped, %d ini bases genuinely excluded (structurally\n\ + ; unreachable -- see [excluded_bases]), %d ini bases genuinely unmapped\n\ + ; (no pattern, no override, no matching sanctoral slug) -> %d emitted\n\ + ; lectionary entries.\n\ ;\n\ ; COVERAGE, BOTH DIRECTIONS (Step 1 of this task's own brief):\n\ ; (1) Temporal-day gap: of the %d days of civil year 2026, %d produce a\n\ - ; Rite_of.Temporal_of slug with NO entry in this file (tried flat,\n\ - ; then both the Sunday- and weekday-cycle letter suffixes -- the\n\ - ; same order Lectionary_of.readings itself tries). Those are days\n\ - ; with no readings via the temporal-slug path (a sanctoral proper\n\ - ; on the same civil day, where one exists, is Lectionary_of's own\n\ + ; Rite_of.Temporal_of slug with NO entry in this file (tried the\n\ + ; date-keyed route first, then flat, then both the Sunday- and\n\ + ; weekday-cycle letter suffixes -- the same order\n\ + ; Lectionary_of.readings itself tries). Those are days with no\n\ + ; readings via the temporal-slug path (a sanctoral proper on the\n\ + ; same civil day, where one exists, is Lectionary_of's own\n\ ; separate first chance). Named set below.\n\ ; (2) Unmapped lectio keys: %d ini base names (of 988 sections, %d\n\ ; distinct bases) map to no colitur slug at all -- data being\n\ ; silently dropped if unreported. Named below.\n\ + ; (3) Sanctoral coverage (Minor, 2026-08-26 review): of the %d shipped\n\ + ; sanctoral slugs (data/of/calendar-2002.sexp + all 13 decree\n\ + ; overlays), %d have a DEDICATED entry in this file (a lectio base\n\ + ; name this generator recognised verbatim as one of them). The\n\ + ; other %d have none and correctly fall through to the day's own\n\ + ; ferial (Lectionary_of.readings' own step 2 -> step 3) -- this is\n\ + ; OLM norms working as designed for a saint with no proper of\n\ + ; their own, not a gap in this data.\n\ ; Regenerate with:\n\ ; eval $(opam env) && dune exec tools/bootstrap_lectionary_of.exe -- %s %s\n" src (sha256 src) (List.length secs) (List.length resolved) report.mapped (List.length excluded_bases) (List.length report.unmapped) (List.length entries) - gap_total gap_count (List.length report.unmapped) (List.length resolved) src dst; + gap_total gap_count (List.length report.unmapped) (List.length resolved) + (Hashtbl.length sanctoral_slugs) report.sanctoral_passthrough + (Hashtbl.length sanctoral_slugs - report.sanctoral_passthrough) src dst; Printf.fprintf oc "; Unmapped lectio bases (%d):\n" (List.length report.unmapped); List.iter (fun b -> Printf.fprintf oc "; %s\n" b) report.unmapped; Printf.fprintf oc "; Temporal_of slugs (2004-2051) with no lectionary entry (%d, informational --\n\ |
