""" world_econ.py — the cost of living. Inflation as pressure. Pulls US CPI (consumer price index) from the Bureau of Labor Statistics (no key, but rate-limited — so it polls rarely; CPI is monthly data anyway) and computes the year-over-year inflation rate. Inflation is a quiet, grinding pressure — it never trends toward zero, it just erodes. Good as a slow "tension" source in the dark half of a set. Emits: /gs/econ/inflation year-over-year CPI change, percent (~0..10) /gs/econ/cpi_mom latest month-over-month change, percent """ from __future__ import annotations import argparse import json import time import urllib.request from pythonosc.udp_client import SimpleUDPClient URL = "https://api.bls.gov/publicAPI/v2/timeseries/data/CUUR0000SA0" # CPI-U all UA = "Godstrument/1.0 (live instrument)" def fetch(): req = urllib.request.Request(URL, headers={"User-Agent": UA}) with urllib.request.urlopen(req, timeout=30) as r: rows = json.load(r)["Results"]["series"][0]["data"] latest = rows[0] idx = float(latest["value"]) # month-over-month (previous data point) mom = ((idx / float(rows[1]["value"])) - 1) * 100 if len(rows) > 1 else 0.0 # year-over-year: same period one year earlier yoy = 0.0 for row in rows: if row["year"] == str(int(latest["year"]) - 1) and row["period"] == latest["period"]: yoy = (idx / float(row["value"]) - 1) * 100 break return yoy, mom, latest["periodName"] + " " + latest["year"] def main(): ap = argparse.ArgumentParser() ap.add_argument("--host", default="127.0.0.1") ap.add_argument("--port", type=int, default=9000) ap.add_argument("--poll", type=float, default=21600.0, help="re-query every Ns (BLS is rate-limited)") args = ap.parse_args() client = SimpleUDPClient(args.host, args.port) print(f"[econ] emitting /gs/econ/inflation|cpi_mom (US CPI) " f"(re-query every {args.poll:g}s)") yoy, mom, last = 0.0, 0.0, None next_poll, retry = 0.0, 60.0 while True: if time.time() >= next_poll: try: yoy, mom, label = fetch() if label != last: print(f" 📈 inflation {yoy:.2f}% YoY ({label}, {mom:+.2f}% MoM)") last = label next_poll, retry = time.time() + args.poll, 60.0 except Exception as e: # noqa # back off on failure: BLS allows ~25 req/day unregistered, so a # 10s retry-storm would exhaust the quota and lock us out all day print(f"[econ] warn: {e}; retry in {retry:.0f}s") next_poll = time.time() + retry retry = min(retry * 2, args.poll) client.send_message("/gs/econ/inflation", yoy) client.send_message("/gs/econ/cpi_mom", mom) time.sleep(10.0) if __name__ == "__main__": main()