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>
This commit is contained in:
Croissant Le Doux
2026-07-17 00:08:12 -04:00
parent 88affbeb4f
commit 4f35dcdb8d
14 changed files with 765 additions and 0 deletions

View File

@@ -24,6 +24,11 @@ export interface MatchDetail {
registrationNumber: string | null;
missionStatement: string | null;
profileConfidence: number | null;
programs: unknown;
knownFunders: unknown;
staff: unknown;
serviceGeography: string | null;
profileSources: unknown;
};
grant: {
id: string;
@@ -103,6 +108,11 @@ export async function serverGetMatchDetail(
.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))
@@ -184,6 +194,11 @@ export async function serverGetMatchDetail(
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,