BuildCalc API
Methodology

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; confidence is one of computed, computed_with_fallback, or computed_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. for trade=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:

ValueWhen returnedWhat it means
measuredMSA path; OR final fallback to OEWS MSA directDirect OEWS measurement. No scaling applied.
computedCounty/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_fallback5-digit NAICS suppressed at county; fell back to state-level or parent NAICS 238Same ratio math but with a coarser numerator. fallback_used field documents what was substituted.
computed_low_coverageCounty 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:

  1. 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 / national if MSA-level NAICS is also suppressed).
  2. Parent NAICS 238 (Specialty Trade Contractors) ratio applied to the OEWS MSA median. confidence: "computed_with_fallback", fallback_used: "parent NAICS 238", plus all_trades_share_ratio: true because all 11 trades end up with the same county adjustment under this branch.
  3. 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 factor

Materials 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_period separately 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

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_ppi
  • GET /v1/costs/sources/oews?msa={code}&soc={code} — raw cost_labor_oews
  • GET /v1/costs/sources/qcew?area={fips}&naics={code} — raw cost_labor_qcew
  • GET /v1/costs/sources/permits/{jurisdiction_code} — raw cost_permits_bps

On this page