mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 08:36:25 +00:00
* feat(linkedin-search): add active status verification for job postings * fix(linkedin-search): scope closed-posting detection to the top card, pin with tests (#280) The first version matched five markers against the whole document, so recruiter boilerplate quoting 'no longer accepting applications' in a description flagged a live job CLOSED. Detection now stops where the description markup begins and matches only the two markers real closed pages carry (closed-job__flavor and the banner text, verified against live guest pages); the three speculative phrases are dropped. Four new fixture tests pin both directions plus the two description false-positive cases - the false-positive pair fails on the unscoped version. * feat(scrape): mark closed-at-source LinkedIn postings expired, never drop (#280) /scrape Step 2 now consumes linkedin-search detail's isActive: a job whose posting page renders the closed banner is written to seen_jobs.json with status expired rather than silently dropped, per the /rank marking pattern - the fix for the ghost-jobs class in #331. isActive: true is documented as absence of the banner, not proof the posting is open. --------- Co-authored-by: Navakanth Reddy Dumpa <navkanthr@gmail.com>
This commit is contained in:
co-authored by
Navakanth Reddy Dumpa
parent
730dcfb079
commit
3d296448bd
@@ -86,6 +86,51 @@ describe("decodeHtmlEntities (via parseJobCards)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseJobDetail active-status detection", () => {
|
||||
// Captured from a real closed guest posting (2026-08-09): the banner LinkedIn
|
||||
// actually renders inside the top card. Its class and its visible text are the
|
||||
// only closed markers that occur in the wild.
|
||||
const closedBanner = `
|
||||
<figure class="closed-job closed-job__flavor topcard__flavor-row">
|
||||
<span class="closed-job__icon closed-job__icon--error-pebble lazy-load"></span>
|
||||
<figcaption class="closed-job__flavor--closed">No longer accepting applications</figcaption>
|
||||
</figure>`;
|
||||
|
||||
const page = (topcardExtra: string, description: string) => `
|
||||
<h1 class="topcard__title">Data Engineer</h1>
|
||||
<span class="topcard__flavor topcard__flavor--bullet">Berlin</span>
|
||||
${topcardExtra}
|
||||
<div class="show-more-less-html__markup">${description}</div>`;
|
||||
|
||||
test("a closed posting's top-card banner yields isActive: false", () => {
|
||||
const job = parseJobDetail(page(closedBanner, "We build things."), "1");
|
||||
expect(job.isActive).toBe(false);
|
||||
});
|
||||
|
||||
test("an open posting yields isActive: true", () => {
|
||||
const job = parseJobDetail(page("", "We are hiring!"), "2");
|
||||
expect(job.isActive).toBe(true);
|
||||
});
|
||||
|
||||
test("recruiter boilerplate in the description does not flag a live posting", () => {
|
||||
// The review's false-positive case: the closed phrase appears in the
|
||||
// *description text* of a job that is very much open.
|
||||
const job = parseJobDetail(
|
||||
page("", "Apply soon - once filled, this posting is no longer accepting applications."),
|
||||
"3",
|
||||
);
|
||||
expect(job.isActive).toBe(true);
|
||||
});
|
||||
|
||||
test("a closed-job class named in the description does not flag a live posting", () => {
|
||||
const job = parseJobDetail(
|
||||
page("", "Our design system documents a closed-job__flavor CSS class."),
|
||||
"4",
|
||||
);
|
||||
expect(job.isActive).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseJobDetail dropped fields", () => {
|
||||
test("emits no applyUrl field", () => {
|
||||
// The extraction regex assumed class-before-href and never matched
|
||||
|
||||
Reference in New Issue
Block a user