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>
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
Pre-built wage and compensation series IDs — OES, ECI, ECEC, CPS earnings.
|
||||
"""
|
||||
|
||||
from ..series import oes_national, eci
|
||||
from ..series import oes_national, eci, ecec
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OES — Occupational Employment & Wage Statistics
|
||||
@ -89,19 +89,58 @@ def eci_dashboard() -> dict:
|
||||
# ---------------------------------------------------------------------------
|
||||
# ECEC — Employer Costs for Employee Compensation
|
||||
# ---------------------------------------------------------------------------
|
||||
def ecec_total_compensation() -> str:
|
||||
"""ECEC — civilian workers, total compensation cost per hour worked."""
|
||||
return "CMU1010000000000D"
|
||||
def ecec_total_compensation(owner: str = "civilian") -> str:
|
||||
"""ECEC — total compensation cost per hour worked."""
|
||||
return ecec("total_compensation", owner)
|
||||
|
||||
|
||||
def ecec_total_benefits() -> str:
|
||||
"""ECEC — civilian workers, total benefits cost per hour worked."""
|
||||
return "CMU1036000000000D"
|
||||
def ecec_wages_and_salaries(owner: str = "civilian") -> str:
|
||||
"""ECEC — wages and salaries cost per hour worked."""
|
||||
return ecec("wages_and_salaries", owner)
|
||||
|
||||
# Note: ECEC benefit subcomponents (health insurance, retirement & savings, etc.)
|
||||
# are encoded as specific benefit-subcell codes in the series ID (suffix stays "D").
|
||||
# Look them up against the ECEC component list before adding helpers — do not
|
||||
# fabricate them with a letter suffix (the old "...H"/"...R" forms were invalid).
|
||||
|
||||
def ecec_total_benefits(owner: str = "civilian") -> str:
|
||||
"""ECEC — total benefits cost per hour worked."""
|
||||
return ecec("total_benefits", owner)
|
||||
|
||||
|
||||
def ecec_paid_leave(owner: str = "civilian") -> str:
|
||||
"""ECEC — paid leave cost per hour worked (vacation, holiday, sick, personal)."""
|
||||
return ecec("paid_leave", owner)
|
||||
|
||||
|
||||
def ecec_supplemental_pay(owner: str = "civilian") -> str:
|
||||
"""ECEC — supplemental pay cost per hour worked (overtime, bonuses, shift diff)."""
|
||||
return ecec("supplemental_pay", owner)
|
||||
|
||||
|
||||
def ecec_health_insurance(owner: str = "civilian") -> str:
|
||||
"""ECEC — health insurance cost per hour worked."""
|
||||
return ecec("health_insurance", owner)
|
||||
|
||||
|
||||
def ecec_retirement(owner: str = "civilian") -> str:
|
||||
"""ECEC — retirement & savings cost per hour worked (defined benefit + contribution)."""
|
||||
return ecec("retirement_and_savings", owner)
|
||||
|
||||
|
||||
def ecec_legally_required(owner: str = "civilian") -> str:
|
||||
"""ECEC — legally required benefits per hour (Social Security, Medicare, UI, workers' comp)."""
|
||||
return ecec("legally_required", owner)
|
||||
|
||||
|
||||
def ecec_dashboard(owner: str = "civilian") -> dict:
|
||||
"""The compensation cost breakdown — what an hour of labor costs an employer."""
|
||||
return {
|
||||
"Total Compensation": ecec_total_compensation(owner),
|
||||
"Wages & Salaries": ecec_wages_and_salaries(owner),
|
||||
"Total Benefits": ecec_total_benefits(owner),
|
||||
"Paid Leave": ecec_paid_leave(owner),
|
||||
"Supplemental Pay": ecec_supplemental_pay(owner),
|
||||
"Health Insurance": ecec_health_insurance(owner),
|
||||
"Retirement & Savings": ecec_retirement(owner),
|
||||
"Legally Required": ecec_legally_required(owner),
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user