Case study - Freelance client work
Map Align - AI-assisted reconciliation pipeline
A 5-stage pipeline that reconciles external sources (websites, maps, PDFs) with an internal database of thousands of physical spaces. The deterministic engine decides; the LLM reviews and can only downgrade — never escalate.
- Pipeline stages
- 5
- Lines of core code
- ~3300
- Proposal types
- 4
- Match threshold
- 0.72
The brief
The client builds a B2B conversational platform for physical venues (airports, malls) with an internal database of thousands of spaces — shops, restaurants, services. External sources (official websites, interactive maps, PDF exports) constantly drift from that database.
They needed a tool that aligns the two worlds without operators comparing lists by hand — and without the AI silently inventing or deleting spaces.
The challenge
An LLM left alone on this task proposes mass deletions on any incomplete source and hallucinates creations on parsing noise. Lineage also matters: which source produced this proposal, which run, against which version of the internal snapshot.
Every source has its own permissions (some can create, others cannot), and the admin must stay in full control before a proposal touches the real database.
The solution
A 5-stage pipeline: extract, unify, compare, apply (plus an internal db-extract). Each stage produces typed artifacts kept for audit. The deterministic engine decides first; the LLM is only a reviewer that can downgrade an action to noop or drop a field change — it can never introduce a deletion or a creation.
Every decision is traced (decisionSource: rule | ai), runs form a parent-child tree, and the admin validates each proposal before the final apply.
The pipeline
Each stage writes its artifacts and its run row to Postgres. The UI surfaces progress, precondition errors (NoIsmSourcesConfiguredError, NoIsmCompareInputsError, etc.) and proposal review.
01Extract
Stage 1Internal DB snapshot plus one child run per configured source (web scraping, map parsing, PDF). Falls back to bootstrap metadata if full extraction fails.
02Unify
Stage 2Candidate clustering via Jaccard name similarity plus weighted hints (terminal, level, zone, area, location). Threshold fixed at 0.72, ambiguity margin at 0.06.
03Compare
Stage 3A deterministic decision tree yields proposals (create/update/delete/noop) enriched with top-5 lookalike shortlists for creates. An LLM reviews and may only downgrade.
04Apply
Stage 4Accepted proposals are materialized in a transaction: space creation, parent hierarchy resolution, lazy creation of unknown taxonomies, upserts of labels, brands, audiences.
05Review
Cross-cuttingEvery proposal has a reviewStatus (pending / accepted / rejected / applied). Operators accept, reject, or ignore before apply touches production data.
Design patterns
- Rules first, AI as reviewer: the LLM can only downgrade a decision, never escalate it
- Sanitization before diff: strip placeholders ('n/a', 'unknown'), extract embedded URLs, drop position keywords from freetext
- Permission-gated actions: each source can independently forbid create/update/delete
- Run lineage: parent-child tree with knowledge_resource_id (null = internal DB source)
- Lazy taxonomy creation: apply creates new space types on the fly if a draft references an unknown code
- Immutable artifacts: candidates, unified candidates, and proposals are preserved for audit
Tech stack
- TanStack Router (file-based, nested routes)
- TanStack Query
- Drizzle ORM + PostgreSQL
- OpenAI GPT (structured outputs with Zod schemas)
- Zod for end-to-end validation
- Turborepo + Bun workspaces
Outcomes
Pipeline running in production for the client. Operators moved from manual hour-long reconciliation passes to reviewing pre-decided proposals.
Zero hallucinated deletions thanks to the deterministic safety net, and every decision stays auditable via the (run_id, decisionSource) pair.