Shaheer Sarfaraz 3da5ea35b4
Deduplicate shared helpers and enforce aliased imports (#228)
* Deduplicate string cleanup helpers and not-found responses

* Enforce aliased imports for infra and shared modules

* Enforce @client/@server aliases for deep relative imports

* Deduplicate visa sponsor and location filter definitions

* Use shared city filter export in extractor location checks
2026-02-22 16:13:52 +00:00

215 lines
6.1 KiB
TypeScript

import { getSetting } from "@server/repositories/settings";
import { LlmService } from "@server/services/llm/service";
import type { JsonSchemaDefinition } from "@server/services/llm/types";
import {
messageTypeFromStageTarget,
normalizeStageTarget,
} from "@server/services/post-application/stage-target";
import type {
Job,
PostApplicationMessageType,
PostApplicationRouterStageTarget,
} from "@shared/types";
import { POST_APPLICATION_ROUTER_STAGE_TARGETS } from "@shared/types";
import { normalizeWhitespace } from "@shared/utils/string";
export const ROUTER_EMAIL_CHAR_LIMIT = 12_000;
const SMART_ROUTER_SCHEMA: JsonSchemaDefinition = {
name: "post_application_email_router",
schema: {
type: "object",
properties: {
bestMatchIndex: {
type: ["integer", "null"],
description:
"Best matching active-job index from provided list (1-based), or null.",
},
confidence: {
type: "integer",
description: "Confidence score 0-100 for routing decision.",
},
stageTarget: {
type: "string",
enum: [...POST_APPLICATION_ROUTER_STAGE_TARGETS],
description:
"Normalized stage target for this message, matching Log Event options.",
},
isRelevant: {
type: "boolean",
description:
"Whether this is a relevant recruitment/application email.",
},
stageEventPayload: {
type: ["object", "null"],
description: "Structured metadata for a potential stage event.",
additionalProperties: true,
},
reason: {
type: "string",
description: "One sentence reason for the routing decision.",
},
},
required: [
"bestMatchIndex",
"confidence",
"stageTarget",
"isRelevant",
"stageEventPayload",
"reason",
],
additionalProperties: false,
},
};
export type IndexedActiveJob = {
index: number;
id: string;
company: string;
title: string;
};
export type SmartRouterResult = {
bestMatchId: string | null;
confidence: number;
stageTarget: PostApplicationRouterStageTarget;
messageType: PostApplicationMessageType;
isRelevant: boolean;
stageEventPayload: Record<string, unknown> | null;
reason: string;
};
export function minifyActiveJobs(jobs: Job[]): Array<{
id: string;
company: string;
title: string;
}> {
return jobs.map((job) => ({
id: job.id,
company: job.employer,
title: job.title,
}));
}
function sanitizeJobPromptValue(value: string): string {
return normalizeWhitespace(value);
}
export function buildIndexedActiveJobs(
jobs: Array<{ id: string; company: string; title: string }>,
): IndexedActiveJob[] {
return jobs.map((job, offset) => ({
index: offset + 1,
id: job.id,
company: sanitizeJobPromptValue(job.company || "Unknown company"),
title: sanitizeJobPromptValue(job.title || "Unknown title"),
}));
}
export function buildCompactActiveJobsList(jobs: IndexedActiveJob[]): string {
return jobs
.map((job) => `${job.index}. ${job.company}: ${job.title}`)
.join("\n");
}
export function normalizeBestMatchIndex(
value: unknown,
max: number,
): number | null {
if (value === null || value === undefined || max <= 0) return null;
const numeric =
typeof value === "number"
? value
: typeof value === "string"
? Number.parseInt(value, 10)
: Number.NaN;
if (!Number.isFinite(numeric)) return null;
const rounded = Math.round(numeric);
if (rounded < 1 || rounded > max) return null;
return rounded;
}
export async function classifyWithSmartRouter(args: {
emailText: string;
activeJobs: Array<{ id: string; company: string; title: string }>;
}): Promise<SmartRouterResult> {
const overrideModel = await getSetting("model");
const model =
overrideModel || process.env.MODEL || "google/gemini-3-flash-preview";
const llmEmailText = args.emailText.slice(0, ROUTER_EMAIL_CHAR_LIMIT);
const indexedActiveJobs = buildIndexedActiveJobs(args.activeJobs);
const compactActiveJobsList = buildCompactActiveJobsList(indexedActiveJobs);
const messages = [
{
role: "system" as const,
content:
"You are a smart router for post-application emails. Return only strict JSON. Ignore sensitive data and include only routing fields.",
},
{
role: "user" as const,
content: `Route this email to one active job if possible.
- Choose bestMatchIndex only from listed job numbers (1-based), or null.
- confidence is 0..100.
- stageTarget must be one of: ${POST_APPLICATION_ROUTER_STAGE_TARGETS.join("|")}.
- isRelevant should be true for recruitment/application lifecycle emails.
- stageEventPayload should be minimal structured data or null.
Active jobs (index. company: title):
${compactActiveJobsList}
Email:
${llmEmailText}`,
},
];
const llm = new LlmService();
const result = await llm.callJson<{
bestMatchIndex: number | null;
confidence: number;
stageTarget: string;
isRelevant: boolean;
stageEventPayload: Record<string, unknown> | null;
reason: string;
}>({
model,
messages,
jsonSchema: SMART_ROUTER_SCHEMA,
maxRetries: 1,
retryDelayMs: 400,
});
if (!result.success) {
throw new Error(`LLM classification failed: ${result.error}`);
}
const confidence = Math.max(
0,
Math.min(100, Math.round(Number(result.data.confidence) || 0)),
);
const bestMatchIndex = normalizeBestMatchIndex(
result.data.bestMatchIndex,
indexedActiveJobs.length,
);
const bestMatchId =
bestMatchIndex !== null
? (indexedActiveJobs[bestMatchIndex - 1]?.id ?? null)
: null;
const stageTarget =
normalizeStageTarget(result.data.stageTarget) ?? "no_change";
const messageType = messageTypeFromStageTarget(stageTarget);
return {
bestMatchId,
confidence,
stageTarget,
messageType,
isRelevant: Boolean(result.data.isRelevant),
stageEventPayload:
result.data.stageEventPayload &&
typeof result.data.stageEventPayload === "object"
? result.data.stageEventPayload
: null,
reason: String(result.data.reason ?? "").trim(),
};
}