openings 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/data/companies.json +6525 -0
- package/package.json +1 -1
- package/src/board-verification.ts +5 -4
- package/src/catalog.ts +9 -3
- package/src/cli.ts +36 -0
- package/src/common-crawl-discovery.ts +8 -2
- package/src/country-coverage.ts +1 -1
- package/src/jobposting-site.ts +162 -0
- package/src/keka-tenants.ts +46 -0
- package/src/locations.ts +4 -0
- package/src/providers.ts +84 -3
- package/src/site-admission.ts +39 -0
- package/src/site-probe.ts +43 -0
- package/src/source-enrichment.ts +1 -1
- package/src/source-verification.ts +15 -6
- package/src/types.ts +3 -2
- package/src/version.ts +1 -1
|
@@ -58,7 +58,7 @@ export async function verifyCandidates(candidates: SourceCandidate[], options: V
|
|
|
58
58
|
const providerFetch = providerCooldowns.get(source.ats)!.wrap(fetcher);
|
|
59
59
|
try {
|
|
60
60
|
const evidence = await probe(candidate, source, providerFetch, timeoutMs, options.resolveHost, options.headTransport, options.pageTransport);
|
|
61
|
-
const replayed = evidence.identityEvidence === "company_redirect" || evidence.identityEvidence === "company_page_link";
|
|
61
|
+
const replayed = evidence.identityEvidence === "company_redirect" || evidence.identityEvidence === "company_page_link" || evidence.providerLinksDomain;
|
|
62
62
|
if (!replayed && !identityMatches(candidate.companyName, candidate.companyDomain, evidence.observedCompanyName, source.token)) {
|
|
63
63
|
rejected.push({ index, value: rejection(candidate, "identity_mismatch", `Expected ${candidate.companyName}; observed ${evidence.observedCompanyName}`) });
|
|
64
64
|
continue;
|
|
@@ -164,23 +164,32 @@ async function probe(candidate: SourceCandidate, source: ResolvedSource, fetcher
|
|
|
164
164
|
throw new VerificationError("unreachable", `HTTP ${response.status}`);
|
|
165
165
|
}
|
|
166
166
|
const contentType = response.headers.get("content-type") ?? "unknown";
|
|
167
|
-
let body: unknown;
|
|
168
|
-
try { body = await response.json(); } catch { throw new VerificationError("invalid_payload", "Endpoint did not return JSON"); }
|
|
169
167
|
const spec = providerSpec(source.ats);
|
|
168
|
+
let body: unknown;
|
|
169
|
+
try { body = spec?.bodyFormat === "text" ? await response.text() : await response.json(); } catch { throw new VerificationError("invalid_payload", "Endpoint did not return JSON"); }
|
|
170
170
|
const jobs = spec ? spec.jobsFromBody(body) : source.ats === "greenhouse" ? recordArray(body, "jobs") : source.ats === "lever" ? array(body) : source.ats === "workday" ? recordArray(body, "jobPostings") : source.ats === "recruitee" ? recordArray(body, "offers") : recordArray(body, "jobs");
|
|
171
171
|
if (!jobs) throw new VerificationError("invalid_payload", "Payload does not contain the expected jobs array");
|
|
172
172
|
if (jobs.length === 0) throw new VerificationError("empty_board", "Source has no jobs, so identity cannot be verified");
|
|
173
|
-
|
|
173
|
+
let providerName = spec ? spec.providerName(jobs, body) : source.ats === "greenhouse" ? majority(jobs.map((job) => stringField(job, "company_name")).filter(Boolean)) : source.ats === "workday" ? workday!.tenant : "";
|
|
174
|
+
let infoWebsite = "";
|
|
175
|
+
if (spec?.companyInfo) {
|
|
176
|
+
const info = await spec.companyInfo(source.token, async (url, format = "json") => { const reply = await fetchProbeWithRetry(fetcher, url, { signal: controller.signal }); if (!reply.ok) { await reply.body?.cancel().catch(() => undefined); throw new VerificationError("unreachable", `HTTP ${reply.status}`); } return format === "text" ? reply.text() : reply.json(); }).catch((): { name: string; website?: string } => ({ name: "" }));
|
|
177
|
+
providerName = providerName || info.name; infoWebsite = info.website ?? "";
|
|
178
|
+
}
|
|
179
|
+
const expectedDomain = candidate.companyDomain.toLowerCase().replace(/^www\./, "");
|
|
180
|
+
// The provider's own tenant record names the company website: that is the company's claim on its own board, stronger than any name heuristic.
|
|
181
|
+
const providerLinksDomain = Boolean(infoWebsite) && (infoWebsite === expectedDomain || infoWebsite.endsWith(`.${expectedDomain}`));
|
|
174
182
|
const tenantMatches = source.ats === "workday" && identityMatches(candidate.companyName, candidate.companyDomain, workday!.tenant, source.token);
|
|
175
183
|
const namedProvider = source.ats === "greenhouse" || tenantMatches || Boolean(spec && providerName);
|
|
176
|
-
const hasDomainLink = !namedProvider && structuredIdentityLinksDomain(jobs, candidate.companyDomain);
|
|
184
|
+
const hasDomainLink = providerLinksDomain || !namedProvider && structuredIdentityLinksDomain(jobs, candidate.companyDomain);
|
|
177
185
|
const hasRedirectEvidence = !namedProvider && await verifiedCompanyRedirect(candidate, source, resolveHost, headTransport, timeoutMs);
|
|
178
186
|
const hasPageLink = !namedProvider && !hasDomainLink && !hasRedirectEvidence && await verifiedCompanyPageLink(candidate, source, resolveHost, pageTransport, timeoutMs);
|
|
179
187
|
if (!namedProvider && !hasDomainLink && !hasRedirectEvidence && !hasPageLink) throw new VerificationError("identity_mismatch", `Neither structured identity fields, a verified company redirect, nor a company careers-page link point to ${candidate.companyDomain}`);
|
|
180
188
|
const observedCompanyName = namedProvider && providerName ? providerName : candidate.companyName;
|
|
181
189
|
return {
|
|
182
190
|
observedCompanyName,
|
|
183
|
-
|
|
191
|
+
providerLinksDomain,
|
|
192
|
+
identityEvidence: providerLinksDomain ? "structured_domain_link" as const : tenantMatches ? "provider_tenant" as const : (namedProvider && providerName ? "provider_company_name" as const : hasDomainLink ? "structured_domain_link" as const : hasRedirectEvidence ? "company_redirect" as const : "company_page_link" as const),
|
|
184
193
|
contentType,
|
|
185
194
|
payloadVersion: spec ? spec.payloadVersion(body) : source.ats === "greenhouse" ? "greenhouse-job-board:v1" : source.ats === "lever" ? "lever-postings:v0" : source.ats === "workday" ? "workday-cxs:v1" : source.ats === "recruitee" ? "recruitee-careers:v1" : `ashby-job-board:${isRecord(body) && typeof body.apiVersion === "string" ? body.apiVersion : "unknown"}`,
|
|
186
195
|
jobCount: source.ats === "workday" && isRecord(body) && typeof body.total === "number" ? body.total : jobs.length,
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/** ATS boards plus "jobposting": a company's own career site read only through its schema.org JobPosting markup (token = careers URL). */
|
|
2
|
+
export const ALL_PROVIDERS = ["greenhouse", "lever", "ashby", "workday", "recruitee", "smartrecruiters", "workable", "breezy", "freshteam", "keka", "zohorecruit", "jobposting"] as const;
|
|
2
3
|
export type Ats = (typeof ALL_PROVIDERS)[number];
|
|
3
4
|
|
|
4
5
|
export interface DomainEvidence {
|
|
@@ -40,7 +41,7 @@ export interface SourceVerification {
|
|
|
40
41
|
checkedAt: string;
|
|
41
42
|
canonicalSourceUrl: string;
|
|
42
43
|
observedCompanyName: string;
|
|
43
|
-
identityEvidence: "provider_company_name" | "provider_tenant" | "structured_domain_link" | "company_redirect" | "company_page_link" | "provider_board";
|
|
44
|
+
identityEvidence: "provider_company_name" | "provider_tenant" | "structured_domain_link" | "company_redirect" | "company_page_link" | "provider_board" | "company_site";
|
|
44
45
|
contentType: string;
|
|
45
46
|
payloadVersion: string;
|
|
46
47
|
jobCount: number;
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.1.
|
|
1
|
+
export const VERSION = "0.1.20";
|