placegraph
The shared platform behind compiled, provenance-tracked local business directories. One engine, many verticals, many places.
It was extracted from Kiln / Makers Map — a directory of Southampton's creative businesses — on the observation that roughly four-fifths of that codebase said nothing about creativity or about Southampton. This repo is that four-fifths, made structural.
What a "vertical" is
A vertical is one directory: one brand, one geography, one taxonomy, one database, one droplet. It depends on placegraph for the machinery and supplies only what is genuinely its own:
| The site supplies | placegraph supplies |
|---|---|
| Brand, copy, theme templates | Base templates, app factory, routes |
| Taxonomy (its sectors, its SIC codes, its keywords) | The two-layer taxonomy framework |
| Score definitions and weights | The explainable signal-accumulation engine |
| Target councils, postcode areas | ONSPD attribution, point-in-polygon fallback |
| Which sources to run, with what caps | Every source adapter |
| Its own LIA/DPIA instances | The compliance surfaces and preflight |
| Venue seed lists, sending domain | Posters, QR/CAC attribution, both Workers |
| Gate thresholds | The gate framework |
Known verticals:
- Makers Map (
makersmap) — creative businesses, Southampton. The origin. Not yet migrated; see docs/backport-kiln.md. - Circuit / Bournemouth Tech (
circuit) — technology businesses, BCP. Lives in../circuit. Built on these packages from day one.
Packages
Ten distributions sharing the placegraph.* namespace (PEP 420), so a site installs only what it uses and each can be versioned independently.
| Package | Module | What it holds |
|---|---|---|
placegraph-core | placegraph.core | SiteConfig, entity/fact/provenance/suppression schema and writes, named scores and their signals, normalisers, deterministic entity resolution, versioned migrations, the source/licence registry, job logging |
placegraph-geo | placegraph.geo | ONSPD load/lookup, postcodes.io fallback, point-in-polygon, LAD attribution |
placegraph-sources | placegraph.sources | Companies House (bulk + REST), ICO, NNDR, Places, Serper search/discovery, polite crawler, iXBRL accounts, website audit, partner directories, synthetic fixtures, and both ends of the refinery contract |
placegraph-scoring | placegraph.scoring | The taxonomy framework, the explainable score engine, and publish — the one place is_public is set |
placegraph-web | placegraph.web | FastAPI app factory, base Jinja layer, directory/profile/claim/removal/Article-14 flows, token-gated admin, single-definition public counts, sitemap, JSON-LD, a11y checks |
placegraph-compliance | placegraph.compliance | LIA, DPIA, Article 14 notice and crawler-disclosure skeletons with completeness checking, consent-wording registry, and the preflight launch checker |
placegraph-outreach | placegraph.outreach | QR posters, letter codes, venue seeding and CAC attribution, available-domain finding, notice + outreach Worker templates |
placegraph-content | placegraph.content | Guide/post engines with checked/published semantics, Commons image pool with licence gating, inline charts |
placegraph-metrics | placegraph.metrics | Gates-as-data, continuous coverage/precision reporting, disclosure-controlled aggregates |
placegraph-deploy | placegraph.deploy | provision/bootstrap/deploy/Caddy/systemd and the post-deploy checklist, parameterised by site slug |
placegraph-core depends on nothing but the standard library. Everything else depends on core and, where genuinely needed, on httpx.
Reading order
- docs/contract.md — the API every package builds against. Read this before touching any package.
- docs/new-vertical.md — how to start a third one.
- docs/refinery.md — the shared national data plane.
- docs/backport-kiln.md — migrating Makers Map onto these packages.
Development
python -m pip install -e ".[dev]"
python -m pytest
The root pyproject.toml is a development convenience that installs every package editable at once. Sites depend on the packages individually, pinned to a git tag — there is no PyPI release until a third consumer exists.
The rules that survived the extraction
These are load-bearing, and each one is here because breaking it cost real time.
- No fact without provenance.
record_factis the only way an attribute reaches the graph, and it demands a source, a fetch timestamp and a confidence. - Suppression is checked on every upsert. A removal request has to survive the next crawl, or it was not a removal.
- Postcodes are not council areas. Ingest by postcode area, attribute by ONSPD, filter and count by
lad_code. - Broad classification codes corroborate; they never classify. One SIC code labelled 332 companies a tattoo studio, one of them a family mediation firm.
- Numbered SQL migrations, never
CREATE TABLE IF NOT EXISTSevolution. - Idempotent everything. A failed overnight run is fixed by running it again.
- Hard spend caps in code for every metered API.
- Publishing is a separate, deliberate act from ingesting.