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>
247 lines
8.3 KiB
TypeScript
247 lines
8.3 KiB
TypeScript
import { desc, eq } from 'drizzle-orm';
|
|
|
|
import type { NpOutreachDatabase, NpOutreachTransaction } from '#~/db/db.js';
|
|
import { schema } from '#~/db/db.js';
|
|
|
|
export interface MatchDetail {
|
|
match: {
|
|
id: string;
|
|
totalScore: number;
|
|
subscores: unknown;
|
|
rationale: unknown;
|
|
easyWin: boolean;
|
|
isHero: boolean;
|
|
fitViable: boolean;
|
|
judgeVerdict: 'strong_fit' | 'plausible' | 'weak' | 'mismatch' | null;
|
|
judgeRationale: string | null;
|
|
judgedAt: Date | null;
|
|
reviewStatus: 'pending' | 'approved' | 'rejected' | 'edited';
|
|
};
|
|
org: {
|
|
id: string;
|
|
name: string;
|
|
city: string | null;
|
|
state: string;
|
|
ein: string | null;
|
|
nteeCode: string | null;
|
|
totalRevenue: number | null;
|
|
registrationNumber: string | null;
|
|
missionStatement: string | null;
|
|
profileConfidence: number | null;
|
|
programs: unknown;
|
|
knownFunders: unknown;
|
|
staff: unknown;
|
|
serviceGeography: string | null;
|
|
profileSources: unknown;
|
|
};
|
|
grant: {
|
|
id: string;
|
|
title: string;
|
|
funder: string;
|
|
funderEin: string | null;
|
|
synopsis: string | null;
|
|
awardFloor: number | null;
|
|
awardCeiling: number | null;
|
|
closeDate: Date | null;
|
|
sourceUrl: string;
|
|
source: string;
|
|
eligibilityEntityTypes: string[] | null;
|
|
geographicScope: string | null;
|
|
applicationEffortEstimate: string;
|
|
};
|
|
/** Part XV application info from the funder's latest parsed 990-PF
|
|
* (form required, deadlines, restrictions, preselected-only flag). */
|
|
funderApplicationInfo: unknown;
|
|
/** For 990-PF matches: the funder's actual giving history into the
|
|
* org's state — the concrete evidence behind the precedent subscore. */
|
|
funderGivingHistory: Array<{
|
|
recipientName: string;
|
|
recipientCity: string | null;
|
|
amount: number | null;
|
|
purpose: string | null;
|
|
taxYear: number;
|
|
/** Federal evidence rows only: recipient name-matched a primary-ICP
|
|
* NH nonprofit (the peer-precedent basis). Null for 990-PF rows. */
|
|
isPeer: boolean | null;
|
|
}>;
|
|
}
|
|
|
|
export async function serverGetMatchDetail(
|
|
db: NpOutreachDatabase | NpOutreachTransaction,
|
|
matchId: string,
|
|
): Promise<MatchDetail | null> {
|
|
const rows = await db
|
|
.select({
|
|
matchId: schema.matches.id,
|
|
totalScore: schema.matches.totalScore,
|
|
subscores: schema.matches.subscores,
|
|
rationale: schema.matches.rationale,
|
|
easyWin: schema.matches.easyWin,
|
|
isHero: schema.matches.isHero,
|
|
fitViable: schema.matches.fitViable,
|
|
judgeVerdict: schema.matches.judgeVerdict,
|
|
judgeRationale: schema.matches.judgeRationale,
|
|
judgedAt: schema.matches.judgedAt,
|
|
reviewStatus: schema.matches.reviewStatus,
|
|
orgId: schema.orgs.id,
|
|
orgName: schema.orgs.name,
|
|
orgCity: schema.orgs.city,
|
|
orgState: schema.orgs.state,
|
|
orgEin: schema.orgs.ein,
|
|
orgNtee: schema.orgs.nteeCode,
|
|
orgRevenue: schema.orgs.totalRevenue,
|
|
orgRegNo: schema.orgs.registrationNumber,
|
|
grantId: schema.grants.id,
|
|
grantTitle: schema.grants.title,
|
|
grantFunder: schema.grants.funder,
|
|
grantFunderEin: schema.grants.funderEin,
|
|
grantSynopsis: schema.grants.synopsis,
|
|
grantFloor: schema.grants.awardFloor,
|
|
grantCeiling: schema.grants.awardCeiling,
|
|
grantClose: schema.grants.closeDate,
|
|
grantSourceUrl: schema.grants.sourceUrl,
|
|
grantSource: schema.grants.source,
|
|
grantEligibility: schema.grants.eligibilityEntityTypes,
|
|
grantGeo: schema.grants.geographicScope,
|
|
grantEffort: schema.grants.applicationEffortEstimate,
|
|
grantProgramAwards: schema.grants.programStateAwards,
|
|
})
|
|
.from(schema.matches)
|
|
.innerJoin(schema.orgs, eq(schema.matches.orgId, schema.orgs.id))
|
|
.innerJoin(schema.grants, eq(schema.matches.grantId, schema.grants.id))
|
|
.where(eq(schema.matches.id, matchId))
|
|
.limit(1);
|
|
|
|
const row = rows[0];
|
|
if (row == null) return null;
|
|
|
|
const profiles = await db
|
|
.select({
|
|
missionStatement: schema.orgProfiles.missionStatement,
|
|
confidence: schema.orgProfiles.confidence,
|
|
programs: schema.orgProfiles.programs,
|
|
knownFunders: schema.orgProfiles.knownFunders,
|
|
staff: schema.orgProfiles.staff,
|
|
serviceGeography: schema.orgProfiles.serviceGeography,
|
|
sources: schema.orgProfiles.sources,
|
|
})
|
|
.from(schema.orgProfiles)
|
|
.where(eq(schema.orgProfiles.orgId, row.orgId))
|
|
.limit(1);
|
|
|
|
let funderGivingHistory: MatchDetail['funderGivingHistory'] = [];
|
|
let funderApplicationInfo: unknown = null;
|
|
if (row.grantFunderEin == null && Array.isArray(row.grantProgramAwards)) {
|
|
// Federal grants: USASpending program awards fill the same evidence
|
|
// table (recipient/amount/year) the 990-PF history uses.
|
|
// Peers first (the actual precedent evidence), newest within each
|
|
// group; capped so a 500-award program doesn't flood the page.
|
|
funderGivingHistory = (
|
|
row.grantProgramAwards as Array<{
|
|
recipientName?: string;
|
|
amount?: number | null;
|
|
startDate?: string | null;
|
|
isPeer?: boolean;
|
|
}>
|
|
)
|
|
.map((a) => ({
|
|
recipientName: a.recipientName ?? 'Unknown recipient',
|
|
recipientCity: null,
|
|
amount: a.amount ?? null,
|
|
purpose: null,
|
|
taxYear: a.startDate != null ? Number(a.startDate.slice(0, 4)) : 0,
|
|
isPeer: a.isPeer ?? null,
|
|
}))
|
|
.sort((a, b) => {
|
|
const peerRank = Number(b.isPeer === true) - Number(a.isPeer === true);
|
|
return peerRank !== 0 ? peerRank : b.taxYear - a.taxYear;
|
|
})
|
|
.slice(0, 40);
|
|
}
|
|
if (row.grantFunderEin != null) {
|
|
const funderRow = await db
|
|
.select({ applicationInfo: schema.funders.applicationInfo })
|
|
.from(schema.funders)
|
|
.where(eq(schema.funders.ein, row.grantFunderEin))
|
|
.limit(1);
|
|
funderApplicationInfo = funderRow[0]?.applicationInfo ?? null;
|
|
const funderRows = await db
|
|
.select({
|
|
recipientName: schema.funderGrants.recipientName,
|
|
recipientCity: schema.funderGrants.recipientCity,
|
|
recipientState: schema.funderGrants.recipientState,
|
|
amount: schema.funderGrants.amount,
|
|
purpose: schema.funderGrants.purpose,
|
|
taxYear: schema.funderGrants.taxYear,
|
|
})
|
|
.from(schema.funderGrants)
|
|
.innerJoin(
|
|
schema.funders,
|
|
eq(schema.funderGrants.funderId, schema.funders.id),
|
|
)
|
|
.where(eq(schema.funders.ein, row.grantFunderEin))
|
|
.orderBy(desc(schema.funderGrants.taxYear), desc(schema.funderGrants.amount))
|
|
.limit(200);
|
|
|
|
// In-state grants first (the precedent evidence), then the rest.
|
|
funderGivingHistory = [...funderRows]
|
|
.sort((a, b) => {
|
|
const aIn = a.recipientState === row.orgState ? 0 : 1;
|
|
const bIn = b.recipientState === row.orgState ? 0 : 1;
|
|
return aIn - bIn;
|
|
})
|
|
.slice(0, 40)
|
|
.map(({ recipientState: _s, ...rest }) => ({ ...rest, isPeer: null }));
|
|
}
|
|
|
|
return {
|
|
match: {
|
|
id: row.matchId,
|
|
totalScore: row.totalScore,
|
|
subscores: row.subscores,
|
|
rationale: row.rationale,
|
|
easyWin: row.easyWin,
|
|
isHero: row.isHero,
|
|
fitViable: row.fitViable,
|
|
judgeVerdict: row.judgeVerdict,
|
|
judgeRationale: row.judgeRationale,
|
|
judgedAt: row.judgedAt,
|
|
reviewStatus: row.reviewStatus,
|
|
},
|
|
org: {
|
|
id: row.orgId,
|
|
name: row.orgName,
|
|
city: row.orgCity,
|
|
state: row.orgState,
|
|
ein: row.orgEin,
|
|
nteeCode: row.orgNtee,
|
|
totalRevenue: row.orgRevenue,
|
|
registrationNumber: row.orgRegNo,
|
|
missionStatement: profiles[0]?.missionStatement ?? null,
|
|
profileConfidence: profiles[0]?.confidence ?? null,
|
|
programs: profiles[0]?.programs ?? null,
|
|
knownFunders: profiles[0]?.knownFunders ?? null,
|
|
staff: profiles[0]?.staff ?? null,
|
|
serviceGeography: profiles[0]?.serviceGeography ?? null,
|
|
profileSources: profiles[0]?.sources ?? null,
|
|
},
|
|
grant: {
|
|
id: row.grantId,
|
|
title: row.grantTitle,
|
|
funder: row.grantFunder,
|
|
funderEin: row.grantFunderEin,
|
|
synopsis: row.grantSynopsis,
|
|
awardFloor: row.grantFloor,
|
|
awardCeiling: row.grantCeiling,
|
|
closeDate: row.grantClose,
|
|
sourceUrl: row.grantSourceUrl,
|
|
source: row.grantSource,
|
|
eligibilityEntityTypes: row.grantEligibility,
|
|
geographicScope: row.grantGeo,
|
|
applicationEffortEstimate: row.grantEffort,
|
|
},
|
|
funderApplicationInfo,
|
|
funderGivingHistory,
|
|
};
|
|
}
|