Install
$ agentstack add skill-impertio-studio-cross-tech-aec-claude-skill-package-crosstech-impl-ifc-erpnext-costing ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
crosstech-impl-ifc-erpnext-costing
Quick Reference
IFC Quantity Types (all 5)
| IFC Type | Value Unit | ERPNext UOM | Typical Use | |----------|-----------|-------------|-------------| | IfcQuantityLength | m | Meter | Beam lengths, pipe runs, cable trays | | IfcQuantityArea | m2 | Square Meter | Wall surfaces, floor areas, roof areas | | IfcQuantityVolume | m3 | Cubic Meter | Concrete volumes, excavation volumes | | IfcQuantityCount | integer | Nos | Door count, window count, fixture count | | IfcQuantityWeight | kg | Kg | Steel tonnage, rebar weight |
Standard Quantity Sets (Qto_ prefix)
| Quantity Set | Element Types | Key Quantities | |-------------|--------------|----------------| | Qto_WallBaseQuantities | IfcWall | Length, Height, Width, GrossSideArea, NetSideArea, GrossVolume, NetVolume | | Qto_SlabBaseQuantities | IfcSlab | Area, NominalThickness, GrossVolume, NetVolume, GrossWeight | | Qto_BeamBaseQuantities | IfcBeam | Length, CrossSectionArea, GrossSurfaceArea, NetVolume | | Qto_ColumnBaseQuantities | IfcColumn | Length, CrossSectionArea, GrossSurfaceArea, GrossVolume | | Qto_DoorBaseQuantities | IfcDoor | Height, Width, Area | | Qto_WindowBaseQuantities | IfcWindow | Height, Width, Area |
Critical Warnings
NEVER map IFC quantities to ERPNext without checking the IFC file's unit system first. Use ifcopenshell.util.unit.calculate_unit_scale(model) to get the scale factor to SI units.
NEVER submit an ERPNext BOM without verifying that all referenced Items exist. The Frappe API returns a 400 error with no rollback if an Item is missing.
NEVER use IfcQuantityVolume for cost estimation of composite elements (e.g., layered walls). The gross volume includes ALL material layers — use material-specific quantities instead.
NEVER assume every IFC element has quantity sets. Many IFC files export with empty Qto_ sets or no quantity sets at all. ALWAYS validate that quantities are non-null before creating BOM Items.
NEVER create duplicate Items in ERPNext from IFC instances of the same type. Group by IfcElementType first, then aggregate quantities across instances.
ALWAYS use ifcopenshell.util.element.get_psets(element, qtos_only=True) to extract quantities — this merges occurrence-level and type-level quantity sets automatically.
Technology Boundary
Side A: IFC Model (IfcOpenShell 0.8.x)
Data format: IFC-SPF (.ifc) or IFC-XML (.ifcXML), parsed into ifcopenshell.file object.
API surface for quantity extraction:
| Function | Purpose | |----------|---------| | ifcopenshell.open(path) | Load IFC file into memory | | model.by_type("IfcWall") | Get all elements of a type | | ifcopenshell.util.element.get_psets(elem, qtos_only=True) | Extract quantity sets | | ifcopenshell.util.element.get_psets(elem, psets_only=True) | Extract property sets | | ifcopenshell.util.element.get_type(elem) | Get the IfcElementType | | ifcopenshell.util.classification.get_references(elem) | Get classification codes | | ifcopenshell.util.unit.calculate_unit_scale(model) | Get SI unit scale factor | | model.schema | Check schema version (IFC2X3, IFC4, IFC4X3) |
Data available: geometry, properties, quantities, classifications, materials, spatial hierarchy, relationships.
Side B: ERPNext ERP (Frappe REST API, ERPNext v15)
Data format: JSON via REST API at /api/resource/:doctype.
Core DocTypes for costing:
| DocType | Purpose | Key Fields | |---------|---------|------------| | Item | Master record for materials/products | item_code, item_name, item_group, stock_uom, description | | BOM | Bill of Materials (assembly recipe) | item, items (child table), rm_cost_as_per | | BOM Item | Component row within a BOM | item_code, qty, uom, rate | | Project | Top-level project container | project_name, company | | Item Group | Hierarchical item categorization | name, parent_item_group |
Authentication: Token-based header: Authorization: token api_key:api_secret.
Data available: pricing, valuation rates, supplier info, warehouse locations, serial/batch tracking, cost centers.
The Bridge: Quantity Extraction to BOM Creation Pipeline
IFC Model (.ifc)
│
▼
[1] Parse with IfcOpenShell
│ ifcopenshell.open("project.ifc")
│ Check units: ifcopenshell.util.unit.calculate_unit_scale(model)
│
▼
[2] Extract element types + quantities
│ Group elements by IfcElementType
│ For each type: aggregate quantities across all instances
│ Map classification codes to ERPNext Item Groups
│
▼
[3] Create Items in ERPNext
│ POST /api/resource/Item for each unique element type
│ Map IFC unit → ERPNext UOM
│ Map IFC classification → Item Group
│
▼
[4] Create BOM in ERPNext
│ POST /api/resource/BOM with aggregated quantities
│ Reference Items created in step 3
│
▼
[5] Submit BOM
PUT /api/resource/BOM/:name with docstatus=1
Directionality: IFC → ERPNext is the primary flow. ERPNext → IFC is NOT supported (ERPNext has no geometry model, no spatial relationships, no IFC schema awareness).
Critical Rules
- ALWAYS check the IFC unit system before extracting quantities. Multiply all quantity values by the unit scale factor to normalize to SI units.
- ALWAYS group IFC elements by their
IfcElementTypebefore creating ERPNext Items. One Item per type, NOT one Item per element instance. - ALWAYS verify that an Item exists in ERPNext (GET request) before referencing it in a BOM. Create missing Items first.
- ALWAYS use the
qtos_only=Trueparameter when extracting quantities to avoid mixing property values with quantity values. - ALWAYS map IFC units to ERPNext UOM names exactly:
m→Meter,m2→Square Meter,m3→Cubic Meter,kg→Kg. - ALWAYS handle the case where quantity sets are empty or missing — skip elements with no quantities instead of creating zero-quantity BOM Items.
- NEVER submit a BOM (docstatus=1) until all Items and quantities are verified. Submitted BOMs are immutable in ERPNext.
- NEVER use hardcoded unit conversions. Use the IFC file's
IfcUnitAssignmentviacalculate_unit_scale(). - NEVER flatten IFC spatial hierarchy for cost tracking without mapping storeys to ERPNext Cost Centers first.
- NEVER send more than 50 API requests per second to ERPNext — use batch creation or rate limiting.
Decision Tree
START: You have an IFC file and need cost estimates in ERPNext
│
├── Q1: Does the IFC file contain quantity sets (Qto_ prefixed)?
│ ├── YES → Use existing quantities. Proceed to Q2.
│ └── NO → Quantities must be calculated from geometry.
│ └── Use IfcOpenShell geometry engine to compute volumes/areas.
│ model.by_type("IfcWall") → create_shape() → compute from mesh.
│ WARNING: Geometry-derived quantities are approximations.
│
├── Q2: What unit system does the IFC file use?
│ ├── Check: ifcopenshell.util.unit.calculate_unit_scale(model)
│ ├── Returns 1.0 → File uses meters (SI). No conversion needed.
│ ├── Returns 0.001 → File uses millimeters. Multiply all lengths by 0.001.
│ └── Returns other → Apply scale factor to ALL extracted quantities.
│
├── Q3: Does the IFC file have classification references?
│ ├── YES → Map classification codes to ERPNext Item Groups.
│ │ ├── Uniclass → Use Identification field (e.g., "Ss_25_10_30")
│ │ └── OmniClass → Use Identification field (e.g., "23-13 21 00")
│ └── NO → Use IFC element type names as Item Group fallback.
│ └── Group by is_a() result: "IfcWall" → "Walls", "IfcSlab" → "Slabs"
│
├── Q4: Single-level or multi-level BOM?
│ ├── SINGLE → One BOM per building storey with all element quantities.
│ └── MULTI → Parent BOM (building) → child BOMs (storeys) → element Items.
│ └── Create hierarchical BOMs: Building BOM references Storey BOMs.
│
└── Q5: Cost source in ERPNext?
├── Valuation Rate → Uses average stock valuation. Set rm_cost_as_per="Valuation Rate".
├── Last Purchase Rate → Uses most recent purchase price.
└── Price List → Uses a specific price list. Specify price_list field.
Essential Patterns
Pattern 1: Extract Quantities with Unit Normalization
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.unit
model = ifcopenshell.open("project.ifc")
# ALWAYS get unit scale first
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(model)
# unit_scale converts file units to meters (e.g., 0.001 for mm files)
for wall in model.by_type("IfcWall"):
qtos = ifcopenshell.util.element.get_psets(wall, qtos_only=True)
qto = qtos.get("Qto_WallBaseQuantities", {})
# Apply unit scale to length/area/volume quantities
length = qto.get("Length", 0) * unit_scale # now in meters
area = qto.get("GrossSideArea", 0) * unit_scale**2 # now in m2
volume = qto.get("GrossVolume", 0) * unit_scale**3 # now in m3
# Count and Weight do NOT need unit_scale conversion
Pattern 2: Group by Element Type for Item Creation
from collections import defaultdict
type_quantities = defaultdict(lambda: {"count": 0, "volume": 0.0, "area": 0.0})
for wall in model.by_type("IfcWall"):
wall_type = ifcopenshell.util.element.get_type(wall)
type_name = wall_type.Name if wall_type else "UNTYPED_WALL"
qtos = ifcopenshell.util.element.get_psets(wall, qtos_only=True)
qto = qtos.get("Qto_WallBaseQuantities", {})
type_quantities[type_name]["count"] += 1
type_quantities[type_name]["volume"] += qto.get("GrossVolume", 0) * unit_scale**3
type_quantities[type_name]["area"] += qto.get("GrossSideArea", 0) * unit_scale**2
# Result: {"Concrete Wall 200mm": {"count": 12, "volume": 45.6, "area": 228.0}, ...}
Pattern 3: Create ERPNext Items from IFC Types
import requests
ERP_URL = "https://erp.example.com"
HEADERS = {
"Authorization": "token api_key:api_secret",
"Content-Type": "application/json"
}
IFC_UNIT_TO_ERP_UOM = {
"length": "Meter",
"area": "Square Meter",
"volume": "Cubic Meter",
"count": "Nos",
"weight": "Kg",
}
def create_item(item_code: str, item_name: str, group: str, uom: str) -> dict:
"""Create an ERPNext Item. Returns the created item data."""
# ALWAYS check if item exists first
check = requests.get(
f"{ERP_URL}/api/resource/Item/{item_code}",
headers=HEADERS
)
if check.status_code == 200:
return check.json()["data"] # Item already exists
item_data = {
"doctype": "Item",
"item_code": item_code,
"item_name": item_name,
"item_group": group,
"stock_uom": uom,
"description": f"Auto-created from IFC model ({item_name})"
}
response = requests.post(
f"{ERP_URL}/api/resource/Item",
json=item_data,
headers=HEADERS
)
response.raise_for_status()
return response.json()["data"]
Common Operations
IFC Unit to ERPNext UOM Mapping
| IFC Unit (from IfcUnitAssignment) | unitscale Result | ERPNext UOM | Conversion | |----------------------------------|-------------------|-------------|------------| | METRE | 1.0 | Meter | Direct | | MILLIMETRE | 0.001 | Meter | value 0.001 | | SQUAREMETRE | 1.0 | Square Meter | Direct | | CUBIC_METRE | 1.0 | Cubic Meter | Direct | | KILOGRAM | 1.0 | Kg | Direct | | POUND | 0.453592 | Kg | value 0.453592 |
IFC Classification to ERPNext Item Group Mapping
| IFC Source | Field | ERPNext Target | Example | |-----------|-------|---------------|---------| | IfcClassificationReference.Identification | Uniclass code | Item.item_group | "Ss2510_30" → "Concrete Wall Systems" | | IfcClassificationReference.Name | Human name | Item.item_name | "Concrete wall systems" | | element.is_a() | IFC entity type | Fallback group | "IfcWall" → "Walls" | | IfcElementType.Name | Type name | Item.item_code | "Basic Wall:200mm Concrete" |
Data Loss at the Boundary
IFC data with NO ERPNext equivalent:
- Geometric representations (IfcShapeRepresentation) — ERPNext has no geometry model
- Spatial relationships (IfcRelContainedInSpatialStructure) — ERPNext uses flat item lists
- Material layer composition (IfcMaterialLayerSetUsage) — ERPNext tracks single-material items
- Connection geometry (IfcRelConnectsPathElements) — no structural analysis in ERPNext
- Parametric definitions — ERPNext stores quantities, not parametric rules
ERPNext data with NO IFC equivalent:
- Pricing and valuation rates — IFC has no cost schema
- Warehouse and bin locations — IFC tracks building locations, not stock locations
- Supplier information — IFC has
IfcOrganizationbut not procurement workflows - Serial numbers and batch tracking — no equivalent in IFC
- Workflow states (Draft, Submitted, Cancelled) — no equivalent in IFC
Frappe API: Creating and Submitting a BOM
def create_bom(building_item: str, bom_items: list[dict]) -> str:
"""Create and submit a BOM in ERPNext. Returns BOM name."""
bom_data = {
"doctype": "BOM",
"item": building_item,
"items": bom_items, # [{"item_code": ..., "qty": ..., "uom": ...}, ...]
"rm_cost_as_per": "Valuation Rate"
}
response = requests.post(
f"{ERP_URL}/api/resource/BOM",
json=bom_data,
headers=HEADERS
)
response.raise_for_status()
bom_name = response.json()["data"]["name"]
# Submit the BOM (makes it active and immutable)
requests.put(
f"{ERP_URL}/api/resource/BOM/{bom_name}",
json={"docstatus": 1},
headers=HEADERS
).raise_for_status()
return bom_name
Reference Links
- [references/methods.md](references/methods.md) — IfcOpenShell quantity API + ERPNext/Frappe REST API
- [references/examples.md](references/examples.md) — End-to-end IFC quantity to ERPNext BOM pipeline
- [references/anti-patterns.md](references/anti-patterns.md) — Costing integration mistakes
Official Sources
- https://docs.ifcopenshell.org/ — IfcOpenShell 0.8.x API documentation
- https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/ — IFC4 quantity set definitions
- https://frappeframework.com/docs/user/en/api/rest — Frappe REST API documentation
- https://docs.erpnext.com/docs/user/manual/en/manufacturing/bill-of-materials — ERPNext BOM documentation
- https://docs.erpnext.com/docs/user/manual/en/stock/item — ERPNext Item documentation
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Impertio-Studio
- Source: Impertio-Studio/Cross-Tech-AEC-Claude-Skill-Package
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.