{
  "markdown": "# acikpoz\n\n<!-- mcp-name: io.github.gulmezeren2-byte/acikpoz -->\n\n**Turkish public construction unit prices, turned from a PDF into data you can compute with.**\n\n[![Python](https://img.shields.io/badge/python-3.10%2B-blue)](pyproject.toml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n\nEvery public construction job in Turkey is priced against a government catalog: the ÇŞB\n(Çevre, Şehircilik ve İklim Değişikliği Bakanlığı) *birim fiyat* books — thousands of unit\nprices (a poz: a code, a description, a unit, a price) that estimators, contractors and\nauditors all read. They ship as long PDFs. acikpoz turns those pages back into structured\nrecords, deterministically, so a cost estimate or a tender preparation can be *computed*\ninstead of copied by hand.\n\nIt is disciplined geometry, not a language model: it groups the words on a page into visual\nrows and reads the **price column's position from the page header** rather than guessing a\nfixed spot. That one decision is what makes it trustworthy — some sections (Sıhhi Tesisat,\nsay) print two numbers per row, the real *Birim Fiyat* and a separate *Montaj Bedeli*, and a\nnaive parser silently reads the wrong one. acikpoz reads the header, so it reads the right\ncolumn.\n\nPaired with [ihalent](https://github.com/gulmezeren2-byte/ihalent) (which structures tender\n*results*), acikpoz covers the other half of Turkey's public construction economy: the\n*prices* the results are measured against.\n\n## The one rule it never breaks\n\nA price is only ever a number the catalog actually printed in the price column. It never\ncoerces, never borrows a neighbour's figure, never guesses. Two consequences, both on\npurpose:\n\n- A **group-header poz** — a category title like `EVİYELER:` whose sub-items carry the real\n  prices — has no price of its own. acikpoz leaves its `fiyat` as `None` and flags\n  `is_group_header`. It does not invent a zero.\n- A poz the catalog left without a printed price is a **gap**, surfaced and counted, not\n  hidden. Every result reports its coverage: how many pozes were priced, how many were\n  headers, how many were genuine gaps.\n\nHalf the value of a cost tool is refusing to make up the numbers the source did not print.\nThat is the same honesty discipline as [andon](https://github.com/gulmezeren2-byte/andon)\nand ihalent.\n\n## Install\n\n```\npip install acikpoz          # add [mcp] for the MCP server: pip install \"acikpoz[mcp]\"\n```\n\n## Quick start\n\nPoint it at an official catalog PDF you have (acikpoz ships the parser, **not** the data):\n\n```\nacikpoz parse bf2026.pdf --pages 8-20\nacikpoz parse bf2026.pdf --json > pozes.jsonl     # one poz per line, for pipelines\nacikpoz parse bf2026.pdf --csv pozes.csv          # Excel-ready (utf-8-sig, Turkish text)\nacikpoz parse bf2026.pdf --group 25               # only Sıhhi Tesisat pozes\nacikpoz parse bf2026.pdf --priced-only            # drop headers and gaps\n```\n\nThe table view — this is a real run against the published 2026 catalog, not a\nmock-up:\n\n![acikpoz parsing one page of the official 2026 ÇŞB catalog: nineteen pozes with codes, units, prices and Turkish descriptions, three of them showing a dash because the catalog printed no price; below it, validate reporting sixteen warnings including five units the parser is not confident about](https://raw.githubusercontent.com/gulmezeren2-byte/acikpoz/main/docs/demo.svg)\n\nTwo things in that picture are the whole point:\n\n- Three rows carry `-` in the price column. The catalog printed no price for\n  them, so acikpoz prints none either — they are counted as **gaps**, not filled\n  in from a neighbour.\n- `validate` then flags what the parse is *not sure about*: on this page five\n  units (`+2000`, `azami`, `Yapı`, `-30°C`, `2,5`) are fragments of description\n  text that landed in the unit column. The extraction is imperfect on some\n  layouts, and the tool reports that itself rather than handing you a clean-looking\n  table with wrong units in it.\n\nReproduce it — the page is chosen, nothing else is:\n\n```\nacikpoz parse    bf2026.pdf --pages 400\nacikpoz validate bf2026.pdf --pages 400\n```\n\nThe catalog is the ministry's own file, [published here][catalog]. The image is\nregenerated by [`scripts/make_demo_svg.py`](scripts/make_demo_svg.py), which\ndownloads that PDF and runs the same code the CLI runs.\n\n[catalog]: https://webdosya.csb.gov.tr/v2/yfk/2026/01/1-BF-202619011535-20260119155143.pdf\n\nThe **grade** (excellent/good/fair/poor) is a glanceable confidence signal, the\nway camelot exposes accuracy: below `good`, review the pages before trusting the\noutput. `--json` includes `price_parse_rate` and `grade` per parse.\n\nOr from Python:\n\n```python\nfrom acikpoz import parse_catalog\n\nresult = parse_catalog(\"bf2026.pdf\", pages=range(8, 20))\nfor p in result.pozes:\n    if p.is_priced:\n        print(p.poz_no, p.birim, p.fiyat)\nprint(result.to_dict()[\"counts\"])   # priced / group_headers / price_gaps\n```\n\n## How it works\n\n1. **Rows.** Words are grouped into visual rows by vertical position (a few points of\n   tolerance), then sorted left-to-right.\n2. **Price column, from the header.** The `Fiyat` header word on the right (x > 400) gives\n   the price column's x. A stray \"fiyat\" in a left-column description can't be mistaken for\n   it.\n3. **Cells.** For each poz row: the leftmost cell is the poz code; the price is the\n   number-shaped token nearest the price-column x; the unit sits just left of it; the rest is\n   the description. Indented continuation lines extend the running description (and can carry\n   a price that spilled over).\n4. **Group headers.** A price-less poz whose description ends in `:` is a category title —\n   flagged, not treated as a gap.\n\n## Compare two catalog years\n\nCatalogs are reissued regularly; the question estimators and auditors track by hand is *how\ndid this year's rates move from last year's?* `acikpoz diff` answers it — it joins two years\nby poz code and classifies each change:\n\n```\nacikpoz diff bf2025.pdf bf2026.pdf --pages 8-400\nacikpoz diff bf2025.pdf bf2026.pdf --tolerance 1   # hide sub-1-TL rounding noise\nacikpoz diff bf2025.pdf bf2026.pdf --json\n```\n\nIt reports price moves (with Δ and %Δ), added and removed pozes, unit changes, and pozes\nthat gained or lost a printed price — plus the **mean price %-change** for the year. As far\nas the research found, no other open tool does year-over-year diffing for ÇŞB catalogs.\n\n## Validate a parse\n\nBefore a parsed catalog feeds a cost estimate, it helps to know it is clean. `acikpoz\nvalidate` runs deterministic quality rules over the pozes — no ML, no fixing, only surfacing:\n\n```\nacikpoz validate bf2026.pdf --pages 8-400\nacikpoz validate bf2026.pdf --json\n```\n\nIt flags duplicate poz codes, malformed codes, non-positive prices (**errors**), and priced\npozes with no unit or a unit outside the known set (**warnings**). It exits non-zero on any\nerror, so it can gate a pipeline (`acikpoz validate … && build-estimate`). This is also how\nacikpoz stays honest about its own limits: in sections that print the unit once on a group\nheader and let the rows inherit it (Sıhhi Tesisat), per-row unit detection is weak, and\n`validate` says so rather than hiding it. Price, poz code and description stay reliable.\n\n## Using acikpoz with AI agents\n\nAn MCP server (`pip install 'acikpoz[mcp]'`, then `acikpoz-mcp`) exposes three tools:\n`parse_catalog` (a PDF → structured pozes with honest coverage), `diff` (two catalog years\n→ classified changes), and `validate` (a PDF → quality findings). The agent gets structured\ndata back, not prose it has to parse. Pair it with ihalent and an agent can reason across\nboth a tender's result *and* the unit prices it was measured against.\n\n```jsonc\n// e.g. Claude Desktop / Claude Code mcp config\n{ \"mcpServers\": { \"acikpoz\": { \"command\": \"acikpoz-mcp\" } } }\n```\n\n## Scope, honestly\n\n- **It reads the standard catalog layout.** The header-driven column detection handles the\n  common single- and two-price-column pages well; an unusual layout may leave more gaps —\n  which it reports rather than papering over. If a section parses badly, that's a bug worth a\n  sample.\n- **It is a parser, not a price database.** It does not bundle or redistribute the catalog.\n  You bring the official PDF; acikpoz turns your copy into data.\n- **Prices are nominal, as printed.** No inflation adjustment is baked in — that's an\n  analysis choice the caller makes knowing the year.\n\n## Data, and why the PDFs aren't here\n\nThe ÇŞB catalogs are official public documents, but this repository does not redistribute\nthem: it ships the parser and nothing else, and `.gitignore` keeps `*.pdf` out. Point acikpoz\nat the catalog you obtained from the official source. This is the same line ihalent draws —\nown the tool, not the data.\n\n## How this project is built\n\nI'm an industrial engineer working in construction; I read these catalogs. I designed the\nparsing approach — geometry over machine learning, honest gaps over invented numbers — and I\nreview every line; I use AI agents heavily for implementation speed, and the commit trailers\nsay so. The contract is the tests: they encode the exact word geometry a real page emits,\nincluding the two-column Sıhhi Tesisat trap and the group-header rule, so green tests mean the\nparser handles the real thing.\n\n## Related\n\n- **[ihalent](https://github.com/gulmezeren2-byte/ihalent)** — the other half of the Turkish public-procurement picture. `acikpoz` reads the unit-price catalogs that say what work *should* cost; `ihalent` reads the tender result notices that say what it *was* awarded for, and at what discount. Same discipline: every figure traceable to its source, nothing invented.\n\nMore tools by [Eren Gülmez](https://github.com/gulmezeren2-byte?tab=repositories).\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n",
  "bytes": 9795,
  "sha": "16cdb95ef500705e40cfa3f79a0fd5c44c23129ed42a708446369c985d73507f",
  "repo_slug": "gulmezeren2-byte/acikpoz",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_gulmezeren2_byte_acikpoz_954d357b/readme"
}