""" Series ID builders for major BLS surveys. These functions construct the coded series IDs used by the BLS API. Pass the returned string directly to BLSClient.fetch(). Example: from bls_client.series import laus_state, ces_national client.fetch([laus_state(11, "rate"), ces_national("00000000", "01")], 2020, 2025) """ # --------------------------------------------------------------------------- # LAUS — Local Area Unemployment Statistics # Series format: LA[adj][area_type+area_code 15 chars][measure 2 chars] # --------------------------------------------------------------------------- _LAUS_MEASURE = { "rate": "03", "unemployed": "04", "employed": "05", "laborforce": "06", "emp_pop": "07", "lfpr": "08", "pop": "09", } def laus_state(state_fips: int, measure: str = "rate", seasonal: bool = False) -> str: """ LAUS series for a state. Args: state_fips: 2-digit state FIPS (e.g. 11=DC, 24=MD, 51=VA) measure: "rate" | "unemployed" | "employed" | "laborforce" seasonal: True for seasonally adjusted Examples: laus_state(11, "rate") → "LAUST110000000000003" laus_state(24, "employed") → "LAUST240000000000005" """ adj = "S" if seasonal else "U" m = _LAUS_MEASURE.get(measure, measure) # Area code = ST(2) + state_fips(2) + 11 zeros = 15 chars → total series = 20 return f"LA{adj}ST{state_fips:02d}{'0'*11}{m}" def laus_county(state_fips: int, county_fips: int, measure: str = "rate", seasonal: bool = False) -> str: """ LAUS series for a county. Args: state_fips: 2-digit state FIPS county_fips: 3-digit county FIPS measure: "rate" | "unemployed" | "employed" | "laborforce" seasonal: True for seasonally adjusted Examples: laus_county(11, 1, "rate") → "LAUCN110010000000003" """ adj = "S" if seasonal else "U" m = _LAUS_MEASURE.get(measure, measure) # Area code = CN(2) + state_fips(2) + county_fips(3) + 8 zeros = 15 chars → total = 20 return f"LA{adj}CN{state_fips:02d}{county_fips:03d}{'0'*8}{m}" def laus_msa(state_fips: int, cbsa_code: int, measure: str = "rate") -> str: """ LAUS series for a Metropolitan Statistical Area. The state_fips should be the primary state in the MSA. Args: state_fips: 2-digit FIPS of the primary state cbsa_code: 5-digit CBSA code measure: "rate" | "unemployed" | "employed" | "laborforce" Examples: laus_msa(11, 47900, "rate") → DC-Arlington MSA unemployment rate laus_msa(24, 12580, "rate") → Baltimore MSA unemployment rate laus_msa(51, 40060, "rate") → Richmond MSA unemployment rate """ m = _LAUS_MEASURE.get(measure, measure) return f"LAUMT{state_fips:02d}{cbsa_code:05d}000000{m}" # --------------------------------------------------------------------------- # CES — Current Employment Statistics (National) # Series format: CE[adj][industry 8 chars][data_type 2 chars] # --------------------------------------------------------------------------- _CES_SUPERSECTOR = { "total_nonfarm": "00000000", "total_private": "05000000", "mining_logging": "10000000", "construction": "20000000", "manufacturing": "30000000", "durable": "31000000", "nondurable": "32000000", "trade_trans_util":"40000000", "wholesale": "41000000", "retail": "42000000", "information": "50000000", "financial": "55000000", "professional": "60000000", "education_health":"65000000", "leisure": "70000000", "other_services": "80000000", "government": "90000000", } _CES_DATATYPE = { "employees": "01", "avg_weekly_hours": "02", "avg_hourly_earn": "03", "prod_employees": "06", "women_employees": "10", "avg_weekly_earn": "11", } def ces_national( industry: str = "total_nonfarm", data_type: str = "employees", seasonal: bool = True, ) -> str: """ CES national employment series. Args: industry: Supersector name (see _CES_SUPERSECTOR) or 8-digit code string data_type: Measure name (see _CES_DATATYPE) or 2-digit code string seasonal: True for seasonally adjusted Examples: ces_national() → "CES0000000001" ces_national("manufacturing", "employees") → "CES3000000001" ces_national("retail", "avg_hourly_earn") → "CES4200000003" """ adj = "S" if seasonal else "U" ind = _CES_SUPERSECTOR.get(industry, industry) dtype = _CES_DATATYPE.get(data_type, data_type) return f"CE{adj}{ind}{dtype}" def ces_state( state_fips: int, industry: str = "total_nonfarm", data_type: str = "employees", seasonal: bool = True, area_code: str = "00000", ) -> str: """ State or metro employment series (SM prefix). Args: state_fips: 2-digit state FIPS industry: Supersector name or 8-digit code data_type: Measure name or 2-digit code seasonal: True for SA area_code: 5-digit metro area code; "00000" = statewide Examples: ces_state(11) → "SMS110000000000001" (DC total nonfarm) ces_state(24, "retail") → "SMS240000042000001" (MD retail) """ adj = "S" if seasonal else "U" ind = _CES_SUPERSECTOR.get(industry, industry) dtype = _CES_DATATYPE.get(data_type, data_type) return f"SM{adj}{state_fips:02d}{area_code}{ind}{dtype}" # --------------------------------------------------------------------------- # CPI — Consumer Price Index # Series format: CU[adj][periodicity][area 4 chars][item 6 chars] # --------------------------------------------------------------------------- _CPI_ITEM = { "all_items": "SA0", "core": "SA0L1E", # all items less food & energy "food": "SAF", "food_at_home": "SAF1", "energy": "SA0E", "shelter": "SAH1", "apparel": "SAA", "transportation":"SAT", "medical": "SAM", "education": "SAE", "recreation": "SAR", "gasoline": "SS47014", "electricity": "SEHF01", "new_vehicles": "SETA01", } def cpi( item: str = "all_items", area: str = "0000", seasonal: bool = False, series: str = "U", ) -> str: """ CPI series ID. Args: item: Item code name (see _CPI_ITEM) or raw item code area: 4-char area code ("0000" = US city average) seasonal: True for seasonally adjusted series: "U" = CPI-U, "W" = CPI-W, "S" = Chained CPI-U Examples: cpi() → "CUUR0000SA0" cpi("core", seasonal=True) → "CUSR0000SA0L1E" cpi("gasoline", seasonal=True) → "CUSR0000SS47014" cpi("all_items", series="W") → "CWUR0000SA0" """ prefix_map = {"U": "CU", "W": "CW", "S": "SU"} prefix = prefix_map.get(series, "CU") adj = "S" if seasonal else "U" itm = _CPI_ITEM.get(item, item) return f"{prefix}{adj}R{area}{itm}" # --------------------------------------------------------------------------- # PPI — Producer Price Index (commodity-based) # --------------------------------------------------------------------------- def ppi_commodity(commodity_code: str = "00000000") -> str: """ PPI commodity series. Args: commodity_code: 8-char commodity code ("00000000" = all commodities) Examples: ppi_commodity() → "WPU00000000" ppi_commodity("1012") → "WPU1012" (iron & steel scrap) """ return f"WPU{commodity_code}" # --------------------------------------------------------------------------- # OES — Occupational Employment & Wage Statistics # --------------------------------------------------------------------------- _OES_DATATYPE = { "employment": "01", "hourly_mean": "03", "annual_mean": "04", "hourly_10pct": "06", "hourly_25pct": "07", "hourly_median":"08", "hourly_75pct": "09", "hourly_90pct": "10", "annual_median":"13", } def oes_national( occupation_code: str = "000000", industry_code: str = "000000", data_type: str = "employment", ) -> str: """ OES national series. Args: occupation_code: 6-digit SOC code or "000000" for all occupations industry_code: 6-digit NAICS or "000000" for cross-industry data_type: Measure name (see _OES_DATATYPE) or 2-digit code Examples: oes_national() → all occupations, employment oes_national("151132", data_type="annual_median") → software devs median wage """ dtype = _OES_DATATYPE.get(data_type, data_type) # OE+U+N(area_type)+0000400(national area 7 chars)+industry(6)+occupation(6)+dtype(2) = 25 return f"OEUN0000400{industry_code:0<6}{occupation_code:0<6}{dtype}" # --------------------------------------------------------------------------- # JOLTS — Job Openings & Labor Turnover # --------------------------------------------------------------------------- _JOLTS_ELEMENT = { "job_openings": "JO", "hires": "HI", "quits": "QU", "layoffs": "LD", "total_separations": "TS", } def jolts( element: str = "job_openings", rate_level: str = "L", industry: str = "000000", ownership: str = "00", seasonal: bool = True, ) -> str: """ JOLTS series. Args: element: "job_openings" | "hires" | "quits" | "layoffs" | "total_separations" rate_level: "L" = level (thousands) | "R" = rate industry: 6-digit industry code; "000000" = total nonfarm ownership: "00" = total | "10" = private | "20" = government seasonal: True for SA Examples: jolts() → "JTS000000000000JOL" (openings level) jolts("quits", rate_level="R") → "JTS000000000000QUR" (quits rate) jolts("hires", seasonal=False) → "JTU000000000000HIL" (hires level, NSA) """ adj = "S" if seasonal else "U" elem = _JOLTS_ELEMENT.get(element, element) # Format: JT+adj+industry(6)+zeros(6)+elem(2)+rate_level(1) = 18 chars ind = industry[:6].ljust(6, "0") return f"JT{adj}{ind}{'0'*6}{elem}{rate_level}" # --------------------------------------------------------------------------- # ECI — Employment Cost Index # --------------------------------------------------------------------------- def eci( worker_type: str = "10", # 10=civilian, 20=private, 30=state/local occupation: str = "00", # 00=all, 10=mgmt/prof, 20=service, etc. industry: str = "000000000", component: str = "A", # A=total comp, W=wages, B=benefits seasonal: bool = True, ) -> str: """ ECI series for employment cost changes. Examples: eci() → "CIU1010000000000A" (civilian, all workers, total compensation) eci(component="W") → wages only """ adj = "S" if seasonal else "U" return f"CI{adj}{worker_type}{occupation}{industry}{component}" # --------------------------------------------------------------------------- # Productivity (Major Sector) # --------------------------------------------------------------------------- _PR_SECTOR = { "business": "85", "nonfarm_business":"86", "manufacturing": "88", "durable_mfg": "89", "nondurable_mfg": "90", } _PR_MEASURE = { "output_per_hour": "092", "output": "041", "hours": "051", "compensation": "061", "real_comp_per_hr": "071", "unit_labor_cost": "111", "unit_nonlabor_pay":"112", } def productivity( sector: str = "business", measure: str = "output_per_hour", seasonal: bool = True, ) -> str: """ Major Sector Productivity series. Examples: productivity() → "PRS85006092" (business output per hour, SA) productivity("manufacturing", "unit_labor_cost") """ adj = "S" if seasonal else "U" sec = _PR_SECTOR.get(sector, sector) mea = _PR_MEASURE.get(measure, measure) return f"PR{adj}{sec}06{mea}"