// Multi-objective combination as data — spaCy's `default_score_weights`. // // A weight map, not a hand-composed closure, so the combination serializes // into a run config. `null` means "compute and report, but don't optimize // on it" — how you watch a tradeoff before committing to it. import type { Schedule, Scorer } from "./types.ts"; export interface WeightedEntry { readonly scorer: Scorer; /** Contribution multiplier; null = reported but unweighted. */ readonly weight: number | null; } /** Combine scorers into one minimizing objective. `max`-direction scorers * contribute negated, so a higher raw value still improves the combined * score. Entries with null/zero weight are excluded from the objective * (use `scoreReport` to still see their values). */ export function weightedScorer( entries: readonly WeightedEntry[], name = "objective", ): Scorer { const active = entries.filter( (e): e is WeightedEntry & { weight: number } => e.weight !== null && e.weight !== 0, ); return { name, direction: "min", score(schedule: Schedule): number { let total = 0; for (const e of active) { const sign = e.scorer.direction === "min" ? 1 : -1; total += sign * e.weight * e.scorer.score(schedule); } return total; }, }; } /** Every scorer's raw value for a schedule, keyed by scorer name — the * reported-but-unweighted view that rides along with the objective. */ export function scoreReport( schedule: Schedule, scorers: readonly Scorer[], ): Record { const report: Record = {}; for (const scorer of scorers) report[scorer.name] = scorer.score(schedule); return report; }