sugiv's picture
Call Gate: corpus, Jev evidence, risk score v1
46d5e00 verified
Raw History Blame Contribute Delete
5.95 kB
// Jev evidence battery (docs/ARCHITECTURE.md §3.3). State is the transcript alone; call metadata stays in code.
export const JEV_CHAMPION = 'typesafe/jev-1.13';
export const JEV_CHALLENGER = 'typesafe/jev-1.14';
const ENDPOINT = 'https://openrouter.ai/api/v1/systemone';
const PROTECTED = ['financial', 'healthcare', 'education'] as const;
export const QUESTIONS = {
claims_sector: {
type: 'choice',
instructions:
'What kind of organization does the caller claim to be from, and what relationship does the message imply with the listener?',
criteria: {
financial: 'A bank, card issuer or credit union contacting the listener about an account or card they already have',
healthcare: 'A pharmacy, doctor, hospital or health insurer contacting the listener about their own care or prescription',
education: 'A school or school district contacting families',
solicitation: 'A lender, dealer or seller offering a new loan, credit, rate, warranty, insurance or other deal',
government: 'Government agency such as IRS, Social Security, police',
utility_telecom: 'Utility, phone or internet provider',
retail_delivery: 'Retailer or delivery company such as Amazon, UPS',
other: 'Some other organization',
none: 'No organization is identified',
},
},
specific_detail: {
type: 'noul',
instructions:
"Does the message contain concrete specifics a genuine organization would know, such as card or account last 4 digits, an exact amount, a merchant, a date, or the recipient's name?",
},
official_channel: {
type: 'noul',
instructions:
'Does the message tell the listener to use an official channel they already have, such as the number on the back of their card, the official app, or the website?',
},
pressure: {
type: 'noul',
instructions: 'Does the message use threats or urgency, such as arrest, lawsuit, account suspension, or a deadline?',
},
agent_or_credentials: {
type: 'noul',
instructions:
'Does the message push the listener to press a key to reach a live agent, or to give or verify personal, account or payment details?',
},
sales_pitch: {
type: 'noul',
instructions: 'Is the message offering a product, loan, rate, warranty, insurance or other deal?',
},
self_assertion: {
type: 'noul',
instructions: "The message itself claims to be legitimate, verified, official, or 'not a scam'.",
},
category: {
type: 'choice',
instructions: 'What kind of automated voicemail is this?',
criteria: {
legit_alert:
'Genuine service alert from an organization the recipient does business with (fraud alert, prescription, appointment, school notice)',
scam: 'Fraud, impersonation or pretext to extract money or credentials',
marketing_robocall: 'Unsolicited sales, loan, warranty or other marketing pitch',
unsure: 'Too short or unclear to tell',
},
},
} as const;
export interface Evidence {
/** "Which known script?" answer: the matched campaign id and its probability, or null for none / no question asked. */
knownScript?: { id: number; p: number } | null;
sector: string;
protected: number;
specific: number;
official: number;
pressure: number;
agent: number;
sales: number;
selfAssert: number;
catLegit: number;
catScam: number;
catMkt: number;
}
export interface JevResult {
evidence: Evidence;
model: string;
raw: unknown;
}
type Answers = Record<string, { choice?: string; probabilities?: Record<string, number>; noul?: number }>;
export interface KnownScript {
id: number;
sample: string;
}
const NONE = 'none';
/** The live battery plus, when junk scripts are known, one choice question listing them (docs/ARCHITECTURE.md §3.2). */
export function buildQuestions(known: KnownScript[]) {
if (!known.length) return QUESTIONS;
const criteria: Record<string, string> = Object.fromEntries(known.map((k) => [`s${k.id}`, `Same campaign as: "${k.sample.slice(0, 300)}"`]));
criteria[NONE] = 'A different message that is not one of the scripts above';
return {
...QUESTIONS,
known_script: {
type: 'choice',
instructions:
'Is this voicemail a new recording of one of these known robocall scripts (same caller campaign, same offer or pretext, possibly reworded), or something different?',
criteria,
},
};
}
export function toEvidence(answers: Answers): Evidence {
const sector = answers.claims_sector.probabilities!;
const cat = answers.category.probabilities!;
const ks = answers.known_script;
const knownScript =
ks && ks.choice && ks.choice !== NONE ? { id: Number(ks.choice.slice(1)), p: ks.probabilities![ks.choice] ?? 0 } : null;
return {
knownScript,
sector: answers.claims_sector.choice!,
protected: PROTECTED.reduce((sum, k) => sum + (sector[k] ?? 0), 0),
specific: answers.specific_detail.noul!,
official: answers.official_channel.noul!,
pressure: answers.pressure.noul!,
agent: answers.agent_or_credentials.noul!,
sales: answers.sales_pitch.noul!,
selfAssert: answers.self_assertion.noul!,
catLegit: cat.legit_alert ?? 0,
catScam: cat.scam ?? 0,
catMkt: cat.marketing_robocall ?? 0,
};
}
export async function askJev(transcript: string, apiKey: string, model = JEV_CHAMPION, known: KnownScript[] = []): Promise<JevResult> {
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model, state: transcript, questions: buildQuestions(known) }),
signal: AbortSignal.timeout(15_000),
});
if (!res.ok) throw new Error(`jev ${model} ${res.status}: ${(await res.text()).slice(0, 200)}`);
const body = (await res.json()) as { model: string; answers: Answers };
return { evidence: toEvidence(body.answers), model: body.model, raw: body.answers };
}