fix(freehire-search): keep the salary period so a yearly figure isn't read as monthly (#504)

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 <noreply@anthropic.com>
This commit is contained in:
Sahil Kumar SahuandClaude Opus 5.5 authored and GitHub committed 2026-09-29 20:22:04 +02:00
1 parent 9bc71de5f8
commit 9187f894a5
4 files changed
+33 -4

No files matched your search

+2 -1
View File
@@ -126,7 +126,8 @@ bun run .agents/skills/freehire-search/cli/src/cli.ts detail <slug|url> [--forma
`slug` is the `id` from a `search` result (e.g. `golang-zensar-2bxu6dxm`). You may
also pass a full `https://freehire.me/jobs/<slug>` 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`
@@ -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 {
@@ -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();