aboutsummaryrefslogtreecommitdiff
path: root/internal/caldata/caldata_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 13:29:41 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 13:29:41 +0200
commit4a1db8df19425736fc82907c967586a3053a7ad1 (patch)
tree821d774eb61428d1e2b6937ef184454dd91ca8ff /internal/caldata/caldata_test.go
parent4722236b26d3439ed56893cb2863121bb705c7cf (diff)
downloadlectio-4a1db8df19425736fc82907c967586a3053a7ad1.tar.gz
lectio-4a1db8df19425736fc82907c967586a3053a7ad1.zip
scripts/gen-sanctoral-ef: fix rank inference, dedup, and class tagging (defect 7)
Six root-cause fixes to the generator, all in scripts/gen-sanctoral-ef.go, then a full regeneration of internal/caldata/tridentine-calendar.ini (323 -> 325 entries) against live missalemeum data: 1. Rank misinferred as "commemoration". A saint never OBSERVED in any of the six reference years was unconditionally hardcoded to RankCommemoration. 15 III-class feasts, all falling 6 March - 5 April, were always demoted by Lent's own privileged ferias in every reference year -- a sampling artifact, not their true rank. Fixed properly, not by hardcoding the 15: the commemoration object's own id names the SAINT's true rank (idParts already extracted it; only the colour half was kept before). But trusting that rank unconditionally for every commemoration-only entry over-corrected: some saints (St Blaise, St Canute, and others) really are without an independent Mass in the 1960-reformed books, demoted even on an ordinary, unprivileged day -- live-confirmed against missalemeum, not inferred. refYearExplainsAbsence distinguishes the two: the id's rank is trusted only when every reference year's OWN temporal day (ignoring sanctoral data entirely) was already strong enough to explain the demotion -- I/II class, an Ember day, or a privileged Lent/Passiontide feria. A single unprivileged-day counter-example keeps RankCommemoration. 2. Two silent slug collisions. entries (the generator's global map) is keyed by slug alone; two different fixed dates whose English titles are identical ("St. Agnes" 21 Jan and 28 Jan; "St. Boniface" 5 June and 14 May) silently dropped the second one add() ever saw. Fixed at the dedup itself: a same-slug collision on a DIFFERENT date is now disambiguated (a small curated slugOverride for these two -- 28 January is the traditional "secundo" commemoration of the same saint; 14 May's Boniface is a different martyr from 5 June's Boniface of Mainz) rather than dropped. 3. Two early-exit bugs in harvestDate, both replaced with `continue`: returning on the FIRST reference year that showed an observed office discarded commemorations only visible in a LATER year (St Theodore, 9 November, missing only from 2025, the first year tried); the Christ-the-King skip used to abandon the whole date outright instead of only that one movable year (St Evaristus, 26 October, invisible because 2025 -- tried first -- happens to be the one reference year Christ the King lands there). 4. A new class of artifact the fix for (2) exposed rather than caused: a movable-transfer feast displayed on whatever civil date it actually landed on in one reference year (St Joseph pushed to 20 March by a Sunday of Lent; the Annunciation deferred past Holy Week; All Souls moved to the Monday; St Matthias shown on the 25th in a leap year) was being harvested as if it were a genuine fixed-date office for THAT transferred date. idHomeDate compares info.id's own embedded date against the mmdd being queried (live-confirmed: 2023-03-20's id is "sancti:03-19:1:w") and skips a mismatch. 5. classOf's substring matching, checked against the calendarium's own verbatim titles: "purification" was never a Lord check at all (calendarium "IN PURIFICATIONE B. MARIAE VIRG.", a BVM feast) and is removed; "holy name" alone matched both "Holy Name of Jesus" and "Most Holy Name of MARY" (calendarium "Sanctissimi Nominis Mariae") and is now conditioned on the title not also naming Mary; "baptism" is a new case -- "Commemoration of the Baptism of the Lord" (13 January) ends "of THE Lord", not "of OUR Lord", the only suffix classOf checked, so it was missing the marker entirely. 6. One curated exclusion, NOT a generator bug: missalemeum's own "St. Romanus" commemoration on 9 August has no calendarium support (the row there reads only "Vigilia, III classis.", no "Com." line) -- an upstream data quirk, recorded in knownSpuriousComm rather than silently reproduced. "St. Eusebius" on 14 August (already known, register M6) gets the same treatment for the same reason. Witnesses (internal/caldata/caldata_test.go): TestTridentineClassOfLord, TestTridentineLentRankNotCommemoration, TestTridentineGenuineCommemorationStaysCommemoration, TestTridentineNoMissingEntries, TestTridentineNoSpuriousRomanus, TestTridentineNoTransferArtifacts, plus TestTridentineLoads' own Purification class assertion corrected from its previous (wrong) "lord" expectation. All fail against the pre-fix ini with messages such as: purification-of-the-blessed-virgin-mary: class = "lord", want "" sts-felicitas-perpetua: rank = "commemoration", want class-3 (not commemoration) missing "theodore" (11-09) romanus present: the calendarium's 9 August row has no Com. line to support it See the report for the full set. internal/caldata/tridentine-calendar.ini is regenerated output (go run scripts/gen-sanctoral-ef.go against live missalemeum data), not hand-edited.
Diffstat (limited to 'internal/caldata/caldata_test.go')
-rw-r--r--internal/caldata/caldata_test.go153
1 files changed, 148 insertions, 5 deletions
diff --git a/internal/caldata/caldata_test.go b/internal/caldata/caldata_test.go
index 12aa765..2cf10a5 100644
--- a/internal/caldata/caldata_test.go
+++ b/internal/caldata/caldata_test.go
@@ -74,12 +74,13 @@ func TestTridentineLoads(t *testing.T) {
t.Fatalf("EF sanctoral has only %d entries; expected the full calendar", len(l.Cels))
}
// Landmark feasts must be present with the right rank/class: Sts Peter & Paul
- // (I class, 06-29), the Purification/Presentation (a II class feast of the
- // Lord, 02-02, which must displace a Sunday), the Immaculate Conception
- // (I class, 12-08).
+ // (I class, 06-29), the Purification/Presentation (II class, 02-02 -- a
+ // feast of the BLESSED VIRGIN, calendarium "IN PURIFICATIONE B. MARIAE
+ // VIRG.", NOT of the Lord; class must be empty, not "lord"), the
+ // Immaculate Conception (I class, 12-08).
want := map[string]struct{ date, rank, class string }{
"sts-peter-paul": {"06-29", "class-1", ""},
- "purification-of-the-blessed-virgin-mary": {"02-02", "class-2", "lord"},
+ "purification-of-the-blessed-virgin-mary": {"02-02", "class-2", ""},
"immaculate-conception-of-the-blessed-virgin-mary": {"12-08", "class-1", ""},
}
for slug, w := range want {
@@ -91,7 +92,7 @@ func TestTridentineLoads(t *testing.T) {
if rc.Fields["date"] != w.date || rc.Fields["rank"] != w.rank {
t.Errorf("%s: date/rank = %q/%q, want %q/%q", slug, rc.Fields["date"], rc.Fields["rank"], w.date, w.rank)
}
- if w.class != "" && rc.Fields["class"] != w.class {
+ if rc.Fields["class"] != w.class {
t.Errorf("%s: class = %q, want %q", slug, rc.Fields["class"], w.class)
}
}
@@ -103,3 +104,145 @@ func TestTridentineLoads(t *testing.T) {
}
}
}
+
+// TestTridentineClassOfLord: scripts/gen-sanctoral-ef.go's classOf tags
+// feasts of the Lord (class = lord). Two entries were wrongly tagged lord
+// (title-substring false positives: "purification" always meant the BVM's
+// own feast, calendarium "IN PURIFICATIONE B. MARIAE VIRG."; "holy name"
+// alone matched both "Holy Name of Jesus" and "Most Holy Name of MARY",
+// calendarium "Sanctissimi Nominis Mariae"), and one was missing the tag
+// entirely (13 January's title ends "of THE Lord", not "of OUR Lord", the
+// only suffix classOf used to check -- calendarium "IN COMMEMORATIONE
+// BAPTISMATIS D. N. I. C."). See TestTridentineLoads for the Purification's
+// own class assertion (kept there, alongside its date/rank, not duplicated).
+func TestTridentineClassOfLord(t *testing.T) {
+ l := Tridentine()
+ want := map[string]string{
+ "most-holy-name-of-mary": "", // BVM, not the Lord
+ "commemoration-of-the-baptism-of-the-lord": "lord",
+ }
+ for slug, wantClass := range want {
+ rc, ok := l.Cels[slug]
+ if !ok {
+ t.Errorf("missing landmark feast %q", slug)
+ continue
+ }
+ if got := rc.Fields["class"]; got != wantClass {
+ t.Errorf("%s: class = %q, want %q", slug, got, wantClass)
+ }
+ }
+}
+
+// TestTridentineLentRankNotCommemoration: 15 III-class feasts, every one
+// falling 6 March - 5 April, were tagged rank = commemoration because all
+// six of the generator's reference years happen to place that fixed date
+// within Lent, where a privileged Lenten feria always outranks an
+// equal-class feast (RG 109(e)) -- the generator mistook "how the entry
+// presented on the sampled days" for the entry's true, intrinsic rank. A
+// sample of the 15 (full list in the report); the calendarium gives each one
+// III class outright, e.g. 5 April "S. Vincentii Ferrerii Conf., III
+// classis."
+func TestTridentineLentRankNotCommemoration(t *testing.T) {
+ l := Tridentine()
+ for _, slug := range []string{
+ "sts-felicitas-perpetua", "thomas-aquinas", "frances-rome",
+ "gregory-the-great", "patrick", "benedict", "francis-of-paola",
+ "isidore-of-seville", "vincent-ferrer",
+ } {
+ rc, ok := l.Cels[slug]
+ if !ok {
+ t.Errorf("missing %q", slug)
+ continue
+ }
+ if got := rc.Fields["rank"]; got != "class-3" {
+ t.Errorf("%s: rank = %q, want class-3 (not commemoration)", slug, got)
+ }
+ }
+}
+
+// TestTridentineGenuineCommemorationStaysCommemoration: the fix above must
+// NOT promote every commemoration-only entry indiscriminately -- some really
+// are without an independent Mass in the 1960-reformed books, demoted even
+// on an ORDINARY, unprivileged day (not explained by Lent/Passiontide or any
+// higher-class temporal day). St Blaise (3 Feb, an ordinary Septuagesima-
+// season feria) is live-confirmed by missalemeum as a mere commemoration
+// even then, so RankCommemoration is the honest, correct rank -- promoting
+// him to class-4 would let him wrongly win an ordinary green-season feria he
+// has no independent Mass to celebrate.
+func TestTridentineGenuineCommemorationStaysCommemoration(t *testing.T) {
+ l := Tridentine()
+ rc, ok := l.Cels["blaise"]
+ if !ok {
+ t.Fatal("missing blaise")
+ }
+ if got := rc.Fields["rank"]; got != "commemoration" {
+ t.Errorf("blaise: rank = %q, want commemoration (genuinely no independent Mass)", got)
+ }
+}
+
+// TestTridentineNoMissingEntries: four entries present in missalemeum (and
+// so in the calendarium) were silently dropped by the generator's own
+// dedup, which keyed a single global map by SLUG alone -- two by a literal
+// slug collision with an unrelated feast of the same English title on a
+// different date (28 January's "St. Agnes", the traditional SECOND
+// commemoration of 21 January's own Agnes; 14 May's "St. Boniface", a
+// different martyr from 5 June's Boniface of Mainz), and two (Evaristus,
+// Theodore) by the generator returning on the first reference year that
+// showed an observed office, before ever reaching the later year that
+// revealed their commemoration.
+func TestTridentineNoMissingEntries(t *testing.T) {
+ l := Tridentine()
+ want := map[string]string{
+ "agnes-secundo": "01-28",
+ "boniface-martyr": "05-14",
+ "evaristus": "10-26",
+ "theodore": "11-09",
+ }
+ for slug, date := range want {
+ rc, ok := l.Cels[slug]
+ if !ok {
+ t.Errorf("missing %q (%s)", slug, date)
+ continue
+ }
+ if got := rc.Fields["date"]; got != date {
+ t.Errorf("%s: date = %q, want %q", slug, got, date)
+ }
+ }
+}
+
+// TestTridentineNoSpuriousRomanus: the calendarium's 9 August row reads only
+// "Vigilia, III classis.", with no "Com." line -- missalemeum's own "St.
+// Romanus" commemoration that day has no calendarium support and is a
+// genuine upstream data quirk (the same standard already applied to "St.
+// Eusebius" on 14 August, also excluded).
+func TestTridentineNoSpuriousRomanus(t *testing.T) {
+ l := Tridentine()
+ if _, ok := l.Cels["romanus"]; ok {
+ t.Error("romanus present: the calendarium's 9 August row has no Com. line to support it")
+ }
+ if rc, ok := l.Cels["eusebius"]; ok && rc.Fields["date"] != "12-16" {
+ t.Errorf("eusebius: date = %q, want 12-16 (14 August's Eusebius has no calendarium support)", rc.Fields["date"])
+ }
+}
+
+// TestTridentineNoTransferArtifacts: a movable-transfer feast displayed on
+// whatever civil date it actually landed on in a given reference year (St
+// Joseph pushed to 20 March by a Sunday of Lent; the Annunciation deferred
+// past Holy Week; All Souls moved to the Monday; St Matthias shown on the
+// 25th in a leap year) must not leak into the sanctoral as a phantom
+// fixed-date entry keyed to that transferred civil date.
+func TestTridentineNoTransferArtifacts(t *testing.T) {
+ l := Tridentine()
+ for _, slug := range []string{
+ "joseph-spouse-of-the-bl-virgin-mary-0320",
+ "annunciation-of-the-blessed-virgin-mary-0405",
+ "annunciation-of-the-blessed-virgin-mary-0408",
+ "annunciation-of-the-blessed-virgin-mary-0409",
+ "commemoration-of-all-souls-1103",
+ "matthias-0225",
+ } {
+ if _, ok := l.Cels[slug]; ok {
+ t.Errorf("phantom transfer-artifact entry %q present", slug)
+ }
+ }
+}