{
  "markdown": "# Gwinnett Index\n\nA machine-readable index of zoning, land-use, and development records for Gwinnett\nCounty, Georgia and its 17 municipalities. Built for AI agents first, humans second.\n\n## Why this exists\n\nA \"Duluth, GA\" mailing address is usually **not** in the City of Duluth. It's in\nunincorporated Gwinnett — a different code, a different board, a different permit\nportal. Nothing on the internet resolves an address to its *governing* jurisdiction\nbefore answering a zoning question, so every AI assistant answering \"what's the\nsetback in Duluth GA\" today is guessing.\n\nUnincorporated Gwinnett is **67.3%** of the county's land area. Getting this wrong\nis the default, not the edge case.\n\n## What's here\n\n| Dataset | Records | Source |\n|---|---|---|\n| Jurisdiction boundaries | 19 (county + unincorporated + 17 cities) | US Census TIGER — public domain |\n| County zoning cases | 11,739 (1970–2026) | Gwinnett `GC_Planning` ArcGIS |\n| Resolved applicants | 7,369 (from 8,246 raw spellings) | derived |\n| Duluth UDC | 860 sections, 18 tables | city PDF, 426 pp |\n| Duluth meeting documents | 356 (7,878 pp, 2023–2026) | duluthga.net |\n| Duluth land-use cases | 109 distinct, 64 with vote records | agenda mining |\n\n## Setup — one time\n\nThe repo already has a starter README, so clone first and copy in — that avoids a\npush conflict:\n\n```bash\ngit clone https://github.com/willmobhill-arch/gwinnett-index.git\ntar -xzf gwinnett-index-repo.tar.gz -C gwinnett-index      # overwrites the stub README\ncd gwinnett-index\ngit add -A && git commit -m \"Gwinnett Index: pipeline, schema, and extracted corpora\"\ngit push\n```\n\nThen, with the repo **public**, load the corpora. Postgres fetches them itself —\nyou don't run anything locally, and no credential is involved:\n\n```sql\n-- set to your repo's raw base\n-- https://raw.githubusercontent.com/willmobhill-arch/gwinnett-index/main/data/\n\nSELECT load_udc_sections_from_url(\n  'https://raw.githubusercontent.com/willmobhill-arch/gwinnett-index/main/data/udc_sections.jsonl');\n\nSELECT * FROM load_duluth_cases_from_url(\n  'https://raw.githubusercontent.com/willmobhill-arch/gwinnett-index/main/data/duluth_cases.jsonl');\n\nSELECT load_meeting_docs_from_url(\n  'https://raw.githubusercontent.com/willmobhill-arch/gwinnett-index/main/data/duluth_meeting_docs.jsonl');\n```\n\nExpected: 860 sections, 228 case rows (109 distinct), 356 meeting documents.\n\n## Architecture\n\nIngestion runs **inside Postgres** via the `http` extension — it fetches the\ncounty's ArcGIS REST services, Census TIGERweb, and this repo's own raw URLs\ndirectly. No external worker, no credentials in transit, and the whole 11,739-case\ncounty load moves zero bytes through a client.\n\n```\ningest/\n  duluth/          UDC PDF -> 860 sections + 18 tables (extract_udc, extract_tables)\n  duluth_agendas/  crawl -> OCR -> parse case items from agendas/minutes/packets\ndb/migrations/     the schema, md5-verified against the live Supabase project\nscripts/           export_snapshot.py (the only thing that talks to Postgres) + loaders\ndata/              extracted corpora, fetched by the SQL loaders above\nsite/              Astro static site + full agent surface (.md twins, llms.txt, DCAT)\nworker/            Cloudflare Worker: REST API + MCP server, five tools, no auth\ndocs/plans/        design doc, build plan, status\n```\n\n### Building the site\n\n```bash\nDATABASE_URL=postgresql://... python3 scripts/export_snapshot.py   # writes data/snapshot/\ncd site && npm ci && SITE_URL=https://your-domain npm run ci        # build + 15 gates\n```\n\nWithout a snapshot the site builds from `site/fixtures/` — a small committed sample with\nidentical shapes — and says so on every page. `npm run ci` fails the build if any route\nlacks a `.md` twin, any page presents records before naming its governing jurisdiction,\nor the bulk export contains geometry.\n\n### The API and MCP server\n\n```bash\ncd worker && npm ci && npm test        # 10 protocol tests, no network\nwrangler secret put SUPABASE_ANON_KEY && wrangler deploy\n```\n\nFive tools, public, read-only, no auth: `resolve_jurisdiction` (always call first),\n`search_cases`, `get_case`, `get_code_section`, `list_jurisdictions`. The REST API mirrors\nthe same five operations from the same implementation and publishes `/openapi.json`.\n\n### The adapter pattern\n\n17 jurisdictions cannot be 17 hand-written scrapers. The vendor landscape\nconsolidates hard — Municode, ArcGIS REST, CivicPlus, CivicClerk, BS&A, Accela —\nso roughly six adapters cover ~90% of them, and adding a city is a config file.\nHealth metric: **if a new city needs new code rather than a new config, the\nadapter layer is leaking.**\n\n## Data quality, stated honestly\n\n- **Jurisdiction resolver**: scored on 1,915 probe points from the county's own\n  zoning layers. At `confidence='high'` it is correct **1,546/1,546 — 100%**.\n  Every error falls inside the low-confidence band. All 22 disagreements were\n  within 141 m of a boundary (annexation lag; Census updates yearly).\n- **Duluth UDC**: 97.3% raw text coverage; the gap is stripped heading lines,\n  leaving 591 unexplained characters across 426 pages.\n- **UDC Table 2-B** (dimensional standards) is verified cell-for-cell against the\n  rendered source page. The other 17 tables are extracted but **not spot-checked**,\n  and Table 2-B's merged PUD/CBD rows remain unreliable. Flags are in the data.\n- **Duluth minutes**: 57–69% are scanned with no text layer (printed, signed,\n  re-scanned). OCR text is stored separately with `text_source='ocr'` and is a\n  reconstruction, never a quotation of the record.\n- **Duluth's 109 cases are a finding aid, not a dataset.** Measured field by field:\n  67 have a \"location\" containing no street number, 57 have a \"request\" that is just the\n  word `ORDINANCE`, 23 have an applicant field that ran on into a mailing address, and\n  only 13 carry a zoning district. Each row links to the PDF it came from; **the document\n  is the record and the fields point at it.** Published as `stats.duluth_extraction`.\n- **Applicant resolution**: 271 variants merged on tight edit distance; **262\n  lower-confidence pairs are queued for human review rather than merged on a\n  guess.** A split entity is visibly wrong; a wrongly merged one looks\n  authoritative and is invisible.\n\n## Legal posture\n\n| Content | Posture | Basis |\n|---|---|---|\n| Ordinance / plan text | Mirror in full | *Georgia v. Public.Resource.Org* (2020) — edicts of government |\n| Case records | Mirror in full | Facts; Georgia Open Records Act |\n| County parcel/zoning geometry | **Proxy only, never rehost** | Gwinnett GIS licence forbids redistribution |\n| Jurisdiction boundaries | Host | US Census TIGER — public domain |\n| IBC/IRC base text | **Never** | ICC copyright; index Georgia amendments only |\n\nThis index is a mirror and derived analysis, not the system of record. Every\nrecord carries `source_url` and `last_verified`.\n\nPublished under CC0.\n",
  "bytes": 6912,
  "sha": "e4e4a150857eee123d93f5680033572bcf9757b7fd2f3759387254012e9a8a14",
  "repo_slug": "willmobhill-arch/gwinnett-index",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_net_gwindex_gwinnett_index_e5c49fe2/readme"
}