fix(jobs): treat isRemote as 100% remote only; tighten cron for Canada QA
Reject hybrid or partial-office postings at ingest so the Remote badge and filters match fully remote roles. Cron can PATCH search geography, remote-only workplace types, and QA search terms before each scheduled pipeline run. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,5 +2,6 @@ export * from "./extractors";
|
||||
export * from "./job-fingerprint";
|
||||
export * from "./job-url-canonical";
|
||||
export * from "./location-support";
|
||||
export * from "./work-arrangement";
|
||||
export * from "./types";
|
||||
export * from "./utils/type-conversion";
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
jobSignalsHybridOrOnsite,
|
||||
normalizeIsRemote,
|
||||
} from "./work-arrangement.js";
|
||||
|
||||
describe("jobSignalsHybridOrOnsite", () => {
|
||||
it("detects hybrid in description", () => {
|
||||
expect(
|
||||
jobSignalsHybridOrOnsite({
|
||||
title: "Automation Test Engineer (SDET)",
|
||||
jobDescription:
|
||||
"Job Type: Hybrid (3 days remote)\nJob Location: Vancouver, BC",
|
||||
isRemote: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("detects N days in office", () => {
|
||||
expect(
|
||||
jobSignalsHybridOrOnsite({
|
||||
jobDescription: "3 days per week in the office, 2 days remote",
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("does not flag fully remote postings", () => {
|
||||
expect(
|
||||
jobSignalsHybridOrOnsite({
|
||||
jobDescription: "100% remote. Work from anywhere in Canada.",
|
||||
isRemote: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeIsRemote", () => {
|
||||
it("downgrades JobSpy false positive when hybrid is mentioned", () => {
|
||||
expect(
|
||||
normalizeIsRemote({
|
||||
title: "Automation Test Engineer (SDET)",
|
||||
jobDescription: "Job Type: Hybrid (3 days remote)",
|
||||
isRemote: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps true when upstream says remote and text is fully remote", () => {
|
||||
expect(
|
||||
normalizeIsRemote({
|
||||
jobDescription: "Fully remote role. No office visits required.",
|
||||
isRemote: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("promotes unknown upstream when text is strongly remote-only", () => {
|
||||
expect(
|
||||
normalizeIsRemote({
|
||||
jobDescription: "100% remote — work from anywhere.",
|
||||
isRemote: undefined,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns null when remote status is unclear", () => {
|
||||
expect(
|
||||
normalizeIsRemote({
|
||||
title: "Software Engineer",
|
||||
location: "Toronto, ON",
|
||||
isRemote: undefined,
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Work-arrangement detection for ingest and scoring.
|
||||
* `isRemote` means 100% remote — hybrid or regular office presence disqualifies.
|
||||
*/
|
||||
|
||||
export interface WorkArrangementSignals {
|
||||
title?: string | null;
|
||||
jobDescription?: string | null;
|
||||
location?: string | null;
|
||||
workFromHomeType?: string | null;
|
||||
jobType?: string | null;
|
||||
isRemote?: boolean | null;
|
||||
}
|
||||
|
||||
function buildBlob(signals: WorkArrangementSignals): string {
|
||||
return [
|
||||
signals.title,
|
||||
signals.jobDescription,
|
||||
signals.location,
|
||||
signals.workFromHomeType,
|
||||
signals.jobType,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/** Posting text strongly indicates fully remote (no office days). */
|
||||
export function jobSignalsStrongRemoteOnly(
|
||||
signals: WorkArrangementSignals,
|
||||
): boolean {
|
||||
const blob = buildBlob(signals);
|
||||
return (
|
||||
/\b100%\s*remote\b/.test(blob) ||
|
||||
/\bfully\s+remote\b/.test(blob) ||
|
||||
/\bremote[\s-]only\b/.test(blob) ||
|
||||
/\bcompletely\s+remote\b/.test(blob) ||
|
||||
/\bwork\s+from\s+anywhere\b/.test(blob) ||
|
||||
/\banywhere\s+in\s+the\s+(us|usa|uk|world)\b/.test(blob)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hybrid, partial-remote, or on-site/office requirements — not 100% remote.
|
||||
*/
|
||||
export function jobSignalsHybridOrOnsite(
|
||||
signals: WorkArrangementSignals,
|
||||
): boolean {
|
||||
const blob = buildBlob(signals);
|
||||
|
||||
const wfh = (signals.workFromHomeType ?? "").toLowerCase();
|
||||
if (wfh.includes("hybrid") || wfh.includes("on-site") || wfh.includes("onsite")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
/\bhybrid\b/.test(blob) ||
|
||||
/\bremote[\s-]?hybrid\b/.test(blob) ||
|
||||
/\bhybrid[\s-]?remote\b/.test(blob) ||
|
||||
/\bpartial(?:ly)?\s+remote\b/.test(blob) ||
|
||||
/\b\d[\d]?\s+days?\s+remote\b/.test(blob) ||
|
||||
/\bremote\s+\d[\d]?\s+days?\b/.test(blob) ||
|
||||
/\b\d[\d]?\s+days?\s+(a|per)\s+week\b.*\b(in[\s-]?office|on[\s-]?site|onsite|at\s+the\s+office)\b/.test(
|
||||
blob,
|
||||
) ||
|
||||
/\b(in[\s-]?office|on[\s-]?site|onsite|at\s+the\s+office)\b.*\b\d[\d]?\s+days?\b/.test(
|
||||
blob,
|
||||
) ||
|
||||
/\b(one|two|three|four|five|six|seven|eight|nine|ten)\s+days?\b.*\b(in[\s-]?office|on[\s-]?site|onsite)\b/.test(
|
||||
blob,
|
||||
) ||
|
||||
/\b(in[\s-]?office|on[\s-]?site|onsite)\b.*\b(one|two|three|four|five|six|seven|eight|nine|ten)\s+days?\b/.test(
|
||||
blob,
|
||||
) ||
|
||||
/\boffice[\s-]based\b/.test(blob) ||
|
||||
/\bon[\s-]?site\s+(role|position|required|mandatory)\b/.test(blob) ||
|
||||
/\b(required|must)\b.*\b(in[\s-]?office|on[\s-]?site|onsite|in[\s-]?person)\b/.test(
|
||||
blob,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize upstream `isRemote` to 100% remote only.
|
||||
* Hybrid / office-day language forces false; strong remote-only text can promote to true.
|
||||
*/
|
||||
export function normalizeIsRemote(
|
||||
signals: WorkArrangementSignals,
|
||||
): boolean | null {
|
||||
if (jobSignalsHybridOrOnsite(signals)) {
|
||||
return false;
|
||||
}
|
||||
if (signals.isRemote === true) {
|
||||
return true;
|
||||
}
|
||||
if (signals.isRemote === false) {
|
||||
return false;
|
||||
}
|
||||
if (jobSignalsStrongRemoteOnly(signals)) {
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Job likely requires office presence (for scoring caps when candidate is remote-only).
|
||||
*/
|
||||
export function jobLikelyRequiresOfficePresence(
|
||||
signals: WorkArrangementSignals,
|
||||
): boolean {
|
||||
if (jobSignalsHybridOrOnsite(signals)) {
|
||||
return true;
|
||||
}
|
||||
if (signals.isRemote === false) {
|
||||
if (jobSignalsStrongRemoteOnly(signals)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user