Persistence and REST service layer over the AEC framing pipeline. Turns a batch pipeline into an operable service: it runs floor plans as jobs, persists every run and its results to PostgreSQL as a queryable read-model, and serves them over a REST API with structured logging and health checks.
floorplan-pipeline -> floorplan-service -> { PostgreSQL read-model, REST API }
(batch orchestrator) (jobs + persistence (query runs, artifacts,
+ service) validation results)
This is the eighth repo in the AEC portfolio. The other seven produce and validate building data; this one makes that production operable -- something you can submit work to, query, retry, and monitor.
The pipeline works: give it a floor plan and it emits walls, panels, framing, a
bill of materials, an assembly sequence, and a valid IFC4 model. But it runs as a
function call that writes files to a folder. There is no record of what ran, no
way to ask "show me every job that produced an invalid IFC", no HTTP surface, and
no health signal. floorplan-service adds exactly that layer and nothing the
pipeline already owns.
This is the question an interviewer asks about a polyrepo, so here is the direct answer.
floorplan-pipeline is deliberately a thin orchestrator: its own ADR-001
says all domain logic lives in the components and the pipeline only wires stages
and validates seams. Its dependency list is six domain packages and nothing else.
It is imported as an editable library and its tests run in seconds with no
external services.
A persistence and web layer has the opposite shape. It needs SQLAlchemy, Alembic,
FastAPI, a database driver, and -- to test honestly -- a running PostgreSQL. Put
that inside floorplan-pipeline and every one of those concerns rides along with
the pure library: a heavier install, a slower test matrix, a Postgres service
container bolted onto a repo that today needs none, and a blurred line between
"the pipeline" and "operating the pipeline".
Keeping it separate holds three properties:
- The pipeline stays pure. It remains a library you can import and run without a database. Its existing test suite and CI are untouched.
- The dependency direction is honest.
floorplan-servicedepends onfloorplan-pipeline, never the reverse. Operational concerns sit downstream of the domain, where they belong. - The seam is explicit. The service calls the pipeline's public API
(
run_pipeline,validate_ifc) and persists what comes back. Swapping the execution model later (for example, a Prefect flow instead of an in-process background task) touches only this repo.
The cost is one more repo and the polyrepo checkout dance in CI. That cost is already paid across this portfolio and is worth the isolation.
| Repo | Role | Relationship to this repo |
|---|---|---|
aec-schema |
JSON Schema contracts + validators | The contracts are the source of truth. The database is a derived read-model; rows are written only after aec-schema validation passes. This repo never introduces a parallel validation path. |
wall-extract, panel-decompose, framing-synth, assembly-sequence, aec-ifc-export |
The five processing stages | Consumed transitively through the pipeline. This repo does not call them directly. |
floorplan-pipeline |
End-to-end orchestrator | The one thing this repo wraps. It runs the pipeline, then persists and serves the results. |
Persistence layer implemented (Stage 3). The package has:
- a PostgreSQL relational read-model (SQLAlchemy 2.0 async models) of the pipeline's
validated artifacts:
jobs,artifacts(file ref + sha256),run_skips,validation_errors, the normalized wall / opening / panel / member projection, and the assembly-sequence DAG edge table. The hierarchical artifact ids become real foreign keys; domain enums are derived from the aec-schema contracts. - Alembic migrations (versioned, reversible).
- an ingestion path that projects a completed run into the read-model, gated on aec-schema validation (a run whose artifact fails validation is never projected).
- tests against a real Postgres, and CI with a Postgres service container.
Still to come:
- the FastAPI application (
POST /jobs,GET /jobs/{id}, retry,/health), - structured logging with a per-job correlation id.
docker run -d --name pg -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=floorplan_test -p 5432:5432 postgres:16
export DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/floorplan_test
make setup
python -m alembic upgrade head
make test
Without DATABASE_URL the database tests skip and only the smoke tests run.
Design decisions are recorded in docs/decisions.md.
make setup # editable-install the contract, components, pipeline, then this package
make lint # ruff
make test # pytest
Requires Python 3.11+ and the sibling repos checked out alongside this one, the same local layout the rest of the portfolio uses.