Files
grant-outreach-engine/packages/outreach-core/src/matches/queries/get-match-detail.server.ts
Croissant Le Doux 4f35dcdb8d feat(profiler): Stage 4 grounded research profiler — cited profiles replace NTEE stubs
profileOrgs (nightly 05:45, 25/run, easy-win orgs best-first per the
plan's coarse-match-gates-profiling rule): two-pass Gemini flash —
Google Search-grounded research (groundingMetadata = citation universe)
then JSON-schema extraction citing only from it. Overwrites the stub
profile (mission/programs/geography/funders/staff/news/sources/
confidence); re-embeds mission fit from researched text unless identity
unconfirmed or confidence <0.5 (then the NTEE embedding stays —
unverified research must not steer scoring). Detail page renders
researched programs w/ source links, funders, staff.

Fixes: org_profiles.confidence is float4 — 0.2 stores as 0.20000000298
so 'confidence <= 0.2' excluded every stub (epsilon comparison); RR7
loader serialization turns unknown into never (hoisted casts).

Live: 55 orgs profiled (41 research-grade, 2 low-confidence, 0 failed).
Scoring effect verified both directions: Annie's Angels x NIH mammalian
models fit 19->12; Seacoast Pathways x Foundation for Seacoast Health
fit 25/30 — and the researched knownFunders independently names that
funder. 161 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 00:08:12 -04:00

222 lines
7.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;
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;
}>;
}
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,
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.
funderGivingHistory = (
row.grantProgramAwards as Array<{
recipientName?: string;
amount?: number | null;
startDate?: string | null;
}>
).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,
}));
}
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);
}
return {
match: {
id: row.matchId,
totalScore: row.totalScore,
subscores: row.subscores,
rationale: row.rationale,
easyWin: row.easyWin,
isHero: row.isHero,
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,
};
}