aboutsummaryrefslogtreecommitdiff
path: root/working/weather-kit-integration/README.org
blob: d04899f7d64495309fc1855575a23e6b1b1c727e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#+TITLE: Weather kit — for the Dupre kit / waybar / Wayland UX
#+AUTHOR: Craig Jennings
#+DATE: 2026-07-18

Built to completion elsewhere and handed to archsetup, which owns the Dupre kit,
waybar, and the Wayland UI/UX and will maintain it from here. This note is the
integration guide: what's in the package, the data contract, and how each
surface consumes it.

* What's in the package

| File                 | Purpose                                                                 |
|----------------------+-------------------------------------------------------------------------|
| =weather=            | The fetcher. Python 3, stdlib only. Hits Open-Meteo, normalises, caches. |
| =weather-glyphs.svg= | The custom condition glyph set (9 symbols, currentColor).               |
| =weather-chip.html=  | Reference renderer: the chosen chip, wired to the contract. Port target.|
| =README.org=         | This file.                                                              |

* The design decisions it encodes (settled over the 2026-07-18 session)

- *Source: Open-Meteo, =model=best_match=.* No API key, no signup, non-commercial
  free tier (10k calls/day; a 15-min poll is ~96/day). =best_match= auto-selects
  the most accurate regional model — NOAA blend in the US, ECMWF/ICON abroad — so
  the same binary is right on ratio in New Orleans and on velox travelling. For a
  bar chip there's no perceptible accuracy gap vs the NWS API, and NWS is US-only,
  so Open-Meteo is the single source. (NWS stays a possible *additive* feature
  later, only for official US severe-weather alerts.)
- *One fetcher, one cache, thin renderers.* Every surface shells out to =weather=
  and renders the JSON. Nobody re-implements the API call or the cache. A shared
  cache means several widgets polling still hit the API at most once per TTL and
  all show the identical reading.
- *Windy is a condition, not a compass reading.* Below the threshold the chip
  shows the direction vane + speed; at/above it, the gust glyph + speed. Same
  width either way. Threshold default *15 mph* (Beaufort 4 — where wind stops
  being ambient). It's a =--windy-threshold= flag, derived at output from
  =wind_mph=, so changing it takes effect without a cache bust.
- *Custom glyphs, not Nerd Font.* Seven conditions + a gust mark + a vane, drawn
  to sit in the instrument aesthetic. =currentColor=, so each surface recolours.

* The contract (=weather --json=)

#+begin_src json
{
  "temp_f": 82, "feels_f": 92,
  "condition": "clear",     // clear | partly | cloud | fog | rain | snow | storm
  "wmo_code": 0,
  "windy": false,           // wind_mph >= threshold
  "wind_mph": 6, "wind_dir": "WNW", "wind_deg": 293,
  "location": "New Orleans",
  "observed": "2026-07-18T01:00", "stale": false
}
#+end_src

=condition= is the coupling to the glyphs. Mapping (also in =weather-chip.html=):
=clear->wx-sun=, =partly->wx-partly=, =cloud->wx-cloud=, =fog->wx-fog=,
=rain->wx-rain=, =snow->wx-snow=, =storm->wx-storm=; plus =wx-wind= (shown when
=windy=) and =wx-vane= (shown otherwise, rotated by =wind_deg=). A consumer never
sees the WMO number unless it wants =wmo_code=.

=stale: true= means the network fetch failed and this is the last good reading —
render it dimmed, never blank the bar.

* WMO weather-code -> condition (what the fetcher collapses)

| WMO code(s)                          | condition |
|--------------------------------------+-----------|
| 0                                    | clear     |
| 1, 2                                 | partly    |
| 3                                    | cloud     |
| 45, 48                               | fog       |
| 51-57, 61-67, 80-82                  | rain      |
| 71-77, 85-86                         | snow      |
| 95, 96, 99                           | storm     |
| (anything unmapped)                  | cloud     |

* Install

Drop =weather= on =PATH= the way the other fleet CLIs travel (=agent-page=, etc.):
into the rulesets/dotfiles bin so =make install= / stow links it to
=~/.local/bin/weather= on every machine. It's dependency-free, so nothing else to
provision.

No coordinates are baked in (nothing personal ships in the repo). Each machine
sets its own location, resolved in order:

1. =--lat/--lon= flags.
2. =$WEATHER_LAT= / =$WEATHER_LON= (and optional =$WEATHER_NAME=).
3. =~/.config/weather/config.json= — ={"lat":29.97,"lon":-90.09,"name":"New Orleans"}=.
   Add ="use_geo": true= (or pass =--geo=) to prefer =whereami= — the right call
   on velox, which travels.

* How each surface consumes it

- *waybar* — a custom module:
  #+begin_src jsonc
  "custom/weather": { "exec": "weather --format waybar", "return-type": "json",
                      "interval": 900, "on-click": "..." }
  #+end_src
  =--format waybar= emits ={text, alt, class, tooltip}=; the stylesheet picks the
  icon by =alt=/=class= (=.clear=, =.windy=, =.stale=, ...).
- *Dupre kit / =svg.el=* — =(json-parse-string (shell-command-to-string "weather --json"))=,
  then render the chip. =weather-chip.html='s =renderChip(reading)= is the exact
  logic to port: glyph, temp, wind slot (gust vs vane by =windy=).
- *clock-panel (Python)* — =json.loads(subprocess.run(["weather","--json"],...).stdout)=.
  No HTTP in clock-panel at all.

* Notes / open choices for the maintainer

- Poll cadence lives with the consumer (waybar =interval=), not the tool; the tool
  just honours its own cache TTL (=--ttl=, default 900s).
- =weather-glyphs.svg= is the canonical glyph source. =weather-chip.html= inlines a
  copy for a standalone demo — if the glyphs change, update both (or have the kit
  build inline from the =.svg=).
- The chip currently shows the wind slot always. If it ever reads busy on a packed
  bar, hiding the slot below the threshold (show only when windy) is a one-line
  change — the data's already there.
- A "windy later today" hint would want the hourly endpoint (=hourly=wind_speed_10m=)
  and a small max-over-next-N-hours; the fetcher is structured to add that as a
  second field without touching consumers.