feat: a calendar of the rules the page states, and a list of the rest - #64
Merged
Merged
Conversation
The first consumer-facing derivation this project ships, so the fence is the feature. `calendar` writes an .ics beside a refusal file, both always: a missing refusal file reads as "no refusals" and an empty list reads as "nothing was refused", and those are different statements. A VEVENT is written only where it re-expresses text the parser already committed to. A residual window is refused on the residual flag itself, not on the absence of times, so it stays refused even carrying hours. A window defined by exception, a window with hours but no stated day type, and a window whose end is at or before its start are each refused with the reason and the citation. A holiday day rule outside a closed grammar is refused rather than approximated: rendering "Day after Thanksgiving" as the fourth Friday would give a guess a calendar entry's authority. Season bounds only where the season names whole months, since BYMONTH can express nothing else; anything narrower is left unbounded and marked partial. No time zone is inferred. Recurrences are anchored to 1970 because a published rule states no year, and every DTSTART is a real instance of its own rule. Nothing reads the clock, so the same parse renders identical bytes.
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.
What was missing
Downstream tools that compute over a time-of-use schedule need machine-checkable
time rules. The parser reads them and emits them as prose plus, where the
document states a bare range, a
startand anend.This is the first consumer-facing derivation the project ships, which makes
the fence more important than the feature: a calendar that quietly omits the
windows it could not express looks like a complete calendar.
What changed
writes
smud-r-tod.icsandsmud-r-tod.refused.json— both, always. Amissing refusal file reads as "no refusals"; an empty list reads as "nothing was
refused". Those are different statements and only one of them is a measurement.
A
VEVENTis written only where it re-expresses text the parser alreadycommitted to. Everything else is refused, with the reason, the citation and the
verbatim text it refused:
hours, and deriving them here would undo that refusal;
during the Peak hours") is refused — the exception is prose this module cannot
subtract;
daily;
splitting it is a decision the document did not make;
day_ruleoutside a closed grammar (fixed day, ordinal weekday,last weekday) is refused.
Day after Thanksgivingis a real rule with adefensible date and it is not in the grammar, so it is refused rather than
rendered as the fourth Friday. A guess in a calendar entry has a calendar
entry's authority.
Season bounds only where the season names whole months.
BYMONTHselectswhole months and nothing else, so
Summer (Jun 1 - Sept 30)becomesBYMONTH=6,7,8,9, whileJun 15 - Sept 30would have to be widened or narrowedand is left unbounded and marked
X-CA-SEASON-BOUNDS:partial.No time zone is inferred, and the file says so. Recurrences are anchored to
1970 because a published rule states no year; the file says only the month, day
and time of a
DTSTARTmean anything, and everyDTSTARTis a real instance ofits own rule so a reader cannot treat it as an extra occurrence.
Nothing reads the clock.
DTSTAMPis the document's own retrieval date, orthe epoch when it states none — a generation time would make two renderings of
one parse differ.
A watch baseline is refused outright: it omits the verbatim prose, and a refusal
that cannot quote what it refused is not one.
On the real documents
smud-r-todsmud-ci-tod1smud-r,smud-ssrsmud-ci-tod1states no bare times at all, so its calendar has no windows in itand the refusal file says why for each of the five.
How it was verified
ruff check,ruff format --check,mypy(strict, 28 files),pytest— allgreen, coverage 94.32% against an 85% floor. 42 new tests. No golden file or
baseline changed.
Negative controls, each read back out of the file before the gate ran, then
restored from a pre-edit byte copy (
diffclean) and re-run green:if False:)One of these found a real weakness in my own test and it is worth naming.
The first run of the residual sabotage came back green. In every committed
document a residual window happens to carry no
start/end, so removing theresidual fence just let the next fence catch it — the test was asserting the
outcome, not the fence. I added a synthetic residual window that does carry
hours and a day type, plus a test that the refusal names exclusion rather than a
missing time. With those, the sabotage goes red. The fence is now gated; before,
it was decoration that happened to be correct.
Closes #57
Prepared with AI assistance; reviewed before submission.