Skip to content

Takeoff schema

The TakeoffReport is the structured contract between the client LLM and downstream estimating tools. It's a Pydantic model, validated strictly by generate_takeoff_report — bad reports come back as readable validation errors, not silent garbage. Field names intentionally match the contractor-bid BOM format (qty/uom/status/source/customFields) so exports drop straight into the ContractorTakeoff suite (see Integrations).

Current schema_version: 1.

TakeoffReport

A complete takeoff for one plan set.

Field Type Default Description
schema_version int 1 Schema version
project str required Project name
source_pdf str required Plan set file the takeoff came from
scope_profile str "fences-gates" Trade profile used
generated_by str "conmcp" Tool identifier
generated_at str or null null ISO timestamp, set at export if missing
items list[TakeoffItem] [] The line items
scope_notes list[str] [] Inclusions/clarifications
risks list[str] [] Red flags a bidder must see

TakeoffItem

One takeoff line item (a BOM row).

Field Type Default Description
id str required Stable item id, e.g. 'fence-cl-6ft'
section str "general" Trade grouping, e.g. 'fences-gates'
item str required Short item code/name, e.g. 'CHAIN-LINK-6FT'
description str required Full description as an estimator would write it
qty float (≥0) required Quantity taken off
uom str required Unit of measure: LF, EA, SF, SY, CY, PAIR, TON, LS
status str "Included" 'Included', 'Excluded', or 'Flag <reason>'
source str "" Sheet reference, e.g. 'C-101 p.2'
location str "" Where on the project, e.g. 'north property line'
assumptions list[str] [] Assumptions behind the quantity
confidence float (0–1) 0.5 Extraction confidence
needs_review bool true Flag for human review before bidding
custom_fields dict[str, str \| float \| bool] {} Trade-specific extras, e.g. post_spacing, fabric_gauge, gate_leaf_count

Note the defaults are deliberately conservative: an item is needs_review: true at 50% confidence unless the model explicitly says otherwise. Sight-unseen trust is not the default.

UOM vocabulary

The shared unit vocabulary (single source of truth in domain/quantities.py):

LF · SF · SY · CY · EA · PAIR · TON · LS

UOMs are normalized to uppercase on validation. A unit outside this list does not fail validation — it gets reported in flagged_uoms by generate_takeoff_report so a human can decide whether it's legitimate (say, SETS) or a mistake before export.

Status values

status is validated: it must be exactly Included, exactly Excluded, or start with Flag followed by a reason:

  • Included — in the bid scope
  • Excluded — documented out of scope (e.g. "BY OTHERS" / NIC items — recorded so the exclusion is explicit, not silent)
  • Flag <reason> — needs a decision, e.g. Flag plan/schedule mismatch — RFI candidate

Export formats

generate_takeoff_report (and conmcp report on the CLI) renders a validated report three ways.

JSON — contractor-bid compatible (to_bid_json)

The BOM line_items shape consumed by contractor-bid:

{
  "schema_version": 1,
  "project": {
    "name": "Oakridge Commerce Park — Phase 2",
    "scope_profile": "fences-gates"
  },
  "line_items": [
    {
      "id": "fence-cl-6ft",
      "section": "fences-gates",
      "item": "CHAIN-LINK-6FT-BW",
      "description": "6' high galvanized chain link fence w/ 3-strand barbed wire",
      "qty": 450.0,
      "uom": "LF",
      "status": "Included",
      "source": "C-101 p.2",
      "notes": "Posts 8'-0\" O.C. per detail 1/C-501",
      "customFields": {
        "post_spacing": 8,
        "fabric_gauge": "9 GA",
        "location": "north property line",
        "confidence": 0.9,
        "needs_review": false
      }
    }
  ],
  "metadata": {
    "extracted_by": "conmcp conmcp",
    "extracted_at": "2026-07-10T14:30:00+00:00",
    "source_pdf": "/Users/you/plans/sample_plan.pdf",
    "scope_notes": ["Gate operator power and loops by electrical contractor (BY OTHERS)"],
    "risks": ["Verify field dimensions prior to fabrication per general note 5"]
  }
}

Mapping notes: item assumptions are joined with "; " into notes; location, confidence, and needs_review travel inside customFields alongside any trade-specific extras.

CSV — pma-standalone takeoff layout

One row per item, columns in this order:

Section, Item, Description, Qty, UOM, Status, Source, Location,
Confidence, NeedsReview, Assumptions, CustomFields
  • Confidence is formatted to two decimals (0.90)
  • NeedsReview is lowercase true/false
  • Assumptions are joined with "; "
  • CustomFields is a JSON string (empty if there are none)

Markdown — human review

An Obsidian-friendly report: header metadata (source, scope profile, generated timestamp, item counts with how many need review), a line-item table with confidence and review flags, then Assumptions, Scope notes, and Risks / RFI candidates sections, plus a warning line if any non-standard UOMs were found. This is the format to read before the CSV or JSON leaves the building.