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:
@@ -63,6 +63,17 @@ export default function MatchDetail({ loaderData }: Route.ComponentProps) {
|
||||
restrictionsOnAwards?: string;
|
||||
} | null;
|
||||
const subscores = (match.subscores ?? {}) as Record<string, number>;
|
||||
const orgPrograms = (org.programs ?? null) as Array<{
|
||||
name: string;
|
||||
description?: string | null;
|
||||
populationServed?: string | null;
|
||||
sourceUrl?: string | null;
|
||||
}> | null;
|
||||
const orgFunders = (org.knownFunders ?? null) as Array<{ name: string }> | null;
|
||||
const orgStaff = (org.staff ?? null) as Array<{
|
||||
name: string;
|
||||
role?: string | null;
|
||||
}> | null;
|
||||
const rationale = (match.rationale ?? {}) as Record<string, unknown>;
|
||||
const nhHistory = funderGivingHistory;
|
||||
|
||||
@@ -143,6 +154,43 @@ export default function MatchDetail({ loaderData }: Route.ComponentProps) {
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
{orgPrograms != null && orgPrograms.length > 0 && (
|
||||
<div className="mt-3 text-sm">
|
||||
<h3 className="font-medium">Programs (researched)</h3>
|
||||
<ul className="ml-4 list-disc">
|
||||
{orgPrograms.map(
|
||||
(p, i) => (
|
||||
<li key={i}>
|
||||
{p.name}
|
||||
{p.populationServed != null && ` — serving ${p.populationServed}`}
|
||||
{p.sourceUrl != null && (
|
||||
<>
|
||||
{' '}
|
||||
<a className="text-blue-700 underline" href={p.sourceUrl} target="_blank" rel="noreferrer">
|
||||
src
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{orgFunders != null && orgFunders.length > 0 && (
|
||||
<p className="mt-2 text-sm">
|
||||
<span className="font-medium">Known funders: </span>
|
||||
{orgFunders.map((f) => f.name).join(', ')}
|
||||
</p>
|
||||
)}
|
||||
{orgStaff != null && orgStaff.length > 0 && (
|
||||
<p className="mt-2 text-sm">
|
||||
<span className="font-medium">Staff: </span>
|
||||
{orgStaff
|
||||
.map((s) => (s.role != null ? `${s.name} (${s.role})` : s.name))
|
||||
.join(', ')}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-3 space-x-3 text-sm">
|
||||
{org.ein != null && (
|
||||
<a
|
||||
|
||||
Reference in New Issue
Block a user