Files
grant-outreach-engine/packages/outreach-core/README.md
Croissant Le Doux 14200edb60 feat: scaffold outreach engine monorepo on the novelpad-desktop stack
Workspaces: config (copied), outreach-core (schema + actions/queries +
hard gates), outreach-ai (Gemini client + embeddings copies, profiler and
mission-fit-judge agent stubs), outreach-worker (DBOS executor with
nightly ingest + hourly expiry workflows), outreach-review (RR7 review
queue v0). Initial drizzle migration incl. pgvector extension.

Stack contract: Yarn 4.5.0 + Turbo, Node 22.16, Drizzle 0.44.6 +
pgvector, DBOS 4.17.6, @google/genai on Vertex, gemini-embedding-001
@1536, React Router v7. Files copied from novelpad-desktop carry
provenance headers @ 62c56b87.

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

55 lines
3.3 KiB
Markdown

# @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](https://gitea.stephenmann.io/NovelPad/novelpad-desktop),
following the same [action/query pattern](https://gitea.stephenmann.io/NovelPad/novelpad-desktop/src/branch/master/docs/features/database-action-query-pattern.md):
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 on `sourceUrl`; an hourly sweep expires anything past its close date.
- `orgs/` — NH nonprofits, plus `org_profiles` (LLM/agent-researched enrichment: mission, programs, known funders, budget band, citation sources).
- `matches/` — a scored `(org, grant)` pair. `hard-gates.ts` is 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`/`NpOutreachTransaction` in `src/db/db.ts`),
not the client/server pair (`NpClientDatabase` vs `NpDatabase`) `@novelpad/core`
needs to support both a local and a remote copy of the same data.
- **No `novelpad_core_dev` export condition.** `@novelpad/core`'s `package.json`
uses a custom `novelpad_core_dev` condition so dev builds resolve straight
to `src/*.ts` without a build step. This package's `exports` map only has
plain `types`/`default` conditions — run `yarn build` (or `yarn dev` for a
watch build) before consuming it from a sibling workspace in dev.
- **No `build:migration-files` bundling step.** `@novelpad/core`'s
`create:migration` also 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, so `bin/create-migration` only runs
`drizzle-kit generate` against the single server config.
- **Root `index.ts` is intentionally small.** It exports only the schema,
DB types, and the pure `hard-gates` predicate — not domain client barrels,
since every domain here is server-only. Everything else lives behind
`index.server.ts` / the `./server` export 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
```