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>
160 lines
5.9 KiB
Python
160 lines
5.9 KiB
Python
"""
|
|
Pre-built wage and compensation series IDs — OES, ECI, ECEC, CPS earnings.
|
|
"""
|
|
|
|
from ..series import oes_national, eci, ecec
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# OES — Occupational Employment & Wage Statistics
|
|
# ---------------------------------------------------------------------------
|
|
def all_occupations_employment() -> str:
|
|
"""National employment across all occupations, all industries."""
|
|
return oes_national("000000", "000000", "employment")
|
|
|
|
|
|
def occupation_annual_median_wage(soc_code: str) -> str:
|
|
"""
|
|
Annual median wage for a specific occupation (national, all industries).
|
|
|
|
Args:
|
|
soc_code: 6-digit SOC code (e.g. "151252" for software developers,
|
|
"291141" for registered nurses, "119021" for construction mgrs)
|
|
"""
|
|
return oes_national(soc_code, "000000", "annual_median")
|
|
|
|
|
|
def occupation_employment(soc_code: str) -> str:
|
|
"""Employment level for a specific occupation (national)."""
|
|
return oes_national(soc_code, "000000", "employment")
|
|
|
|
|
|
# Common occupation codes
|
|
SOC_CODES = {
|
|
"software_developers": "151252",
|
|
"registered_nurses": "291141",
|
|
"teachers_elementary": "252021",
|
|
"accountants": "132011",
|
|
"construction_managers": "119021",
|
|
"truck_drivers": "533032",
|
|
"janitors": "372011",
|
|
"retail_salespersons": "412031",
|
|
"first_line_supervisors_mfg":"511011",
|
|
"lawyers": "231011",
|
|
"physicians": "291229",
|
|
"police_officers": "333051",
|
|
"social_workers": "211029",
|
|
"financial_analysts": "132051",
|
|
"data_scientists": "152051",
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ECI — Employment Cost Index
|
|
# ---------------------------------------------------------------------------
|
|
def eci_total_compensation(seasonal: bool = False) -> str:
|
|
"""ECI — civilian workers, all industries, total compensation (12-mo % change)."""
|
|
return eci(owner="10", component="10", seasonal=seasonal)
|
|
|
|
|
|
def eci_wages(seasonal: bool = False) -> str:
|
|
"""ECI — civilian workers, wages and salaries only (12-mo % change)."""
|
|
return eci(owner="10", component="20", seasonal=seasonal)
|
|
|
|
|
|
def eci_benefits(seasonal: bool = False) -> str:
|
|
"""ECI — civilian workers, benefit costs only (12-mo % change)."""
|
|
return eci(owner="10", component="30", seasonal=seasonal)
|
|
|
|
|
|
def eci_private(seasonal: bool = False) -> str:
|
|
"""ECI — private sector, total compensation (12-mo % change)."""
|
|
return eci(owner="20", component="10", seasonal=seasonal)
|
|
|
|
|
|
def eci_state_local(seasonal: bool = False) -> str:
|
|
"""ECI — state and local government, total compensation (12-mo % change)."""
|
|
return eci(owner="30", component="10", seasonal=seasonal)
|
|
|
|
|
|
def eci_dashboard() -> dict:
|
|
return {
|
|
"Total Compensation (Civilian)": eci_total_compensation(),
|
|
"Wages & Salaries (Civilian)": eci_wages(),
|
|
"Benefits (Civilian)": eci_benefits(),
|
|
"Total Comp (Private)": eci_private(),
|
|
"Total Comp (State/Local Gov)": eci_state_local(),
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ECEC — Employer Costs for Employee Compensation
|
|
# ---------------------------------------------------------------------------
|
|
def ecec_total_compensation(owner: str = "civilian") -> str:
|
|
"""ECEC — total compensation cost per hour worked."""
|
|
return ecec("total_compensation", owner)
|
|
|
|
|
|
def ecec_wages_and_salaries(owner: str = "civilian") -> str:
|
|
"""ECEC — wages and salaries cost per hour worked."""
|
|
return ecec("wages_and_salaries", owner)
|
|
|
|
|
|
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),
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CPS Earnings (LE)
|
|
# ---------------------------------------------------------------------------
|
|
def median_weekly_earnings(seasonal: bool = True) -> str:
|
|
"""Median usual weekly earnings — full-time wage and salary workers."""
|
|
return "LEU0252881600" if seasonal else "LEU0252881500"
|
|
|
|
|
|
def median_weekly_earnings_men() -> str:
|
|
return "LEU0252882800"
|
|
|
|
|
|
def median_weekly_earnings_women() -> str:
|
|
return "LEU0252882900"
|