GAEB DA XML in pure Rust: read German construction tenders, bids and orders, validate them, write schema-valid X83, X84 and X86, and turn a tender into a priced bid. One core, a CLI, and pythonic bindings.
gaebboss.dev · Docs · PyPI · crates.io · Benchmarks
GAEB is how German construction procurement moves: the Leistungsverzeichnis leaves the planner as an X83, comes back from every bidder as an X84, and becomes an order as an X86. Reading it should not take a DOM, a float, or a port of somebody else's guesses. gaebboss is built from the official gaeb.de schemas and the Fachdokumentation: the typed model, the readers, the writers and the per-phase rules are generated from the XSDs, money and quantities are fixed-point, and the engine is a lenient reader that tolerates what real AVA exports do wrong and reports it instead of refusing. One core behind the gaebboss CLI and the gaebboss Python package.
Highlights
- Generated from the standard: 525 elements of DA XML 3.1, 3.2 and 3.3 typed, read and written straight from the vendored XSDs;
cargo xtask gen --checkfails CI when the code drifts from the schema (coverage). - Every phase, every version: X80 to X89 in DA XML 3.0 to 3.3 read losslessly; X83, X84 and X86 written in 3.2 and 3.3, element order and phase restrictions from the phase XSD, proven valid against the official schemas with lxml (conformance).
- Fastest GAEB implementation measured: 710 MB/s parsing real files from Python, about 215× pygaeb and 2.6× raw lxml; the 75 MB X81 in the corpus reads in 29 ms (benchmarks).
- No floats near money:
Decimal { mantissa, scale }throughout, kaufmännisch rounding and item, discount and category totals per Fachdokumentation 4.6.17. - Tender to bid:
Bid::from_tendertakes an X83, applies unit prices, price components, not-offered marks, free quantities, bidder text complements, discount and VAT, and writes the X84 the way the "Regeln für X84" demand it, in the version the tender came in. - Bill-of-quantities semantics: Ordnungszahlen formed from
BoQBkdn, item kinds (Grund-, Alternativ-, Bedarfs-, Pauschal-, Zulage-, Stundenlohnposition), categories, markup items, declared against computed totals. - Texts as text: long and short texts, bidder fields and placeholders projected to plain text or Markdown without a DOM.
- Lenient, and it says so: encodings repaired, mismatched tags closed, unknown vendor elements kept and re-emitted, every tolerated defect a
Warningwith a byte offset. Panics are denied by the workspace lints.
Install
pip install gaebboss # prebuilt abi3 wheels (CPython 3.12+), no toolchain required
cargo install gaebboss-cli # the `gaebboss` binary
Usage
gaebboss info tender.X83 # version, phase, project, bill, item and category counts
gaebboss items tender.X83 --tsv # OZ, kind, quantity, unit, short text; --counting drops what the total ignores
gaebboss text tender.X83 --oz 01.02.0010 --markdown
gaebboss json tender.X83 --compact # the whole document, decimals as strings
gaebboss validate bid.X84 --tender tender.X83 # exit 1 on errors; structure and ids checked against the tender
gaebboss roundtrip tender.X83 -o copy.X83 --pretty # parse and write back; --phase X84 --version 3.3 convert
gaebboss bid tender.X83 --prices prices.csv --bidder bidder.toml -o bid.X84
prices.csv is OZ;unit price (;, tab or ,, a - price marks an item not offered, an optional third column is the quantity of a free-quantity item). bidder.toml carries name1, street, pcode, city and optionally vat_percent and discount_percent.
from decimal import Decimal
import gaebboss
tender = gaebboss.Document("tender.X83") # or Document(data=raw_bytes, name="tender.X83")
print(tender.version, tender.phase, tender.project.name, tender.boq.name)
for item in tender.items(): # items(kind="counting") for what the total counts
print(item.oz, item.kind, item.quantity, item.unit, item.short_text)
print(item.long_text_markdown)
for warning in tender.warnings: # every tolerated defect, with its byte offset
print(warning.code, warning.offset, warning.message)
bid = gaebboss.Bid.from_tender(tender, {"name1": "Bau GmbH", "street": "Weg 1", "pcode": "12345", "city": "Berlin"})
for item in tender.items():
bid.price(item.id, Decimal("12.50"))
bid.not_offered(tender.items()[0].id)
bid.vat_percent(Decimal("19"))
x84 = bid.finish() # a Document; raises on unpriced items
open("bid.X84", "wb").write(x84.write())
print(x84.validate_against(tender)) # findings: severity, code, oz, message
use gaebboss_core::bid::{Bid, Bidder};
use gaebboss_core::decimal::Decimal;
use gaebboss_core::{Document, validate};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let tender = Document::open(std::path::Path::new("tender.X83"))?;
for item in tender.items() {
println!("{} {:?} {:?} {}", item.oz, item.kind(), item.quantity(), item.short_text());
}
let totals = tender.computed_totals();
let bidder = Bidder { name1: "Bau GmbH".into(), ..Bidder::default() };
let mut bid = Bid::from_tender(&tender, bidder)?;
for item in tender.items() {
bid.price(item.id(), Decimal::parse("12.50")?)?;
}
let x84 = bid.finish()?;
std::fs::write("bid.X84", x84.write(&x84.x84_options())?)?;
println!("{totals:?} {:?}", validate::validate_against_tender(&x84, &tender));
Ok(())
}
More: the layers underneath, writing options, JSON
The typed model is one struct per schema type under gaebboss_core::model, generated from the 3.3 Lib and extended with what only 3.1 and 3.2 carry; strings are spans into the input, numbers are Decimal, and every struct keeps the children it does not know as raw nodes that are written back verbatim. Below it sit bytes::normalize (BOM, UTF-16, Latin-1, CP1252 and repair), lexer::lex (one memchr pass into a flat tape of spans) and version::detect (namespace, GAEBInfo/Version, Award/DP and the file extension).
use gaebboss_core::Document;
use gaebboss_core::write::WriteOptions;
use gaebboss_core::version::{Phase, Version};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let doc = Document::open(std::path::Path::new("tender.X83"))?;
let (bytes, report) = doc.write_with_report(&WriteOptions {
version: Version::V33,
phase: Phase::X84,
indent: Some(2),
prog_system: None,
strict: false,
})?;
println!("{} bytes, dropped {:?}, missing {:?}", bytes.len(), report.dropped, report.missing);
println!("{}", gaebboss_core::json::to_json(&doc));
Ok(())
}
strict: true turns a missing schema-required element into an error instead of a report line; real X83 files miss them often enough that reporting is the default. Document::edit hands out the typed tree for in-place changes; write applies the target phase's restrictions (what X84 drops from X83, what it makes mandatory) and the per-version element order.
From tender to bid
What the "Regeln für X84" require, Bid does: the project, the bill and its structure are taken over unchanged from the tender, CTR becomes mandatory, OWN, Requester, construction site and owner texts are dropped, every BoQCtgy and the BoQInfo get totals, a not-offered item carries no unit price, a quantity is only written for free-quantity items, items that do not count toward the total (Bedarfspositionen ohne Gesamtbetrag, Alternativpositionen) carry no item total, and a price of 0.00 is transmitted as such. Item totals are round2(Qty × UP), discounts apply to the rounded total and are rounded again, categories sum rounded item totals; the worked example of the Fachdokumentation (73.789 × 9.77 = 720.92, 710.11 after 1.5 %) is a unit test.
A 3.0 or 3.1 tender is answered in 3.2, the oldest version with phase schemas; 3.2 and 3.3 tenders are answered in their own version.
Validation
validate reports what a receiving AVA system would reject or recompute: elements a phase does not allow or requires, duplicate or dangling ids, Ordnungszahlen that are not unique, not ascending or too long, decimals with more places than the schema gives them, item totals that do not match quantity times price, category and bill totals that do not match their items, unit prices on items not offered, quantities on non-free items in an X84. validate --tender adds the X84 against its X83: items missing or added, ids and structure that differ.
Benchmarks
Against pygaeb (the fullest open GAEB reader, Python on lxml) and raw lxml over 40 real-world files (MB/s, higher is faster):
gaebboss is the fastest on both operations, including against lxml, which builds no GAEB model at all: 710 MB/s opening and parsing against lxml's 269 and pygaeb's 3.3, and 389 MB/s walking every item with its Ordnungszahl, quantity and short text against lxml's 217 and pygaeb's 3.3.
Method and fine print
Best-of-3 per file over the first 40 files of a 164-file corpus of third-party exports (22 MB, DA XML 3.0 to 3.3), measured on an aarch64 workstation (Cortex-X925, 20 cores, single-threaded throughout) from the installed Python packages, every row in one session. The corpus is not redistributable; reproduce on your own files with benchmarks/bench.py. The lxml rows parse the file and iterate Item elements reading RNoPart and span text: no Ordnungszahl formation, no kind, no decimals, so they are a floor for any lxml-based reader, not a competitor. pygaeb reads every text eagerly; its two rows coincide because parsing already does the work.
The Rust stages, from criterion on the same machine (details):
| stage | dense synthetic 10 MB X83 | 75 MB real X81 |
|---|---|---|
| tokenize | 2.36 GiB/s | 15.2 GiB/s |
| typed parse | 675 MiB/s | 2.59 GiB/s |
| write back | 497 MiB/s |
Over the whole corpus, cargo xtask corpus reads 189.6 MB at 866 MB/s with zero errors and writes it back at 421 MB/s.
Proof
- Every official sample (gaeb.de Musterdateien 3.1, BVBS Prüfdateien 3.3) and all 164 corpus files read with zero errors; writing what was read gives a byte-identical file, and a second round trip is idempotent.
make conformancewrites X81 to X84 files from the official fixtures and validates them against the official XSDs with lxml: 8 of 8 valid.- A failure-mode gallery (
tests/fixtures/failure-modes) pins the lenient paths: wrong encodings, mismatched tags, duplicated totals, vendor elements, depth bombs. cargo xtask gen --checkandcargo xtask coverage --checkkeep the generated code and the schema coverage report in step with the vendored schemas.
Layout
| path | what |
|---|---|
crates/gaebboss-core |
the engine: bytes, tape, decimal, generated model, readers, writers, profiles, bill semantics, texts, validation, bid, JSON |
crates/gaebboss-cli |
the gaebboss binary |
crates/gaebboss-py, python/gaebboss |
the Python package (pyo3, maturin) |
crates/gaebboss-testkit |
fixture builder and corpus discovery |
xtask |
cargo xtask gen (schemas to code), coverage, corpus |
standard/ |
the official XSDs, vendored unmodified with checksums |
tests/fixtures |
official sample files and the failure-mode gallery |
docs/ |
the mdBook |
make help lists the targets; make ci runs what the Rust CI runs. Real-world files for the corpus tests and make bench-py are found through GAEBBOSS_CORPUS.
Scope
Read: DA XML 3.0 to 3.3, phases X80 to X89. Written with phase rules: X83, X84, X86 in 3.2 and 3.3. Detected and named but not parsed: GAEB 90 (.d8x), GAEB 2000 (.p8x), DA XML 2.x with German element names, ÖNORM. The trade, cost and quantity phases (X31, X5x, X9x) and the Zeitvertrag phases are typed by the generated model but carry no business rules. See the limitations.
License
MIT or Apache-2.0, at your option. The schema files under standard/ remain the work of GAEB / DIN; see standard/README.md.
Metadata
Release files for gaebboss 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| gaebboss-0.1.1.tar.gz | 217.7 kB | With provenance | Details for gaebboss-0.1.1.tar.gz |
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| gaebboss-0.1.1-cp312-abi3-win_amd64.whl | CPython 3.12 | abi3 | Windows x86-64 | Details for gaebboss-0.1.1-cp312-abi3-win_amd64.whl |
| gaebboss-0.1.1-cp312-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | CPython 3.12 | abi3 | Linux glibc 2.17+ x86-64 | Details for gaebboss-0.1.1-cp312-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
| gaebboss-0.1.1-cp312-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl | CPython 3.12 | abi3 | Linux glibc 2.17+ ARM64 | Details for gaebboss-0.1.1-cp312-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
| gaebboss-0.1.1-cp312-abi3-macosx_11_0_arm64.whl | CPython 3.12 | abi3 | macOS 11.0+ ARM64 | Details for gaebboss-0.1.1-cp312-abi3-macosx_11_0_arm64.whl |
Total release size: 3.4 MB
Release files / gaebboss-0.1.1.tar.gz
| Download URL | gaebboss-0.1.1.tar.gz |
|---|---|
| Size | 217.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
943cfcdce60af62820e368b51ae5fd2c01b08434c71d0b2a1c3be82e71c89a5d
|
|
BLAKE2b-256 checksum How to use checksums |
705d2fabefc61e4c0867c71c0661ab203ec944f8c7129f249b152cd24acaa884
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / gaebboss-0.1.1-cp312-abi3-win_amd64.whl
| Download URL | gaebboss-0.1.1-cp312-abi3-win_amd64.whl |
|---|---|
| Size | 726.5 kB |
| Tags | CPython 3.12 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
88d89382dd76bb870a085ab7d709390555cfd8831613f2a160f4a23f95b1ccc4
|
|
BLAKE2b-256 checksum How to use checksums |
d4678fa6c657778f525b5b394705b6c3716ae311cb4920b44cbdeeb5b5db7f27
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / gaebboss-0.1.1-cp312-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | gaebboss-0.1.1-cp312-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 854.2 kB |
| Tags | CPython 3.12 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
620e277ca2577acc1e586f4ae5c8ef3e25131605a677efa03c58cc845e013390
|
|
BLAKE2b-256 checksum How to use checksums |
6f10c734625f60227e7291ebc21c20a8f696cc33e28930764562eced9ab6a18a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / gaebboss-0.1.1-cp312-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
| Download URL | gaebboss-0.1.1-cp312-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
|---|---|
| Size | 841.6 kB |
| Tags | CPython 3.12 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
48c7f4b15757bf9b36905139312efcc2109fb42f159b8ab3dadd2823a2175d9c
|
|
BLAKE2b-256 checksum How to use checksums |
76db4c04f2de885d1bf43c27eefced953f3727c7553fa0bac2c46598ce556deb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / gaebboss-0.1.1-cp312-abi3-macosx_11_0_arm64.whl
| Download URL | gaebboss-0.1.1-cp312-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 796.6 kB |
| Tags | CPython 3.12 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
62463c4854e564a6486071341330cb0c75dfcdab51d88d26ae1de4ac759384f3
|
|
BLAKE2b-256 checksum How to use checksums |
f8c612a0280fdd0daa3290d3d7d08492fa29d6b4cf0fdd8211e20142a6c70f8b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logSecurity for gaebboss 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 5 files in this release have been uploaded with provenance metadata.
Attestation for gaebboss-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.