1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
(* Table-driven, one Alcotest case per Tabula entry, so a transcription error
names the entry it broke. Mirrors test/test_precedence_ef.ml. *)
open Colitur_kernel
module Vocab_of = Rite_of.Vocab_of
let mk_cel ?(layer = Rite_of.Precedence_of.universal_layer)
?(status = Celebration.Feast) ?(colour = Colour.White)
?(subject = Subject.Saint) ~slug ~rank () =
{ Celebration.slug = Slug.of_string_exn slug;
names = Names.empty; rank; status; colour; subject;
citations = []; layer }
let mk ?layer ?status ?colour ?subject ~slug ~rank ~origin () =
{ Precedence.cel = mk_cel ?layer ?status ?colour ?subject ~slug ~rank ();
origin }
let ctx ~iso ~season =
let date = Date.of_iso8601 iso |> Result.get_ok in
{ Precedence.date; season; weekday = Date.weekday date }
(* Each row: label, context, candidate, expected band value. *)
let part1_cases =
[ ( "entry 1: Good Friday",
ctx ~iso:"2026-04-03" ~season:Vocab_of.Lent,
mk ~slug:"of-lent-6-friday" ~rank:Vocab_of.Feria ~origin:Precedence.Temporal (),
10 );
( "entry 1: Holy Saturday",
ctx ~iso:"2026-04-04" ~season:Vocab_of.Lent,
mk ~slug:"of-lent-6-saturday" ~rank:Vocab_of.Feria ~origin:Precedence.Temporal (),
10 );
( "entry 1: Easter Sunday",
ctx ~iso:"2026-04-05" ~season:Vocab_of.Easter,
mk ~slug:"of-easter-sunday" ~rank:Vocab_of.Sollemnitas ~origin:Precedence.Temporal (),
10 );
( "entry 2: Holy Thursday is a Holy Week feria, not the Triduum",
ctx ~iso:"2026-04-02" ~season:Vocab_of.Lent,
mk ~slug:"of-lent-6-thursday" ~rank:Vocab_of.Feria ~origin:Precedence.Temporal (),
20 );
( "entry 2: the Nativity",
ctx ~iso:"2026-12-25" ~season:Vocab_of.Christmas,
mk ~slug:"of-nativity" ~rank:Vocab_of.Sollemnitas ~origin:Precedence.Temporal (),
20 );
( "entry 2: a Sunday of Advent",
ctx ~iso:"2026-11-29" ~season:Vocab_of.Advent,
mk ~slug:"of-advent-sunday-1" ~rank:Vocab_of.Sollemnitas ~origin:Precedence.Temporal (),
20 );
( "entry 2: Ash Wednesday",
ctx ~iso:"2026-02-18" ~season:Vocab_of.Lent,
mk ~slug:"of-ash-wednesday" ~rank:Vocab_of.Feria ~origin:Precedence.Temporal (),
20 );
( "entry 2: a day within the Octave of Easter",
ctx ~iso:"2026-04-07" ~season:Vocab_of.Easter,
mk ~slug:"of-easter-octave-day-3" ~rank:Vocab_of.Sollemnitas ~origin:Precedence.Temporal (),
20 );
( "entry 3: a universal solemnity of a saint",
ctx ~iso:"2026-06-29" ~season:Vocab_of.Ordinary_time,
mk ~slug:"of-peter-and-paul" ~rank:Vocab_of.Sollemnitas ~origin:Precedence.Sanctoral (),
30 );
( "entry 4: a proper solemnity",
ctx ~iso:"2026-06-29" ~season:Vocab_of.Ordinary_time,
mk ~layer:"proper:diocese-of-poznan" ~slug:"of-local-patron"
~rank:Vocab_of.Sollemnitas ~origin:Precedence.Sanctoral (),
40 ) ]
let test_part1 () =
List.iter
(fun (label, c, cand, expected) ->
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;
Alcotest.test_case "Tabula part II (entries 5-9)" `Quick test_part2 ] )
|