Metadata-Version: 2.4
Name: qto-takeoff
Version: 0.1.0
Summary: Open quantity takeoff from IFC, with pluggable cost classification (UniFormat, MasterFormat, NRM, OmniClass, định mức)
Author: Nguyen Thu Thuy
License: MIT
Project-URL: Homepage, https://github.com/sophie-nguyenthuthuy/qto
Project-URL: Issues, https://github.com/sophie-nguyenthuthuy/qto/issues
Keywords: ifc,bim,quantity-takeoff,qto,cost-estimating,uniformat,masterformat,nrm,omniclass
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Manufacturing
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: ifc-spf>=0.1
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# 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.

```bash
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/`](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](https://github.com/sophie-nguyenthuthuy/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

```bash
pip install qto-takeoff
```

or from a checkout:

```bash
pip install -e .
```

## Use

```bash
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](https://github.com/sophie-nguyenthuthuy/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](qto/units.py).
* **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:

```toml
[[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`](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](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](https://github.com/sophie-nguyenthuthuy/qto/issues/1) lists what
is open — claim one by commenting.**

```bash
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:

```python
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`](https://github.com/sophie-nguyenthuthuy/ecfactors)
(embodied carbon) and
[`plancheck`](https://github.com/sophie-nguyenthuthuy/plancheck) (building-code
rules) all sit on top of them.

## Licence

MIT.
