""" world_bank.py — the human condition, slow and heavy. Pulls World Bank development indicators (no key) for the whole world: extreme poverty, undernourishment (food insecurity), fertility, life expectancy. These are annual figures — they barely move — so they're near-DC ballast, a moral weight under the set rather than a rhythm. (The API is annual and occasionally throttles rapid requests, so this polls slowly and holds the last value.) Emits: /gs/world/poverty extreme poverty headcount, % of population /gs/world/hunger prevalence of undernourishment, % /gs/world/fertility births per woman /gs/world/lifeexp life expectancy at birth, years """ from __future__ import annotations import argparse import json import time import urllib.request from pythonosc.udp_client import SimpleUDPClient BASE = "https://api.worldbank.org/v2/country/WLD/indicator/{ind}?format=json&mrnev=1" UA = "Godstrument/1.0 (live instrument)" INDICATORS = { "SI.POV.DDAY": ("world/poverty", "extreme poverty %"), "SN.ITK.DEFC.ZS": ("world/hunger", "undernourishment %"), "SP.DYN.TFRT.IN": ("world/fertility", "fertility"), "SP.DYN.LE00.IN": ("world/lifeexp", "life expectancy"), } def fetch(ind: str): req = urllib.request.Request(BASE.format(ind=ind), headers={"User-Agent": UA}) with urllib.request.urlopen(req, timeout=30) as r: raw = r.read().decode("utf-8-sig").strip() # World Bank prepends a BOM d = json.loads(raw) if isinstance(d, list) and len(d) > 1 and d[1]: row = d[1][0] if row.get("value") is not None: return float(row["value"]), row["date"] return None, None 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") args = ap.parse_args() client = SimpleUDPClient(args.host, args.port) print("[worldbank] emitting /gs/world/poverty|hunger|fertility|lifeexp " f"(re-query every {args.poll:g}s)") values: dict[str, float] = {} next_poll, retry = 0.0, 60.0 while True: if time.time() >= next_poll: got = False for ind, (addr, name) in INDICATORS.items(): try: v, year = fetch(ind) if v is not None: values[addr] = v got = True print(f" 🌍 {name}: {v} ({year})") except Exception as e: # noqa print(f"[worldbank] {ind} warn: {e}") time.sleep(1.0) # be gentle with the API if got: next_poll, retry = time.time() + args.poll, 60.0 else: # nothing came back — back off print(f"[worldbank] no values; retry in {retry:.0f}s") next_poll = time.time() + retry retry = min(retry * 2, args.poll) for addr, v in values.items(): client.send_message("/gs/" + addr, v) time.sleep(10.0) if __name__ == "__main__": main()