# @coda/search

Semantic search microservice for the Abacus royalties platform. Provides hybrid search (BM25 + HNSW vector + glossary) over two backends:

- **GraphQL schema** — introspects the federated gateway for query fields and types
- **Snowflake metadata** — queries ACCOUNT_USAGE for tables, columns, and inferred FK relationships

For architecture details, search pipeline, and algorithm internals, see [Search Architecture](../../docs/architecture/search.md).

## Quick start

```bash
# From repo root
cp apps/search/.env.shadow apps/search/.env
pnpm dev:search        # http://localhost:8081
```

## Key files

- `src/index.ts` — entry point (server startup, engine registration, graceful shutdown)
- `src/server.ts` — ConnectRPC + Express app factory
- `src/engine/` — `IndexEngine<TDoc>` lifecycle manager, strategies (`GraphQLStrategy`, `SnowflakeStrategy`)
- `src/handlers/` — ConnectRPC handler implementations (search, describe, admin)
- `src/glossaries/` — domain glossary JSON files for query expansion
- `src/snapshots/` — S3 snapshot persistence and retention
- `.env.shadow` — env template with all configuration variables

## Testing

```bash
cd apps/search
pnpm test:unit                      # unit tests
npx jest --no-coverage              # fast run without coverage
npx jest --testPathPattern <name>   # run specific test
```

### Quality benchmark

The search library (`@coda/common/search`) includes an offline quality benchmark that validates NDCG@10, MRR, and Recall@10 against regression thresholds. It runs in keyword + glossary mode (no embedding model required), executes in < 0.5s, and is part of the standard test suite.

```bash
cd packages/common
npx jest --testPathPattern=benchmark   # run quality benchmark only
```

## Document Processing

SearchEngine consumes raw items from a SchemaFetcher and processes
them through four interfaces. Only DocumentTransformer is required.

| Interface                       | Required | Purpose                                | Lifecycle          |
| ------------------------------- | -------- | -------------------------------------- | ------------------ |
| DocumentTransformer<TRaw, TDoc> | Yes      | Transform, identify, project, tokenize | init, poll, search |
| GraphBuilder<TDoc, TContext>    | No       | Build/update relationship graph        | init, poll         |
| GlossaryProvider                | No       | Domain-specific query expansion terms  | init, poll         |

### Adding a New Domain

1. Implement DocumentTransformer (required)
2. Implement GraphBuilder if your domain has entity relationships
3. Implement GlossaryProvider if you have curated search terms
4. Provide a `Filter` if only a subset of fetched items should be indexed
5. Create a SchemaFetcher to supply raw items
6. Register in engine-factory.ts: buildEngines()

### Type Safety

SearchEngineConfig<TRaw, TDoc, TContext> enforces that the fetcher's
TContext matches the graph builder's TContext at compile time.

| Domain    | TRaw                | TDoc                | TContext   |
| --------- | ------------------- | ------------------- | ---------- |
| GraphQL   | QueryFieldEntry     | QueryFieldEntry     | TypeEdge[] |
| Snowflake | SnowflakeTableEntry | SnowflakeTableEntry | undefined  |

## Health checks

- `GET /health` — always 200
- `GET /health/ready` — 200 when all configured indexes are ready, 503 otherwise
