Skip to main content

qto — open quantity takeoff with pluggable classification

Point it at an IFC model, name a cost-code standard, get a CSV of quantities per element mapped to that standard.

qto model.ifc --classification uniformat
global_id,entity,predefined_type,name,type_name,storey,material,standard,code,code_title,quantity,unit,quantity_name,status,embedded_code
000000000000000000000D,IFCWALL,SOLIDWALL,W-01 Tường trục A,Tường bao 220,Tầng 1,Gạch đất sét nung,uniformat,B2011,Exterior Wall Construction,22.8,m2,NetSideArea,ok,B2010

Each row records which quantity was believed (NetSideArea), so a number you disagree with is traceable back to the model in one step.

The engine is generic. The mappings are the contribution. UniFormat, MasterFormat, NRM, OmniClass and Vietnamese định mức are all just TOML files in qto/mappings/ — no Python involved in adding a standard, a national cost-norm system, or your firm's in-house code list.

One dependency — ifc-spf, the standard-library IFC reader this project was extracted from. Python 3.11+. No geometry kernel, no IfcOpenShell, no BIM server.


Why this exists

Every estimating tool re-implements the same two halves: read the model, then map elements to whatever cost breakdown your client's contract uses. The first half is the same everywhere. The second half is where all the local knowledge lives — and it is the half that is always locked inside a commercial tool.

So: the first half is a library, the second half is a folder of data files anyone can send a PR against. A quantity surveyor who has never written Python can fix a code, add a work section, or contribute a whole national standard by editing TOML.

Install

pip install qto-takeoff

or from a checkout:

pip install -e .

Use

qto model.ifc -c uniformat                       # one row per element
qto model.ifc -c nrm --group-by code             # elemental bill of quantities
qto model.ifc -c nrm --group-by code,storey      # ...broken down by level
qto model.ifc -c dinh-muc -f md                  # markdown for a report
qto model.ifc -c ./our-house-standard.toml       # your own mapping
qto model.ifc -c uniformat -o takeoff.csv        # write a file

qto inspect model.ifc                            # what is actually in there
qto standards                                    # what mappings ship
qto validate qto/mappings/nrm.toml               # lint a mapping

Grouped by code, the sample model in tests/fixtures/ gives:

| code  | code_title                       | quantity | unit | elements |
|:------|:---------------------------------|---------:|:-----|---------:|
| A1012 | Column Foundations and Pile Caps |    1.152 | m3   |        2 |
| A1031 | Standard Slab on Grade           |       48 | m2   |        1 |
| B1011 | Floor Structural Frame           |    2.209 | m3   |        5 |
| B1012 | Floor Decks, Slabs and Toppings  |     46.5 | m2   |        1 |
| B1022 | Roof Decks and Sheathing         |       52 | m2   |        1 |
| B2011 | Exterior Wall Construction       |    95.22 | m2   |        5 |
| B2021 | Exterior Windows                 |        3 | nr   |        3 |
| B2031 | Exterior Doors                   |        2 | nr   |        2 |
| B3011 | Roof Coverings                   |       52 | m2   |        1 |
| C1011 | Fixed Partitions                 |    20.82 | m2   |        2 |
| C1021 | Interior Doors                   |        1 | nr   |        1 |
| C2011 | Stair Construction               |        1 | nr   |        1 |
| C2013 | Stair Railings and Balustrades   |      4.5 | m    |        1 |
| C3021 | Floor Finishes                   |     46.5 | m2   |        1 |
| C3031 | Ceiling Finishes                 |     46.5 | m2   |        1 |
| D2020 | Domestic Water Distribution      |       20 | m    |        2 |

Every run prints a coverage summary to stderr, because what it missed is the number you actually need before you trust a takeoff:

model      model.ifc (IFC4, 303 instances)
standard   uniformat — UNIFORMAT II elemental classification (ASTM E1557) [draft]
elements   31 taken off, 1 parts suppressed
classified 30 (96.8%), 0 without a usable quantity, 1 unclassified
codes      16 of 20 in the mapping
gaps       IFCBUILDINGELEMENTPROXY x1

The standards that ship

Standard What it is Rules Coverage on the sample model
uniformat UNIFORMAT II elemental (ASTM E1557) 21 96.8%
masterformat CSI MasterFormat work results 21 96.8%
nrm RICS NRM1 elemental cost planning 20 96.8%
omniclass OmniClass Table 21 (Elements) 21 96.8%
dinh-muc Định mức dự toán XD (TT 12/2021/TT-BXD) 8 48.4%

All five are marked status = "draft". They are a working skeleton written from the published structure of each standard, not a certified transcription — qto standards prints each mapping's declared gaps, and the định mức mapping is deliberately shipped half-finished because the people who can finish it are the ones who do dự toán for a living, not the ones who wrote the parser.

The same model, two standards, on purpose:

uniformat  B2011 Exterior Wall Construction        95.22 m2
dinh-muc   AE.222 Xây tường gạch, dày ≤ 33 cm      18.31 m3

Vietnamese cost norms measure masonry by volume where UNIFORMAT measures walls by area. One model has to answer both questions without being re-authored — that constraint is why the engine is generic.

How it reads a model

  • The IFC reader is ifc-spf — a standard-library STEP parser plus name-addressed schema tables and unit resolution, extracted from this project so a building-code checker could use the same reader. IFC is a text format; a takeoff only needs to walk the instance graph.
  • Quantities come from IfcElementQuantity — the Qto_*BaseQuantities your authoring tool exports. Nothing is derived from geometry, so a number in the output is a number someone's BIM tool actually wrote. If the model carries no quantities, qto inspect says so instead of inventing them.
  • Units are normalised through SI. Real exports mix them — millimetre lengths beside square-metre areas is routine — and an SI prefix on an area unit is squared, not applied once. That is the 10⁶ error that ruins takeoffs, so it has its own module and its own tests.
  • Properties are inherited from the type. Pset_WallCommon.IsExternal usually sits on IfcWallType, not on the wall; occurrence values override.
  • Materials resolve through layer sets, and the thickest layer wins as the primary material — a 220 mm wall is brick, not the 15 mm render on it.
  • Assemblies never double count. For a stair and its flights, whichever side carries the quantities is the side taken off. --include-parts opts out; the coverage line tells you how many were suppressed.

Speed is unremarkable and honest about it: a 6.8 MB model with 91k instances takes about 1.7 s end to end, scaling roughly linearly. The whole file is parsed into memory, so a 200 MB federated model wants a machine with room for it.

Writing a mapping

A rule says which elements it claims and which quantity to take:

[[rule]]
code = "B2011"
title = "Exterior Wall Construction"
unit = "m2"
entity = ["IfcWall"]                                    # subtypes included
property = { "Pset_WallCommon.IsExternal" = true }
quantity = ["NetSideArea", "GrossSideArea"]             # in preference order

Elements are claimed by what the model says they are, not by what someone typed in a name field. Rules can also match predefined_type, material, type_name, name, storey, and any property with regex / in / min / max / exists tests, plus an exclude block. The most specific rule wins; priority overrides that when you need it to.

Full reference: docs/mapping-format.md.

What this does not do

Stated plainly, because a takeoff tool that overstates itself is dangerous:

  • It does not measure geometry. No areas from BREPs, no wall girths, no deductions for openings beyond what the exporter already netted off. If the model has no Qto_* quantities, there is nothing to take off.
  • It does not apply measurement rules. NRM2's "no deduction under 1.00 m²", SMM7 conventions, VN quy tắc đo bóc — none of that is implemented. Output is model quantities mapped to codes, not a compliant BoQ.
  • It does not price anything. Codes out; rates are your estimating system's job.
  • The shipped code lists are drafts and need review by people who use each standard professionally.

Contributing

The engine needs very little. The mappings need a lot.

Adding or fixing a standard is one TOML file plus one golden CSV, and CI checks it lints, has no dead rules, and produces the bill of quantities you reviewed. See CONTRIBUTING.md.

Especially wanted: NRM2 detailed measurement, DIN 276, Uniclass 2015, Vietnamese định mức chapters AB/AI and cửa/vách codes, and any national standard whose knowledge currently lives only inside paid software. Issue #1 lists what is open — claim one by commenting.

make test       # run the suite
make validate   # lint every mapping
make golden     # refresh the expected takeoffs after a mapping change

Using it as a library

The reader is meant to be shared. If you are writing another IFC tool, import the layers you need instead of writing a fourth STEP parser:

from qto.spf import load            # STEP -> entities, refs, typed values
from qto.model import Model         # indexing, deref, inverse refs, units
from qto.extract import extract     # elements with quantities in SI
from qto import rules, report       # classification and output

model = Model.open("model.ifc")
model.scale.length                  # metres per model length unit
elements = extract(model)           # normalised, unit-converted, no geometry

qto.spf and qto.units are deliberately domain-free — qto's own cost mappings, ecfactors (embodied carbon) and plancheck (building-code rules) all sit on top of them.

Licence

MIT.

Metadata

Release files for qto-takeoff 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for qto-takeoff 0.1.0
File Size Uploaded
qto_takeoff-0.1.0.tar.gz 39.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for qto-takeoff 0.1.0
File Interpreter ABI Platform
qto_takeoff-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 70.8 kB

Release files / qto_takeoff-0.1.0.tar.gz

Download URL qto_takeoff-0.1.0.tar.gz
Size 39.1 kB
Tags Source
SHA-256 checksum
How to use checksums
f332413ac03e1e738dbdc2134167797303d14a172567d5b9957de78ae327a358
BLAKE2b-256 checksum
How to use checksums
953af5b6ce0ebe9c841b486cfb79ab94361b841f56084906b34d67babd2b102b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Aug 9, 2026.

Transparency log

Release files / qto_takeoff-0.1.0-py3-none-any.whl

Download URL qto_takeoff-0.1.0-py3-none-any.whl
Size 31.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cc9ef1b442a3f6dd6953284a28a37a8ac6f6d97082af226e452220b5b97fbc67
BLAKE2b-256 checksum
How to use checksums
9e835cda2c55fe41f7fab64ef4d824f624627d0d7d72797a72a42997962e14e6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Aug 9, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page