summaryrefslogtreecommitdiff
path: root/tools/bootstrap_lectionary_of.ml
diff options
context:
space:
mode:
Diffstat (limited to 'tools/bootstrap_lectionary_of.ml')
-rw-r--r--tools/bootstrap_lectionary_of.ml228
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\