summaryrefslogtreecommitdiff
path: root/internal/calendar/precedence_ef.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 17:15:03 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-12 17:15:03 +0200
commit3b32c002d3eddda5ece9422442717657b9fee63b (patch)
tree5dd82811c100de6daa995bebd115a12434296508 /internal/calendar/precedence_ef.go
parent2386a4551aec94252168a8554269ee423d516882 (diff)
parentc655c525ef07deafb113a291489b396c81d7ce4a (diff)
downloadlectio-3b32c002d3eddda5ece9422442717657b9fee63b.tar.gz
lectio-3b32c002d3eddda5ece9422442717657b9fee63b.zip
Merge branch 'fix/ef-precedence-defects': seven EF calendar defects
Fixes seven defects in the EF calendar, each found by differential and oracle comparison against an independently-written engine and each backed by a cited paragraph of the Rubricae Generales verified against the 1962 Missal: RG 96 transfers must skip II-class days (the Precious Blood was landing on the Visitation) RG 91 a II-class privileged feria yields to an equal-class feast (St Matthew was losing to the September Ember Wednesday) RG 11 all Sundays of Advent, Lent and Passiontide are I class (only Advent I was), which also settles St Joseph on a Lent Sunday RG 131 Rose on Gaudete and Laetare, which the EF path never emitted RG 122 Holy Thursday is white and the sanctoral generator itself, whose rank inference had tagged 15 III-class feasts as commemorations, dropped four entries to slug collisions, and mis-tagged three class fields. The EF oracle test asserted Season only, which is why none of this ever failed. It now asserts rank and colour, with a cited allow-list. Two commemorations had been deleted on a primary-source claim the primary source contradicts: the Archivum Liturgicum transcription silently drops commemoration lines where the photographic scans carry them. Six of the seven affected dates are now present; the seventh (25 December) is blocked by an ingestion limit that reads only the first of three Christmas Masses, recorded at its cause. Known gap, recorded in source: RG 95/97/98 chained transfers, where two I-class feasts translate to the same day.
Diffstat (limited to 'internal/calendar/precedence_ef.go')
-rw-r--r--internal/calendar/precedence_ef.go86
1 files changed, 72 insertions, 14 deletions
diff --git a/internal/calendar/precedence_ef.go b/internal/calendar/precedence_ef.go
index 4fda626..9f5016f 100644
--- a/internal/calendar/precedence_ef.go
+++ b/internal/calendar/precedence_ef.go
@@ -1,6 +1,9 @@
package calendar
-import "sort"
+import (
+ "sort"
+ "strings"
+)
// efRankOrder orders EF (1960) ranks: class-1 highest, then class-2..4,
// commemoration, ferial lowest.
@@ -22,34 +25,89 @@ func efRankOrder(r Rank) int {
}
// precedenceEF ranks an EF candidate for occurrence (lower = higher precedence):
-// by class first, then a tie-break at equal class (1960 occurrence table).
+// by class first, then a tie-break at equal class (RG 91's Table of
+// Precedence, read entry-by-entry within each class).
//
-// The tie-break is asymmetric by season. An ORDINARY feria (per annum, Advent,
-// Septuagesima) yields to an equal-class feast — the feast is celebrated and the
-// feria commemorated (e.g. St Francis Xavier, Dec 3, on an Advent feria). The
-// ferias of Lent and Passiontide are privileged: they outrank an equal-class
-// feast, which is only commemorated. Sundays and named temporal feasts likewise
-// win their ties. The *2 class spacing means the ±1 tie-break never crosses a
-// class boundary.
+// The tie-break is asymmetric by season AND, at class 2, by whether the
+// temporal day is a Sunday or a privileged FERIA:
+// - III/IV class: an ORDINARY feria (per annum, Advent to 16 Dec,
+// Septuagesima) yields to an equal-class feast (entries 24/25 above the
+// feria) -- the feast is celebrated, the feria commemorated (e.g. St
+// Francis Xavier, Dec 3). The ferias of Lent and Passiontide are
+// privileged (entry 22, ABOVE entry 24): they outrank an equal-class
+// feast, which is only commemorated.
+// - II class: an ordinary SUNDAY wins its tie (entry 15, above entry 16's
+// feasts) -- but a II-class privileged FERIA (the Ember days, the
+// late-Advent 17-23 Dec ferias, entry 18) sits BELOW entry 16 and yields
+// to an equal-class feast, the opposite of a Sunday's own tie. This is
+// the same table shape as III class's Advent/per-annum ferias, just
+// inverted for which II-class temporal days count as "privileged".
+// - I class: Sundays (entry 6) and I-class ferias (entry 7, Ash
+// Wednesday/Holy Week) both sit above entry 11's feasts, so they always
+// win -- there is no I-class "ordinary feria yields" case at all.
+//
+// The *2 class spacing means the ±1 tie-break never crosses a class boundary.
func precedenceEF(c candidate) int {
p := (6 - efRankOrder(c.Cel.Rank)) * 2 // class-1 -> 2, ferial -> 12
if c.Temporal {
- ordinaryFeria := (c.Cel.Rank == RankClass3 || c.Cel.Rank == RankClass4) &&
- c.Season != Lent && c.Season != Passiontide
- if ordinaryFeria {
- p++ // an ordinary feria yields to an equal-class feast
+ yieldsToFeast := false
+ switch c.Cel.Rank {
+ case RankClass3, RankClass4:
+ yieldsToFeast = c.Season != Lent && c.Season != Passiontide
+ case RankClass2:
+ // A II-class Sunday (entry 15) wins; a II-class privileged feria --
+ // Ember days, the late-Advent 17-23 Dec ferias (entry 18) -- yields
+ // to an equal-class feast (entry 16), e.g. St Matthew (21 Sep)
+ // beating the September Ember Wednesday, or St Thomas (21 Dec)
+ // beating an Advent late feria.
+ yieldsToFeast = !c.Sunday
+ }
+ if yieldsToFeast {
+ p++
} else {
- p-- // Sundays, named feasts, and penitential ferias win their ties
+ p-- // Sundays, I-class ferias, and penitential (Lent/Passiontide) ferias win their ties
}
} else if c.Cel.Class == ClassLord && c.Cel.Rank == RankClass2 {
// A II class feast of the Lord takes the place of a II class Sunday it
// falls on (unlike a saint's feast, which is only commemorated). Give it
// the edge over the Sunday's tie-break bonus.
p -= 2
+ } else if c.Cel.Rank == RankClass1 && beatsClass1Sunday(c.Cel.Slug) {
+ // RG 91 entries 4 (Immaculate Conception, Assumption BVM) and 5
+ // (Vigil & Octave day of the Nativity) both sit ABOVE entry 6
+ // (Sundays of Advent/Lent/Passiontide, Low Sunday) -- unlike an
+ // ORDINARY I-class feast (entry 11, e.g. St Joseph, the Precious
+ // Blood), which yields to a I-class Sunday, these win the tie.
+ // Found while verifying defect 4 (Sunday ranks): before that fix,
+ // Advent/Lent Sundays were wrongly II class, so these feasts beat
+ // them outright on base class alone, accidentally right; once
+ // Sundays became I class the tie-break mattered, and without this
+ // branch the Sunday would wrongly win. Two live cases in the fixed
+ // calendar: the Immaculate Conception (8 December) on an Advent
+ // Sunday, and the Vigil of Christmas (24 December) on Advent IV --
+ // the latter is not merely a wrong winner but a WORSE bug without
+ // this branch: transferIfImpededEF has no way to re-place a
+ // transfer that crosses the Dec31/Jan1 boundary (celebrationDate
+ // re-resolves a fixed date using the YEAR OF THE DAY BEING QUERIED,
+ // so a walk landing in January of the following year can never
+ // match the query that produced it), so the Vigil simply vanished
+ // for the year instead of landing on the wrong day. The Assumption
+ // (15 August) never falls in Advent or Lent, so its own share of
+ // this branch is citation-complete but not live.
+ p -= 2
}
return p
}
+// beatsClass1Sunday reports whether slug is one of RG 91's entries 4 or 5 --
+// the Immaculate Conception, the Assumption, or the Vigil of Christmas --
+// all of which sit above entry 6's Sundays in the Table of Precedence.
+func beatsClass1Sunday(slug string) bool {
+ return strings.Contains(slug, "immaculate-conception") ||
+ strings.Contains(slug, "assumption-of-the-blessed-virgin-mary") ||
+ slug == "vigil-of-christmas"
+}
+
// pickEF returns the observed EF celebration and the commemorations, ordered
// deterministically by precedence then slug.
func pickEF(cands []candidate) (candidate, []candidate) {