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>
@novelpad/outreach-review
Human review queue for (org, grant) matches produced by the scoring pipeline in packages/outreach-core. A reviewer sees each pending match — organization, hero grant, total score, easy-win flag — and approves or rejects it. Approved matches are picked up downstream and synced into Apollo sequences.
Auth
There is no authentication in this app, by design. It is a tiny internal tool intended to run behind:
- Google IAP (Identity-Aware Proxy) in any hosted environment, or
localhostonly, for local development
Do not add better-auth (or any other auth library) to this workspace. If this app ever needs to leave an IAP-fronted network, that's a deliberate follow-up decision, not something to bolt on ad hoc here.
Development
yarn dev # react-router dev
yarn build # react-router build
yarn start # serve the production build
yarn typecheck # react-router typegen && tsc --noEmit
Requires DATABASE_URL pointing at the outreach Postgres instance (see packages/outreach-core). Optional: PG_POOL_MAX, PG_CONNECTION_TIMEOUT_MS.
Data contract
This app is a thin consumer of @novelpad/outreach-core. It assumes the following exports (server-side query/action functions live under the ./server subpath, matching the packages/core convention in novelpad-desktop):
// @novelpad/outreach-core
export const schema: /* drizzle schema */;
export type NpOutreachDatabase = /* PgDatabase<..., typeof schema> */;
export interface PendingReviewMatch {
id: string;
orgName: string;
heroGrantTitle: string;
totalScore: number;
isEasyWin: boolean;
}
export type MatchReviewDecision = 'approved' | 'rejected';
// @novelpad/outreach-core/server
export function serverListPendingReviewMatches(
db: NpOutreachDatabase,
): Promise<PendingReviewMatch[]>;
export function serverSetMatchReview(
db: NpOutreachDatabase,
matchId: string,
decision: MatchReviewDecision,
): Promise<void>;
If packages/outreach-core's matches domain (src/matches/queries, src/matches/actions) lands with different names or a different PendingReviewMatch shape, update app/db.server.ts and app/routes/_index.tsx to match rather than reshaping data client-side.