mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-11-02 13:12:50 +00:00
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:
1 parent
9bc71de5f8
commit
9187f894a5
4 files changed
+33
-4
No files matched your search
@@ -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
|
`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)
|
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,
|
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
|
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`
|
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_min?: number
|
||||||
salary_max?: number
|
salary_max?: number
|
||||||
salary_currency?: string
|
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 {
|
function formatSalary(e: FreehireJob["enrichment"]): string | null {
|
||||||
if (e.salary_min == null && e.salary_max == null) return null
|
if (e.salary_min == null && e.salary_max == null) return null
|
||||||
const cur = e.salary_currency ? `${e.salary_currency} ` : ""
|
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}`
|
const per = e.salary_period ? `/${e.salary_period}` : ""
|
||||||
return `${cur}${e.salary_min ?? e.salary_max}`
|
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 {
|
function numericEntity(cp: number): string {
|
||||||
|
|||||||
@@ -68,6 +68,18 @@ describe("toDetail — adds cleaned description + enrichment", () => {
|
|||||||
expect(d.salary).toBe("EUR 90000–120000");
|
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", () => {
|
test("null enrichment fields when the enrichment object is empty", () => {
|
||||||
const d = toDetail(job({ enrichment: {} }));
|
const d = toDetail(job({ enrichment: {} }));
|
||||||
expect(d.seniority).toBeNull();
|
expect(d.seniority).toBeNull();
|
||||||
|
|||||||
@@ -105,6 +105,16 @@ per-file diff commands.
|
|||||||
|
|
||||||
### Fixed
|
### 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
|
- **`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`) -
|
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
|
`_fetch()` ran curl with `text=True` and no encoding, so Python decoded the response with
|
||||||
|
|||||||
Reference in new issue
Block a user