/** * Judge pass 2 for products (M12a): resolve only the rows pass 1 could not. * * Same contract as M7's judge pass (`core/scoring/judge.ts`) with one deliberate * difference: the rate cap is 50%, not 30%. Product names are messier than brand * names - single-token names, model numbers, engines that recommend in prose - * so more rows land ambiguous and the budget to resolve them is larger. What is * actually spent is reported in the run's sampling metadata (rule #6). * * Hitting either bound (the rate cap or the cost cap) stops the pass without * throwing; unjudged rows simply stay as pass 1 read them. */ import { CostGuard } from '../costs.js'; import type { EngineAnswer, JudgeClient, Sentiment } from '../types.js'; import { type ProductMention } from './detect.js'; /** * Fraction of product rows that may receive a judge call. Higher than the brand * check's {@link JUDGE_RATE_CAP} for the reason above; the cost guard still * bounds the money regardless of this number. */ export declare const SHOPPING_JUDGE_RATE_CAP = 0.5; export interface RefineProductsDeps { judge: JudgeClient; guard: CostGuard; /** Projected per-call cost for authorization; defaults to a per-model estimate. */ projectedCostUsd?: number; } export interface RefineProductsOptions { /** Max share of rows judged (default {@link SHOPPING_JUDGE_RATE_CAP}). */ judgeRateCap?: number; } export interface RefineProductsResult { results: ProductMention[]; /** Rows that actually received a judge call (what the run paid for). */ judged: number; } /** What the judge decided about one product in one answer. */ export interface ProductVerdict { mentioned: boolean; /** 1-based rank on the answer's list, or null when it named no order. */ position: number | null; sentiment?: Sentiment; /** The products the answer recommends, in order, as the judge read them. */ recommended?: string[]; } /** * Parse the judge's JSON verdict, or `null` when the output is not a verdict at * all. * * `null` matters: unlike the brand judge, a product verdict must not default to * "not mentioned" on unparseable output. Pass 1 already found a real term match * for most of these rows, and silently overwriting it with `false` would delete * a mention the answer genuinely contains. */ export declare function parseProductVerdict(text: string): ProductVerdict | null; /** * Re-judge the ambiguous product rows (up to the rate cap and the cost cap), * returning a new results array plus how many rows were judged. * * `results[i]` must describe `answers[i]` (one row per product/answer pair). */ export declare function refineProductMentions(results: ProductMention[], answers: EngineAnswer[], deps: RefineProductsDeps, opts?: RefineProductsOptions): Promise;