Files
L'électron rare 60112b0790 feat(data-feeds): GDELT, Wikipedia, Tides, ATC
4 nouvelles sources open-data publiques, sans cle :

- gdelt : GDELT 2.0 events CSV.zip toutes les 15 min ; emet
  /event lat lon tone country eid + /batch n countries avg_tone.
  Dedup par GLOBALEVENTID, ring buffer 8192.
- wikimedia : EventStreams SSE firehose recentchange (toutes
  langues). Sample ~5% des edits + rate per second toutes
  les 5 s. Reconnect auto sur disconnect.
- tides : NOAA CO-OPS water level observe + predit pour une
  station (Boston par defaut, station code configurable) +
  phase lunaire calculee (Conway approx, sans dep). Poll 6 min.
- atc : LiveATC hub listeners scraping (KJFK/KLAX/KSFO/KORD/
  EGLL/LFPG par defaut). Polite 500ms entre hubs, total
  poll 5 min.
2026-05-13 19:17:36 +02:00

65 lines
2.2 KiB
Python

"""NOAA tides (station configurable) + phase lunaire calculee.
NOAA CO-OPS API : observed water level + predicted, station codee
(defaut Boston). Phase lunaire algorithme Conway approx (sans dep).
OSC out :
/data/tides/level water_level_m predicted_m residual_m
/data/tides/moon phase_0_1 illum_0_1
"""
from __future__ import annotations
import asyncio
import logging
import math
import time
import httpx
LOG = logging.getLogger("feed.tides")
NOAA_URL = ("https://api.tidesandcurrents.noaa.gov/api/prod/datagetter"
"?product={product}&application=AV-Live&format=json"
"&time_zone=gmt&datum=MLLW&units=metric"
"&date=latest&station={station}")
def _moon_phase(t: float) -> tuple[float, float]:
"""t epoch -> (phase 0..1 ou 0 = new, 0.5 = full ; illum 0..1).
Approx Conway, +-1 jour de precision suffisant."""
# Reference : 2000-01-06 18:14 UTC ~ new moon
new = 946755300.0
cycle = 29.530588853 * 86400.0
phase = ((t - new) % cycle) / cycle
illum = (1.0 - math.cos(2 * math.pi * phase)) / 2.0
return phase, illum
async def _fetch_level(cli: httpx.AsyncClient, station: str
) -> tuple[float, float]:
obs_url = NOAA_URL.format(product="water_level", station=station)
pred_url = NOAA_URL.format(product="predictions", station=station)
obs = await cli.get(obs_url)
pred = await cli.get(pred_url)
obs.raise_for_status()
pred.raise_for_status()
o = obs.json().get("data", [{}])[0]
p = pred.json().get("predictions", [{}])[0]
return float(o.get("v") or 0.0), float(p.get("v") or 0.0)
async def run(ctx) -> None:
cfg = ctx.cfg
station = str(cfg.get("station", "8443970")) # Boston by default
period = float(cfg.get("poll_seconds", 360.0)) # 6 min
async with httpx.AsyncClient(timeout=20.0) as cli:
while True:
try:
obs, pred = await _fetch_level(cli, station)
ctx.send("level", obs, pred, obs - pred)
except Exception as e: # noqa: BLE001
LOG.warning("tides fetch failed: %s", e)
phase, illum = _moon_phase(time.time())
ctx.send("moon", float(phase), float(illum))
await asyncio.sleep(period)