← All posts

Reading GAEB files in Python with pyGAEB

A practical guide to parsing GAEB DA XML files in Python with the open-source pyGAEB library — from a one-line parse to iterating items, validation, and writing a bid back.

GAEB DA XML is the standard for exchanging construction bills of quantities in Germany — and parsing it by hand is miserable. Multiple schema versions (2.0 through 3.3), a dozen exchange phases, and XML that is happy to hide an XXE payload or a billion-laughs bomb. pyGAEB is an open-source (MIT) Python library that turns all of that into one clean, typed model. Here is how to actually use it.

Install

# Core parser + writer + export, zero LLM dependencies
pip install pyGAEB

# Optional: LLM-powered item classification (100+ providers via LiteLLM)
pip install pyGAEB[llm]

Parse any file with one call

You do not need to know the version or phase up front — pyGAEB auto-detects both:

from pygaeb import GAEBParser

doc = GAEBParser.parse("tender.X83")   # DA XML 3.x
doc = GAEBParser.parse("old.D83")      # DA XML 2.x — same call

print(doc.source_version)              # SourceVersion.DA_XML_33
print(doc.exchange_phase)              # ExchangePhase.X83
print(doc.grand_total)                 # Decimal("1234567.89")

Note the Decimal — money and quantities are never floats. That matters the moment you sum a few thousand positions.

Iterate the items

iter_items() works across every document kind — procurement, trade, cost, and quantity determination — so you can write generic code:

for item in doc.iter_items():
    print(item.oz)            # "01.02.0030"  — ordinal number
    print(item.short_text)    # "Mauerwerk der Innenwand…"
    print(item.qty)           # Decimal("1170.000")
    print(item.unit)          # "m2"
    print(item.unit_price)    # Decimal("45.50")
    print(item.total_price)   # Decimal("53235.00")

Validate what you parsed

By default pyGAEB is lenient — it keeps parsing and collects issues so you can decide what to do with them. You can also fail fast, or register your own rules:

from pygaeb import GAEBParser, ValidationMode

# Lenient (default): collect warnings, keep going
doc = GAEBParser.parse("tender.X83")
for issue in doc.validation_results:
    print(issue.severity, issue.message)

# Strict: raise on the first ERROR
doc = GAEBParser.parse("tender.X83", validation=ValidationMode.STRICT)

Price it and write the bid back

The most common real-world task: you receive a D83/X83 request, fill in unit prices, and return a D84/X84 bid with the identical structure. That round-trip is a first-class operation:

from pygaeb import GAEBParser, GAEBWriter, ExchangePhase
from decimal import Decimal

doc = GAEBParser.parse("tender.X83")
item = doc.award.boq.get_item("01.02.0030")
item.unit_price = Decimal("48.00")

GAEBWriter.write(doc, "bid.X84", phase=ExchangePhase.X84)

Because the writer starts from the original document, the returned bid keeps the exact positions and identifiers the client's software expects — no structural drift.

Convert between versions

Received a 2.x file but your downstream tooling only speaks 3.3? Or need to downgrade for a partner on an older system?

from pygaeb import GAEBConverter, SourceVersion

report = GAEBConverter.convert("old.D83", "modern.X83")
report = GAEBConverter.convert(
    "tender.X83", "compat.X83",
    target_version=SourceVersion.DA_XML_32,
)
print(report.items_converted, report.has_data_loss)

The has_data_loss flag is honest about what a downgrade cost you — useful to log rather than discover later.

Export for humans

For quick analysis or handing data to non-developers:

from pygaeb.convert import to_json, to_csv

to_json(doc, "boq.json")   # full nested BoQ tree
to_csv(doc, "items.csv")   # flat item table with classification columns

Where this fits

pyGAEB is the parsing and writing foundation — the layer that means software (and estimators) stop caring which format a tender arrived in. It is what the DatumOS platform is built on: the same engine, plus classification, pricing, and a review workflow on top.

It is MIT-licensed and on GitHub — issues, PRs, and edge-case sample files are all welcome. If you are wrestling with GAEB in Python, start with GAEBParser.parse() and go from there.

Not writing code? pyGAEB now also ships a Model Context Protocol server, so you can ask an AI assistant about a tender file in plain language — no Python required.

Comments

Comments are reviewed before they appear.

Loading comments…

    Only used if we need to reply to you directly.

    Your name and comment will be published after review. Privacy policy.