Overview¶
Cartex is a Python pipeline that enriches construction schedule rows with data from auxiliary tables, general notes, legend diagrams, and dimension cards extracted from architectural PDF documents.
The problem¶
Construction takeoff documents — window schedules, door schedules, curtain wall schedules — contain a main table of line items alongside auxiliary reference tables, general notes, legend diagrams, and dimension detail drawings. A human estimator reads the main schedule row, cross-references auxiliary tables by code, applies rules from general notes, matches style codes to legends, and extracts dimensions from detail cards. This process is manual, error-prone, and slow.
Cartex automates the cross-referencing step. Given structured extraction data and a user-defined output schema, it produces one enriched row per schedule item with every target column filled from the best available source.
Where Cartex fits¶
Cartex is the enrichment layer in a two-stage pipeline. Cato-v2 owns extraction — it converts PDF pages into structured ExtractionResult objects via Gemini vision AI. Cartex owns enrichment — it takes those extraction results and fills a user-defined takeoff template.
The following diagram shows the end-to-end flow.
flowchart LR
PDF["PDF page"]
EXT["Extractor<br/>(Cato-v2)"]
ER["ExtractionResult"]
ENR["Enricher<br/>(Cartex)"]
OUT["list[EnrichedRow]"]
PDF --> EXT --> ER --> ENR --> OUT
Key design decisions¶
Specialist routing over monolithic prompts. Rather than sending all extraction data to a single Gemini prompt, Cartex routes to focused specialist prompts based on what evidence the extraction contains. Each specialist handles one enrichment strategy (auxiliary table lookups, text rule application, legend matching, etc.) and runs concurrently. A deterministic merge step combines their outputs.
Schema-driven output. The user provides a UserTableSchema that defines the target columns. Every specialist produces rows conforming to that schema. The merge step ensures every schema column appears in every output row, even if empty.
Row identity preservation. Each main schedule row receives a __row_id__ derived from its primary key column. Specialists must echo this ID back verbatim. Rows that no specialist produces output for are recovered as empty EnrichedRow objects with confidence 0.0 — the pipeline never silently drops rows.