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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qto_takeoff-0.1.0.tar.gz (39.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qto_takeoff-0.1.0-py3-none-any.whl (31.6 kB view details)

Uploaded Python 3

File details

Details for the file qto_takeoff-0.1.0.tar.gz.

File metadata

  • Download URL: qto_takeoff-0.1.0.tar.gz
  • Upload date:
  • Size: 39.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for qto_takeoff-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f332413ac03e1e738dbdc2134167797303d14a172567d5b9957de78ae327a358
MD5 c86b9d6ba75b8bae42deda8749bad9c5
BLAKE2b-256 953af5b6ce0ebe9c841b486cfb79ab94361b841f56084906b34d67babd2b102b

See more details on using hashes here.

Provenance

The following attestation bundles were made for qto_takeoff-0.1.0.tar.gz:

Publisher: release.yml on sophie-nguyenthuthuy/qto

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file qto_takeoff-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: qto_takeoff-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for qto_takeoff-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cc9ef1b442a3f6dd6953284a28a37a8ac6f6d97082af226e452220b5b97fbc67
MD5 fb6d7b8bbd0054f8a9eac02435f2dfd4
BLAKE2b-256 9e835cda2c55fe41f7fab64ef4d824f624627d0d7d72797a72a42997962e14e6

See more details on using hashes here.

Provenance

The following attestation bundles were made for qto_takeoff-0.1.0-py3-none-any.whl:

Publisher: release.yml on sophie-nguyenthuthuy/qto

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page