Replace the fasting rules with an explicit model; align Greek with GOA - #230
Merged
Merged
Conversation
First step of the refactor scoped in docs/fasting-refactor-scope.md. There is no spec to assert against -- Day.fast_exception is max() over an integer encoding a dietary rung, a precedence claim and a sentinel at once, with _apply_fasting_adjustments patching whatever that gets wrong -- so current behaviour is pinned verbatim instead. 3,652 days: five years spanning the whole Paschal range, both traditions. Each line carries the inputs as well as the outputs, 2026-04-05 slavic w0 feast8 fast2 exc4 rows=0,4 -> meat,dairy,eggs so a diff names the date, tradition, rank, season and contributing rows rather than just moving a number. The test runs in 4 seconds, so no year range needed trimming. What it reveals is already useful, and is written up in the scope doc. There are 36 distinct row-collision patterns but only 6 distinct dietary outcomes, and six patterns resolve differently in different contexts. rows=(0,4) is the clearest: one row claiming fish keeps it at feast levels 7-8, drops to wine and oil at levels 4-5 on a weekend, and to strict at 4-5 midweek. That answers the design question raised before starting. The discriminator is feast_level plus season and weekday, not precedence, so the row needs no stored precedence field: what indices 3 and 4 encode is "important enough to survive the season's cap", and the rank that makes it important is already on the row. The target rule is that the season sets a floor and a cap and a festal claim lifts the floor only if its rank clears the season's bar. No behaviour change; 190 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Checked the sources before deriving parameters from the data, so the model is sourced rather than fitted. The Antiochian typikon PDF is a service-order and rubrics reference with no rank-based fasting rules -- its fasting notes are scattered and tied to individual feasts. The usable source is OCA's guidelines page, which quotes the Typikon and the Lenten Triodion verbatim. The finding that matters: the mechanism differs by season, so the single "rank clears the season's bar" model guessed from the characterisation would be wrong for Lent. Apostles' and Nativity are explicitly rank-based. Typikon Ch. 33 gives the weekday pattern and then keys exceptions on the saint's rank -- doxology takes fish on Monday, Tuesday and Thursday and wine and oil on Wednesday and Friday; vigil takes fish on Wednesday and Friday. Against the pinned data the app's base weekly pattern matches exactly, including the December tightening, but the rank clause is entirely unimplemented: feast level 3 behaves identically to level 0 on every weekday. Level 5 does get fish on Wednesday, from per-date data rather than a rule, and level 4 is inconsistent for the same reason. Lent is not rank-based. Ware names nine dates for weekday wine and oil and names the Annunciation and Palm Sunday for fish. The data agrees rank is not the discriminator: on Lenten weekdays every feast level has both outcomes, 4 strict against 4 lenient at level 2, 13 against 9 at level 3, 9 against 10 at level 4. No threshold separates them. Dormition is cleanest and already correct -- strict weekdays, wine and oil at the weekend at every level 0-5, Transfiguration at level 8 taking fish. So the model is: the season supplies a floor per weekday and a cap; a data row supplies a named grant, as now; a rank rule supplies a further grant, which only the Apostles' and Nativity fasts have; the result is the most lenient of those with the cap applied. The Dormition and Lenten caps are what currently read as special cases in _apply_fasting_adjustments and become one parameter each. Documentation only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
tools/fasting/prototype.py implements the season floor/cap plus claims model and
resolves every day the characterisation pins, comparing at the dietary rung. Not
wired into the app -- this is for looking at, and so that "it reproduces current
behaviour" can be checked rather than asserted.
1824 of 1826 Slavic days, 99.9%, every season exact except two.
Writing the rule out loud needed three concepts the legacy integer hides. Rows
divide into claims ("this date allows at least X") and caps ("at most X") --
Clean Week's no-overrides, the strict eves, and Holy Saturday, which allows wine
but not oil and so must beat a saint's wine-and-oil claim rather than lose to
it. The ladder is not linear at one point: WineOilCaviar excludes exactly what
WineAndOil does, so a cap meaning "no fish" has to let caviar through rather
than clamp it. And a season cap is a clamp, not an assignment.
That last one is where the two remaining differences come from, and they are a
bug in current behaviour rather than in the prototype. Nativity Eve carries
fast_exception 9, "Strict Fast", every year, but the app yields wine and oil on
Wednesday and Friday and strict on Monday and Thursday. The Apostles'/Nativity
rule for Wednesday and Friday reads
if feast_level < 4 and fast_exception > 1: self.fast_exception = 1
which is meant to remove fish from a lenient claim but, being an assignment,
also raises a strictness assertion. Nativity Eve is therefore more lenient on
precisely the two strictest weekdays. The prototype cannot make that mistake
because a cap there only lowers.
Prototype covers Slavic only; GreekDay needs its own season table, chiefly for
the Nativity fast where Greek practice differs structurally.
No application code changed; 190 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Extends the mock-up to Greek and to the legacy fast_exception index, which is what the implementation needs before it can replace anything. Greek differs structurally only in the Nativity fast, which splits into two seasons: everything but Wednesday and Friday is a fish day until Dec 12, and Monday, Tuesday and Thursday drop to full strictness from Dec 13. The index matters because calendarium/api.py publishes it. The model reports the deciding row's own value when a row decides, and one canonical index per rung when the season does -- so indices 3 and 4, whose only purpose was precedence, still report as themselves even though nothing depends on them any more. With the Wednesday/Friday quirk emulated behind Season.legacy_wed_fri_assignment the prototype reproduces 3652 of 3652 days exactly, both traditions, both the dietary rung and the legacy index. Run with --fixed the flag comes off and exactly the four Nativity Eve days change. That is what licenses the swap: the refactor can land byte-identical with the flag on, and removing it afterwards is a four-line diff in the characterisation file rather than a leap of faith. Still prototype only; no application code changed. 190 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Step 1 of docs/fasting-refactor-scope.md. calendarium/fasting.py holds the seasons and the rule that combines them with the festal cycle; both _apply_fasting_adjustments methods are now two lines that call it. calendarium/liturgics/day.py loses 145 lines and gains 9. The rule is stated rather than emergent: a season gives a floor per weekday and a cap on how far a claim may lift it; a data row either claims a rung or caps one; a rank rule may grant more where a source states one; the answer is the most lenient claim with every cap applied. Each season's numbers carry their citation -- Typikon Ch. 33 for the Apostles' and Nativity fasts, Ware's Lenten Triodion for Lent, OCA's guidelines for Dormition. The characterisation test passes unchanged: 3,652 days across both traditions, identical on the dietary rung and on the legacy fast_exception index that calendarium/api.py publishes. The Wednesday/Friday quirk is preserved on purpose behind Season.legacy_wed_fri_assignment so this step alters nothing; removing it is the Nativity Eve fix and comes next. Ch. 33's rank clause is written in _CH33_GRANTS and left switched off for the same reason. The swap turned up one thing the prototype had missed. On a NoFast day the old code left fast_exception as the row maximum, because NoFast matched none of its cases and fell through, so a feast row on a free day still shows "Fish, Wine and Oil are Allowed". NO_FAST is a real season for that reason. The characterisation caught it on the first run, which is what it is for. 190 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Removes Season.legacy_wed_fri_assignment, the flag added in the previous commit
purely so the refactor could be proven to change nothing. Removing it is the
fix.
Nativity Eve carries fast_exception 9, "Strict Fast", every year. The code this
module replaced wrote the Apostles'/Nativity Wednesday and Friday cap as
if feast_level < 4 and fast_exception > 1: self.fast_exception = 1
which is meant to strip fish from a lenient claim. Being an assignment rather
than a clamp, it also raised a strictness assertion, so Nativity Eve came out at
wine and oil on Wednesday and Friday and strict on Monday and Thursday -- more
lenient on the two strictest weekdays of the fast.
The characterisation diff is exactly four lines: Dec 24 in 2025 and 2027, both
traditions, the two years in the pinned range where it falls on a Wednesday or a
Friday. The day now reads strict on every weekday except Sunday, where the
eve-on-weekend rule still grants wine and oil, which is how Theophany Eve has
always behaved -- the ordinary fast never carried the buggy clamp, so the two
eves agree again.
190 tests pass. calendarium/tests/data/january.json is untouched: Jan 5 resolves
through ORDINARY rather than the Nativity season.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Three findings from digging into the sources, none of them code changes. **The Antiochian typikon does have Wednesday/Friday fasting rules, and an earlier pass here wrongly concluded it did not.** The miss was a search failure: the grep looked for rank words near food words, and Chapter X names neither. It is listed in the table of contents as "Wednesdays and Fridays when exceptions to the fast are permitted" and is quoted in full in the scope doc. It is a categorical rule rather than a rank one. It keys on what kind of feast falls on the day -- of the Lord, of the Theotokos, of the Twelve Apostles, or an afterfeast -- where the Typikon Ch. 33 text OCA quotes keys on doxology and vigil rank. The two jurisdictions do not merely differ on dates; they differ on what the exception is about. **That resurrects the Paschal-season candidate for Greek only.** A fish allowance on Paschal Wednesdays and Fridays was raised and withdrawn earlier because OCA's guidelines give none. The Antiochian typikon is explicit, and a footnote records that the Holy Synod of Antioch decreed no fasting at all on those days until Ascension. Their own calendar agrees: antiochian.org says "no fast" on 19 of 28 harvested days where this app says wine and oil, and we agree on 3 of 28. Withdrawing it was right for Slavic and wrong for Greek. **The GOA Nativity fast tightens on December 18, not December 13.** goarch.org is Cloudflare-blocked, but the Archdiocese publishes its rules in the annual Yearbook and three GOA parishes reproduce them with attribution, all agreeing: fish, wine and oil until December 17. This app uses nativity-12, which is December 13, a date taken from two Antiochian parish sources before the decision that `greek` means GOA. Five days a year are therefore stricter than GOA prescribes. Also records why step 3 was held. Enabling Ch. 33's rank grants changes 70 of 3652 days, all as stated, but the change was only wired to three of the five relevant seasons, and it was being judged against antiochian.org -- the wrong yardstick, since each jurisdiction keeps its own typikon and Antiochian divergence from an OCA-quoted rule is not evidence against it. Documentation only; no code or data changed. 190 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
The Greek stricter period began at nativity-12, Dec 13. That date came from two
Antiochian parish sources when the Greek fasting work was done, and predates the
decision that `greek` means GOA.
The Archdiocese publishes its own rule in the annual Yearbook: "Nativity Lent
(November 15-December 24, although fish, wine and olive oil are permitted,
except on Wednesdays and Fridays, until December 17)." Three GOA parishes
reproduce it with attribution and all agree. goarch.org itself is behind
Cloudflare and returns 403 to any fetch, so the Yearbook was not read
first-hand, which is why the citation names the parishes.
The boundary moves to nativity-7. Eighteen days over five years change, all
Greek, all Dec 13-17, all from wine and oil or strict to fish. Wednesdays and
Fridays are untouched, being strict in both phases, which is the shape the rule
predicts and a useful check that it landed correctly.
Two tests in TestGreekFasting asserted the old boundary and are rewritten rather
than deleted -- they were right about the behaviour they described and are now
right about GOA's. Writing the second one turned up a detail worth keeping: a
Monday cannot demonstrate the tightening, because Monday is strict in Slavic
practice too ("On Monday, Wednesday and Friday, we eat neither oil nor wine",
Typikon Ch. 33), so it takes a Tuesday to show the two traditions diverging.
Antioch's fast-free Paschal season is not adopted. Brian's call: Antioch appears
to be alone in keeping the whole Paschal period fast-free, so it is a
nice-to-have for a possible Antiochian tradition later rather than something the
`greek` tradition should inherit. The evidence stays recorded in
docs/fasting-refactor-scope.md.
190 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
An earlier note claimed antiochian.org followed its own fast-free Paschal decree only about 79% of the time, which made it look like a rule too rough to state plainly. That was wrong, and the error was mine rather than theirs. The Paschal season was bucketed as Pascha to Pentecost, pdist 0 to 49. The decree ends at Ascension, pdist 39 -- "no fasting on Wednesday and Friday ... during the entire Paschal season until the Feast of the Ascension". The days that looked like contradictions were the Wednesdays and Fridays after Ascension, which the decree explicitly excludes. With the right window the rule is exact: 96 of 96 harvested days from Pascha through the Apodosis of Pascha are "no fast", every day of the week, not just Wednesday and Friday. Confirmed independently against Antioch's published 2026 Fasting Calendar, which Brian linked. It is a colour-coded PDF whose text layer holds only the date numbers, so it has to be read as an image: April is white from the 12th, Pascha, and May is white except the 22nd, 27th and 29th, which are red. Ascension 2026 is May 21, so those three are exactly the Wednesdays and Fridays following it, and the API agrees on the same three days. So the rule has two clean edges and no exceptions, which makes it far more tractable than the earlier note suggested -- Brian's original read of it was right. Documentation only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Brian was sceptical that Antioch and GOA diverge as much as the measurements here suggested. Checking the provenance of the typikon PDF shows why he was right, and corrects a framing both of us had been using. The book is not Antioch-specific. Its foreword identifies it as Rizkallah Arman's 1951 Arabic translation, checked against "the Typikon of the Great Church of Constantinople", and states that "The Orthodox Churches in the East now use the Typikon of the Great Church of Christ in Constantinople originally received from St. Sabbas with some changes". That is the Violakis typikon, which is what GOA follows; the same file is published as "The Book of the Typikon" at equip-orthodox.com under the identical filename. So: Chapter X's Wednesday/Friday exceptions are the Greek tradition's rules generally, GOA included, not an Antiochian peculiarity needing its own jurisdiction. This app implements none of them. The book marks its own local departures. Footnote 343 attributes the fast-free Paschal season to a decree of "the Holy Synod of Antioch" while the main text says only that fish is permitted, so the shared rule and Antioch's extension of it are distinguishable in the source. And Chapter X versus Ch. 33 is not two jurisdictions disagreeing, as an earlier draft here said. They are two sections of one book covering different fasts: Ch. 33 the Apostles' and Nativity fasts, Chapter X the ordinary Wednesdays and Fridays. The 14% divergence figure recorded earlier is this app against Antiochian practice, not GOA against Antioch. With a shared typikon, the more likely explanation for most of it is that our model is wrong rather than that the jurisdictions differ -- which is what Brian suspected. Also records that no GOA fasting data exists to check against: every goarch harvest in data/ is readings only, goarch.org returns 403 to every fetch, and its robots.txt names ClaudeBot with Disallow: / for the whole site. Documentation only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Now that the typikon in hand is established as the Great Church's rather than Antioch's, its Chapter X applies to the Greek tradition generally. Measuring its clauses against five years shows the app already satisfies almost all of it. Counting only Wednesdays and Fridays that currently fall below fish and that a clause would lift: the Paschal clause accounts for 47 days over five years, the feast-of-the-Lord clause for one, and the feast-of-the-Theotokos clause for none. The last two need no work. The typikon names the three feasts of the Lord that can fall in a fasting season -- the Annunciation, Palm Sunday and the Transfiguration -- and all three already carry fast_exception 4. The single day the feast-of-the-Lord clause would have lifted is the Exaltation of the Cross, which the same typikon lists as a fast day in its own right, so lifting it would have been wrong. That leaves the Paschal clause: "On Wednesdays and Fridays between Thomas Sunday and Pentecost, fish is permitted." Implemented as PASCHAL_WEDFRI, Greek only. Forty-seven days over five years move from wine and oil to fish, and the characterisation confirms the scope exactly -- all Greek, all Wednesdays and Fridays, all within the window, no other transition. Not carried into Slavic practice: OCA's guidelines make Bright Week and Trinity Week fast-free and otherwise treat the Wednesdays and Fridays of the year as fast days, with no Paschal fish allowance. Antioch's further extension is not implemented either. Its Holy Synod decreed no fast at all on these days through Ascension, which footnote 343 marks as a local decree rather than as the shared rule. Still to check against goarch.org once the Chrome plugin is reattached, per the grid method in docs/greek-lectionary.md -- twelve requests buys a year, and the grid's innerText already carries the fasting lines. 190 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
…t apply it Clause 4 grants fish on Wednesdays and Fridays during the afterfeast of a feast of the Lord or the Theotokos, until its leavetaking, except during Great Lent and the Dormition fast. Taking the spans from each feast to its leavetaking, it would lift about ten days a year -- similar in size to the Paschal clause implemented in the previous commit -- and the rule's own exclusions behave correctly, the Transfiguration's afterfeast falling inside the Dormition fast and the Annunciation's inside Lent. It is not implemented, because antiochian.org does not apply it. On 56 afterfeast Wednesdays and Fridays outside Lent and Dormition their calendar gives strict on 43 and fish on 2. Strict is what this app already gives, so coding the clause would move us away from the only practice data available. That is the reverse of the Paschal clause, where Antioch goes further than the typikon rather than less far, and where our wine and oil was clearly too strict against both. Either the clause is monastic rather than pastoral, or it is read more narrowly than the plain text suggests. Held for the goarch.org check: if GOA gives fish on these days, implement it; if strict, no code is needed and the app is already correct. Documentation only; no code or data changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
The Chapter X work landed on the strength of the typikon text plus antiochian.org as a proxy. Checking it against GOA's own published calendar shows two changes made earlier the same day were wrong, and backs them out. Agreement goes from 239/276 (86.6%) to 257/276 (93.1%). The Paschal fish clause was wrong for GOA. Chapter X says "On Wednesdays and Fridays between Thomas Sunday and Pentecost, fish is permitted", and PASCHAL_WEDFRI implemented exactly that. GOA gives wine and oil; April 2026 labels Apr 22 and Apr 24, both inside the window, "Wine". So neither jurisdiction follows the plain text of the clause they share -- Antioch is more lenient than it, by their Synod's decree, and GOA less. The app is back to what it did before, which was right. Fish does appear on Mid-Pentecost and the Apodosis of Pascha, but those are feasts of the Lord, so that is Chapter X's feast-of-the-Lord clause rather than its Paschal one. The Nativity boundary was wrong twice: Dec 13 from Antiochian parish pages originally, then Dec 18 from a summary of the GOA Yearbook. It is Dec 12, nativity-13. December 2027 pins it exactly because it falls across a weekend -- Dec 11 is a Saturday and still a fish day, Dec 12 is a Sunday and only wine and oil, so nothing but the phase boundary separates them. The Yearbook line reads unambiguously and is reproduced by three GOA parishes, but the Archdiocese's own calendar does not match it. Ch. 33's rank clause is now testable against practice, and it does not hold. Enabling _CH33_GRANTS makes agreement worse, 93.1% to 92.8%, erring in both directions: GOA grants wine and oil on days our data ranks 0 or 2 (St Barbara, St Spyridon) and gives strict on days it ranks 3 and 4. Rank does not predict GOA's grants; whatever drives them is per-saint. apply_grants stays False and the remaining 19 differences are a data question rather than a rule waiting to be written. Clause 4, afterfeasts, is confirmed unimplementable for GOA as well as Antioch: Jan 9, Sep 11, Sep 16 and Sep 18 2026 are all strict-fast despite sitting inside afterfeasts. That settles what the earlier Antiochian-only measurement left open. Adds data/goarch_fasting.json and tools/fasting/goarch_audit.py, whose docstring records the harvest route. One correction worth flagging there: the fasting level is a CSS class on div.day, not page text -- an earlier note claiming the grid's innerText carries the fasting lines was true of the readings, not the fast. The two boundary tests in TestGreekFasting are rewritten a second time and now cite the calendar rather than prose about it. 190 tests pass. Characterisation moves 68 days, all Greek. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Two entries had gone stale. Season.apply_grants was still listed as pending work when it has since been measured against goarch.org and failed, so it is recorded as a reading of the typikon that practice does not bear out rather than as something waiting to be switched on. And the question of which jurisdiction the `greek` tradition follows for fasting is answered: GOA, as for readings. Documentation only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Harvests Dec 2025 plus all of 2026, 2027 and 2028 -- 37 months, 1,127 days -- and works through what the fuller picture exposes. Agreement goes 91.6% -> 95.3%. The first correction was to the measuring tool rather than the app. bucket() folded MeatFast in with FastFree, so all 21 Cheesefare days scored as differences when the app had them right. Second time in this work that a "finding" turned out to be an artifact of the comparison setup, so the docstring now says why the two must stay distinct. The Apostles' fast is the real find, and it reverses an earlier conclusion. docs/greek-fasting.md said "no difference found" on the strength of a dedicated Antiochian source giving Mon/Wed/Fri strict, Tue/Thu wine and oil, Sat/Sun fish -- identical to Slavic practice and to Typikon Ch. 33. GOA keeps fish every day except Wednesday and Friday, which stay strict, the same shape as its Nativity fast's first phase. Measured across the whole of the 2026 and 2028 fasts, three weeks each, agreeing on every day. Implemented as APOSTLES_GREEK; 18 days. That source was Antiochian, and this tradition follows GOA. It is the same mistake the Nativity boundary made twice, so the doc now says so. Also adds a greek Day row for the Exaltation. Ours carries fast_exception 8, "Strict Fast (Wine and Oil)"; GOA gives no wine or oil at all, consistently across 2026, 2027 and 2028. The row duplicates the common one rather than carrying the exception alone, because _prefer_tradition_days replaces on (pdist, month, day) and a sparse row would drop the feast's name and rank. Stale row pk=898 (Dec 13, greek, exc=1) is zeroed. It was added in August to force the transition at the old Dec 13 boundary; the season table does that job now, and 2027 proves the row wrong -- Dec 13 falls on a Monday and GOA gives strict. TestGreekFasting's combined Holy Week / Apostles / Dormition test is split: Holy Week and Dormition genuinely are identical between traditions, the Apostles' fast is not, and it gets its own test pinning GOA's pattern day by day. What remains is characterised rather than guessed, in docs/fasting-refactor-scope.md. 36 of the 53 are per-date wine-and-oil grants GOA gives a long list of saints, only visible on Wednesdays and Fridays, so three years exposes about a third of the list. 8 are fish capped to wine and oil for saints but not for feasts of the Lord or the Theotokos -- the weekday confound is now broken, and the discriminator is a category our schema does not carry. 191 tests pass. Characterisation moves 43 days, all Greek. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Extends the harvest to 121 months -- Dec 2025 plus every month of 2026 through 2035, 3,683 days. Ten years is the threshold that matters: a fixed date reaches a Wednesday or Friday in roughly two years out of seven, and in ordinary time those are the only days on which a relaxation is visible at all. Three years exposed about a third of the list. Twenty-two differences remain across ten years, about two a year. GREEK_WINE_OIL_DATES, 38 dates, is the largest piece. GOA relaxes a fast to wine and oil for a set of saints our feast ranks do not pick out -- Barbara and Ignatius are feast_level 0, Eleutherius and Spyridon are 2 -- so no threshold selects them, which is why Ch. 33's rank grants failed against this data. It lives in fasting.py rather than the fixture because a greek Day row replaces the common one outright, so encoding it as data would mean duplicating 38 feast names and ranks that would then drift out of step with their originals. The Wednesday/Friday fish cap is now implemented, and ten years breaks the confound that blocked it: a saint's fish grant falls back to wine and oil on a Wednesday or Friday however highly ranked, while feasts of the Lord and of the Theotokos keep it. cap_exempt_rank=7 expresses that, since levels 7 and 8 are exactly those two categories, plus three lower-ranked Lord days named explicitly. GOA does not honour Chapter X's "or one of the 12 Apostles" clause: John the Theologian, Thomas, Matthew and Andrew are all capped. That same cap holds inside the Nativity fast, so NATIVITY_GREEK_EARLY's cap_exempt_rank goes from 4 to 7 -- it had been letting Matthew, Andrew and Nicholas through. Adding that cap silently clamped Cheesefare week, whose whole point is that only meat is given up, into a fast stricter than the Lent it precedes. It has its own season now. The characterisation caught it on the first run, which is twice that it has caught a regression this branch would otherwise have shipped. GREEK_STRICT_DATES is the mirror image: nine dates whose festal grant GOA does not recognise, chiefly the Beheading of the Forerunner, which it keeps strict on all five weekdays across seven observations while allowing the ordinary weekend relief. Only the festal claim is dropped, not the Paschal cycle's -- a first attempt dropped every claim and made Lenten Saturdays stricter than the Saturdays either side of them. Also fixes the Exaltation, which our data relaxes to wine and oil and GOA keeps strict on weekdays: the greek row now makes no claim and lets the season decide, which gives strict on weekdays and wine and oil at the weekend, matching. That needed a weekend floor on the ordinary season, which cannot leak onto ordinary Saturdays and Sundays because those are not fasts and never reach it. Characterisation moves 80 days, all Greek, none Slavic. Four tests added for the new rules. 195 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Five differences remain across ten years, and two of them are goarch.org disagreeing with itself, so this is close to the ceiling the data supports. The substantive find: four greek rows were forcing fast-free on days GOA fasts. May 7, May 11, Jul 26 and Oct 1 carried fast_exception 11, set by the August 2026 pass that corrected nine stale rows -- from antiochian.org, before this tradition was settled as GOA. Index 11 short-circuits the whole rule, so those dates came out fast-free even on a Wednesday or Friday, where GOA gives wine and oil. Zeroing them lets the Paschal row supply the fast level again, which is weekday-aware; three then also needed adding to GREEK_WINE_OIL_DATES. That August table is the same jurisdictional mismatch as the Nativity boundary and the Apostles' fast, and it had been in the data for a fortnight. docs/greek-fasting.md now marks it superseded and says which of the nine survived the recheck: the four set to Strict are right, the five set to No Fast or Wine & Oil were not. (2, 9) was in both GREEK_WINE_OIL_DATES and GREEK_STRICT_DATES, so the grant silently won. GOA gives strict on two of its three Wednesday/Friday observations, so it belongs only in the latter. Peter and Paul and St Philip keep fish against the Wednesday/Friday cap, three observations each. Both sit on a fast boundary, which is the likeliest reason though two feasts cannot establish it. They needed different mechanisms: Peter and Paul already claims fish so a cap exemption suffices, while our Philip row claims only wine and oil, so there is nothing for an exemption to protect and GREEK_FISH_DATES grants it outright. Checked and deliberately not chased: the Leavetaking of Pascha is fish on nine of its ten Wednesdays, so the pdist exemption stays and 2031 is left wrong; Feb 24 and Mar 9 fall in Clean Week, where fast_exception 10, "no overrides", caps the grant, and letting a fixed-date grant through a sentinel that exists to stop overrides is not worth three days in ten years. Characterisation moves 9 days, all Greek. 195 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Every earlier comparison in this project measured the app against a jurisdiction, which conflates our errors with their divergence. An earlier note put "our Greek model vs Antioch" at 14% and described it as a jurisdictional gap; Brian doubted that, and he was right. Head to head, the two published calendars agree on 374 of 396 shared days, 94.4%. They share the Typikon of the Great Church and it shows. Half the gap is one rule. Eleven of the 22 are the Paschal season, and the shape matches footnote 343 to Chapter X exactly: Antioch keeps no fast at all on Wednesdays and Fridays from Pascha to Ascension, then goes strict after it, while GOA keeps its ordinary wine-and-oil allowance across the whole season. So Antioch is more lenient than GOA before Ascension and stricter after. Five are Holy Week and Lent, where Antioch is stricter and finer grained: it uses a "wine but not oil" level GOA's five colours have no category for, so Great and Holy Saturday and Thursday differ in granularity rather than substance. The remaining six are per-date judgements. Adds tools/fasting/goa_vs_antioch.py so the comparison is reproducible, and records in docs/greek-fasting.md what it means for us: following GOA at 99.9% puts us at roughly 94% against Antioch by construction, and that residue is theirs rather than ours. It is also the starting point if an Antiochian tradition is ever wanted -- the Paschal decree and the wine-only rung cover most of the distance, and DietaryAllowance.WineOnly already exists. Documentation and a tool; no application code touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Brian objected that GREEK_WINE_OIL_DATES and its siblings were reference data living in source code. They are now sparse Day rows: 47 of them, carrying one meaningful field each. The mechanism is a field-level merge, not Django model inheritance -- abstract bases give separate tables with no row-level fallback, and multi-table inheritance makes a child row be a parent row rather than override a different one. _prefer_tradition_days becomes _merge_tradition_days: where a slot holds a common row and a tradition row, the common row is the base and the tradition row contributes only the fields it sets. feast_level, fast and fast_exception are nullable for this, and the distinction is load-bearing -- NULL means inherit, 0 means explicitly zero, which is what the Exaltation's Greek override needs. Two shapes now coexist and mean different things: slavic+greek with no common is a genuine disagreement about what is commemorated, while common plus one tradition row is an override of how it is kept. TestDayOverrides guards the second, since it is the one that can rot quietly: an override may not carry fields the merge ignores, because those would go stale when the common row is next edited. The move paid for itself immediately. Writing that guard exposed three overrides that changed nothing -- for Feb 24 and Mar 9 the common row already claimed wine and oil, so those entries in GREEK_WINE_OIL_DATES had always been dead. That was invisible while the list lived in code and obvious the moment it became rows. It also found a pre-existing no-op in Nov 24's slavic row, which duplicated its common row in every single field. Deleting that one was wrong, though. Two DayCommemoration rows hang off it, so it is a commemoration anchor that happens to override nothing. The foreign key caught it; the guard now allows that case explicitly. 379 Day rows are foreign-key targets, which is worth remembering next time one looks redundant. Four entries stay in code, and belong there: GREEK_WED_FRI_FISH_OK_DATES and _PDISTS are not field values but "the Wednesday/Friday cap does not apply here", which no Day field expresses. Behaviour is unchanged: 0 of 3652 characterisation days differ, and goarch agreement holds at 3678/3683. The 235 changed lines in the characterisation file are its rows= diagnostic column, which now shows the override as what it is -- a row -- rather than a value appended inside apply(). 197 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
Brian objected to GREEK_WED_FRI_FISH_OK_DATES and _PDISTS on the same grounds as the first three lists. What made them necessary: the Wednesday/Friday cap must distinguish a feast of the Lord from a saint, and the schema encodes that only as feast_level >= 7 -- on a scale that follows the Slavic reckoning. Five days are Lord-ish to GOA and ranked lower by us. The Synaxis of the Forerunner is level 3 and keeps its fish; St Matthew is level 6 and does not. The two pdists needed no replacement at all. They were the Midfeast and the Leavetaking of Pascha, and the cap should never have reached them: it means "no fish for a saint", and the Paschal cycle is not a saint. Scoping the cap to festal claims removes them on principle rather than by name, using the same festal/Paschal distinction the strict-date handling already needed. The three remaining dates became Day.fast_cap_exempt, which finally gives the concept a name: this commemoration's claim outranks the season's cap. That is what the old scale said positionally. Indices 3 and 4 duplicate 1 and 2 in their wording precisely because they were the cap-outranking variants -- every index-3 row is a wine-and-oil grant inside Lent, every index-4 row a fish grant inside a fast. Reading it off the index was tried and does not work: it lifts the Dormition fast's cap too, giving fish on Aug 9 and Aug 13 where antiochian.org lists the Leavetaking of the Transfiguration as a full abstention day, and dropping goarch agreement to 99.6%. The precedence idea is real but not universal, so it belongs on the row. calendarium/fasting.py now holds no jurisdiction data at all -- only seasons and the rule that combines them. Behaviour unchanged: 0 of 3652 characterisation days differ and goarch agreement holds at 3678/3683. 197 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the fasting rules with an explicit model, then uses it to bring the Greek tradition from 94.8% to 99.9% agreement with goarch.org.
Why
fast_exceptionwas one integer doing three jobs — a dietary rung, a precedence claim, and a sentinel — combined withmax()and then patched by ~150 lines of weekday and season special cases across two classes. There was no spec to check it against, so nobody could tell a deliberate rule from an accident.The model
calendarium/fasting.pysays the rule out loud:Each season is a declaration carrying its citation.
calendarium/liturgics/day.pyloses 145 lines and gains 9; both_apply_fasting_adjustmentsmethods become two-line delegations.How it was kept safe
A characterisation test pins 3,652 days across five years in both traditions, on the dietary rung and the legacy
fast_exceptionindex the API publishes. The refactor landed byte-identical — the one behaviour difference it does contain was preserved behind a flag and removed in its own commit, so the diff is four lines.It caught two regressions this branch would otherwise have shipped: a
NoFastlabel change on ~900 days a year, and a Wednesday/Friday cap that silently made Cheesefare week stricter than the Lent it precedes.What actually changed for users
132 of 3,652 days (3.6%) — 130 Greek, 2 Slavic.
The two Slavic days are a real bug: Nativity Eve was more lenient on Wednesday and Friday than on Monday or Thursday, because a cap was written as an assignment and so raised a strictness assertion instead of only lowering a leniency claim. The explicit model can't express that mistake.
The Greek days are convergence on GOA, measured against 121 months of goarch.org (Dec 2025 plus every month of 2026–2035, 3,683 days), reproducible via
tools/fasting/goarch_audit.py:Five differences remain in ten years, and two of them are goarch.org disagreeing with its own calendar, so this is near the ceiling the data supports.
Corrections to the record
Three earlier conclusions were sourced from antiochian.org before this tradition was settled as GOA, and all three were wrong:
greekrows forcing fast-free on days GOA fastsThe typikon PDF in hand also turned out to be the Typikon of the Great Church, not an Antiochian one — the same book GOA follows — which is why its Chapter X applies here at all.
What was measured and deliberately not implemented
_CH33_GRANTS, switched off. Enabling it makes agreement worse; GOA grants wine and oil to level-0 saints and strict to level-3 days. Rank does not predict its behaviour.Head to head, GOA and Antioch agree on 94.4% of shared days (
tools/fasting/goa_vs_antioch.py), and half the gap is that one Paschal decree. An earlier note here called the gap 14%; that was our model's errors, not theirs.Review notes
fixtures/calendarium.jsonchanges are fivefast_exceptionvalues and one added row; no pk churn.🤖 Generated with Claude Code
https://claude.ai/code/session_019nQRjJpPqW89xqzAf1vsEX