From df7919cdf545f1f69fd9834d7e27aff5d981f1e5 Mon Sep 17 00:00:00 2001 From: Oscar Madera <80536682+oscarbol09@users.noreply.github.com> Date: Wed, 19 Aug 2026 01:32:49 -0500 Subject: [PATCH] fix(jobbank-search): emit date key derived from posted in search output (#342) jobbank search output was missing the shared contract's date field; it now emits date as YYYY-MM-DD derived from posted (kept unchanged), null when the feed item has no pubDate. The inline result mapping is extracted into an exported normalizeSearchItem (behavior-preserving) so the derivation is pinned by tests. Co-authored-by: oscarbol09 <80536682+oscarbol09@users.noreply.github.com> --- .agents/skills/jobbank-search/cli/README.md | 2 + .../jobbank-search/cli/src/commands/search.ts | 37 +++++++++--------- .../cli/tests/search-normalization.test.ts | 38 +++++++++++++++++++ 3 files changed, 60 insertions(+), 17 deletions(-) create mode 100644 .agents/skills/jobbank-search/cli/tests/search-normalization.test.ts diff --git a/.agents/skills/jobbank-search/cli/README.md b/.agents/skills/jobbank-search/cli/README.md index 6655810..337f0aa 100644 --- a/.agents/skills/jobbank-search/cli/README.md +++ b/.agents/skills/jobbank-search/cli/README.md @@ -247,6 +247,7 @@ bun run src/cli.ts search --education 24 --suitable-for 2 --since 2026-03-01 "description": "Fuldtidsjob hos Novo Nordisk, Bagsværd (Ansøgningsfrist: 12.04.2026)", "url": "https://jobbank.dk/job/1234567/novo-nordisk/senior-data-scientist", "posted": "2026-03-02T00:00:00+01:00", + "date": "2026-03-02", "deadline": "2026-04-12" } ] @@ -265,6 +266,7 @@ bun run src/cli.ts search --education 24 --suitable-for 2 --since 2026-03-01 | `description` | string | Raw RSS description field (single-line summary) | | `url` | string | Full URL to job posting | | `posted` | string | Publication date in ISO 8601 | +| `date` | string \| null | Publication date as `YYYY-MM-DD` (derived from `posted`), or `null` if absent | | `deadline` | string \| null | Application deadline as `DD.MM.YYYY` string, or `null` if "løbende" / not present | > `meta.total` is fetched from the HTML page `` in a secondary request (pattern: `"{N} relevante job og karriereopslag"`). If the secondary request fails, `meta.total` is `null`. diff --git a/.agents/skills/jobbank-search/cli/src/commands/search.ts b/.agents/skills/jobbank-search/cli/src/commands/search.ts index c0981af..9f4664a 100644 --- a/.agents/skills/jobbank-search/cli/src/commands/search.ts +++ b/.agents/skills/jobbank-search/cli/src/commands/search.ts @@ -1,6 +1,24 @@ import { defineCommand, option } from "@bunli/core" import { z } from "zod" -import { rssFetch, fetchWithUA, writeError, parseRssDescription, extractJobIdFromUrl, BASE_URL } from "../helpers.js" +import { rssFetch, fetchWithUA, writeError, parseRssDescription, extractJobIdFromUrl, BASE_URL, type RssItem } from "../helpers.js" + +export function normalizeSearchItem(item: RssItem): Record<string, unknown> { + const parsed = parseRssDescription(item.description) + const id = extractJobIdFromUrl(item.link) + const posted = item.pubDate ? new Date(item.pubDate).toISOString() : "" + return { + id, + title: item.title, + company: parsed.company, + location: parsed.location, + jobType: parsed.jobType, + description: item.description, + url: item.link, + posted, + date: posted ? posted.slice(0, 10) : null, + deadline: parsed.deadline, + } +} export const search = defineCommand({ name: "search", @@ -134,22 +152,7 @@ export const search = defineCommand({ } // Normalize items - let results = items.map((item) => { - const parsed = parseRssDescription(item.description) - const id = extractJobIdFromUrl(item.link) - const posted = item.pubDate ? new Date(item.pubDate).toISOString() : "" - return { - id, - title: item.title, - company: parsed.company, - location: parsed.location, - jobType: parsed.jobType, - description: item.description, - url: item.link, - posted, - deadline: parsed.deadline, - } - }) + let results = items.map(normalizeSearchItem) // Apply limit if (flags.limit !== undefined) { diff --git a/.agents/skills/jobbank-search/cli/tests/search-normalization.test.ts b/.agents/skills/jobbank-search/cli/tests/search-normalization.test.ts new file mode 100644 index 0000000..494fb82 --- /dev/null +++ b/.agents/skills/jobbank-search/cli/tests/search-normalization.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, test } from "bun:test"; +import { normalizeSearchItem } from "../src/commands/search"; +import type { RssItem } from "../src/helpers"; + +function rssItem(): RssItem { + return { + title: "Data Scientist", + description: "Fuldtidsjob hos Acme A/S, København (Ansøgningsfrist: 31.07.2026)", + link: "https://jobbank.dk/job/12345/acme/data-scientist", + pubDate: "Fri, 14 Aug 2026 09:30:00 +0200", + }; +} + +describe("Jobbank search normalization", () => { + test("derives the /scrape contract date from posted as YYYY-MM-DD", () => { + const result = normalizeSearchItem(rssItem()); + + expect(result.posted).toBe("2026-08-14T07:30:00.000Z"); + expect(result.date).toBe((result.posted as string).slice(0, 10)); + expect(result.date).toBe("2026-08-14"); + }); + + test("emits a null date when pubDate is absent (posted is empty)", () => { + const result = normalizeSearchItem({ ...rssItem(), pubDate: "" }); + + expect(result.posted).toBe(""); + expect(result.date).toBeNull(); + }); + + test("keeps the native fields alongside the contract date (additive)", () => { + const result = normalizeSearchItem(rssItem()); + + expect(result.company).toBe("Acme A/S"); + expect(result.location).toBe("København"); + expect(result.url).toBe("https://jobbank.dk/job/12345/acme/data-scientist"); + expect(result.deadline).toBe("31.07.2026"); + }); +}); \ No newline at end of file