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:
Croissant Le Doux
2026-07-17 10:52:50 -04:00
parent 7cefbbbfa1
commit cbc4512ffa
37 changed files with 4379 additions and 163 deletions

View File

@@ -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)}