Sunday, 15 November 2026

Building Intersection Intelligence: the slides and the story

By Mo Khan · Presentation edition · 28 slides

I had never built a mapping application before Intersection Intelligence. I started with a question about a place: what is here, and how do we know? That question became my personal mapping experiment and an internal research application, built with help from Codex and Claude. It is not being used commercially.

This talk follows the journey from South Africa to a single corridor: learning the geospatial stack, importing imperfect datasets, preserving human knowledge, tuning rendering performance and choosing an achievable research MVP. It includes the software architecture, technology stack and Linux deployment.

Open the full-screen presentation ↗

Use Next to reveal each build, or choose a slide from the menu. Show all reveals a whole slide; Notes opens the accompanying explanation. For detailed maps and diagrams, use Full screen. On a phone, opening the presentation in a new tab and rotating to landscape gives it more room.

Click inside the deck to use the keyboard: arrows or Space to advance, A to reveal, N for notes, F for fullscreen. The text version below remains available without the player.

The South African screenshots have company and provider names, commercial values and installation counts obscured. Infrastructure identifiers are omitted. Deterministic masks preserve the original map pixels, labels and geometry. The cover is a fictional intersection illustration generated with AI.

Map data © OpenStreetMap contributors; Protomaps cartography. Research use does not remove attribution or licence obligations.

The story behind the slides

Expand a title to read its explanation.

01 · Building Intersection Intelligence

I had no previous experience building mapping applications. Intersection Intelligence is my personal map-learning experiment and an internal research tool. It is not being used commercially. This talk explains the decisions, mistakes, performance cycles and accepted research MVP. Codex helped implement and verify the work. Claude challenged architecture and reviewed recorded increments. I remained responsible for intent and acceptance. The fictional cover illustration was generated with AI. Revised 14 November 2026.

02 · A question about a place

The starting point was a gap between aggregate knowledge and specific places. A spreadsheet could tell me how many assets were reported in a region. I wanted to select an intersection and understand the evidence behind it. That includes who has responsibility, what equipment is recorded and what remains unknown. I am deliberately using generic terms here because the engineering story does not depend on publishing our commercial information. The aim was to connect geographic context with attributable knowledge. This is the product question that drove the rest of the architecture.

03 · Start with South Africa

Start at country scale in South Africa. This is a screenshot of the signed-in research app captured on 14 November 2026. Company and provider names, commercial values and installation counts are redacted. Map data © OpenStreetMap contributors, https://www.openstreetmap.org/copyright; Protomaps cartography. Published administrative boundaries are reference geography, not cadastral boundaries. The map is a learning and research environment with uneven source coverage. It is not a commercial service. Sensitive UI is covered with deterministic pixel masks. The original map pixels, labels and geometry are unchanged.

04 · The journey in the repository

The design existed before the late-August implementation. The first standalone slice landed on 30 August with deterministic fixtures. Real Gauteng data followed immediately, and MapLibre with PMTiles arrived by 1 September. September then expanded into team contributions, source integration, identity preservation and analytics. Performance work became central later that month. Signal AI reached its first native production increment at the end of September. October and early November added country coverage and hardened deployment boundaries. These are milestones reconstructed from git and release records. They are not an assertion that every broader product ambition was complete at each milestone.

05 · Zoom into Gauteng

The same map moves into Gauteng, where the first real-data slice taught me what the prototype had hidden. Early rendering repairs bounded visible objects, reused coordinates and improved initial paint. By 1 September we moved to MapLibre GL JS with PMTiles. The screenshot shows the later research app, not the original prototype. Sensitive UI is redacted. Map data © OpenStreetMap contributors; Protomaps. https://www.openstreetmap.org/copyright Sensitive UI is covered with deterministic pixel masks. The original map pixels, labels and geometry are unchanged.

06 · Find the city-scale question

The app focuses on Johannesburg and a useful cluster of mapped junctions. The selected city, shortlist and rendered evidence need consistent scope. A fast pan is insufficient if the displayed answer belongs to an earlier viewport. Sensitive UI is redacted. Map data © OpenStreetMap contributors; Protomaps. https://www.openstreetmap.org/copyright Sensitive UI is covered with deterministic pixel masks. The original map pixels, labels and geometry are unchanged.

07 · Follow a corridor

A corridor turns isolated points into a connected geographical question. This view shows a published Johannesburg route, not traffic simulation or a prediction of flow improvement. The screenshot is from the research app on 14 November 2026. Sensitive UI is redacted. Map data © OpenStreetMap contributors; Protomaps. https://www.openstreetmap.org/copyright Sensitive UI is covered with deterministic pixel masks. The original map pixels, labels and geometry are unchanged.

08 · One place, several kinds of evidence

OSM can describe multiple signal nodes and roads. An authority register can report an equipment asset. A colleague can contribute an observation. A regional summary can count assets without giving coordinates. These are different grains. Summing them can double count or create a population that means nothing. We preserved their identities and provenance, then resolved the view appropriate to the user's question. Aggregate counts never became fabricated point locations. Unknown, absent, unavailable and zero remained distinct.

09 · Every source needed a way in

Every provider or authority had different workbook layouts, street conventions, identifiers and completeness. We built custom importers, preserved original values and source identity, then shared only stable matching mechanics. Fuzzy road-name and road-pair matching plus geographic evidence produced candidates. The documented browser workflow used the app and Google Maps to review confusing locations. That did not confer copying rights, establish ownership or turn an agent-assisted result into a human confirmation. Ambiguous and unpositioned rows remained in the inventory. Several assets could share a physical marker. Import validation checked dossiers and exports as well as parsed rows.

10 · Refreshes had to preserve human knowledge

Once people contribute, stable identity becomes a product requirement. Source geography can change, and grouping can split or merge candidates. Rebuilding the map must preserve the human evidence already attached to it. We retained source identities, tracked aliases and surfaced unresolved reconciliation rather than silently reminting records. A specific September correction preserved deleted heads as well as active API rows. That detail shows why checking only the visible current records was insufficient. The diagram is an illustrative identity model, not an actual production record.

11 · Codex and Claude worked through evidence

Codex was the implementation owner in the documented collaboration loop. Claude acted as a peer architect and read-only independent reviewer, inspecting the same repository. I supplied product intent and resolved decisions the evidence could not make for us. The curated ledger recorded questions, findings, corrections and decisions. It was an engineering notebook rather than a message transport. This arrangement reduced the amount of manual copying between assistants. It did not mean that every commit had independent review. Separate workstreams sometimes had different ownership.

12 · Independent review changed the work

These are examples of what the review loop was for. Early MVP reconciliation retired stale assumptions about Leaflet, hosted extraction and the deployment model. Later Signal AI review caught places where shared infrastructure assumed a financial business unit. We also kept responsibility for execution clear: Claude's source review was not a claim that it had run the tests. Codex had to produce that evidence. I had to manage the process too. During the long September AI refactor I pushed for delivery, separated unrelated work and ultimately requested solo continuation. The records preserve that boundary. They do not claim Claude approval for later solo work.

13 · Software architecture

The native JavaScript atlas uses MapLibre and PMTiles. The shared chat client brings its React conversation surface into the map host. The standalone Fastify API owns product semantics and authorised access. Prepared files support map and dossier reads; verified read-only SQLite workers serve analytics. Human evidence uses a separate mutable repository. Signal AI shares execution and operations while Intersection owns its tools and geographic context. The existing platform remains the entitlement authority. Everything is in the monorepo, but the deployment package is isolated. A root-checkout import is not proof that the deployed service starts.

14 · The technology stack

The browser uses a native JavaScript atlas with MapLibre, PMTiles and Protomaps, plus the shared React chat client. Node 24 and Fastify serve product-specific geography and evidence contracts. Python handles heavy geospatial preparation and custom source adapters off-host. Versioned files and read-only SQLite serve prepared evidence. Blob events and conditional heads preserve human contributions. Entra ID, the shared AI platform and GitHub CI supply integrations. This stack diagram describes concrete technologies; the preceding architecture shows responsibility and request flow.

15 · Building and serving are different workloads

The early real-data preparation exceeded the small serving host's memory envelope. We responded by separating the workloads. Operator-triggered Python processing acquires pinned sources, builds geography and validates the resulting release. The application serves prepared artifacts and accepts bounded edits. Application CI packages code; it does not run national extraction. This also makes rollback more understandable because a release has an explicit application identity and an explicit data identity. The benefits are architectural, not a claim of a specific infrastructure saving.

16 · Deployment architecture

This is a generalised view of the documented early-November deployment. App Service sits behind Entra authentication. The browser obtains short-lived read-only access to the geographic basemap account. Restricted source evidence and ownership overlays follow authenticated application paths. Restricted evidence storage restricts network access to the application's integration subnet via a Storage service endpoint. This is a restricted public endpoint design, not Private Link. Prepared application data remain on persistent storage and verified local analytical copies. The map application calls existing entitlement and AI services. Resource names, endpoints, network ranges and credentials are omitted.

17 · A small Linux host shaped the design

The recorded host is one Azure App Service Linux B2 instance, with the built-in Node.js 24 LTS runtime and Always On. Microsoft lists B2 as two cores, 3.5 GB RAM and 10 GB plan storage. Plan storage differs from the temporary local filesystem whose actual free capacity was measured for SQLite copies. Heavy extraction runs away from this worker. Local query copies are recoverable caches backed by verified persistent releases. These specifications do not establish sustained concurrent-user capacity. No resource names, endpoints, prices or private identifiers are shown.

18 · A map release must preserve contribution history

The immutable release holds prepared geographic facts and read models. Mutable human intelligence holds events, current heads and reviews. The write path first creates an event and conditionally advances a head. A stale version receives a conflict. The accepted history follows committed ancestry, so a failed competing event is not automatically an accepted edit. Releases and rollbacks must preserve that history. The same principle applies to map refreshes: current visible rows alone do not prove preservation of every committed head. Explain why this matters to the user: knowledge gained from a site visit should survive a technical improvement to the map.

19 · Rendering improved through repeated cycles

The early prototype needed bounded visible objects, clustering and coordinate reuse. The September traces distinguished acquisition, preparation, transfer and browser source updates from GPU paint. Changing UI heights had resized the map and produced old-bounds queries followed by repeat requests. Reserving layout space and synchronising final dimensions fixed that path. Skipping unchanged resize calls avoided renderer work. Short-lived viewport caches then reused complete contexts, with invalidation for access changes and edits. These were separate increments, not one renderer rewrite.

20 · Measure the visible result, keep the caveats

Thirty primed desktop filter changes produced 30 cache hits with zero new requests and zero aborts. Visible counts and source inputs matched. Coarse application completion was 90.5 ms median and 116.5 ms p95; this was not GPU paint latency. In the September 19 hosted comparison, n=30 per arm, Cape Town street viewport median improved from 771 to 529 ms, while overview median worsened from 1596 to 1687 ms. Sequential host and I/O variation prevents attributing every change to the code. The scope is a dated navigation experiment, not a universal responsiveness or multi-user capacity claim.

21 · A measured improvement in repeated queries

This is one dated production experiment, not a current whole-product benchmark. On 22 September we compared isolated execution with a resident SQLite worker. The median of the three repeated provider-count requests fell from 855 milliseconds to 276 milliseconds. The first request was essentially unchanged. Other shapes improved by much less, and some remained multi-second operations. All timed comparisons used verified local SQLite. The run checked result/evidence hashes and CSV parity. This small sample did not establish sustained capacity, twenty-user support or an application-wide speedup.

22 · Performance meant doing less repeated work

The recurring issue was avoidable work. Reopening connections, preparing national evidence for a local query and repeatedly acquiring unchanged data all mattered. We introduced bounded reuse and resident workers, and moved scope selection earlier where it preserved semantics. But early filtering is not correct if it changes what the user asked to count. A page is not a total, and a cache needs an invalidation boundary. We measured the path from user action through acquisition and preparation to SQL and rendering. A faster inner function cannot establish a faster or more capable whole application.

23 · AI in the product uses authorised evidence

Separate development AI from product AI. The product's shared chat interface receives the selected map context. Product-owned read tools retrieve bounded facts through the application boundary. Deterministic code supplies counts and comparisons. The model explains those facts, including gaps and partial results. Saved turns retain their original map context, so panning later does not redefine an earlier 'here'. A nonfinancial application has its own scope rather than a fabricated business unit. Reusing the engine also meant reusing the conversation UX and central operations.

24 · The first usable release required an owner decision

The late-September AI task illustrates the limits of an autonomous engineering process. There was substantial implementation and review effort, but the live feature was still pending. I had to bring the work back to the next useful increment, preserve the existing application's behaviour and separate optional improvements. The first native AI release reached production at the end of September under a bounded owner-approved launch policy. It kept authentication and data-integrity gates while retaining quality and capacity work as explicit follow-up. This is not advice to weaken safeguards. It is a reminder that product priorities require an accountable human decision.

25 · The accepted research slice

As of this source snapshot, the recorded product has a working atlas, dossiers, source-aware analytics, contributions and contextual AI over a nine-country catalogue. Coverage depth varies. The product ledger still tracks performance and concurrency acceptance, richer topology and source coverage, and the validation required for future opportunity models. After storage separation, data and ownership-publication maintenance are gated pending a reviewed procedure. Application and UI releases continue to be supported. The app is a discovery-and-capture alpha, not a live traffic-control system or a proof of traffic benefit. Evidence date: source master retrieved on 9 November, latest commit 7 November. Current state comes from capability and product ledgers, not old roadmap summaries.

26 · Original vision, accepted MVP

The original vision was a rich digital representation of intersections and the surrounding road network, with responsibility, equipment and planning context. The accepted research MVP was discovery and capture: navigate from a country to a junction or corridor, inspect source-aware evidence, preserve reviewed corrections and ask bounded contextual questions. Complete national coverage and a universal current twin were never established. Live control, calibrated traffic benefits and sustained multi-user capacity remain outside acceptance. The distinction matters because a persuasive map can make an unfinished model look authoritative.

27 · What I would repeat

These are the practices I would carry into another unfamiliar stack. Start with a concrete problem and move to representative real data early. Define the meaning of records, totals and missing values before the interface makes them look authoritative. Give independent reviewers bounded questions and ask what evidence would falsify the proposed design. Keep the implementation owner responsible for the tests. Finally, distinguish code completion, deployment and acceptance by a user. The AI agents made implementation accessible and productive, but they still needed the product context and constraints I brought. This is a personal reflection grounded in the development records, not a measured productivity comparison.

28 · Learning a new stack with AI

Return to the original ambition. I wanted a rich picture of a place and its surrounding network. I accepted a smaller research MVP: navigate, inspect evidence, preserve a reviewed correction and ask a bounded question in context. Complete coverage, rich topology, calibrated benefits and sustained capacity remain open. This is my personal learning experiment and internal research tool, not used commercially. Codex and Claude helped me learn the stack while I owned scope and acceptance. OSM licensing still applies to research use.

Further reading

My earlier post, Two AIs, One Branch, No Human Clipboard, explains the collaboration pattern. For the mapping building blocks, see the MapLibre GL JS documentation and the PMTiles guide.

I had never built a mapping app. Then I built Intersection Intelligence, with the help of AI agents (not vibe coding).

November 2026 · South Africa · By Mo Khan

Conceptual aerial artwork of an intersection becoming a digital map.

Cover illustration generated with AI. 

I had never built a mapping application before Intersection Intelligence.

I knew the problem I wanted to solve. I could look at a summary of infrastructure across a region, but I could not follow that summary down to a particular intersection and reliably answer a few ordinary questions. What is here? Who is responsible for it? What evidence supports that answer? When someone learns something new about the site, where does that knowledge go?

A spreadsheet could tell me a total. I wanted to be able to point at a place.

That gap became Intersection Intelligence: my personal map-learning experiment and an internal research application for exploring road intersections, understanding the evidence around them, and capturing knowledge that would otherwise remain scattered across documents and people's heads. It now brings together an interactive atlas, site dossiers, corridor views, analytical comparisons, reviewed contributions and contextual AI chat.

It is not being used commercially. I am sharing it as an account of learning a new technical domain, rather than announcing a commercial offering.

I built it with Codex and Claude. I brought the product intent, the domain questions and the decisions about what mattered. The agents helped me work through an unfamiliar geospatial stack, implement it, challenge the design and test what we had actually delivered. This was a substantial extension of my existing software and systems experience into a domain I had not built in before.

Looking back through the git history, the interesting story is how often the meaning of a seemingly simple feature changed once we tried to make it trustworthy.

The experiment started with a question about a place

The early vision was much bigger than a map with coloured pins. I wanted a useful digital representation of infrastructure: the road context around a junction, the equipment recorded there, the organisations with different responsibilities, and the evidence we still needed to gather.

The longer-term ambition includes richer network analysis and better support for planning. The delivered research application is a discovery-and-capture alpha. It does not operate traffic signals, ingest a universal live traffic feed, or demonstrate that a proposed intervention would improve traffic flow. Those capabilities would require additional data and validation. Keeping that boundary visible has become part of the application's design.

The workflow I kept coming back to was straightforward. Start with a country or area. Find a junction or corridor. Open its dossier. Understand what the sources say. Add or review evidence. Compare the resulting picture with another area, or ask a question in the context already on screen.

That last part matters. A useful map should help someone carry a question through the application without continually restating where they are looking.

South Africa in the atlas, with sensitive UI regions redacted.

The journey starts at country scale. These South African screenshots were captured from the signed-in research app on 14 November 2026 in the OSM junction view. Company and provider names, commercial summaries and installation counts are obscured. Sensitive UI is covered with deterministic pixel masks; the original map pixels, labels and geometry are unchanged. Map data © OpenStreetMap contributors; Protomaps cartography. Published administrative boundaries are reference geography, not cadastral boundaries.

Late August: a prototype became a real geography problem

The design documents had been evolving for months before the first standalone implementation landed on 30 August. That first slice used a deterministic demonstration dataset. It let us exercise the service, authentication, edits and interface without pretending we had already solved national geography processing.

The next step exposed the difference immediately. On 31 August we began proving the application against real OpenStreetMap data in Gauteng. The early browser implementation went through a succession of rendering repairs: limiting visible objects, clustering points, avoiding repeated coordinate work and making the dossier respect the selected scope.

The git log is wonderfully unromantic about this. There are commits for getting the map to initialise, making clusters usable and ensuring the first preview actually paints. These are the details that disappear in a polished demo, but they determine whether someone can use the thing.

By 1 September, the atlas had moved to MapLibre GL JS and a self-hosted PMTiles basemap. MapLibre supplies the browser renderer. PMTiles packages tiled geography into an archive whose readers can fetch the relevant byte ranges on demand. That helped us serve the map without transferring an entire country's geography to the browser. MapLibre documentation and the PMTiles format guide explain those building blocks.

I was learning the difference between the basemap and the application's own evidence. The streets and labels provide geographic context. The records layered over them have their own identities, source dates and access rules. A fast basemap cannot compensate for a slow dossier endpoint or a misleading count.

The atlas focused on Gauteng, with sensitive UI regions redacted.

Country to province: the same interface now shows Gauteng. Zoom changes the amount and form of visible geographic evidence. Provider names and commercial values are redacted.

The Johannesburg city view in the research atlas.

Province to city: Johannesburg becomes the working area. The shortlist and map must agree about the selected scope. Map data © OpenStreetMap contributors; Protomaps cartography.

A Johannesburg corridor in the atlas, with sensitive UI regions redacted.

City to corridor: selecting a named route makes a connected stretch of road the focus. This is a view of published route geography, not a simulation of traffic flow. Map data © OpenStreetMap contributors; Protomaps cartography.

We also learned early that building geography and serving geography were different workloads. The first real extraction experiments exceeded the small application's memory budget. The architecture changed accordingly: prepare and validate the heavy datasets away from the serving worker, then publish versioned artifacts for the application to read.

That was one of the first important decisions AI helped me reach. It came from running the work and measuring it, rather than assuming the initial hosting plan would cope.

The hardest question was what one dot meant

Consider an illustrative junction. OpenStreetMap may describe several signal nodes and the roads around them. An authority register may describe an equipment installation. A colleague may have a recent observation about the same location. A regional spreadsheet may contain an aggregate count that includes it.

Those sources describe different things. Adding their rows together would produce a confident-looking number with no coherent meaning.

The application therefore keeps several kinds of evidence distinct:

Evidence What it can establish What it cannot establish by itself
OpenStreetMap geography Mapped locations, road structure and recorded tags Complete equipment ownership or current operational status
Authority records What a particular source reports about an asset A certain match to a mapped junction when the link is ambiguous
Human contributions An attributable observation or reviewed correction An automatic rewrite of the original source
Regional summaries Counts at their stated area and reporting date Coordinates for individual sites

Four evidence types retain their identities before the application resolves a view.

The public explanation of the evidence model. Examples are generic. No commercial populations or installation records are shown.

We made a particularly important rule explicit: an aggregate count must never become a collection of invented map points. If a source reports more assets than we can position, that difference remains a coverage gap.

We also separated unknown from zero. An empty field does not prove that a piece of equipment is absent. A source that has not been loaded does not establish that an area has no assets. An automated proximity match is a proposal until there is sufficient evidence to accept it.

This is where the software became much more interesting to me. The map made disagreements visible. The real product work was deciding how to preserve those disagreements without making the application incomprehensible.

Onboarding a dataset was its own engineering workflow

The phrase “import the data” concealed a surprising amount of work. Every provider or authority brought its own conventions: workbook layouts, local asset identifiers, street abbreviations, road-pair descriptions, direction suffixes, coordinates of varying quality and missing fields. A parser that worked for one register could confidently misread the next.

We built source-specific importers first, then shared the mechanics that actually proved common. Each adapter preserved the source identity, original values, reporting context and reconciliation status. Re-importing a source needed to replace that source's snapshot predictably, without erasing unrelated contributions or making duplicate assets.

Matching was a separate stage. We normalised road names and aliases, compared road pairs, used fuzzy name matching and geographic evidence, and ranked possible links to OSM junctions. Several source assets could legitimately occupy one physical location. An unmatched row still belonged in the inventory; lack of a reliable position was a fact to retain, not a reason to discard it.

The difficult cases brought us back to the browser. We inspected the application and used Google Maps during the documented location-review workflow to check confusing road names and possible junctions. That helped identify candidates, but a plausible visual match did not establish equipment ownership or turn an agent-assisted review into human confirmation. Google Maps was a review aid, not a source we could freely copy into OSM or redistribute. Third-party sources retain their own terms, and OSM explicitly warns against adding copyrighted source data without permission. OSM source and licensing guidance.

Dataset onboarding from source-specific import through matching, review and publication.

The workflow preserves uncertain and unpositioned records. A candidate link and an accepted relationship are different states.

In one September review, Claude challenged the distinction between an OSM-derived location hint and a canonical junction identity. During browser verification, Codex also found that the UI could expose an old fuzzy candidate after the assisted workflow had selected a different target. Codex corrected the data and consumer boundaries, and verified the resulting dossiers in the browser. This was exactly where the collaboration earned its place: the importer could look correct in isolation while the person clicking the map still received the wrong story.

Making the map feel usable took several tuning cycles

My early mental model was that a slow map meant too many things were being drawn. Sometimes that was true. The first prototype needed bounded visible objects, clustering, coordinate reuse and repairs to its initial paint. Moving to MapLibre and PMTiles gave us a better basis for exploring real geography, but did not finish the performance work.

By September I was judging the experience with a mouse: drag, zoom, change a filter, revisit an area, and inspect a junction without losing my place. We profiled each stage rather than calling every delay “rendering”. Request acquisition, evidence preparation, network transfer, browser source updates and the renderer's worker/GPU work are different costs.

One tuning cycle found that changing labels and panels changed the map's dimensions. The client could query old bounds, then resize and request again. We reserved space for changing content, synchronised the final camera dimensions before querying and skipped unchanged resize calls. The initial city focus then used the intended discovery request followed by the final viewport request.

Another cycle found that filter actions were clearing useful in-memory viewport results. We preserved short-lived entries under complete context keys, while retaining expiry and invalidation for edits and access changes. In the recorded desktop browser run, 30 primed filter changes produced 30 cache hits, zero new requests and zero aborts, with exact visible-count and source-input parity. Median application completion was 90.5 ms, with 116.5 ms at p95. That timing used coarse polling; it was not a GPU paint measurement or a universal sub-100 ms guarantee.

The server still mattered on a cache miss. Spatial pruning and moving selection ahead of expensive enrichment reduced work without changing the answer. In the 19 September hosted comparison, the street-scale Cape Town viewport median fell from 771 ms to 529 ms across 30 requests per arm. The overview median went the other way, from 1,596 ms to 1,687 ms. Those sequential observations include host and I/O variation, so I cannot attribute every millisecond to one code change or claim that every navigation became faster.

The lesson was cumulative: reduce unnecessary drawing, stop accidental repeat requests, reuse only the right fresh evidence, then improve the miss path. Each cycle had to preserve the junction identities, filters, visible counts, dossiers and exports. A quick map showing stale or incomplete evidence would have failed the experiment.

September: preserving what people had learned

Once colleagues could contribute information, a data refresh became a more serious event.

OpenStreetMap changes. Grouping logic improves. A set of source nodes that once represented one candidate may later split or join another group. If the next build simply minted new identities, human observations could become detached from the places they described.

Stable identity and reconciliation became central to the system. We retained source identities, tracked aliases and treated ambiguous changes conservatively. The September junction-accuracy work repeatedly tested whether refreshed geography preserved the facts and review decisions already accepted by people.

One correction in that history is especially revealing: preservation had to include deleted record heads as well as the active records returned by the API. Looking only at what was currently visible would miss part of the committed history.

The write model follows the same principle. A contribution creates an event, and a conditional update advances the record's current head. If two users edit the same version, the stale write conflicts instead of silently overwriting the newer one. The accepted history follows the committed ancestry. A failed competing write does not become an accepted edit just because an event file exists.

Immutable geographic releases and mutable human evidence therefore live behind separate storage boundaries. Publishing a new map, or rolling back an application release, must preserve contribution history.

I had started with the desire to click on an intersection. I was now thinking about identity, concurrency and the durability of institutional knowledge.

The software architecture we arrived at

Intersection Intelligence lives in the same repository as the wider platform, but runs as a standalone application. It reuses the platform's identity and AI capabilities without absorbing the financial application's runtime or its data model.

Software architecture showing the atlas, product API, evidence stores and shared AI platform.

Software architecture, simplified for public discussion. Arrows show the main request and evidence paths. Product-specific datasets and deployment identifiers are omitted.

The map interface is a lean JavaScript application using MapLibre and PMTiles. It requests bounded viewports and opens detailed evidence when needed. The shared Signal AI chat client brings its React-based conversation interface into that native map host.

A Node.js/Fastify service owns the product API. Shared domain code supplies the geographic and evidence semantics used across views. Prepared files support map and dossier reads. Read-only SQLite query workers support analytical requests. A separate mutable repository stores contributions, review state and their history.

The offline preparation pipeline uses Python geospatial tooling to work through OpenStreetMap extracts and other approved sources. It builds the territorial hierarchy, junction and corridor artifacts, indexes and query stores. Source-specific adapters retain provenance rather than assuming every authority's spreadsheet is the same format.

Technology stack from browser renderer to offline processing and hosting.

The concrete technology stack, separate from the request-flow architecture above. Python prepares geography; JavaScript serves and presents it. Shared AI and identity remain explicit integrations.

The fact that the software sits in one repository does not remove these boundaries. The deployed package must contain what its runtime imports. A test passing because a sibling package happens to exist in the checkout is insufficient evidence that the isolated service will start.

This architecture also gave us a useful way to expand. The country catalogue describes administrative structures instead of hardcoding every place into a South African province-and-municipality hierarchy. By late October, the published catalogue covered nine countries across southern Africa and the United Kingdom. That is geographic breadth, with different levels of source coverage in different places. It is not a claim of complete infrastructure knowledge in any of them.

Deployment became part of the design

There are two distinct release activities. An application change packages and deploys code. A geography change prepares, validates and promotes a data release. The second is an explicit operator workflow, separate from ordinary application CI.

Deployment architecture showing offline preparation, code deployment, authenticated application hosting and separated storage.

Deployment architecture as documented in early November 2026. Cloud resource names, hostnames, network ranges, account identifiers, commercial data and operational credentials are intentionally omitted.

The research application runs on one Azure App Service Linux B2 instance, with two cores and 3.5 GB RAM, the built-in Node.js 24 LTS runtime and Always On. Microsoft's published B2 plan lists 10 GB of plan storage; that quota is distinct from the worker's temporary local disk, whose actual headroom we measured for query copies. These are configuration details, not a pricing or capacity promise. Azure App Service Linux plan specifications.

That modest memory envelope shaped the design. Heavy extract processing runs elsewhere. The serving process reads prepared artifacts, bounds query work and keeps recoverable SQLite copies on local disk. Local disk can disappear when a worker is replaced, so verified persistent releases remain the source of recovery. The host is protected by Microsoft Entra authentication. It calls the existing platform API for entitlements and the shared AI service for AI execution. Prepared immutable application datasets reside on persistent application storage, with verified local SQLite copies for ordinary analytical execution.

Basemap archives now occupy a separate storage account. Authorised browsers receive short-lived, read-only signed access to geographic tile ranges. Restricted source evidence remains behind the application, and its storage network access is restricted to the application's integration subnet. Ownership overlays follow the authenticated application path rather than being treated as ordinary basemap content.

The network design uses a storage service endpoint and a restricted public endpoint. It does not use Private Link. That distinction matters when explaining what we built, even in a diagram with all the resource names removed.

Releases also became more selective. A UI-only change should not copy the map dataset or restart the analytical worker. An application release should retain the active data root. A data release needs a validated candidate and a verified recovery path. The release procedure checks what is actually running, rather than treating a successful upload as proof of completion.

There are still operational limits. After the November storage separation, data-release and ownership-publication maintenance remain gated pending the reviewed access procedure. Ordinary application and UI releases remain supported. That is an open engineering task, not something I want a simplified architecture picture to conceal.

The next performance cycle moved into analytical queries

The map also had to coexist with analytical requests. Improving browser navigation would achieve little if a large query repeatedly reopened stores or prepared the same evidence on that small worker.

We profiled the path from the user's action through acquisition, preparation, query execution and rendering. The work led to bounded viewport caches, reuse of freshly validated evidence, local query stores and supervised resident SQLite workers. We moved filtering ahead of expensive enrichment where the semantics allowed it.

The important qualification is “where the semantics allowed it.” Filtering early is only an improvement if it preserves the population the user asked about. Pagination must not turn a partial page into an apparent total. Caching must retain freshness and invalidate when relevant evidence changes.

One small production comparison captures both the value and the limits. In the 22 September resident-worker experiment, the median of three repeated provider-count requests fell from 855 ms to 276 ms. The first request was essentially unchanged, and other query shapes improved by much less. The same comparison checked result equality and export parity.

That is a useful improvement in a measured path. It does not establish a threefold improvement in the whole application, or prove sustained multi-user capacity. The project records keep those acceptance questions open.

I have learned to be suspicious of performance stories that only show the fastest inner function. What matters is whether the person using the map gets the same answer, with the right evidence, sooner.

AI helped build the experiment, then became part of it

These are two different roles for AI in this story.

During development, Codex did sustained implementation and verification work. Claude served as a peer architect and independent reviewer in recorded collaboration sessions. Inside the application, Signal AI answers questions through authorised, read-only tools over the application's evidence.

I had already written about the development arrangement in Two AIs, One Branch, No Human Clipboard. For Intersection Intelligence, the same working pattern became a way to explore a new technical domain while retaining a clear owner for the implementation.

The owner sets intent, Codex implements and verifies, and Claude reviews the evidence.

The collaboration pattern used in the recorded development sessions. Roles varied in separate workstreams; this does not imply that every commit received independent review.

Codex and Claude could examine the same repository and discuss bounded decisions without me copying every response between applications. The durable record was a curated engineering ledger: the question, the evidence, the finding, the correction and the remaining decision.

The independent review was useful because it sometimes changed the design. The early reconciliation explicitly retired obsolete assumptions about the map renderer and running heavy extraction on the serving host. Later AI-platform reviews challenged financial-context assumptions, conversation handling and the boundary between a client timeout and work that was still consuming server resources.

Agreement between two models was never sufficient proof. Codex still had to run the checks and produce execution evidence. A source review did not magically become a browser test, and an approved design did not mean the feature was deployed.

I also had to manage the process. The late-September AI integration grew into a long refactor and review exercise. I pushed for a first usable release, separated unrelated boundary work from that delivery, and eventually asked Codex to continue without the reviewer. The history preserves that change of responsibility. It would be misleading to describe the subsequent work as independently approved by Claude.

The agents made unfamiliar implementation work much more accessible to me. They did not remove the need to set scope, challenge complexity or decide when the next useful increment should reach a user.

Giving the map a conversational interface

The existing platform already had Signal AI. Bringing it into Intersection Intelligence prompted a broader question: which parts should every future application be able to reuse?

The answer included both the engine and the interface. We separated the shared conversation client, authenticated execution and central operations from product-owned tools and context. Intersection Intelligence owns the meaning of its geography and evidence. The financial application retains the meaning of its financial tools.

A selected map area cannot be represented by a made-up business unit just to satisfy an existing financial gate. Likewise, “here” in a saved conversation must retain the area that was selected for that turn. Panning the map later must not silently change what an earlier question meant.

The model receives bounded evidence through the product's read tools. Counts and calculated comparisons come from deterministic code. The answer must distinguish complete results, partial pages, unknown fields and unavailable evidence. The model helps explain those facts in conversation.

This is also why I care about reusable chat UX. People should not have to learn a different conversation panel for every application. The map keeps its own navigation and space, while the shared client supplies familiar conversation controls and rich answer rendering.

The first native AI release reached production at the end of September under an explicitly bounded launch decision. Answer quality, latency and capacity continued to need work. Reuse gave us a foundation for improving that experience across applications.

What I would carry into the next unfamiliar stack

I would start again with a concrete user question and a narrow, real-data slice. The demonstration dataset helped us prove interfaces. Real geography exposed the work those interfaces actually needed to do.

I would establish evidence and identity rules earlier than feels necessary. A map is persuasive. That makes it especially important to explain what its colours, counts and points mean before people depend on them.

I would keep the build pipeline separate from the serving path, and measure the actual user journey early. I would also keep the distinction between implementation, deployment and acceptance visible throughout the project. Those are different achievements.

And I would keep using AI as an engineering collaborator. The productive loop was specific: explain the intent, inspect the existing system, propose a bounded change, implement it, challenge it with evidence, and check the result a user would actually receive. Long conversations alone were never the measure of progress.

Today I can navigate to a place, inspect its evidence, preserve an attributable correction and ask questions in context. There is still more to do on coverage, richer topology, performance and validation of future analytical capabilities.

I began without experience building mapping applications. I now have a working geospatial research application and a much better understanding of the questions underneath one. The most valuable change is that I can look at an unfamiliar stack and begin with a useful problem, knowing that I can work through the engineering with AI while remaining responsible for the result.

Returning to the vision: what counted as an acceptable MVP?

The original vision imagined a rich digital representation of intersections and their wider road network. It included questions about responsibility, equipment, corridors and future planning. The requirements were useful as a direction, but building every ambition before trying the map would have postponed the learning I wanted.

The acceptable MVP became a discovery-and-capture research alpha: follow a place from the map to its evidence, preserve a reviewed correction, and ask a bounded question in context. Here is how I now compare the vision with the delivered slice.

Original requirement or ambition Accepted research MVP Still outside that acceptance
Navigate from a country to a useful local place Country, area, junction and published corridor navigation Complete, equally detailed coverage everywhere
Bring disparate source records together Source-specific imports, provenance and explicit candidate links Automatic certainty for ambiguous matches
Build a durable shared picture Dossiers, attributable contributions, review and preserved identities A complete, continuously current national twin
Ask questions about what is on screen Contextual AI using authorised read tools and deterministic facts Unbounded answers or predictions unsupported by data
Make exploration usable on modest hosting Measured navigation and query improvements on one B2 host A universal latency target or sustained multi-user capacity claim
Explore network and planning possibilities Published corridors and evidence for further research Live control, calibrated traffic simulation or proven intervention benefits

That was a deliberate stopping point for a useful first experiment, not a claim that the entire vision had shipped. Coverage, richer topology, AI quality, sustained capacity and the gated data-maintenance procedure remain work to do.

For me, the result is already meaningful. I can move from a country map to a road, inspect why a record is there, and see where the evidence runs out. I learned how the stack fits together by building it with Codex and Claude, testing the awkward cases and making the acceptance decisions myself. The project remains my personal learning experiment and an internal research tool, with no commercial use.

Technology and further reading

The stack combines MapLibre GL JS, PMTiles and Protomaps, OpenStreetMap extracts, Python geospatial preparation, Node.js/Fastify, SQLite, Azure App Service and Blob Storage, Microsoft Entra authentication, and the shared Signal AI platform.

OpenStreetMap is a community-built data source available under the ODbL, including for commercial use subject to its terms. Calling this project research does not remove attribution, licence or applicable share-alike obligations. Data licensing and access to OSM-operated tile services are separate matters; this application uses prepared PMTiles basemaps. See OpenStreetMap attribution and licensing and the OSM tile service policy.