Files
bls-data/tests/test_live_smoke.py
Dave Boyd 9e2c55c568 Add QCEW CSV module, ECEC breakdown, response caching + typed errors
Item 1 — QCEW county/industry detail:
- new bls_client/qcew.py: thin client over the QCEW Open Data CSV service
  (area/industry/size slices; no API key, no quota), with filter_rows helper
- covers the county/industry detail the timeseries API can't reach

Item 2 — ECEC benefit breakdown:
- new series.ecec() builder from the authoritative cm.estimate decode table
- real helpers: wages, total benefits, paid leave, supplemental pay,
  health insurance, retirement & savings, legally required + ecec_dashboard()
- FIX: ecec_total_benefits was CMU1036... (education/health industries only);
  correct all-civilian total benefits is CMU1030000000000D

Item 3 — caching + typed errors:
- bls_client/cache.py FileCache (on-disk, TTL); BLSClient(cache=True),
  queries_used counter (cache hits don't spend quota)
- bls_client/errors.py: BLSError / BLSQuotaError / BLSRequestError; quota
  exhaustion now raises a clear BLSQuotaError instead of a generic RuntimeError

Tests: 119 offline (+24) incl. ECEC goldens, QCEW fixture parse, cache/quota
behavior; live smoke extended to ECEC + QCEW. Helper sweep: 96/96 live.
README: caching/QCEW usage, updated coverage + limitations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 10:38:12 -04:00

76 lines
2.3 KiB
Python

"""Live smoke test — hits the real BLS API.
Opt-in only: deselected by default (see addopts in pyproject.toml). Run with:
pytest -m live
Requires network and a BLS API key (BLS_API_KEY env var, or config.py).
Guards against silent series-ID drift: BLS occasionally retires codes (e.g. the
2010->2018 SOC change), which unit tests alone can't catch.
"""
import os
import pytest
from bls_client import BLSClient
from bls_client.queries import employment as E, prices as P, wages as W, productivity as PR
def _api_key():
key = os.environ.get("BLS_API_KEY")
if key:
return key
try:
from config import BLS_API_KEY # local, gitignored
if BLS_API_KEY and BLS_API_KEY != "YOUR_KEY_HERE":
return BLS_API_KEY
except Exception:
pass
return None
pytestmark = pytest.mark.live
@pytest.fixture(scope="module")
def client():
key = _api_key()
if not key:
pytest.skip("no BLS_API_KEY available")
return BLSClient(key)
# One representative, previously-broken helper per repaired survey.
REPAIRED = {
"JOLTS": E.jolts_dashboard(), # dict of 5
"OES wage": W.occupation_annual_median_wage("151252"),
"OES employ": W.all_occupations_employment(),
"ECI": W.eci_total_compensation(),
"Productivity": PR.business_output_per_hour(),
"QCEW": E.qcew_national_private(),
"PPI final": P.ppi_final_demand(),
"ECEC health": W.ecec_health_insurance(),
"ECEC retire": W.ecec_retirement(),
}
def _flatten(v):
return list(v.values()) if isinstance(v, dict) else [v]
def test_repaired_surveys_return_data(client):
"""Every repaired survey must return at least one real observation."""
all_ids = []
for ids in REPAIRED.values():
all_ids.extend(_flatten(ids))
res = client.fetch(all_ids, 2024, 2025)
missing = [sid for sid in all_ids if not res.get(sid, {}).get("data")]
assert not missing, f"no data returned for: {missing}"
def test_headline_payrolls_plausible(client):
res = client.fetch_latest(E.nonfarm_payrolls(), years=1)
obs = client.latest_obs(next(iter(res.values())))
assert obs is not None
# US nonfarm payrolls are ~150,000 (thousands); sanity bound, not exact.
assert 100_000 < float(obs["value"]) < 200_000