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 suppliesplacegraph supplies
Brand, copy, theme templatesBase templates, app factory, routes
Taxonomy (its sectors, its SIC codes, its keywords)The two-layer taxonomy framework
Score definitions and weightsThe explainable signal-accumulation engine
Target councils, postcode areasONSPD attribution, point-in-polygon fallback
Which sources to run, with what capsEvery source adapter
Its own LIA/DPIA instancesThe compliance surfaces and preflight
Venue seed lists, sending domainPosters, QR/CAC attribution, both Workers
Gate thresholdsThe 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.

PackageModuleWhat it holds
placegraph-coreplacegraph.coreSiteConfig, 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-geoplacegraph.geoONSPD load/lookup, postcodes.io fallback, point-in-polygon, LAD attribution
placegraph-sourcesplacegraph.sourcesCompanies 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-scoringplacegraph.scoringThe taxonomy framework, the explainable score engine, and publish — the one place is_public is set
placegraph-webplacegraph.webFastAPI 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-complianceplacegraph.complianceLIA, DPIA, Article 14 notice and crawler-disclosure skeletons with completeness checking, consent-wording registry, and the preflight launch checker
placegraph-outreachplacegraph.outreachQR posters, letter codes, venue seeding and CAC attribution, available-domain finding, notice + outreach Worker templates
placegraph-contentplacegraph.contentGuide/post engines with checked/published semantics, Commons image pool with licence gating, inline charts
placegraph-metricsplacegraph.metricsGates-as-data, continuous coverage/precision reporting, disclosure-controlled aggregates
placegraph-deployplacegraph.deployprovision/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

  1. docs/contract.md — the API every package builds against. Read this before touching any package.
  2. docs/new-vertical.md — how to start a third one.
  3. docs/refinery.md — the shared national data plane.
  4. 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.

  1. No fact without provenance. record_fact is the only way an attribute reaches the graph, and it demands a source, a fetch timestamp and a confidence.
  2. Suppression is checked on every upsert. A removal request has to survive the next crawl, or it was not a removal.
  3. Postcodes are not council areas. Ingest by postcode area, attribute by ONSPD, filter and count by lad_code.
  4. Broad classification codes corroborate; they never classify. One SIC code labelled 332 companies a tattoo studio, one of them a family mediation firm.
  5. Numbered SQL migrations, never CREATE TABLE IF NOT EXISTS evolution.
  6. Idempotent everything. A failed overnight run is fixed by running it again.
  7. Hard spend caps in code for every metered API.
  8. Publishing is a separate, deliberate act from ingesting.