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 {
+ 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