Quantity Determination Phases (X31)¶
GAEB X31 exchanges quantity take-off data (Mengenermittlung) — the actual measurements used to determine quantities for construction items. Unlike procurement phases, X31 documents do not contain item descriptions or prices; they reference an external BoQ and supplement it with measurement rows following the REB 23.003 standard.
Quick Start¶
from pygaeb import GAEBParser
from pygaeb.api.document_api import DocumentAPI
doc = GAEBParser.parse("measurements.X31")
api = DocumentAPI(doc)
assert api.is_quantity
qd = api.qty_determination
# Metadata
print(qd.info.method) # "REB23003-2009"
print(qd.boq.ref_boq_name) # reference to procurement BoQ
# Iterate items
for item in api.iter_items():
print(f"OZ: {item.oz}, Qty: {item.qty}")
for dm in item.determ_items:
print(f" Row: {dm.takeoff_row.raw}")
Document Structure¶
X31 introduces DocumentKind.QUANTITY with its own root model:
GAEBDocument
├── gaeb_info: GAEBInfo
└── qty_determination: QtyDetermination
├── prj_info: PrjInfoQD (reference to project)
├── info: QtyDetermInfo (REB method, dates, creator/profiler)
├── owner: Address
├── contractor: Address
└── boq: QtyBoQ
├── ref_boq_name / ref_boq_id (external BoQ reference)
├── bkdn: list[BoQBkdn]
├── catalogs: list[Catalog]
├── body: QtyBoQBody → QtyBoQCtgy → QtyItem
├── ctlg_assigns: list[CtlgAssign]
└── attachments: list[QtyAttachment]
Quantity Items¶
QtyItem is a thin BoQ position — it only carries an OZ number and measurement data, enabling cross-referencing with procurement items:
for item in doc.iter_items():
print(f"OZ: {item.oz}")
print(f" Determined qty: {item.qty}")
print(f" Measurement rows: {len(item.determ_items)}")
Each item may contain multiple QDetermItem entries, each with a QTakeoffRow — a measurement line following the REB 23.003 format:
Cross-Referencing with Procurement¶
The primary use case for X31 is to supplement a procurement BoQ with verified measurements. Use QtyItem.oz to match items:
proc_doc = GAEBParser.parse("tender.X83")
qty_doc = GAEBParser.parse("measurements.X31")
proc_api = DocumentAPI(proc_doc)
qty_api = DocumentAPI(qty_doc)
for qty_item in qty_api.iter_items():
proc_item = proc_api.get_item(qty_item.oz)
if proc_item:
print(f"{proc_item.short_text}: measured = {qty_item.qty}")
The ref_boq_name and ref_boq_id fields on QtyBoQ identify which procurement BoQ the measurements belong to:
qd = qty_api.qty_determination
print(qd.boq.ref_boq_name) # "Tender BoQ 2024"
print(qd.boq.ref_boq_id) # GUID reference
Catalogs¶
X31 uses a catalog system for classification (DIN 276 cost groups, BIM references, locality, etc.):
# Catalog definitions
for cat in qd.boq.catalogs:
print(f"{cat.ctlg_name} ({cat.ctlg_type})")
# Catalog assignments on items
for item in api.iter_items():
for ca in item.ctlg_assigns:
print(f" {ca.ctlg_id}: {ca.ctlg_code}")
Catalog assignments appear at multiple levels: BoQ, category, item, and even on individual measurement rows.
Attachments¶
X31 supports base64-encoded attachments (photos, sketches, PDFs) at the BoQ level. These are referenced from QTakeoff rows using #Bild <name> syntax:
for att in qd.boq.attachments:
print(f"{att.name}: {att.mime_type}, {att.size_bytes} bytes")
# att.data contains the decoded binary content
Quantity Determination Info¶
QtyDetermInfo provides metadata about the measurement process:
info = qd.info
print(info.method) # "REB23003-1979" or "REB23003-2009"
print(info.order_descr) # Contract description
print(info.project_descr) # Project description
print(info.service_start) # datetime
print(info.service_end) # datetime
# Creator and profiler addresses
if info.creator:
print(f"Creator: {info.creator.name}, {info.creator.city}")
if info.profiler:
print(f"Profiler: {info.profiler.name}")
Writing X31 Documents¶
Round-trip editing works the same as other document kinds:
from pygaeb import GAEBWriter, ExchangePhase
doc = GAEBParser.parse("measurements.X31")
# ... modify quantities ...
GAEBWriter.write(doc, "updated.X31", phase=ExchangePhase.X31)
Document API¶
The DocumentAPI provides quantity-specific accessors:
api = DocumentAPI(doc)
api.is_quantity # True
api.qty_determination # QtyDetermination root model
api.get_qty_item("01.0010") # Find by OZ
# Universal iteration works
for item in api.iter_items():
print(item.oz, item.qty)
# Summary includes QD-specific fields
s = api.summary()
print(s["method"]) # REB method
print(s["ref_boq_name"]) # Referenced BoQ
print(s["catalogs"]) # Number of catalogs
print(s["attachments"]) # Number of attachments
Note on LLM Classification¶
X31 items have no text content (no short_text/long_text), so LLM classification and structured extraction are not applicable to QtyItem. The classifier and extractor will naturally skip them. Use the procurement document for classification and cross-reference via OZ.