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>
@novelpad/outreach-core
Domain/data package for the HelmDocs grant-match outreach engine: the Drizzle schema for grants, orgs, org profiles, contacts, matches, and pipeline events, plus the actions/queries that read and write them.
Modeled on packages/core in novelpad-desktop,
following the same action/query pattern:
every read lives in a domain's queries/, every write in its actions/,
both barrel-exported, and route handlers/workflows/hooks never touch
db.select()/db.insert() directly.
Domains
grants/— open grant opportunities (Grants.gov, NH state, 990-PF, PND RSS, Candid). Ingestion upserts onsourceUrl; an hourly sweep expires anything past its close date.orgs/— NH nonprofits, plusorg_profiles(LLM/agent-researched enrichment: mission, programs, known funders, budget band, citation sources).matches/— a scored(org, grant)pair.hard-gates.tsis the deterministic, DB-free pass/fail predicate (entity eligibility, geography, ≥21-day runway, ≥$10k ceiling, application-form support) that runs before the weighted LLM subscoring pass — see its module doc for why it's kept pure.pipeline/— outreach lifecycle events (enrolled→sent→opened/replied/... →demo_booked→converted), synced from Apollo and recorded internally.
Deliberate deviations from @novelpad/core
This package is server-only — there is no client app, so several things
@novelpad/core does for its PGlite/OPFS client don't apply here:
- No PGlite, no client schema, no sync. There's exactly one database
handle type (
NpOutreachDatabase/NpOutreachTransactioninsrc/db/db.ts), not the client/server pair (NpClientDatabasevsNpDatabase)@novelpad/coreneeds to support both a local and a remote copy of the same data. - No
novelpad_core_devexport condition.@novelpad/core'spackage.jsonuses a customnovelpad_core_devcondition so dev builds resolve straight tosrc/*.tswithout a build step. This package'sexportsmap only has plaintypes/defaultconditions — runyarn build(oryarn devfor a watch build) before consuming it from a sibling workspace in dev. - No
build:migration-filesbundling step.@novelpad/core'screate:migrationalso runs a script that bundles generated SQL into a TS module the client can import for its OPFS-based local migrator. There is no local migrator here, sobin/create-migrationonly runsdrizzle-kit generateagainst the single server config. - Root
index.tsis intentionally small. It exports only the schema, DB types, and the purehard-gatespredicate — not domain client barrels, since every domain here is server-only. Everything else lives behindindex.server.ts/ the./serverexport condition.
Scripts
yarn build # tsc
yarn dev # tsc --watch
yarn typecheck # tsc --noEmit
yarn test # vitest run
yarn create:migration <name> # drizzle-kit generate against drizzle.config.ts
yarn migrate # drizzle-kit migrate
yarn studio # drizzle-kit studio