Costs methodology
How BuildCalc API computes labor wages and permits — the math, the federal data sources, and the 7 known limitations every caller should understand.
This page documents how /v1/costs/labor and /v1/costs/permits
produce their numbers, the assumptions baked into the math, and the
limitations every caller should surface to end users.
If you arrived here from a see_also link inside a labor response,
this is the canonical reference for confidence, confidence_reason,
paired_trades, all_trades_share_ratio, and systematic_error_pct_range.
TL;DR
- MSA path (
?msa=) — returns OEWS SOC x MSA median wage directly. Direct measurement,confidence: "measured". - County path (
?county=) and ZIP path (?zip=) — return the OEWS MSA median scaled by the QCEW county-to-MSA wage ratio for the matching NAICS. Combines two authoritative BLS products with documented assumptions;confidenceis one ofcomputed,computed_with_fallback, orcomputed_low_coverage. - Permits (
/v1/costs/permits) — Census Building Permits Survey monthly data, 4 structure-type breakouts (1-unit / 2-unit / 3-4 unit / 5+ unit) for buildings, units, and valuation.
The formula (county and ZIP paths)
estimated_median_hourly = oews_msa_median × (qcew_county / qcew_msa)Where:
oews_msa_median— Latest OEWS SOC x MSA median hourly wage. E.g. fortrade=electrician(SOC 47-2111) at MSA 19100 (Dallas-Fort Worth-Arlington), the 2025 OEWS median.qcew_county— Latest QCEW county-level average weekly wage for the 5-digit NAICS that maps to the trade. For electrician, that's NAICS 23821 (Electrical Contractors).qcew_msa— Same NAICS, MSA level, same period.
For ZIP queries, we first resolve the ZIP to its dominant county
(highest res_ratio in the HUD USPS-CBSA crosswalk), then apply the
county formula. The resolved_county_fips and resolved_msa_code
fields in the response let auditors verify the resolution.
The 4-level confidence taxonomy
The confidence field in every labor response is one of:
| Value | When returned | What it means |
|---|---|---|
measured | MSA path; OR final fallback to OEWS MSA direct | Direct OEWS measurement. No scaling applied. |
computed | County/ZIP path with 5-digit NAICS reported at both county AND MSA, county coverage >=50% | OEWS x QCEW ratio applied. Most accurate scaling path. |
computed_with_fallback | 5-digit NAICS suppressed at county; fell back to state-level or parent NAICS 238 | Same ratio math but with a coarser numerator. fallback_used field documents what was substituted. |
computed_low_coverage | County coverage under 50% (BLS suppressed over 5 of 11 trade NAICS for this county) | Computation succeeded but the county data is thin; expect higher systematic error. |
confidence_reason is a free-form short string that names the exact
reason for the chosen level — e.g.,
"NAICS 23821 reported at county and MSA; 5-digit ratio used"
or "All 5-digit specialty NAICS suppressed at county 48199; using parent NAICS 238. All trades share this ratio.".
Fallback chain
When the 6-digit (per NAICS-2017, our 5-digit per NAICS-2022) county NAICS is suppressed, the endpoint walks this chain in order:
- State-level 5-digit NAICS ratio applied to the OEWS MSA median.
confidence: "computed_with_fallback",fallback_used: "state-level 5-digit NAICS / msa"(or/ nationalif MSA-level NAICS is also suppressed). - Parent NAICS 238 (Specialty Trade Contractors) ratio applied
to the OEWS MSA median.
confidence: "computed_with_fallback",fallback_used: "parent NAICS 238", plusall_trades_share_ratio: truebecause all 11 trades end up with the same county adjustment under this branch. - OEWS MSA median, unscaled.
confidence: "measured"with reason "fell back to MSA-level; no county data available".
The chain stops at the first level that has usable data. Every
fallback level still emits inputs.geographic_adjustment_ratio and
the NAICS / area pairs used so an auditor can reproduce the
computation.
The 7 known limitations
The math is defensible; the framing is the honest part. Every labor
endpoint response carries limitations_note pointing at this section.
1. Occupational composition assumption
The QCEW industry-wide ratio assumes counties and MSAs have the same occupational mix within an industry. In practice:
- Rural counties have more apprentices and helpers (depressing the average wage).
- Large metros have more white-collar PMs and estimators (inflating it).
Expected systematic error: 8-15% in counties with skewed
composition. Surfaced in every response as
systematic_error_pct_range: [8, 15].
2. NAICS suppression bias
BLS suppresses cells with fewer than 3 establishments or where any single employer is over 80% of employment.
- About 40-60% of US counties have NAICS 23821 (or equivalent trade NAICS) suppressed.
- Counties that do report systematically have larger contracting firms, biased toward higher wages than typical rural reality.
When you see confidence: "computed_low_coverage", this bias is
likely contributing — only a few trade NAICS made it past suppression
in this county.
3. Trade-pair coupling
The OEWS SOC taxonomy distinguishes some trades that share a QCEW NAICS:
- plumber (47-2152) + hvac-tech (49-9021) both map to NAICS 23822 (Plumbing, Heating, and Air-Conditioning Contractors).
- brickmason (47-2021) + stonemason (47-2022) both map to NAICS 23814 (Masonry Contractors).
For paired trades the county geographic ratio is identical —
county-level variation between paired trades is not modeled. The
response sets paired_trades to the partner trade name (e.g.,
["hvac-tech"] for a plumber query) so callers can surface this.
When the fallback chain drops to parent NAICS 238, all 11
specialty trades end up with the same ratio for the county. The
response sets all_trades_share_ratio: true in that case.
4. Dominant-county degeneracy in small MSAs
When a single county is the dominant share of a small MSA (e.g.,
Hartford County in Hartford-West Hartford-East Hartford CT MSA), the
qcew_county / qcew_msa ratio asymptotes toward 1.0 — the
county-level answer collapses to the OEWS MSA value. This is
mathematically correct (the county is most of the MSA) but means
the geographic scaling doesn't add much information.
The response sets degenerate_msa: true when the ratio falls within
0.5% of 1.0 so callers can detect this case.
5. Wage vs bid distinction
This product reports what workers earn (wages), NOT what contractors charge (bids). A bid is:
bid = wage + materials + overhead + margin + productivity factorMaterials are available separately via /v1/costs/materials
(BLS Producer Price Index — national, monthly). Overhead, margin,
and productivity factor are NOT modeled.
AI agents estimating contractor bids should multiply the wage by an
industry markup factor (typically 1.4-2.2x for construction
trades) and add materials separately. Surfaced in every response as
bid_markup_factor_range: [1.4, 2.2] and wage_vs_bid_note.
6. Time-series misalignment
OEWS is annual (May reference period, published the following April). QCEW is quarterly (published ~5 months after quarter end). At any query time, the two inputs are from different periods.
Drift inside the ratio is typically under 1.5% because both indexes move
together. The response surfaces oews_period and qcew_county_period
qcew_msa_periodseparately so the temporal mismatch is auditable.
7. Geographic completeness over precision
RSMeans City Cost Index uses trade-specific local wage surveys for ~700 cities (more precise but limited coverage). This methodology covers all ~3,100 US counties via federal-data scaling (less precise but complete).
For counties in major metros, RSMeans would be better when available. This product fills the "everywhere else" gap with documented assumptions.
Sources
- BLS OEWS (Occupational Employment and Wage Statistics): https://www.bls.gov/oes/
- BLS QCEW (Quarterly Census of Employment and Wages): https://www.bls.gov/cew/
- Census BPS (Building Permits Survey): https://www.census.gov/construction/bps/
- HUD USPS ZIP-CBSA crosswalk: https://www.huduser.gov/portal/datasets/usps_crosswalk.html
- NAICS taxonomy (2022 revision — current in QCEW): https://www.census.gov/naics/
- SOC taxonomy (2018 revision — current in OEWS): https://www.bls.gov/soc/
Raw escape hatches
Every endpoint has a /v1/costs/sources/... partner that returns the
underlying database row with no aggregation. Use these to audit a
specific number or to drive your own computations against the same
authoritative data:
GET /v1/costs/sources/ppi/{series_id}— raw cost_materials_ppiGET /v1/costs/sources/oews?msa={code}&soc={code}— raw cost_labor_oewsGET /v1/costs/sources/qcew?area={fips}&naics={code}— raw cost_labor_qcewGET /v1/costs/sources/permits/{jurisdiction_code}— raw cost_permits_bps