From b067928da77f753dda950c48593ab3c0aada29b0 Mon Sep 17 00:00:00 2001 From: Mads Lorentzen Date: Wed, 19 Aug 2026 20:29:40 +0200 Subject: [PATCH] fix(jobindex-search): map ASAP deadlines to null per the /scrape contract apply_deadline_asap was emitted as the string "ASAP" on ~half of live results - undocumented, contradicting the CLI's own README, and breaking every consumer that does date arithmetic on the field (rank's sweep, outcome's deadline check, notion-sync's typed date column). ASAP means "no stated deadline", which the schema already defines null to mean. The flag wins over any date field that happens to be present. Review finding F12 (2026-08-19), decision approved by Mads. Co-Authored-By: Claude Opus 5 (1M context) --- .agents/skills/jobindex-search/cli/README.md | 2 +- .agents/skills/jobindex-search/cli/src/helpers.ts | 6 +++++- .../jobindex-search/cli/tests/search-page.test.ts | 15 +++++++++++++++ CHANGELOG.md | 7 +++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.agents/skills/jobindex-search/cli/README.md b/.agents/skills/jobindex-search/cli/README.md index 4316dd8..a13b69a 100644 --- a/.agents/skills/jobindex-search/cli/README.md +++ b/.agents/skills/jobindex-search/cli/README.md @@ -169,7 +169,7 @@ bun run src/cli.ts detail h1647303 --format plain ``` **Field notes:** -- `deadline` — application deadline date string; `null` if not listed. +- `deadline` — application deadline date string (`YYYY-MM-DD`); `null` if not listed. Postings flagged "ASAP" by the portal carry no fixed deadline and also map to `null`. - `employmentType` — e.g. `"Fastansættelse"`, `"Midlertidig ansættelse"`; `null` if not listed. - `hours` — e.g. `"Fuldtid"`, `"Deltid"`; `null` if not listed. - `applyUrl` — the external application URL (resolved from the Jobindex redirect link `/c?t=...`); `null` if not available. diff --git a/.agents/skills/jobindex-search/cli/src/helpers.ts b/.agents/skills/jobindex-search/cli/src/helpers.ts index 3852921..e6db4aa 100644 --- a/.agents/skills/jobindex-search/cli/src/helpers.ts +++ b/.agents/skills/jobindex-search/cli/src/helpers.ts @@ -160,7 +160,11 @@ export function parseSearchPage(html: string): SearchPageResult { } } let deadline: string | null = null - if (r.apply_deadline_asap) deadline = "ASAP" + // apply_deadline_asap means the posting states no fixed deadline ("apply + // now"). The /scrape contract represents that as null, and consumers do + // date arithmetic on this field - so the flag maps to null, and wins over + // any date field that happens to be present. + if (r.apply_deadline_asap) deadline = null else if (typeof r.apply_deadline === "string") deadline = r.apply_deadline.slice(0, 10) else if (typeof r.lastdate === "string") deadline = r.lastdate diff --git a/.agents/skills/jobindex-search/cli/tests/search-page.test.ts b/.agents/skills/jobindex-search/cli/tests/search-page.test.ts index b9a2414..6891db3 100644 --- a/.agents/skills/jobindex-search/cli/tests/search-page.test.ts +++ b/.agents/skills/jobindex-search/cli/tests/search-page.test.ts @@ -45,6 +45,21 @@ describe("parseSearchPage", () => { }); }); + test("maps an ASAP posting's deadline to null (no stated deadline)", () => { + // apply_deadline_asap means "no fixed deadline, apply now". The /scrape + // schema defines null as exactly that, and every consumer (rank's expiry + // sweep, notion-sync's typed date column) does date arithmetic on this + // field - a bare "ASAP" string broke all of them on half of live results + // (review finding F12, 2026-08-19). lastdate present too: the flag wins. + const [job] = parseSearchPage( + stashPage({ + hitcount: 1, + results: [{ ...RESULT, apply_deadline: undefined, apply_deadline_asap: true, lastdate: "2026-09-30" }], + }), + ).results; + expect(job.deadline).toBeNull(); + }); + test("falls back to lastdate when apply_deadline is absent", () => { const [job] = parseSearchPage( stashPage({ hitcount: 1, results: [{ ...RESULT, apply_deadline: undefined, lastdate: "2026-09-30" }] }), diff --git a/CHANGELOG.md b/CHANGELOG.md index daae35f..ffd16c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,13 @@ per-file diff commands. ### Fixed +- **`jobindex-search` maps ASAP postings' deadline to `null`** - the portal's + `apply_deadline_asap` flag was emitted as the literal string `"ASAP"` on roughly half + of live results, contradicting the CLI's own README ("date string; null if not + listed") and the `/scrape` schema, and breaking every consumer that does date + arithmetic (`/rank`'s urgency and expiry sweep, `/outcome`'s deadline check, + `/notion-sync`'s typed date column). ASAP means "no stated deadline", which the + contract already represents as `null`. Pinned in `tests/search-page.test.ts`. - **`/gmail-sync` no longer restricts its search to the Inbox** - the query used `in:inbox` to "skip sent/drafts", but that operator also excludes every archived message, and self-defeatingly the mail matched by the very job-search label Step 3.1