/** * Lever ATS Adapter * * Interacts with public Lever job board APIs: * https://api.lever.co/v0/postings/{board_token}?mode=json */ import { IJobSourceAdapter, DiscoveryResult, SourceHealth, RateLimitPolicy, NormalizedJobRecord } from "../types"; import { generateContentFingerprint, normalizeJobUrl, classifyExperienceLevel } from "../deduplication"; export class LeverSourceAdapter implements IJobSourceAdapter { readonly id: string; readonly name: string; readonly sourceType = "ATS" as const; readonly provider = "lever" as const; private boardToken: string; constructor(boardToken: string, companyName?: string) { this.boardToken = boardToken.toLowerCase().trim(); this.name = companyName || `Lever (${boardToken})`; this.id = `lever_${this.boardToken}`; } async healthCheck(): Promise { const start = Date.now(); try { const url = `https://api.lever.co/v0/postings/${this.boardToken}?mode=json`; const res = await fetch(url, { headers: { "User-Agent": "JobsBoard-Acquisition/1.0" } }); return { healthy: res.ok, statusCode: res.status, latencyMs: Date.now() - start, errorMessage: res.ok ? undefined : `HTTP ${res.status}: ${res.statusText}`, }; } catch (err: any) { return { healthy: false, latencyMs: Date.now() - start, errorMessage: err.message, }; } } getRateLimitPolicy(): RateLimitPolicy { return { requestsPerMinute: 60, burstLimit: 10, retryAfterMs: 2000, }; } async discoverJobs(options: { limit?: number } = {}): Promise { const limit = options.limit || 50; const errors: string[] = []; const normalizedJobs: NormalizedJobRecord[] = []; try { const url = `https://api.lever.co/v0/postings/${this.boardToken}?mode=json`; const res = await fetch(url, { headers: { "User-Agent": "JobsBoard-Acquisition/1.0" } }); if (!res.ok) { throw new Error(`Lever API error HTTP ${res.status}: ${res.statusText}`); } const rawList = await res.json(); if (!Array.isArray(rawList)) { throw new Error(`Unexpected Lever API response format (expected array)`); } for (const item of rawList.slice(0, limit)) { try { const title = (item.text || "").trim(); const rawJobUrl = item.hostedUrl || ""; if (!title || !rawJobUrl) continue; const jobUrl = normalizeJobUrl(rawJobUrl); const categories = item.categories || {}; const location = categories.location || "Remote, USA"; const workplaceType = categories.workplaceType || ""; const isRemote = workplaceType.toLowerCase().includes("remote") || location.toLowerCase().includes("remote") || title.toLowerCase().includes("remote"); const deptName = categories.department || "Software & Engineering"; const cleanDesc = (item.descriptionPlain || item.description || "") .replace(/<[^>]+>/g, " ") .replace(/\s+/g, " ") .trim(); const fingerprint = generateContentFingerprint(this.name, title, location, isRemote); normalizedJobs.push({ title, company: this.name, location, isRemote, department: deptName, experienceLevel: classifyExperienceLevel(title), description: cleanDesc.slice(0, 5000) || `Position at ${this.name}. Apply on Lever.`, jobUrl, datePosted: item.createdAt ? new Date(item.createdAt) : new Date(), source: "lever", sourceId: this.id, fingerprintHash: fingerprint, rawPayload: item, }); } catch (itemErr: any) { errors.push(`Failed to parse Lever job ${item.id}: ${itemErr.message}`); } } } catch (err: any) { errors.push(`Discovery failed for ${this.id}: ${err.message}`); } return { sourceId: this.id, provider: this.provider, totalDiscovered: normalizedJobs.length, jobs: normalizedJobs, errors, }; } }