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

@@ -44,6 +44,10 @@ import {
runIngestNhdojOrgsNow,
setIngestNhdojOrgsDeps,
} from './workflows/ingest-nhdoj-orgs.js';
import {
runJudgeMatchesNow,
setJudgeMatchesDeps,
} from './workflows/judge-matches.js';
import {
runMatchGrantsNow,
setMatchGrantsDeps,
@@ -68,6 +72,7 @@ const RUNNERS: Record<string, () => Promise<void>> = {
ingest990pf: runIngest990PfNow,
federalPrecedent: runFederalPrecedentNow,
profileOrgs: runProfileOrgsNow,
judgeMatches: runJudgeMatchesNow,
};
const FIRST_RUN_ORDER = [
@@ -81,6 +86,7 @@ const FIRST_RUN_ORDER = [
'embedGrants',
'matchGrants',
'profileOrgs',
'judgeMatches',
];
if (process.env.DATABASE_URL == null) {
@@ -120,6 +126,7 @@ async function main() {
setIngest990pfDeps({ db });
setFederalPrecedentDeps({ db });
setProfileOrgsDeps({ db });
setJudgeMatchesDeps({ db });
DBOS.setConfig({
name: 'helmdocs-outreach-worker',