aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/rites/rite_of/precedence_of.ml63
-rw-r--r--test/test_precedence_of.ml88
2 files changed, 146 insertions, 5 deletions
diff --git a/lib/rites/rite_of/precedence_of.ml b/lib/rites/rite_of/precedence_of.ml
index bca6ca9..d777f68 100644
--- a/lib/rites/rite_of/precedence_of.ml
+++ b/lib/rites/rite_of/precedence_of.ml
@@ -24,10 +24,23 @@ let unclassified = max_int
(* Not a Normae citation. The Tabula distinguishes universal-calendar entries
(3, 7, 10) from proper-calendar ones (4, 8, 11), and nothing in
Celebration.t marks that distinction except [layer]. Same mechanism, and
- same naming, as Precedence_ef.{universal_layer,indult_prefix}. *)
+ same naming, as Precedence_ef.{universal_layer,indult_prefix}.
+
+ [universal_layer] itself is exported (hand-authored sanctoral data is
+ tagged with it) but [band] no longer tests [layer = universal_layer]
+ directly anywhere in this chain: every temporal-origin candidate
+ {!Rite_of.Temporal_of.temporal} constructs carries [layer = "temporal"]
+ unconditionally (temporal_of.ml, [build]'s own hardcoded ~layer), never
+ [universal_layer] -- so a positive "is this the universal layer" test
+ would silently exclude Trinity Sunday, Corpus Christi, Christ the King,
+ Mary Mother of God, Holy Family and the Baptism of the Lord, none of
+ which are proper-calendar in the sense the Tabula's own 4/8/11 mean.
+ Entries 3, 5 and 7 (all universal-or-temporal, never proper) therefore
+ test [not (is_proper layer)] instead -- true for both [universal_layer]
+ and ["temporal"], false only for a genuine ["proper:..."] candidate. Only
+ entries 4 and 8 test [is_proper] itself. *)
let universal_layer = "of-universal"
let proper_prefix = "proper:"
-let is_universal layer = String.equal layer universal_layer
let is_proper layer = String.starts_with ~prefix:proper_prefix layer
(* Days measured from Easter, so the Triduum and the Easter Octave are
@@ -78,6 +91,36 @@ let is_easter_octave_day ctx =
let o = easter_offset ctx in
o >= 1 && o <= 7
+(* Tabula II.6: "Dominicae temporis Nativitatis et dominicae 'per annum'".
+ The privileged Sundays (Advent, Lent, Easter) are entry 2 and are tested
+ first, so this branch sees only the remaining two seasons. *)
+let is_ordinary_sunday (ctx : Vocab_of.season Precedence.context) c =
+ ctx.weekday = Date.Sun
+ && (match ctx.season with
+ | Vocab_of.Christmas | Vocab_of.Ordinary_time -> true
+ | Vocab_of.Advent | Vocab_of.Lent | Vocab_of.Easter -> false)
+ && c.Precedence.origin = Precedence.Temporal
+
+(* Tabula II.9, three clauses. "Feriae Adventus a die 17 ad 24 decembris
+ inclusive" -- the late-Advent ferias, which outrank an obligatory memorial
+ where an ordinary Advent feria (entry 13) does not. *)
+let is_late_advent_feria (ctx : Vocab_of.season Precedence.context) =
+ ctx.season = Vocab_of.Advent
+ && Date.month ctx.date = 12
+ && Date.day ctx.date >= 17
+ && Date.day ctx.date <= 24
+
+(* "Dies infra octavam Nativitatis": 26-31 December. The Nativity itself
+ (25 December) is entry 2; 1 January is the Octave Day, a solemnity in the
+ sanctoral, not a feria. *)
+let is_nativity_octave_day (ctx : Vocab_of.season Precedence.context) =
+ Date.month ctx.date = 12 && Date.day ctx.date >= 26 && Date.day ctx.date <= 31
+
+(* "Feriae Quadragesimae". Ash Wednesday and the Holy Week ferias are entry 2
+ and are tested first. *)
+let is_lenten_feria (ctx : Vocab_of.season Precedence.context) =
+ ctx.season = Vocab_of.Lent
+
let band (ctx : Vocab_of.season Precedence.context)
(c : Vocab_of.rank Precedence.candidate) : int =
let temporal = c.Precedence.origin = Precedence.Temporal in
@@ -90,8 +133,20 @@ let band (ctx : Vocab_of.season Precedence.context)
|| is_holy_week_feria ctx
|| is_easter_octave_day ctx)
then 20 (* Tabula I.2 *)
- else if c.cel.rank = Vocab_of.Sollemnitas && is_universal layer then
- 30 (* Tabula I.3 *)
+ else if c.cel.rank = Vocab_of.Sollemnitas && not (is_proper layer) then
+ 30 (* Tabula I.3 -- universal or temporal-origin, never proper; see the
+ [is_proper] comment above for why this is not [is_universal] *)
else if c.cel.rank = Vocab_of.Sollemnitas && is_proper layer then
40 (* Tabula I.4 *)
+ else if c.cel.rank = Vocab_of.Festum && c.cel.subject = Subject.Lord
+ && not (is_proper layer) then 50 (* Tabula II.5 *)
+ else if is_ordinary_sunday ctx c then 60 (* Tabula II.6 *)
+ else if c.cel.rank = Vocab_of.Festum && not (is_proper layer) then
+ 70 (* Tabula II.7 -- BVM and saints; entry 5 already took subject = Lord *)
+ else if c.cel.rank = Vocab_of.Festum && is_proper layer then
+ 80 (* Tabula II.8 *)
+ else if
+ temporal
+ && (is_late_advent_feria ctx || is_nativity_octave_day ctx || is_lenten_feria ctx)
+ then 90 (* Tabula II.9 *)
else unclassified
diff --git a/test/test_precedence_of.ml b/test/test_precedence_of.ml
index bf514ff..a06fb25 100644
--- a/test/test_precedence_of.ml
+++ b/test/test_precedence_of.ml
@@ -68,5 +68,91 @@ let test_part1 () =
Alcotest.(check int) label expected (Rite_of.Precedence_of.band c cand))
part1_cases
+let part2_cases =
+ [ ( "entry 5: a universal feast of the Lord",
+ ctx ~iso:"2026-08-06" ~season:Vocab_of.Ordinary_time,
+ mk ~subject:Subject.Lord ~slug:"of-transfiguration" ~rank:Vocab_of.Festum
+ ~origin:Precedence.Sanctoral (),
+ 50 );
+ ( "entry 6: a Sunday per annum",
+ ctx ~iso:"2026-07-12" ~season:Vocab_of.Ordinary_time,
+ (* rank is Festum, not the brief's original Sollemnitas: a real Sunday
+ per annum is never Sollemnitas (temporal_of.ml only grades Advent,
+ Lent and Easter Sundays that way, and those are entry 2, tested
+ before this one), and a Sollemnitas candidate here would hit entry 3
+ first regardless of layer -- the brief's own adjacent "Sunday of
+ Christmas time" row already uses Festum for the identical reason. *)
+ mk ~slug:"of-ordinary-time-sunday-15" ~rank:Vocab_of.Festum
+ ~origin:Precedence.Temporal (),
+ 60 );
+ ( "entry 6: a Sunday of Christmas time",
+ ctx ~iso:"2027-01-03" ~season:Vocab_of.Christmas,
+ mk ~slug:"of-christmas-sunday-2" ~rank:Vocab_of.Festum
+ ~origin:Precedence.Temporal (),
+ 60 );
+ ( "entry 7: a universal feast of a saint",
+ ctx ~iso:"2026-04-25" ~season:Vocab_of.Easter,
+ mk ~subject:Subject.Saint ~slug:"of-mark" ~rank:Vocab_of.Festum
+ ~origin:Precedence.Sanctoral (),
+ 70 );
+ ( "entry 7: a universal feast of the BVM",
+ ctx ~iso:"2026-05-31" ~season:Vocab_of.Ordinary_time,
+ mk ~subject:Subject.Bvm ~slug:"of-visitation" ~rank:Vocab_of.Festum
+ ~origin:Precedence.Sanctoral (),
+ 70 );
+ ( "entry 8: a proper feast",
+ ctx ~iso:"2026-04-25" ~season:Vocab_of.Easter,
+ mk ~layer:"proper:diocese-of-poznan" ~slug:"of-cathedral-dedication"
+ ~rank:Vocab_of.Festum ~origin:Precedence.Sanctoral (),
+ 80 );
+ ( "entry 9: an Advent feria on 17 December",
+ ctx ~iso:"2026-12-17" ~season:Vocab_of.Advent,
+ mk ~slug:"of-advent-3-thursday" ~rank:Vocab_of.Feria
+ ~origin:Precedence.Temporal (),
+ 90 );
+ ( "entry 9: a day within the Octave of the Nativity",
+ ctx ~iso:"2026-12-29" ~season:Vocab_of.Christmas,
+ mk ~slug:"of-christmas-0-tuesday" ~rank:Vocab_of.Feria
+ ~origin:Precedence.Temporal (),
+ 90 );
+ ( "entry 9: a Lenten feria",
+ ctx ~iso:"2026-03-03" ~season:Vocab_of.Lent,
+ mk ~slug:"of-lent-2-tuesday" ~rank:Vocab_of.Feria
+ ~origin:Precedence.Temporal (),
+ 90 );
+ (* MY RULING (2026-08-25, task-2-brief correction): every temporal-origin
+ celebration Temporal_of.temporal constructs carries [layer = "temporal"],
+ never [Precedence_of.universal_layer] -- so entries 3, 5 and 7 must test
+ [not (is_proper layer)], not [is_universal layer]. These four rows pin
+ exactly what that correction fixes: without it, Corpus Christi comes out
+ unclassified (a hole) and Trinity/Christ the King land at entry 6 instead
+ of entry 3. *)
+ ( "entry 3: Trinity Sunday is a solemnity of the general calendar",
+ ctx ~iso:"2026-05-31" ~season:Vocab_of.Ordinary_time,
+ mk ~slug:"of-trinity" ~rank:Vocab_of.Sollemnitas ~origin:Precedence.Temporal (),
+ 30 );
+ ( "entry 3: Corpus Christi, a Thursday, is not unclassified",
+ ctx ~iso:"2026-06-04" ~season:Vocab_of.Ordinary_time,
+ mk ~slug:"of-corpus-christi" ~rank:Vocab_of.Sollemnitas ~origin:Precedence.Temporal (),
+ 30 );
+ ( "entry 5: the Baptism of the Lord is a feast OF THE LORD",
+ ctx ~iso:"2026-01-11" ~season:Vocab_of.Christmas,
+ mk ~subject:Subject.Lord ~slug:"of-baptism-of-the-lord" ~rank:Vocab_of.Festum
+ ~origin:Precedence.Temporal (),
+ 50 );
+ ( "entry 6, not 5: an ordinary Christmas Sunday has no Lord subject",
+ ctx ~iso:"2027-01-03" ~season:Vocab_of.Christmas,
+ mk ~subject:Subject.Temporal ~slug:"of-christmas-sunday-2" ~rank:Vocab_of.Festum
+ ~origin:Precedence.Temporal (),
+ 60 ) ]
+
+let test_part2 () =
+ List.iter
+ (fun (label, c, cand, expected) ->
+ Alcotest.(check int) label expected (Rite_of.Precedence_of.band c cand))
+ part2_cases
+
let suite =
- ("precedence-of", [ Alcotest.test_case "Tabula part I (entries 1-4)" `Quick test_part1 ])
+ ( "precedence-of",
+ [ Alcotest.test_case "Tabula part I (entries 1-4)" `Quick test_part1;
+ Alcotest.test_case "Tabula part II (entries 5-9)" `Quick test_part2 ] )