Case study · 02 / 03

Project overview
A web application that analyses the planning and investment context of a Polish land parcel. It combines GIS data and spatial-planning datasets, shows the result on a map and produces a PDF report with a verifiable audit package.
- Source
- Public repository
Problem
Assessing a plot of land in Poland means collecting scattered public data (local and general plans, flood risk, protected areas, terrain, roads and neighbouring buildings) from separate registers, each with its own coverage and freshness. The dangerous mistake is reading a missing answer as “no restriction”. The platform automates the collection and keeps explicit what was checked, what was not, and where each value came from.
Product and users
For: anyone assessing the investment potential of a parcel in Poland. What the application lets them do:
- Look up a parcel by its identifier or by address
- See planning zones (POG), local plans (MPZP) and other layers on a map
- Read a status for every section: checked, partial or no coverage
- Download a PDF report and an audit package with a SHA-256 manifest
- Verify the package independently with a short Python script
My contribution
GitHub lists a single contributor for this repository: me. Backend, frontend, data imports, tests and documentation are my own work.
- Backend
- FastAPI service split into domain modules for planning, imports, reports and audit export, with Alembic migrations.
- Frontend and map
- Next.js and React application with a MapLibre map, an object inspector and the analysis result panel.
- Data and imports
- PostGIS schema and importers for planning acts (POG) and BDOT10k releases, parallel, with progress and resume after interruption.
- Quality and delivery
- CI gates for backend and frontend, locked and hashed dependencies, and an evaluation against a reference corpus of 30 parcels.
Architecture
A FastAPI backend with domain modules reads planning data from PostGIS and calls live public services for the rest. A Next.js application renders the map and results. Importers load planning acts and BDOT10k releases into PostGIS ahead of time.
Next.js 16 · React 19
MapLibre map · result panel
Next.js 16
MapLibre map
Python · domain modules
Analysis · reports · audit export
Python · FastAPI
Analysis · reports
Live GIS and planning services
Parcel lookup · flood · terrain
Live GIS and
planning services
Planning zones · vector tiles
Analysis snapshots
PostgreSQL 16
Zones · snapshots
POG acts · BDOT10k releases
Parallel · resumable
POG · BDOT10k
Resumable
Planning acts are read from local imports, so a municipality that was never imported shows as “no coverage” rather than as an empty answer. Live public services supply the rest, and their answers are cached for a short time when the result is only partial.
Key engineering challenges
Honest status when data is missing
Coverage differs by municipality and source. Sections report checked, partial or no coverage, and a completeness bar shows how much of the scope was actually verified.
Importing national planning data
Planning acts come from a public register. Imports run in parallel, report progress with an estimate and resume after an interruption.
Reading planning documents
Local-plan texts are unstructured. A deterministic engine with calibrated confidence extracts values; an optional LLM path only proposes candidates that need a verified quote.
Reproducible output
Reports come from immutable snapshots, and the audit ZIP carries a manifest with SHA-256 hashes that a script using only Python 3 can verify.
Technical decisions and trade-offs
| Decision | Why | Trade-off |
|---|---|---|
| Missing data is never shown as the absence of a restriction | For an investor, an unchecked source and a confirmed lack of restrictions mean opposite things. | Most analyses report “partial” until the relevant data has been imported. |
| Docker Compose as the default way to run | One command starts the backend, the frontend and PostGIS, with no local PostgreSQL to install. | It runs on a single machine. There is no hosted instance. |
| LLM extraction off by default, output always labelled | A model reading is a candidate: it needs a verified quote and is marked as an automatic reading that requires confirmation. | Evaluation against live model calls is a manual step outside CI, because it costs money. |
| Locked, hashed dependencies and pinned base images | The production image is reproducible and contains no test tooling. | Updates are done by hand, with audits catching vulnerable versions. |
Stack
Backend
- Python
- FastAPI
- SQLAlchemy
- GeoAlchemy
- Alembic
Frontend & maps
- Next.js 16
- React 19
- TypeScript
- MapLibre
Spatial data & infrastructure
- PostgreSQL 16
- PostGIS 3.4
- GIS / spatial data processing
- Docker Compose
Testing and quality
Backend and frontend gates run in CI and in containers, so a clean machine needs only Docker. The analysis is informational and the README says it is not an official document.
- Coverage gates
- Backend tests run with a minimum of 80% coverage (pytest), alongside ruff, mypy with a baseline and pip-audit.
- Kinds of tests
- pytest for the backend, Vitest and React Testing Library for the frontend, plus a weekly live smoke run against the real services.
- Evidence limits
- All 30 reference parcels return a partial status until planning data is imported. MPZP extraction with a language model is experimental.
Screenshots
Pinned screenshots from a local run of the project. It is not deployed, so there is no live demo.






Source code
Public repositoryCase studyThis page
igorkadziela.dev/projects/parcel-analysis