Workflow
When this skill triggers, follow these steps in order.
Step 1 — Parse the scope into quantities
Read the project description carefully and extract a quantity takeoff table:
| Item | Quantity | Unit |
|---|---|---|
| Receptacles | N | each |
| Switches | N | each |
| Fixtures | N | each |
| Conduit | N | linear ft |
| Panel(s) | N | each |
| Breakers | N | each |
| Boxes | N | each |
| Cable / wire | N | ft or rolls |
| Consumables | N | packs |
Rules for this step:
- Convert linear footage to conduit sticks (divide by 10 for 10ft sticks, round up).
- Derive outlet box count from device + fixture count (one box per device/fixture).
- Note the stated labor rate and markup percentage from the scope; use defaults ($85/hr, 15% markup) if not provided.
- Flag any scope items that have no matching SKU in the price list — list them as
"sku": "UNLISTED"withunit_cost: 0and a note.
Step 2 — Match each quantity to a SKU in the price list
Load resources/prices.csv. For each takeoff item:
- Search for the best-matching row by description keywords (wire gauge, device type, conduit size, amperage).
- Record the matched
sku,unit,unit_cost, andlabor_hours(per-unit labor from the price list). - If multiple SKUs could match, prefer the one whose unit matches the takeoff unit exactly.
- If no SKU matches, record
sku: UNLISTEDand set bothunit_costandlabor_hoursto 0 so arithmetic is not corrupted.
Step 3 — Compute material, labor, and line totals
For each line item, compute:
material_total = qty * unit_cost (round to 2 decimal places)
labor_hours = qty * labor_hours_per_unit_from_csv (round to 2 decimal places)
labor_total = labor_hours * labor_rate_per_hour (round to 2 decimal places)
line_total = material_total + labor_total (round to 2 decimal places)
Then roll up totals:
totals.material = sum of all material_total values
totals.labor = sum of all labor_total values
totals.subtotal = totals.material + totals.labor
totals.markup = totals.subtotal * (markup_pct / 100)
totals.grand_total = totals.subtotal + totals.markup
Round every totals field to 2 decimal places. Double-check: sum the line_total column independently and confirm it equals subtotal before emitting output.
Step 4 — Emit the JSON estimate
Output a single valid JSON document with this schema:
{
"project": "<short project description>",
"assumptions": {
"labor_rate_per_hour": <number>,
"markup_pct": <number>,
"notes": ["<any scope flags or UNLISTED items>"]
},
"line_items": [
{
"sku": "<sku>",
"description": "<description>",
"qty": <number>,
"unit": "<unit>",
"unit_cost": <number>,
"material_total": <number>,
"labor_hours": <number>,
"labor_total": <number>,
"line_total": <number>
}
],
"totals": {
"material": <number>,
"labor": <number>,
"subtotal": <number>,
"markup": <number>,
"grand_total": <number>
}
}
The output must be pure JSON — no markdown fences, no commentary outside the JSON object. Every number must be a JSON number (not a quoted string). The document must pass JSON.parse without error.
Productionization gap
This skill uses the sample price list in resources/prices.csv. Real production use requires:
- Live supplier pricing: integration with distributor APIs (e.g., Graybar, Anixter, Rexel) or a regularly refreshed price feed.
- True takeoff from plans: PDF/DXF plan parsing to auto-extract quantities instead of relying on a plain-English scope description.
- Regional labor rates: NECA or Davis-Bacon wage tables by zip code rather than a single flat rate.
- Tax and fee tables: jurisdiction-specific sales tax on materials and permit fee schedules.
Example
Input: examples/input.md — 1,200 sq ft office TI scope with 18 receptacles, 12 LED troffers, 1 new 100A subpanel, ~400 ft of 1/2 in EMT, 6 switches.
Output: examples/output.md — valid JSON estimate with 18 line items, internally consistent arithmetic, $85/hr labor rate, 15% markup, grand total $7,895.10.