Godstrument/workers/world_bank.py
monsterrobotparty 60173d44cb Add socio-economic data, performance mode, spells (full-state templates)
- world_bank.py: poverty, food security, fertility, life expectancy (World Bank)
  folded into the "shadow" / "bright" concept groups
- spells: save/load now captures the full live mix (gains/mutes/freezes), not
  just the wiring; TEMPLATES panel renamed SPELLS
- performance mode (press P): clean projection view + rotating "dispatches from
  the world" text readout for live shows

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 02:37:03 +10:00

80 lines
2.8 KiB
Python

"""
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] = {}
last_poll = 0.0
while True:
if time.time() - last_poll > args.poll or not values:
for ind, (addr, name) in INDICATORS.items():
try:
v, year = fetch(ind)
if v is not None:
values[addr] = v
print(f" 🌍 {name}: {v} ({year})")
time.sleep(1.0) # be gentle with the API
except Exception as e: # noqa
print(f"[worldbank] {ind} warn: {e}")
last_poll = time.time()
for addr, v in values.items():
client.send_message("/gs/" + addr, v)
time.sleep(10.0)
if __name__ == "__main__":
main()