diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-23 23:01:42 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-23 23:01:42 +0200 |
| commit | 8faf89f89bd6783a518b6fc0b48c6ee452ba2e94 (patch) | |
| tree | 6aa160722b0c8cf0cc29df7e7ee5cce6082e4429 /tools/extract_lms_ordo.ml | |
| parent | 0806fe65e388a9502035bc9d4f551528ac74b26d (diff) | |
| download | colitur-8faf89f89bd6783a518b6fc0b48c6ee452ba2e94.tar.gz colitur-8faf89f89bd6783a518b6fc0b48c6ee452ba2e94.zip | |
feat(ordo): wire the LMS preface column into the witness suite
Extends tools/extract_lms_ordo.ml's row type with a praef field (the
first "Pr of ..." / "Common Pr" match in each day's universal block,
same first-in-reading-order discipline already used for Gl/Cr) and
regenerates all three LMS fixtures. Also brings lms-ordo-2024-2025.sexp
under the generalised, probe-verified provenance template the Witnesses
task previously added only for the two newer editions -- it had kept
Task 6's original hand-written header.
Characterisation finding: this Ordo prints OPTION LISTS ("Pr of X or Pr
of Y or Common Pr"), not a single value per day, and several named
options (Martyrs, All Saints and Patron Saints, the Dedication of a
Church, the Most Holy Sacrament, St John the Baptist, the Angels) are
genuinely absent from RG 484-497's own fourteen named prefaces. Every
instance found in the universal block pairs such an extra with a
genuine RG-482 answer, so test/test_lms_ordo.ml compares by membership
(classify_praef_options, a 15-entry prefix table split on the
standalone token "or"), not string equality.
Opposite-prediction probes (Nativity/Apostles/Requiem) are hard-
verified inside the extractor itself before any fixture is trusted, the
same discipline already used for the Creed column.
Result: 396/396, 399/399, 398/398 comparable rows agree across the
three editions (Good Friday excluded per edition, no Mass that day),
zero allow-list entries needed.
extraordinaryform.org's own three editions were characterised but
deliberately not wired in this pass -- see
.superpowers/sdd/2026-08-21-colitur-celebrant-rubrics-phase1/preface-witnesses-report.md
for the full account (the field is genuinely per-day, contrary to a
naive header-count read, but carries its own classification anomaly
during Advent and reconfirms the corpus's known St Lawrence vigil
defect, and building a full extractor for it is a separate, comparably
large task not needed to decide the RG 497 question this same report
settles).
Claude-Session: https://claude.ai/code/session_017ZBxCCRM2ojnBupp3SBxV9
Diffstat (limited to 'tools/extract_lms_ordo.ml')
| -rw-r--r-- | tools/extract_lms_ordo.ml | 198 |
1 files changed, 189 insertions, 9 deletions
diff --git a/tools/extract_lms_ordo.ml b/tools/extract_lms_ordo.ml index 8680a91..582fb6e 100644 --- a/tools/extract_lms_ordo.ml +++ b/tools/extract_lms_ordo.ml @@ -62,6 +62,24 @@ type row = { matched as a substring of "IV Mass of BVM"). *) gloria : bool option; (** [None] only if genuinely not found -- reported, not silently dropped *) creed : bool option; + praef : string option; + (** RAW trailing text from the FIRST "Pr of ..." / "Common Pr" match + in the universal block's own reading order -- CAPTURED, NOT + CLASSIFIED here, the same discipline + tools/extract_fiuv_ordo.ml's own [row.praef] documents for a + different publisher's format. See the provenance header's own + "Pr of" characterisation bullet for what this column can and + cannot show: this Ordo prints OPTION LISTS ("Pr of X or Pr of Y + or Common Pr"), some of whose named options ("Martyrs", "All + Saints and Patron Saints", "the Dedication of a Church", "the + Most Holy Sacrament", "St John the Baptist", "the Angels") are + NOT among the fourteen the 1962 Missale Romanum's own RG 484-497 + enumerate -- ad libitum extras this publisher includes alongside + a genuine RG 482 answer, never the sole option on any row found + in this corpus. Classification into {!Colitur_kernel.Preface.t} + happens test-side (test/test_lms_ordo.ml), by MEMBERSHIP in the + parsed option set, not string equality -- see that file's own + [classify_praef] citation for the full account. *) has_diocesan_variant : bool; (** whether a diocese-specific block followed the universal one anywhere in this day's raw text (informational only -- see the @@ -126,10 +144,10 @@ let bvm_numeral_of_line line = List.assoc_opt line (List.map (fun (n, s) -> (s, (* [lines]: this day's own raw continuation lines, marker-stripped and trimmed, title line NOT included (the title never carries Gl/Cr/"Mass of" itself in this source). Returns (formulary_override, bvm_numeral, - gloria, creed, has_diocesan_variant), each derived from the portion - BEFORE the first colon-terminated (diocesan) line -- see the module - header for why this is belt-and-suspenders, not load-bearing on its - own. *) + gloria, creed, praef, has_diocesan_variant), each derived from the + portion BEFORE the first colon-terminated (diocesan) line -- see the + module header for why this is belt-and-suspenders, not load-bearing on + its own. *) let scan_block lines = let rec universal_prefix = function | [] -> [] @@ -161,7 +179,36 @@ let scan_block lines = reads as [true], the same as any other non-"No" predecessor would). *) let gloria = find_flag "Gl" ("" :: all_tokens) in let creed = find_flag "Cr" ("" :: all_tokens) in - (formulary_override, bvm_numeral, gloria, creed, has_diocesan) + (* [praef]: LINE-SCOPED (not [all_tokens]'s flattened stream), because + the captured text runs to the END of whichever physical line carries + it -- flattening first would glue an unrelated following line's own + prose onto it. "Pr" always abbreviates "Preface" in this source's own + body text (checked directly: its only OTHER appearance is the front- + matter Abbreviations glossary entry "Pr Preface", outside the Ordo + body this tool scans), so a whole-token ("Pr","of") pair, or a bare + ("Common","Pr") pair when no proper/de-Tempore option is offered, is + the sole anchor needed -- no reject-list, unlike the BVM roman + numeral's own substring trap, because nothing else in this corpus's + body ever produces either two-token sequence. *) + let find_praef_in_line line = + let toks = Array.of_list (split_ws line) in + let n = Array.length toks in + let rec go i = + if i + 1 >= n then None + else if (toks.(i) = "Pr" && toks.(i + 1) = "of") || (toks.(i) = "Common" && toks.(i + 1) = "Pr") then + Some (String.concat " " (Array.to_list (Array.sub toks i (n - i)))) + else go (i + 1) + in + go 0 + in + let praef = + let rec find = function + | [] -> None + | l :: rest -> ( match find_praef_in_line l with Some s -> Some s | None -> find rest) + in + find universal + in + (formulary_override, bvm_numeral, gloria, creed, praef, has_diocesan) (* --- the top-level line scan --------------------------------------------- *) @@ -334,25 +381,29 @@ let () = die "%s: printed weekday %S does not match computed weekday %S -- date resolution bug" (Date.to_iso8601 date) b.b_weekday (Date.weekday_to_string (Date.weekday date)) | None -> die "%s: unrecognised weekday abbreviation %S" (Date.to_iso8601 date) b.b_weekday); - let formulary_override, bvm_numeral, gloria, creed, has_diocesan_variant = scan_block b.b_lines in + let formulary_override, bvm_numeral, gloria, creed, praef, has_diocesan_variant = scan_block b.b_lines in { date = Date.to_iso8601 date; weekday = b.b_weekday; title = b.b_title; formulary_override; bvm_numeral; - gloria; creed; has_diocesan_variant }) + gloria; creed; praef; has_diocesan_variant }) blocks in let missing_gloria = List.filter (fun r -> r.gloria = None) rows in let missing_creed = List.filter (fun r -> r.creed = None) rows in + let missing_praef = List.filter (fun r -> r.praef = None) rows in if missing_gloria <> [] then Printf.printf "extract_lms_ordo: WARNING %d rows with no Gloria found: %s\n" (List.length missing_gloria) (String.concat ", " (List.map (fun r -> r.date) missing_gloria)); if missing_creed <> [] then Printf.printf "extract_lms_ordo: WARNING %d rows with no Creed found: %s\n" (List.length missing_creed) (String.concat ", " (List.map (fun r -> r.date) missing_creed)); + if missing_praef <> [] then + Printf.printf "extract_lms_ordo: WARNING %d rows with no Pr-of/Common-Pr found: %s\n" (List.length missing_praef) + (String.concat ", " (List.map (fun r -> r.date) missing_praef)); let with_variant = List.length (List.filter (fun r -> r.has_diocesan_variant) rows) in let with_override = List.length (List.filter (fun r -> r.formulary_override <> None) rows) in let with_bvm = List.length (List.filter (fun r -> r.bvm_numeral <> None) rows) in Printf.printf "extract_lms_ordo: %d rows; %d with a diocesan variant present; %d with a formulary override; \ - %d with a BVM roman numeral\n" - (List.length rows) with_variant with_override with_bvm; + %d with a BVM roman numeral; %d with no Pr-of/Common-Pr text\n" + (List.length rows) with_variant with_override with_bvm (List.length missing_praef); (* --- provenance header + write ----------------------------------------- *) let sha256_of_file path = let cmd = Printf.sprintf "sha256sum %s" (Filename.quote path) in @@ -401,6 +452,24 @@ let () = "NOT found by URL-pattern web search this session (unlike the 2023-2024 and 2024-2025 editions, no exact download link matching this PDF's own filename/version turned up under lms.org.uk/sites/default/files/u5374/; several near-miss candidates were checked and rejected -- none matched this PDF's own metadata) -- left honestly blank rather than guessed; the file was supplied locally to this session, not fetched by this tool", {|28 November 2025 ("End of November 2025", the same shape as the 2024-2025 edition -- Advent Sunday 2025 fell on 30 November, so only 3 tail days remained; note this excludes 27 November 2025, which the PRECEDING edition's own window (2024-2025, through 2025-12-31) already covers -- a one-day-short overlap between consecutive editions' own windows, not a gap or a bug in either extraction|} ) + | "lms-ordo-2024-2025.sexp" -> + (* Preface-witnesses task (2026-08-23): brought into this same + auto-generated, probe-verified header template so all three + editions carry the identical characterisation discipline -- + Task 6's own original HAND-WRITTEN header (never regenerated by + the Witnesses task, which only added table entries for the two + NEW editions) is superseded by this run; every fact below is + re-derived from this PDF's own text/metadata, not copied + blind from that older header (though it agrees with it in + every particular checked). *) + ( "2024-2025", + "9781739209636", + "2024", + {|Author "Peter Day-Milne", Producer "LibreOffice 7.4", CreationDate 2024-11-06, 140 pages|}, + "https://lms.org.uk/sites/default/files/u5374/ordo_2.21_2024-2025.pdf", + "identified via web search matching the PDF's own version number and filename to the publisher's own hosting path (product page: https://lms.org.uk/product/latin-mass-society-ordo-2024-25); NOT independently re-downloaded and byte-compared in this session -- recorded honestly rather than presented as verified", + {|27 November 2024 ("End of November 2024", the same shape as the 2025-2026 edition -- Advent Sunday 2024 fell on 1 December, so 4 tail days remained (27-30 November))|} + ) | _ -> die "no provenance/characterisation entry for %s -- add one to extract_lms_ordo.ml's own per-edition table before generating this fixture (the tool refuses to emit an under-characterised header)" base in let creed_of_date rows d = List.find_map (fun r -> if String.equal r.date d then r.creed else None) rows in @@ -420,6 +489,12 @@ let () = "St Laurence, II class martyr, no Lord/BVM/apostle clause applies, RG 476(b) -- St Joachim (16 August) is IMPEDED by a Sunday in this edition's own calendar (12th Sunday after Pentecost), so this edition needed a DIFFERENT sharp non-octave II-class pairing than the 2023-2024/2024-2025 editions used; a real, edition-specific finding, not an extraction quirk" ); ("2026-08-15", true, "the Assumption, I class, RG 475(b)") ] + | "lms-ordo-2024-2025.sexp" -> + [ ("2024-12-01", true, "Advent Sunday, RG 475(a)"); + ("2024-12-02", false, "ordinary Advent feria, RG 476(b)/(d)"); + ("2024-12-26", true, "St Stephen, II class, inside the Nativity octave, RG 475(d)"); + ("2025-08-15", true, "the Assumption, I class, RG 475(b)"); + ("2025-08-16", false, "St Joachim, II class, no Lord/BVM/apostle clause applies, RG 476(b)") ] | _ -> die "no probe list for %s" base in List.iter @@ -430,6 +505,54 @@ let () = | None -> die "characterisation probe FAILED: %s (%s) -- no row extracted for this date at all" d desc) probes; Printf.printf "extract_lms_ordo: all %d characterisation probes confirmed for %s\n" (List.length probes) edition_label; + (* Preface-witnesses task (2026-08-23): a SECOND opposite-prediction + probe set, over [praef] rather than [creed] -- the same discipline, + applied to the new column this task adds, per the task brief's own + "probe dates where the rule predicts OPPOSITE answers before + adjudicating anything" instruction. Three universal, unambiguous + fixed-date feasts per edition, each landing on a DIFFERENT named + preface (RG 484 Nativity, RG 497 Apostles, RG 499 Requiem) -- checked + directly against this run's own [rows] before this header is + written, a hard [die] on failure, not a printed claim. All Souls' + own date is edition-specific (2 November, TRANSFERRED to 3 November + in the one edition -- 2024-2025 -- where 2 November falls on a + Sunday; verified directly against each PDF's own text, not assumed + from RG 96 alone). *) + let praef_of_date rows d = List.find_map (fun r -> if String.equal r.date d then r.praef else None) rows in + let praef_probes = + match base with + | "lms-ordo-2023-2024.sexp" -> + [ ("2023-12-25", "Pr of the Nativity", "the Nativity of Our Lord, I class, RG 484(a)"); + ("2024-06-29", "Pr of the Apostles", "SS Peter & Paul, I class, RG 497"); + ("2024-11-02", "Pr of the Dead", "All Souls' Day, I class, RG 499") ] + | "lms-ordo-2024-2025.sexp" -> + [ ("2024-12-25", "Pr of the Nativity", "the Nativity of Our Lord, I class, RG 484(a)"); + ("2025-06-29", "Pr of the Apostles", "SS Peter & Paul, I class, RG 497"); + ( "2025-11-03", "Pr of the Dead", + "All Souls' Day, I class, RG 499 -- TRANSFERRED from 2 November, a Sunday in this edition's own calendar" ) + ] + | "lms-ordo-2025-2026.sexp" -> + [ ("2025-12-25", "Pr of the Nativity", "the Nativity of Our Lord, I class, RG 484(a)"); + ("2026-06-29", "Pr of the Apostles", "SS Peter & Paul, I class, RG 497"); + ("2026-11-02", "Pr of the Dead", "All Souls' Day, I class, RG 499") ] + | _ -> die "no praef probe list for %s" base + in + List.iter + (fun (d, expected_prefix, desc) -> + match praef_of_date rows d with + | Some got when has_prefix ~prefix:expected_prefix got -> () + | Some got -> + die "praef characterisation probe FAILED: %s (%s) -- expected prefix %S, extracted %S" d desc + expected_prefix got + | None -> die "praef characterisation probe FAILED: %s (%s) -- no praef extracted for this date at all" d desc) + praef_probes; + Printf.printf "extract_lms_ordo: all %d praef characterisation probes confirmed for %s\n" (List.length praef_probes) + edition_label; + let praef_probe_lines = + String.concat "\n" + (List.map (fun (d, expected_prefix, desc) -> Printf.sprintf "; %s (%s) -> %S" d desc expected_prefix) praef_probes) + in + let missing_praef_count = List.length missing_praef in let probe_lines = String.concat "\n" (List.map @@ -529,6 +652,62 @@ let () = ; and the "When may I say...?"/Appendix tail are never scanned at ; all (the extractor's own start/stop markers bound it to the Ordo ; proper). +; +; 5. THE "Pr of" (PREFACE) COLUMN (Preface-witnesses task, 2026-08-23) -- +; [row.praef], the RAW text from the FIRST "Pr of ..." / "Common Pr" +; match in the universal block's own reading order (the same +; FIRST-in-reading-order discipline finding 1 above already +; establishes for Gl/Cr). CAPTURED, NOT CLASSIFIED by this tool -- +; classification into {!Colitur_kernel.Preface.t} and the comparison +; against {!Rite_ef.Rubrics_ef.preface} both happen test-side +; (test/test_lms_ordo.ml's own [classify_praef]). +; +; CHARACTERISATION FINDING, non-negotiable per the same discipline as +; finding 2: this Ordo prints OPTION LISTS ("Pr of X or Pr of Y or +; Common Pr"), not always a single value -- and several of the named +; options ("Martyrs", "All Saints and Patron Saints" [almost always +; diocesan -- a Patronal feast's own preface -- but checked directly to +; occur ONCE in THIS edition's own UNIVERSAL block too, All Saints' Day +; itself, 1 November, paired with "or Common Pr"/"or Pr of the +; Trinity" depending on whether 1 November falls on a Sunday that +; edition, exactly matching {!Rite_ef.Rubrics_ef.preface}'s own RG +; 494(b) Sunday-gated Trinity fallback either way -- see that +; function's own citation], "the Dedication of a Church", "the Most Holy +; Sacrament" [Corpus Christi's own universal entry], "St John the +; Baptist", "the Angels") are genuinely NOT among the fourteen the +; 1962 Missale Romanum's own RG 484-497 enumerate (docs/research/LT.txt, +; lines 3936-4020, checked directly: no "de Martyribus"/"de Angelis"/ +; "de Dedicatione"/"de Ss.mo Sacramento"/"de S. Ioanne Baptista" clause +; exists anywhere in 482-499). Every one of these extras, in every +; instance found in this edition's UNIVERSAL block, is printed +; ALONGSIDE a genuine RG 482 answer (most often "or Common Pr"; on 24 +; March-shaped days, "or Pr of Lent", the live RG 486(b) de-Tempore +; answer) -- never as the row's OWN AND ONLY option. This is why the +; test-side comparison checks colitur's single computed answer is a +; MEMBER of this row's own parsed option set (dropping any unmapped +; extra name from that set first), not string equality against +; whichever option happens to print first -- the same "capture, don't +; force a match invented at extraction time" discipline +; tools/extract_fiuv_ordo.ml's own [row.praef] doc comment already +; states for a different publisher's format. +; +; OPPOSITE-PREDICTION PROBES against Rite_ef.Rubrics_ef.preface (RG +; 482-499), the SAME discipline as finding 2, applied to this new +; column -- RE-CHECKED BY THIS TOOL RUN before this header was +; written; a failed probe is a hard [die]: +%s +; All %d predictions confirmed -- this column, too, is discriminating, +; not a constant, across three different RG-cited outcomes (Nativity/ +; Apostles/Requiem). +; +; COVERAGE: %d of %d rows have no "Pr of"/"Common Pr" text found at +; all -- checked directly against this window's own Good Friday +; (Easter-2): its block carries NO Gl/Cr/Pr line whatsoever in this +; source (the 1955-restored Holy Week's own liturgical action has no +; Mass that day at all -- the SAME structural fact +; {!test_creed_coverage}'s own [window_good_fridays] already asserts +; for Gl/Cr), so [praef] shares that single expected gap rather than +; having a coverage gap of its own. |} dest edition_label isbn copyright_year pdf_meta url_note url pdf_base pdf_base pdf_sha (today ()) pdf_base pdf_base dest @@ -537,6 +716,7 @@ let () = (match List.rev rows with r :: _ -> r.date | [] -> "?") window_open_desc probe_lines (List.length probes) with_variant (List.length rows) (Printf.sprintf "%.1f" (100.0 *. float_of_int with_variant /. float_of_int (List.length rows))) + praef_probe_lines (List.length praef_probes) missing_praef_count (List.length rows) in let body = Sexplib.Sexp.to_string_hum ~indent:2 (sexp_of_list sexp_of_row rows) in let oc = open_out dest in |
