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>
222 lines
7.3 KiB
TypeScript
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,
|
|
};
|
|
}
|