From 9187f894a5a4a06f5550362fad6d2f34beed801d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Sahu Date: Tue, 29 Sep 2026 23:52:04 +0530 Subject: [PATCH] fix(freehire-search): keep the salary period so a yearly figure isn't read as monthly (#504) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit formatSalary printed only the currency and amounts, although freehire's enrichment records the pay period. `detail software-developer-trainee-codifi-fra635ux` printed `INR 300000–300000` while the API returns `salary_period: "year"` for that posting, so a yearly salary screened against a monthly figure read as twelve times its real monthly value. Append the period when freehire records one (`INR 300000–300000/year`, `INR 12000/month`); a record without a period prints exactly as before. Two new parsing.test.ts cases pin it; both fail on the old formatter. Co-authored-by: Claude Opus 5.5 --- .agents/skills/freehire-search/SKILL.md | 3 ++- .agents/skills/freehire-search/cli/src/helpers.ts | 12 +++++++++--- .../skills/freehire-search/cli/tests/parsing.test.ts | 12 ++++++++++++ CHANGELOG.md | 10 ++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.agents/skills/freehire-search/SKILL.md b/.agents/skills/freehire-search/SKILL.md index aa98c04c..22d7344b 100644 --- a/.agents/skills/freehire-search/SKILL.md +++ b/.agents/skills/freehire-search/SKILL.md @@ -126,7 +126,8 @@ bun run .agents/skills/freehire-search/cli/src/cli.ts detail [--forma `slug` is the `id` from a `search` result (e.g. `golang-zensar-2bxu6dxm`). You may also pass a full `https://freehire.me/jobs/` URL. Returns the full (HTML-stripped) description, skills, region/country, and — when the posting is enriched — seniority, -category, employment type, and salary. +category, employment type, and salary. `salary` keeps its period when freehire records +one (`INR 300000–300000/year`, `INR 12000/month`); compare pay only in one period. Use it for a posting you already have a slug for — a tracked application, a shared link, or a closed posting search no longer lists. Re-fetching a hit that `search` diff --git a/.agents/skills/freehire-search/cli/src/helpers.ts b/.agents/skills/freehire-search/cli/src/helpers.ts index 2d950566..cf4e4e89 100644 --- a/.agents/skills/freehire-search/cli/src/helpers.ts +++ b/.agents/skills/freehire-search/cli/src/helpers.ts @@ -107,6 +107,7 @@ export interface FreehireJob { salary_min?: number salary_max?: number salary_currency?: string + salary_period?: string // e.g. "year", "month" } } @@ -176,12 +177,17 @@ export function toDetail(j: FreehireJob): JobDetailResult { } } -/** Human-readable salary line from the enrichment fields, or null when absent. */ +/** + * Human-readable salary line from the enrichment fields, or null when absent. + * The period is kept when freehire records one ("INR 300000–300000/year"): + * without it a yearly figure is indistinguishable from a monthly one. + */ function formatSalary(e: FreehireJob["enrichment"]): string | null { if (e.salary_min == null && e.salary_max == null) return null const cur = e.salary_currency ? `${e.salary_currency} ` : "" - if (e.salary_min != null && e.salary_max != null) return `${cur}${e.salary_min}–${e.salary_max}` - return `${cur}${e.salary_min ?? e.salary_max}` + const per = e.salary_period ? `/${e.salary_period}` : "" + if (e.salary_min != null && e.salary_max != null) return `${cur}${e.salary_min}–${e.salary_max}${per}` + return `${cur}${e.salary_min ?? e.salary_max}${per}` } function numericEntity(cp: number): string { diff --git a/.agents/skills/freehire-search/cli/tests/parsing.test.ts b/.agents/skills/freehire-search/cli/tests/parsing.test.ts index 2c6910f4..d5f7179b 100644 --- a/.agents/skills/freehire-search/cli/tests/parsing.test.ts +++ b/.agents/skills/freehire-search/cli/tests/parsing.test.ts @@ -68,6 +68,18 @@ describe("toDetail — adds cleaned description + enrichment", () => { expect(d.salary).toBe("EUR 90000–120000"); }); + test("keeps the salary period, so a yearly figure is not read as monthly", () => { + const d = toDetail( + job({ enrichment: { salary_min: 300000, salary_max: 300000, salary_currency: "INR", salary_period: "year" } }), + ); + expect(d.salary).toBe("INR 300000–300000/year"); + }); + + test("a single-bound salary keeps its period too", () => { + const d = toDetail(job({ enrichment: { salary_min: 12000, salary_currency: "INR", salary_period: "month" } })); + expect(d.salary).toBe("INR 12000/month"); + }); + test("null enrichment fields when the enrichment object is empty", () => { const d = toDetail(job({ enrichment: {} })); expect(d.seniority).toBeNull(); diff --git a/CHANGELOG.md b/CHANGELOG.md index cbcd6970..a32ccaf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,16 @@ per-file diff commands. ### Fixed +- **`freehire-search detail` keeps the salary period, so a yearly figure no longer reads as + monthly** (`.agents/skills/freehire-search/cli/src/helpers.ts`, `SKILL.md`) - `formatSalary` + printed only the currency and amounts, although freehire's enrichment records the period: + `detail software-developer-trainee-codifi-fra635ux` printed `INR 300000–300000` while the + API returns `salary_period: "year"` for that posting, so anyone screening pay against a + monthly figure read a yearly salary as a monthly one, twelve times its real monthly value. + The period is now appended when freehire records one (`INR 300000–300000/year`, + `INR 12000/month`); a record without a period prints exactly as before. Pinned by two new + `parsing.test.ts` cases, both failing on the old formatter. + - **`robots_check` decodes curl output as UTF-8, so a non-ASCII response no longer reads as UNCONFIRMED on Windows** (`tools/robots_check.py`, `tests/test_robots_check.py`) - `_fetch()` ran curl with `text=True` and no encoding, so Python decoded the response with