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
|
(** The rite-parameterised resolver: RG 91 says who wins, RG 92-95 says what
happens to the loser, RG 108-111 says how many commemorations are admitted.
Three separate rite-supplied functions, because the loser's fate depends on
the loser's own rank, not the winner's -- conflating them would resist
extension to a second rite. *)
(** Which of the day's two office streams a candidate came from. *)
type origin = Temporal | Sanctoral [@@deriving sexp]
(** RG 111: an admitted commemoration's own standing, distinct from its rank. *)
type privilege = Privileged | Ordinary [@@deriving sexp]
(** What becomes of a losing candidate. *)
type disposition =
| Omit (** yields with no trace in the day's celebration *)
| Commemorate of privilege (** kept as a commemoration of the observed day *)
| Transfer (** moved to the next free day (RG 92-95) *)
| Repose (** kept only in a votive/private sense; not commemorated today *)
[@@deriving sexp]
(** A celebration together with the office stream it was drawn from. Parameterised
by the rite's rank type only, matching {!Celebration.t}. *)
type 'r candidate = { cel : 'r Celebration.t; origin : origin } [@@deriving sexp]
(** The day a resolution is computed for. Parameterised by the rite's season
type only -- a context has no rank of its own. *)
type 's context = { date : Date.t; season : 's; weekday : Date.weekday }
(** The rite's three resolution functions. *)
type ('s, 'r) rules = {
band : 's context -> 'r candidate -> int;
(** RG 91: orders candidates for the day; lower wins. *)
disposition : winner:'r candidate -> loser:'r candidate -> disposition;
(** RG 92-95: the loser's fate, which depends on the loser's own rank. *)
admit :
observed:'r candidate ->
('r candidate * privilege) list ->
('r candidate * privilege) list;
(** RG 108-111: how many commemorations are admitted, and in what order;
anything filtered out here is recorded in {!resolution.omitted}, not
dropped.
OBLIGATION ON THE IMPLEMENTATION, not enforced by this type: every
candidate this function returns must be a value taken UNCHANGED
from its input list, never rebuilt (e.g. via a [{ c with ... }]
record update, even one that copies every field back unchanged).
{!resolve}'s own [omitted] accounting distinguishes an admitted
candidate from a dropped one by PHYSICAL equality ([==]) on the
candidate value, not structural equality -- a rebuilt record is
[=] to the original but not [==], so {!resolve} would then count
it as dropped a SECOND time (once because it is genuinely absent
from the admitted set, once because its identity no longer
matches its own admitted copy), silently double-counting rather
than raising. This obligation previously lived only in one rite's
own module documentation (Rite_ef.Precedence_ef.admit); stated
here because this signature -- not any one rite's implementation
of it -- is what an author of the next rite reads. *)
}
(** The outcome of resolving one day's candidates. *)
type 'r resolution = {
observed : 'r candidate;
commemorations : ('r candidate * privilege) list;
deferred : 'r candidate list;
omitted : ('r candidate * string) list; (** each with a reason *)
}
(** Total: the temporal candidate is passed separately, so there is no
empty-candidate case. Ties break on slug, so the result never depends on
input order. A [Commemoration_only] celebration is held out of the contest
and can never be [observed]. Every input candidate appears exactly once in
[observed], [commemorations], [deferred] or [omitted] — nothing is dropped
silently. *)
val resolve :
('s, 'r) rules -> 's context -> temporal:'r candidate ->
sanctoral:'r candidate list -> 'r resolution
|