feat(scoring): v4 — peer precedent, mission-fit floor, LLM match judge
Fixes the federal mismatch class (boys' camp × NIH research center): - Peer precedent: federalPrecedent paginates USASpending (≤500 awards/ program) and name-matches every recipient against primary-ICP NH registry orgs (shared normalizeOrgNameForMatching, also used by the self-match gate). The 25-pt precedent tiers now key off program_state_peer_award_count — Dartmouth renewals and SBIR LLCs no longer grant precedent to community nonprofits. Raw count + peer- annotated award list stay as review evidence (peer badges, peers-first). - Mission-fit floor (12/30, grants_gov only): below it a match is stored with fit_viable=false and hidden from the pending queue, hero selection, and easy-win. Foundation-synthesized grants exempt (generic synopses). - Mission-fit judge live (judgeMatches, 06:15, 200/night best-first): JUDGE_MODEL reads the synopsis against the org profile with an explicit ignore-eligibility-breadth instruction; graded verdict with required citations; deterministic verdict→points map (27/18/8/0) sets missionFit, total, easy-win, and viability. Verdicts survive nightly re-scores via an upsert splice and re-enter the judge queue when the org profile is re-researched (org_profiles.updated_at). First sweep: 81/149 programs have NH history, only 6 have peer history; queue-head judging zeroes the research-mechanism garbage (mismatch) while surfacing genuine strong fits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,13 @@ const SUBSCORE_MAX: Record<string, number> = {
|
||||
runway: 5,
|
||||
};
|
||||
|
||||
const JUDGE_VERDICT_STYLE: Record<string, string> = {
|
||||
strong_fit: 'bg-green-100 text-green-800',
|
||||
plausible: 'bg-blue-100 text-blue-800',
|
||||
weak: 'bg-amber-100 text-amber-800',
|
||||
mismatch: 'bg-red-100 text-red-800',
|
||||
};
|
||||
|
||||
export default function MatchDetail({ loaderData }: Route.ComponentProps) {
|
||||
const { detail } = loaderData ?? {};
|
||||
if (detail == null) return null;
|
||||
@@ -95,6 +102,12 @@ export default function MatchDetail({ loaderData }: Route.ComponentProps) {
|
||||
{match.easyWin ? ' · Easy win' : ''}
|
||||
{match.isHero ? ' · Hero' : ''} · Status: {match.reviewStatus}
|
||||
</p>
|
||||
{!match.fitViable && (
|
||||
<p className="mt-1 text-sm font-medium text-red-700">
|
||||
Hidden from the pending queue — mission fit below floor or
|
||||
judged non-viable.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex shrink-0 gap-2">
|
||||
<Form method="post">
|
||||
@@ -289,6 +302,26 @@ export default function MatchDetail({ loaderData }: Route.ComponentProps) {
|
||||
)}
|
||||
</section>
|
||||
|
||||
{match.judgeVerdict != null && (
|
||||
<section className="rounded border p-4">
|
||||
<h2 className="mb-2 text-lg font-semibold">
|
||||
Mission-fit judge{' '}
|
||||
<span
|
||||
className={`ml-1 rounded px-2 py-0.5 text-sm font-medium ${JUDGE_VERDICT_STYLE[match.judgeVerdict] ?? ''}`}
|
||||
>
|
||||
{match.judgeVerdict.replace('_', ' ')}
|
||||
</span>
|
||||
</h2>
|
||||
<p className="whitespace-pre-wrap text-sm">{match.judgeRationale}</p>
|
||||
{match.judgedAt != null && (
|
||||
<p className="mt-2 text-xs text-gray-500">
|
||||
Judged {new Date(match.judgedAt).toLocaleDateString('en-US')} —
|
||||
the verdict replaces the embedding mission-fit score above.
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{applyInfo != null && (
|
||||
<section className="rounded border p-4">
|
||||
<h2 className="mb-2 text-lg font-semibold">
|
||||
@@ -346,7 +379,7 @@ export default function MatchDetail({ loaderData }: Route.ComponentProps) {
|
||||
The concrete evidence behind the precedent score —{' '}
|
||||
{grant.source === 'irs_990pf'
|
||||
? 'who this funder actually paid, sorted in-state first.'
|
||||
: 'who this federal program actually funded in NH recently. Watch for one incumbent recapturing renewals vs. genuine spread across orgs.'}
|
||||
: 'who this federal program actually funded in NH recently, peers first. Only "peer" recipients (registered NH nonprofits our size) count toward the precedent score — universities, hospital systems, and companies do not.'}
|
||||
</p>
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
@@ -361,7 +394,14 @@ export default function MatchDetail({ loaderData }: Route.ComponentProps) {
|
||||
<tbody>
|
||||
{nhHistory.map((g, i) => (
|
||||
<tr key={i} className="border-b align-top">
|
||||
<td className="py-1 pr-2">{g.recipientName}</td>
|
||||
<td className="py-1 pr-2">
|
||||
{g.recipientName}
|
||||
{g.isPeer === true && (
|
||||
<span className="ml-1 rounded bg-green-100 px-1 text-xs font-medium text-green-800">
|
||||
peer
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-1 pr-2">{g.recipientCity ?? '—'}</td>
|
||||
<td className="py-1 pr-2 text-right tabular-nums">
|
||||
{dollars(g.amount)}
|
||||
|
||||
Reference in New Issue
Block a user