Let a parser report that a page states it lists no events

A count of zero has two causes and nothing downstream can tell them apart:
a source whose selectors all broke, and a game that is simply between
patches. Both parse to nothing, so `canParse` plus a row count cannot
separate them — and the pipeline currently has to assume the worst, which
is right for a redesign and wrong for a quiet week.

`statesNoEvents` is the seam for the one thing that can settle it: the
page's own words. Optional, because most pages say nothing either way, and
absent means the strict gate stands.

No parser implements it yet and nothing reads it — the contract widens here
so the parser and the refresh gate can land as their own changes.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-09-03 18:44:51 +02:00
co-authored by Claude Opus 5
parent d5301c5ceb
commit 0809fa1122
3 changed files with 25 additions and 0 deletions
+3
View File
@@ -227,6 +227,9 @@ function toAdapter(spec: SourceSpec): Adapter {
parserId: spec.parserId,
minIntervalMs: spec.minIntervalMs ?? SIX_HOURS_MS,
priority: spec.priority ?? 0,
statesNoEvents(html: string): boolean {
return parser.statesNoEvents?.(html) ?? false;
},
parse(html: string, ctx: ParseContext): GachaEvent[] {
// A site redesign should fail the run loudly rather than publish an empty
// calendar, which would read as "no events" to a user.
+8
View File
@@ -33,6 +33,14 @@ export interface Adapter {
priority: number;
parse(html: string, ctx: ParseContext): GachaEvent[];
/**
* True when the page itself states that it currently lists no events, so an
* empty parse is this source's real answer rather than a source that broke.
* Absent unless the underlying parser implements it — see
* `SourceParser.statesNoEvents`.
*/
statesNoEvents?(html: string): boolean;
}
export const SIX_HOURS_MS = 6 * 60 * 60 * 1000;
+14
View File
@@ -26,5 +26,19 @@ export interface SourceParser {
*/
canParse(html: string): boolean;
/**
* True when the document is one this parser understands *and* the document
* itself says it currently lists no events.
*
* Optional, and absent for every parser whose pages do not say so. It exists
* because `canParse` and a row count cannot together tell a source that broke
* from a game that is simply between versions: both yield nothing. A page
* that states its own emptiness can, and the refresh runner stores that as a
* real answer instead of counting it as a failure — see
* `scripts/refresh-sources.ts` § the parse gate. Anything less explicit than
* the page's own words belongs on the strict side of that gate.
*/
statesNoEvents?(html: string): boolean;
parse(html: string, ctx: ParseContext): GachaEvent[];
}