Skip to main content

Structured, machine-readable data from Turkish public construction unit-price (birim fiyat) catalogs.

Project description

acikpoz

Turkish public construction unit prices, turned from a PDF into data you can compute with.

Python License: MIT

Every public construction job in Turkey is priced against a government catalog: the ÇŞB (Çevre, Şehircilik ve İklim Değişikliği Bakanlığı) birim fiyat books — thousands of unit prices (a poz: a code, a description, a unit, a price) that estimators, contractors and auditors all read. They ship as long PDFs. acikpoz turns those pages back into structured records, deterministically, so a cost estimate or a tender preparation can be computed instead of copied by hand.

It is disciplined geometry, not a language model: it groups the words on a page into visual rows and reads the price column's position from the page header rather than guessing a fixed spot. That one decision is what makes it trustworthy — some sections (Sıhhi Tesisat, say) print two numbers per row, the real Birim Fiyat and a separate Montaj Bedeli, and a naive parser silently reads the wrong one. acikpoz reads the header, so it reads the right column.

Paired with ihalent (which structures tender results), acikpoz covers the other half of Turkey's public construction economy: the prices the results are measured against.

The one rule it never breaks

A price is only ever a number the catalog actually printed in the price column. It never coerces, never borrows a neighbour's figure, never guesses. Two consequences, both on purpose:

  • A group-header poz — a category title like EVİYELER: whose sub-items carry the real prices — has no price of its own. acikpoz leaves its fiyat as None and flags is_group_header. It does not invent a zero.
  • A poz the catalog left without a printed price is a gap, surfaced and counted, not hidden. Every result reports its coverage: how many pozes were priced, how many were headers, how many were genuine gaps.

Half the value of a cost tool is refusing to make up the numbers the source did not print. That is the same honesty discipline as andon and ihalent.

Install

pip install acikpoz          # add [mcp] for the MCP server: pip install "acikpoz[mcp]"

Quick start

Point it at an official catalog PDF you have (acikpoz ships the parser, not the data):

acikpoz parse bf2026.pdf --pages 8-20
acikpoz parse bf2026.pdf --json > pozes.jsonl     # one poz per line, for pipelines
acikpoz parse bf2026.pdf --csv pozes.csv          # Excel-ready (utf-8-sig, Turkish text)
acikpoz parse bf2026.pdf --group 25               # only Sıhhi Tesisat pozes
acikpoz parse bf2026.pdf --priced-only            # drop headers and gaps

The table view (shape shown with illustrative values, not real catalog figures):

poz          birim  fiyat TL   tanım
15.100.1003  m³        54,88    1 m³ taşın taşıtlara yüklenmesi
15.100.1005  Ton      434,05    1 ton çelik borunun taşıtlara yüklenmesi
25.110.1000  -             -    ALATURKA HELA TESİSATI:            (group header)

407 priced · 6 group headers · 43 price gaps · grade good (90% of non-header
pozes priced) · 16 page(s) read.

The grade (excellent/good/fair/poor) is a glanceable confidence signal, the way camelot exposes accuracy: below good, review the pages before trusting the output. --json includes price_parse_rate and grade per parse.

Or from Python:

from acikpoz import parse_catalog

result = parse_catalog("bf2026.pdf", pages=range(8, 20))
for p in result.pozes:
    if p.is_priced:
        print(p.poz_no, p.birim, p.fiyat)
print(result.to_dict()["counts"])   # priced / group_headers / price_gaps

How it works

  1. Rows. Words are grouped into visual rows by vertical position (a few points of tolerance), then sorted left-to-right.
  2. Price column, from the header. The Fiyat header word on the right (x > 400) gives the price column's x. A stray "fiyat" in a left-column description can't be mistaken for it.
  3. Cells. For each poz row: the leftmost cell is the poz code; the price is the number-shaped token nearest the price-column x; the unit sits just left of it; the rest is the description. Indented continuation lines extend the running description (and can carry a price that spilled over).
  4. Group headers. A price-less poz whose description ends in : is a category title — flagged, not treated as a gap.

Compare two catalog years

Catalogs are reissued regularly; the question estimators and auditors track by hand is how did this year's rates move from last year's? acikpoz diff answers it — it joins two years by poz code and classifies each change:

acikpoz diff bf2025.pdf bf2026.pdf --pages 8-400
acikpoz diff bf2025.pdf bf2026.pdf --tolerance 1   # hide sub-1-TL rounding noise
acikpoz diff bf2025.pdf bf2026.pdf --json

It reports price moves (with Δ and %Δ), added and removed pozes, unit changes, and pozes that gained or lost a printed price — plus the mean price %-change for the year. As far as the research found, no other open tool does year-over-year diffing for ÇŞB catalogs.

Using acikpoz with AI agents

An MCP server (pip install 'acikpoz[mcp]', then acikpoz-mcp) exposes two tools: parse_catalog (a PDF → structured pozes with honest coverage) and diff (two catalog years → classified changes). The agent gets structured data back, not prose it has to parse. Pair it with ihalent and an agent can reason across both a tender's result and the unit prices it was measured against.

// e.g. Claude Desktop / Claude Code mcp config
{ "mcpServers": { "acikpoz": { "command": "acikpoz-mcp" } } }

Scope, honestly

  • It reads the standard catalog layout. The header-driven column detection handles the common single- and two-price-column pages well; an unusual layout may leave more gaps — which it reports rather than papering over. If a section parses badly, that's a bug worth a sample.
  • It is a parser, not a price database. It does not bundle or redistribute the catalog. You bring the official PDF; acikpoz turns your copy into data.
  • Prices are nominal, as printed. No inflation adjustment is baked in — that's an analysis choice the caller makes knowing the year.

Data, and why the PDFs aren't here

The ÇŞB catalogs are official public documents, but this repository does not redistribute them: it ships the parser and nothing else, and .gitignore keeps *.pdf out. Point acikpoz at the catalog you obtained from the official source. This is the same line ihalent draws — own the tool, not the data.

How this project is built

I'm an industrial engineer working in construction; I read these catalogs. I designed the parsing approach — geometry over machine learning, honest gaps over invented numbers — and I review every line; I use AI agents heavily for implementation speed, and the commit trailers say so. The contract is the tests: they encode the exact word geometry a real page emits, including the two-column Sıhhi Tesisat trap and the group-header rule, so green tests mean the parser handles the real thing.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

acikpoz-0.1.0.tar.gz (127.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

acikpoz-0.1.0-py3-none-any.whl (18.2 kB view details)

Uploaded Python 3

File details

Details for the file acikpoz-0.1.0.tar.gz.

File metadata

  • Download URL: acikpoz-0.1.0.tar.gz
  • Upload date:
  • Size: 127.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.27 {"installer":{"name":"uv","version":"0.11.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for acikpoz-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2a1a87928e001f55122c8efb7a235a514b5fcd6a38ae7321d05cd4695cb535f4
MD5 aef7c36b06a95c8a7ad8e31025158741
BLAKE2b-256 50e22e7cba05b0f83332edf9f54b598dc7626c4377d15b52daa69a3432232806

See more details on using hashes here.

File details

Details for the file acikpoz-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: acikpoz-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.27 {"installer":{"name":"uv","version":"0.11.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for acikpoz-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0a17564f8636613fcb652d66ae7566a4a6a2675c0a5c364ce56c7d27b99e3e06
MD5 35cf93fa5719561c185091d8c87eb790
BLAKE2b-256 c89d61e2f2c446893ce32e934b02f8542becdf115adf6b994f433b7de9268972

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page