Costing
You are turning one or more decarbonization measures into a validated measure.cost object,
per measure, by calling the live costing MCP's 9 tools and assembling their results into the
canonical contract in soapbox-agent —
soapbox-agent/skills/construction-costing/schema/measure-cost.schema.json. This skill does
not vendor or copy that schema; it describes the shape below and points to the canonical
file as the single source of truth. Downstream consumers (decarb-plan, quality-review) read
measure.cost objects that conform to it.
Positioning: this skill's job ends at producing a validated measure.cost object per
measure. It does not compute IRR, NPV, payback, or landlord-share capture — that economics
step belongs to decarb-plan, which consumes this skill's output alongside energy-savings
estimates. If you find yourself computing a discounted cashflow here, stop — that work belongs
downstream.
Ground rules
- No invented numbers. Every
capex,opex_delta_yr,electrical_capacity, andefficiency_alternativefigure must come from an MCP tool result (or be explicitly flagged as a tuned-base/placeholder figure the tool itself reports, e.g.get_regional_factor's BLS-informed placeholders). If a tool has no coverage for a measure/system/region, say so — do not guess a number to fill the gap. There is no invented figure anywhere in this method. - Always surface references. Every cost figure is only as credible as its citation. Call
get_references(or read thereferences[]array aget_measure_capexresult already carries) and attach the backing citations to the assembled measure. An uncited number is low-confidence and must be flagged as such — never presented as if it were verified. - Never collapse an UNVERIFIED electrical-capacity range.
estimate_service_upgradereturnsflag: "UNVERIFIED"whenever actual service capacity is unknown, withupgrade_cost.low = 0(best case: existing headroom covers the increase) andupgrade_cost.high= full new-service cost. Do not average, midpoint, or otherwise collapse that range into a point estimate — the spread communicates real risk todecarb-plan's economics and toquality-review. Only aflag: "VERIFIED"result (a confirmed survey or quote) may be reported as a single number. - Every fuel-switch measure gets an
efficiency_alternative. Anymeasure_kind: fuel_switchmeasure must carry a second, non-switching high-efficiency option (a secondget_measure_capexcall against the non-electrifying sibling measure), sodecarb-plancan screen "switch fuels" against "stay on the same fuel but get more efficient." - Flag coverage gaps rather than fabricating a false point. Missing rows in
list_measures, no tariff match, no PVWatts coverage for a location — report the gap explicitly (commercial-electrical and lab/fume-hood measures are common gaps) instead of silently substituting an unrelated figure. - Feed new sources back. When you encounter a citable source not yet in the reference
library (a new survey, DEER/TRM entry, or vendor quote), register it via
add_referenceso the library grows for future runs. - Validate before handing off. Every
measure.costobject must match the canonical contract's required keys exactly before it goes todecarb-plan.
The measure.cost contract (canonical shape — do not vendor)
The full JSON Schema lives at
soapbox-agent/skills/construction-costing/schema/measure-cost.schema.json. This skill produces
exactly these keys per measure:
measure_id string
measure_kind "fuel_switch" | "efficiency" | "envelope" | "controls" | "other"
cost:
capex { low, base, high }
opex_delta_yr number (POSITIVE = OpEx rises, NEGATIVE = OpEx falls — never flip)
electrical_capacity { demand_increase_kw, service_capacity_known, upgrade_cost{low,high}, flag }
— REQUIRED whenever measure_kind is fuel_switch / electrification
efficiency_alternative { measure, capex, opex_delta_yr }
— REQUIRED whenever measure_kind is fuel_switch
Plus the additive extension this skill also populates from get_measure_capex:
contingency_pct, cost_breakdown{material,labour,equipment},
escalation{base_year,index,index_vintage,escalated_to}.
Non-fuel-switch measures omit electrical_capacity and efficiency_alternative entirely.
Tool → contract mapping (authoritative)
| MCP tool | Feeds | Notes |
|---|---|---|
list_measures |
measure discovery | Enumerate available measure_ids, categories, kinds, confidence before costing a roster. |
get_measure_capex |
cost.capex, cost_breakdown, contingency_pct, escalation, references[] |
Curated low/base/high, escalated to current-year dollars. For fuel-switch measures, call a second time against the non-switching sibling measure to populate efficiency_alternative. |
estimate_service_upgrade |
cost.electrical_capacity |
REQUIRED on any fuel_switch/electrification measure. Returns an UNVERIFIED range (never collapsed) unless service_capacity_known is confirmed, in which case it returns a VERIFIED point. |
get_energy_prices + get_tariff |
derive cost.opex_delta_yr |
get_energy_prices gives the retail electricity/gas price basis; get_tariff supplies demand charges a flat blended rate would omit. Combine both — positive result means OpEx rises. |
get_der_economics |
cost.capex / cost.opex_delta_yr for solar/storage/GHP measures |
size units vary by system (kW solar, kWh battery, tons GSHP); pass lat/lon for real PVWatts production on solar. |
get_regional_factor |
regionalizes get_measure_capex's labour/material split |
Apply the labour factor to cost_breakdown.labour and the material factor to cost_breakdown.material; equipment stays at national cost. Placeholder BLS-informed figures — flag as such. |
get_references |
citations attached to the assembled measure | Pass measure_id for a specific measure's citations, system_type to browse by system, or neither to list the whole register. |
add_reference |
grows the reference library (ops tool, not analysis) | Use when you encounter a new citable source not already in the register. |
Full per-tool call recipes — exact args, exact returned-field mapping, and the fuel-switch /
opex-delta worked examples — are in references/tool-orchestration.md.
Method
Step 1: Read the measure roster and building/Audette context
Load the measure roster for the engagement (the candidate list from decarb-plan's measure
generation, or a single measure the user names directly) and the corresponding Audette building
model — archetype, climate, size, end-use energy breakdown, and, for any fuel-switch candidate,
existing peak electrical demand and (if known) electrical service capacity. Call list_measures
to confirm coverage for each roster entry's measure_id before costing it. If a required field
is missing (e.g. no peak-demand data for a candidate fuel-switch measure), flag it — do not fill
the gap with an assumption.
Step 2: Per measure, call the mapped MCP tools
For each measure, follow the tool → contract mapping table above (detailed recipes in
references/tool-orchestration.md):
get_measure_capex(measure_id, region, size)→capex,cost_breakdown,contingency_pct,escalation. Regionalize the labour/material split withget_regional_factorif a region is known.- If
measure_kindisfuel_switch: callestimate_service_upgradeforelectrical_capacity, and a secondget_measure_capexagainst the non-switching sibling measure forefficiency_alternative. - Derive
opex_delta_yrfromget_energy_prices(retail price basis) combined withget_tariff(demand-charge basis, where present). For DER measures, useget_der_economicsinstead ofget_measure_capex/energy-prices for bothcapexandopex_delta_yr. - Pull citations via
get_references(or readreferences[]off theget_measure_capexresult directly) and attach them to the assembled measure.
Step 3: Assemble the measure.cost object
Build the object per measure exactly as shown in the contract shape above. Example for a fuel-switch measure:
{
"measure_id": "central-ashp",
"measure_kind": "fuel_switch",
"cost": {
"capex": { "low": 1400000, "base": 2100000, "high": 3200000 },
"opex_delta_yr": 48000,
"electrical_capacity": {
"demand_increase_kw": 420,
"service_capacity_known": false,
"upgrade_cost": { "low": 0, "high": 1800000 },
"flag": "UNVERIFIED"
},
"efficiency_alternative": {
"measure": "high-efficiency condensing boiler replacement",
"capex": 520000,
"opex_delta_yr": -14000
}
}
}
Step 4: Validate against the canonical soapbox-agent schema
Validate every assembled measure against
soapbox-agent/skills/construction-costing/schema/measure-cost.schema.json before handing it
downstream — required keys present, measure_kind in the allowed enum, electrical_capacity
and efficiency_alternative present on every fuel_switch measure, and no UNVERIFIED
electrical_capacity with upgrade_cost.low === upgrade_cost.high (a disguised point estimate).
Fix any measure that fails validation — do not hand an invalid measure.cost object downstream.
Step 5: Hand off to decarb-plan
Once validated, the measure.cost objects go to decarb-plan's measure-screening step, which
combines them with energy-savings estimates to compute NPV/IRR/payback and apply landlord-share
capture. This skill's job ends here — it does not compute IRR or any other economic-return
metric; that is decarb-plan's responsibility. quality-review separately reads these objects
back when auditing a plan's cost basis for defensibility.