- 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>
71 lines
2.6 KiB
Python
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()
|