Godstrument/workers/world_zodiac.py
monsterrobotparty 7f4b4bcabe Chinese zodiac + seasons + live wildfires; overlapping concept groups
- world_zodiac.py: Chinese zodiac year/element + 2-hour animal, N/S summer (computed)
- world_fire.py: live wildfires from NASA EONET (the fire element)
- overlapping groups: a source can now belong to many concept groups at once
  (elements fire/earth/air/water/spirit, plus wealth + summer vibes) — they stack
- transform.py: member_groups (1:many) replaces 1:1 grouping

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

71 lines
2.6 KiB
Python

"""
world_zodiac.py — the Chinese zodiac + the turning seasons. Computed, no API.
The year's animal and element (a 12- and 60-year wheel), plus the "hour animal"
that turns every two hours — a slow calendar sequencer the day walks through.
And the seasons: northern vs southern summer intensity from the day of the year,
so "all data that happens in summer" has a signal to gate against, both
hemispheres.
Emits:
/gs/zodiac/year the year's animal (0..1 over Rat..Pig)
/gs/zodiac/element the year's element (0..1 over Wood/Fire/Earth/Metal/Water)
/gs/zodiac/hour the current 2-hour animal (0..1) — a 12-step day cycle
/gs/season/north northern-hemisphere summer intensity (0..1)
/gs/season/south southern-hemisphere summer intensity (0..1)
"""
from __future__ import annotations
import argparse
import datetime
import math
import time
from pythonosc.udp_client import SimpleUDPClient
ANIMALS = ["Rat", "Ox", "Tiger", "Rabbit", "Dragon", "Snake",
"Horse", "Goat", "Monkey", "Rooster", "Dog", "Pig"]
ELEMENTS = ["Wood", "Fire", "Earth", "Metal", "Water"]
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("--interval", type=float, default=30.0)
args = ap.parse_args()
client = SimpleUDPClient(args.host, args.port)
print("[zodiac] emitting /gs/zodiac/* and /gs/season/* (computed, no API)")
last_hour = None
while True:
try:
utc = datetime.datetime.now(datetime.timezone.utc)
y = utc.year
year_animal = (y - 4) % 12
year_element = ((y - 4) % 10) // 2
h = datetime.datetime.now().hour # local hour
hour_animal = ((h + 1) // 2) % 12
doy = utc.timetuple().tm_yday
north = 0.5 + 0.5 * math.cos(2 * math.pi * (doy - 172) / 365.25)
south = 1.0 - north
client.send_message("/gs/zodiac/year", year_animal / 11.0)
client.send_message("/gs/zodiac/element", year_element / 4.0)
client.send_message("/gs/zodiac/hour", hour_animal / 11.0)
client.send_message("/gs/season/north", north)
client.send_message("/gs/season/south", south)
if hour_animal != last_hour:
print(f" ☯ year of the {ELEMENTS[year_element]} "
f"{ANIMALS[year_animal]} · hour of the {ANIMALS[hour_animal]}")
last_hour = hour_animal
except Exception as e: # noqa
print(f"[zodiac] warn: {e}")
time.sleep(args.interval)
if __name__ == "__main__":
main()