fix(discovery): block countries in vague locations via job description
CI / Linting (Biome) (push) Failing after 41s
CI / Tests (push) Successful in 5m22s
CI / Type Check (adzuna-extractor) (push) Successful in 1m9s
CI / Type Check (gradcracker-extractor) (push) Successful in 1m14s
CI / Type Check (hiringcafe-extractor) (push) Successful in 1m11s
CI / Type Check (orchestrator) (push) Successful in 1m28s
CI / Type Check (startupjobs-extractor) (push) Successful in 1m13s
CI / Type Check (ukvisajobs-extractor) (push) Successful in 1m12s
CI / Documentation (push) Successful in 2m0s

QAJobsBoard and similar feeds often store Worldwide/Remote while the real
country is only in the description. Scan title and description when location
is vague, and prefer concrete locations from QAJobsBoard postings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-16 17:15:18 -04:00
co-authored by Cursor
parent f0261711c6
commit 0a63316100
5 changed files with 213 additions and 7 deletions
+39
View File
@@ -1,5 +1,7 @@
import { describe, expect, it } from "vitest";
import {
inferCountryKeysFromJobText,
isVagueJobLocation,
jobMatchesBlockedCountries,
normalizeBlockedCountryTokens,
resolveBlockedCountriesFromStoredString,
@@ -34,4 +36,41 @@ describe("blocked-countries", () => {
expect(jobMatchesBlockedCountries("Remote", blocked)).toBe(false);
expect(jobMatchesBlockedCountries(null, blocked)).toBe(false);
});
it("treats worldwide and remote-only locations as vague", () => {
expect(isVagueJobLocation("Worldwide")).toBe(true);
expect(isVagueJobLocation("Remote")).toBe(true);
expect(isVagueJobLocation("Toronto, Canada")).toBe(false);
});
it("finds blocked countries in description when location is worldwide", () => {
const blocked = resolveBlockedCountriesFromStoredString('["india"]');
expect(
jobMatchesBlockedCountries(
{
location: "Worldwide",
jobDescription:
"Job Location: Mumbai/Nagpur. We are hiring in India for this role.",
},
blocked,
),
).toBe(true);
expect(
jobMatchesBlockedCountries(
{
location: "Worldwide",
jobDescription: "Fully remote team across North America.",
},
blocked,
),
).toBe(false);
});
it("infers country names embedded in free text", () => {
expect(
inferCountryKeysFromJobText(
"Harrier is hiring in India. Job Location: Mumbai/Nagpur",
),
).toContain("india");
});
});
+75 -3
View File
@@ -7,6 +7,27 @@ import { inferCountryKeysFromJobLocation } from "./search-cities.js";
const supportedCountryKeySet = new Set(SUPPORTED_COUNTRY_KEYS);
/** Location strings that do not pin a hiring country (check description too). */
const VAGUE_LOCATION_VALUES = new Set([
"worldwide",
"global",
"anywhere",
"remote",
"wfh",
"work from home",
"unknown",
"multiple locations",
"multiple countries",
]);
const VAGUE_COUNTRY_KEYS = new Set(["worldwide", "global"]);
export interface JobBlockedCountrySignals {
location?: string | null;
jobDescription?: string | null;
title?: string | null;
}
/**
* Parse stored settings value for blocked countries.
* Accepts JSON string array (normal) or legacy plain comma/newline-separated text.
@@ -43,14 +64,65 @@ export function normalizeBlockedCountryTokens(tokens: string[]): string[] {
return [...keys];
}
/** True when the job location mentions a blocked country (unknown location is kept). */
export function isVagueJobLocation(location: string | null | undefined): boolean {
if (!location?.trim()) return true;
const normalized = location.trim().toLowerCase();
if (VAGUE_LOCATION_VALUES.has(normalized)) return true;
const keys = inferCountryKeysFromJobLocation(location);
if (keys.length === 0) return true;
return keys.every((key) => VAGUE_COUNTRY_KEYS.has(key));
}
/**
* Infer supported country keys mentioned anywhere in free text (title, description).
*/
export function inferCountryKeysFromJobText(
text: string | null | undefined,
): string[] {
if (!text?.trim()) return [];
const keys = new Set(inferCountryKeysFromJobLocation(text));
const lower = text.toLowerCase();
for (const countryKey of SUPPORTED_COUNTRY_KEYS) {
if (VAGUE_COUNTRY_KEYS.has(countryKey)) continue;
const pattern = countryKey.replace(/\s+/g, "\\s+");
if (new RegExp(`\\b${pattern}\\b`, "i").test(lower)) {
keys.add(countryKey);
}
}
return [...keys];
}
function collectJobCountryKeys(signals: JobBlockedCountrySignals): string[] {
const keys = new Set<string>();
for (const key of inferCountryKeysFromJobLocation(signals.location)) {
keys.add(key);
}
if (isVagueJobLocation(signals.location)) {
const blob = [signals.title, signals.jobDescription]
.filter(Boolean)
.join("\n");
for (const key of inferCountryKeysFromJobText(blob)) {
keys.add(key);
}
}
return [...keys];
}
/**
* True when the job mentions a blocked country in location and/or (when location
* is vague) title/description. Unknown location with no country in text is kept.
*/
export function jobMatchesBlockedCountries(
location: string | null | undefined,
locationOrSignals: string | null | undefined | JobBlockedCountrySignals,
blockedCountryKeys: readonly string[],
): boolean {
if (blockedCountryKeys.length === 0) return false;
const blocked = new Set(blockedCountryKeys);
const jobCountries = inferCountryKeysFromJobLocation(location);
const signals: JobBlockedCountrySignals =
typeof locationOrSignals === "object" && locationOrSignals !== null
? locationOrSignals
: { location: locationOrSignals };
const jobCountries = collectJobCountryKeys(signals);
if (jobCountries.length === 0) return false;
return jobCountries.some((key) => blocked.has(key));
}