# Get Rid Of — Integration Checklist

Artifact date: rendered at build time by the About page. See the live surface at `/get-rid-of/about#integration`.

This checklist is the DSNY IT / 311 integration reference for embedding, deploying, or extending the Get Rid Of product. Every section maps 1:1 to a section that renders on the About page under `#integration`.

---

## 1. Auth boundary

- Public read paths are open (no token): typeahead, category browse, near-me, freshness, feedback submission.
- The chat / answer path (`POST /api/get-rid-of/ask`) is gated by the `NYC_ACCESS_TOKEN` environment variable in production.
- Requests must present `x-nyc-access-token: <token>` header when the env var is set. Missing / mismatched token returns `401 { error: "missing_token" }`. The model is never called.
- When `NYC_ACCESS_TOKEN` is not set (dev convenience), the endpoint is open to allow the pipeline to run locally.
- Rate limiting is enforced per session hash (10 answer requests / minute; 60 / day). Rate limit state is persisted in `nyc.get_rid_of_rate_limit_bucket` using an UPSERT on the window key. Rate-limited requests do NOT write to `nyc.get_rid_of_query_log`.

## 2. Hosting assumptions

- Runtime: Next.js 16.x App Router deployed as node runtime routes. All API routes under `src/app/api/get-rid-of/*` use `export const runtime = "nodejs"` and `export const dynamic = "force-dynamic"`.
- Data plane: Neon Postgres 16 with the `pgvector` extension enabled. All product tables live in the `nyc` schema under the `get_rid_of_*` prefix.
- Connection strings: `DATABASE_URL` (pooled, used by app runtime) and `DATABASE_URL_UNPOOLED` (unpooled, used by ingest scripts). Never share either with the browser.
- Static assets served from Next.js. Map tiles: MapLibre GL with an open-source raster style, no API key.
- Address autocomplete: `geosearch.planninglabs.nyc/v2/autocomplete` via a same-origin proxy at `/api/get-rid-of/geosearch`. No key required. The raw address is not persisted anywhere.

## 3. Data-flow narrative

1. Ingest jobs (`npm run db:ingest:get-rid-of:*`) fetch DSNY pages, the DSNY search-function.csv, and three Socrata datasets (`gkgs-za6m`, `242c-ru4i`, `if26-z6xq`) into Neon. Each job records a row in `nyc.get_rid_of_ingest_runs`.
2. The corpus embed job chunks each page's cleaned Markdown and calls Voyage `voyage-3-lite` (512 dim) to store per-chunk embeddings in `nyc.get_rid_of_embeddings`.
3. At request time, `/api/get-rid-of/ask` embeds the user question with the same model, retrieves the top-K chunks from the current corpus version via pgvector cosine, gates on similarity thresholds, and calls Anthropic `claude-sonnet-4-6` with the retrieved chunks as sources.
4. Answers must cite provided sources; ungrounded answers are rewritten to `not_covered` before reaching the client.
5. Every completed answered / not-covered / source-error request logs one row to `nyc.get_rid_of_query_log`. Feedback events land in `nyc.get_rid_of_feedback_events`.

## 4. Security controls

- No PII is collected. Session ID is client-generated, hashed to 32 hex chars server-side.
- Query text is capped at 200 chars and scrubbed for phone/email patterns before persistence.
- Feedback notes are capped at 500 chars and scrubbed for phone/email patterns before persistence.
- Latitude and longitude are validated against the NYC bounding box; out-of-bounds requests return 400.
- IP is only used opportunistically for session hashing when no client session ID is present. IP is not stored.
- All external links open in a new tab with `rel="noopener noreferrer"`.

## 5. Embed contract

- Product routes are mounted at `/get-rid-of` and `/api/get-rid-of/*`. All CSS and color tokens are scoped under the `data-get-rid-of` attribute on the layout wrapper so styles do not leak into other host products.
- The product uses shared Tailwind design tokens from the top-level `globals.css` and adds a small set of category swatch tokens in `src/app/get-rid-of/get-rid-of.css`.
- No component under `src/app/get-rid-of/**` imports from another product's folder (`nyc-budget`, `nyc-pilot`, `vendor-integrity`, `affordability`).
- Assets: any icon, map tile, or asset used by the product is either from the shared Tailwind system, from open-source MapLibre resources, or from the DSNY public site (linked, never re-hosted).

## 6. Refresh cadence

| Source | Cadence | Notes |
| --- | --- | --- |
| DSNY guidance corpus | Monthly (content-hash diffing) | Producing a new `corpus_version` only when at least one page's `content_hash` changes |
| DSNY search-function.csv | Monthly | Legacy synonym vocabulary |
| DonateNYC directory | Weekly | Socrata `gkgs-za6m` |
| DSNY Special Waste sites | Monthly | Socrata `242c-ru4i` |
| DSNY Food Scrap drop-off | Weekly | Socrata `if26-z6xq` |
| Voyage embedding index | On every corpus change | Rebuilt with new `corpus_version` |
| Anthropic Claude | On-demand, per request | No persistence, rate-limited |
| NYC GeoSearch | Live lookup | No persistence |

## 7. Runbook

- To land data in a fresh environment: `npm run db:setup:get-rid-of` then `npm run db:ingest:get-rid-of:all`.
- To upgrade the retrieval model: change the model constant in `src/app/api/get-rid-of/ask/route.ts` and `scripts/get-rid-of/chunk-and-embed.ts`, then re-run the embed job. Store the calibration evidence in `DATA_REPORT.md`.
- To enforce the answer service in production: set `NYC_ACCESS_TOKEN=<random>` and share the token with the host page's server-side integration point. Do not embed the token in the client bundle.
