lucashudsn commited on
Commit
5550159
·
1 Parent(s): 64d8003

Restructure repo to AGENTS.md layout; pin Python 3.12; refresh deps

Browse files

- pyproject: requires-python >=3.12, hatchling build system, drop
langchain-community + unused anthropic, add gradio/daggr/openai/httpx/
pydantic (pytest as dev dep)
- create wavereader/ package with documented stubs (spots, forecasts,
scoring, agent, tools, api, app, daggr_pipeline); fold main.py's
hello-world into api.py and delete main.py
- forecasts.py stub records Open-Meteo spike findings (marine endpoint
serves waves only, wind from forecast API, ~10km offshore grid snap)
- add evals/golden_questions.json draft (10 golden questions), smoke tests,
.env.example, Open-Meteo Snapper Rocks sample fixture (7 days verified
sane: 0.58-1.32 m hourly waves)

.env.example ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Copy to .env and fill in. Never commit .env.
2
+ # HF token (https://huggingface.co/settings/tokens) — router provider default.
3
+ HF_TOKEN=
4
+ # NVIDIA NIM key (build.nvidia.com) — provider switch alternative.
5
+ NVIDIA_API_KEY=
.python-version CHANGED
@@ -1 +1 @@
1
- 3.14
 
1
+ 3.12
data/samples/openmeteo_snapper.json ADDED
@@ -0,0 +1,1573 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "meta": {
3
+ "spot": "Snapper Rocks",
4
+ "fetched": "2026-08-30",
5
+ "spot_lat": -28.173,
6
+ "spot_lon": 153.556
7
+ },
8
+ "marine": {
9
+ "latitude": -28.041668,
10
+ "longitude": 153.62502,
11
+ "generationtime_ms": 0.19097328186035156,
12
+ "utc_offset_seconds": 0,
13
+ "timezone": "GMT",
14
+ "timezone_abbreviation": "GMT",
15
+ "elevation": 0.0,
16
+ "hourly_units": {
17
+ "time": "iso8601",
18
+ "wave_height": "m",
19
+ "wave_period": "s",
20
+ "wave_direction": "\u00b0",
21
+ "wind_wave_height": "m",
22
+ "swell_wave_height": "m"
23
+ },
24
+ "hourly": {
25
+ "time": [
26
+ "2026-08-30T00:00",
27
+ "2026-08-30T01:00",
28
+ "2026-08-30T02:00",
29
+ "2026-08-30T03:00",
30
+ "2026-08-30T04:00",
31
+ "2026-08-30T05:00",
32
+ "2026-08-30T06:00",
33
+ "2026-08-30T07:00",
34
+ "2026-08-30T08:00",
35
+ "2026-08-30T09:00",
36
+ "2026-08-30T10:00",
37
+ "2026-08-30T11:00",
38
+ "2026-08-30T12:00",
39
+ "2026-08-30T13:00",
40
+ "2026-08-30T14:00",
41
+ "2026-08-30T15:00",
42
+ "2026-08-30T16:00",
43
+ "2026-08-30T17:00",
44
+ "2026-08-30T18:00",
45
+ "2026-08-30T19:00",
46
+ "2026-08-30T20:00",
47
+ "2026-08-30T21:00",
48
+ "2026-08-30T22:00",
49
+ "2026-08-30T23:00",
50
+ "2026-08-31T00:00",
51
+ "2026-08-31T01:00",
52
+ "2026-08-31T02:00",
53
+ "2026-08-31T03:00",
54
+ "2026-08-31T04:00",
55
+ "2026-08-31T05:00",
56
+ "2026-08-31T06:00",
57
+ "2026-08-31T07:00",
58
+ "2026-08-31T08:00",
59
+ "2026-08-31T09:00",
60
+ "2026-08-31T10:00",
61
+ "2026-08-31T11:00",
62
+ "2026-08-31T12:00",
63
+ "2026-08-31T13:00",
64
+ "2026-08-31T14:00",
65
+ "2026-08-31T15:00",
66
+ "2026-08-31T16:00",
67
+ "2026-08-31T17:00",
68
+ "2026-08-31T18:00",
69
+ "2026-08-31T19:00",
70
+ "2026-08-31T20:00",
71
+ "2026-08-31T21:00",
72
+ "2026-08-31T22:00",
73
+ "2026-08-31T23:00",
74
+ "2026-09-01T00:00",
75
+ "2026-09-01T01:00",
76
+ "2026-09-01T02:00",
77
+ "2026-09-01T03:00",
78
+ "2026-09-01T04:00",
79
+ "2026-09-01T05:00",
80
+ "2026-09-01T06:00",
81
+ "2026-09-01T07:00",
82
+ "2026-09-01T08:00",
83
+ "2026-09-01T09:00",
84
+ "2026-09-01T10:00",
85
+ "2026-09-01T11:00",
86
+ "2026-09-01T12:00",
87
+ "2026-09-01T13:00",
88
+ "2026-09-01T14:00",
89
+ "2026-09-01T15:00",
90
+ "2026-09-01T16:00",
91
+ "2026-09-01T17:00",
92
+ "2026-09-01T18:00",
93
+ "2026-09-01T19:00",
94
+ "2026-09-01T20:00",
95
+ "2026-09-01T21:00",
96
+ "2026-09-01T22:00",
97
+ "2026-09-01T23:00",
98
+ "2026-09-02T00:00",
99
+ "2026-09-02T01:00",
100
+ "2026-09-02T02:00",
101
+ "2026-09-02T03:00",
102
+ "2026-09-02T04:00",
103
+ "2026-09-02T05:00",
104
+ "2026-09-02T06:00",
105
+ "2026-09-02T07:00",
106
+ "2026-09-02T08:00",
107
+ "2026-09-02T09:00",
108
+ "2026-09-02T10:00",
109
+ "2026-09-02T11:00",
110
+ "2026-09-02T12:00",
111
+ "2026-09-02T13:00",
112
+ "2026-09-02T14:00",
113
+ "2026-09-02T15:00",
114
+ "2026-09-02T16:00",
115
+ "2026-09-02T17:00",
116
+ "2026-09-02T18:00",
117
+ "2026-09-02T19:00",
118
+ "2026-09-02T20:00",
119
+ "2026-09-02T21:00",
120
+ "2026-09-02T22:00",
121
+ "2026-09-02T23:00",
122
+ "2026-09-03T00:00",
123
+ "2026-09-03T01:00",
124
+ "2026-09-03T02:00",
125
+ "2026-09-03T03:00",
126
+ "2026-09-03T04:00",
127
+ "2026-09-03T05:00",
128
+ "2026-09-03T06:00",
129
+ "2026-09-03T07:00",
130
+ "2026-09-03T08:00",
131
+ "2026-09-03T09:00",
132
+ "2026-09-03T10:00",
133
+ "2026-09-03T11:00",
134
+ "2026-09-03T12:00",
135
+ "2026-09-03T13:00",
136
+ "2026-09-03T14:00",
137
+ "2026-09-03T15:00",
138
+ "2026-09-03T16:00",
139
+ "2026-09-03T17:00",
140
+ "2026-09-03T18:00",
141
+ "2026-09-03T19:00",
142
+ "2026-09-03T20:00",
143
+ "2026-09-03T21:00",
144
+ "2026-09-03T22:00",
145
+ "2026-09-03T23:00",
146
+ "2026-09-04T00:00",
147
+ "2026-09-04T01:00",
148
+ "2026-09-04T02:00",
149
+ "2026-09-04T03:00",
150
+ "2026-09-04T04:00",
151
+ "2026-09-04T05:00",
152
+ "2026-09-04T06:00",
153
+ "2026-09-04T07:00",
154
+ "2026-09-04T08:00",
155
+ "2026-09-04T09:00",
156
+ "2026-09-04T10:00",
157
+ "2026-09-04T11:00",
158
+ "2026-09-04T12:00",
159
+ "2026-09-04T13:00",
160
+ "2026-09-04T14:00",
161
+ "2026-09-04T15:00",
162
+ "2026-09-04T16:00",
163
+ "2026-09-04T17:00",
164
+ "2026-09-04T18:00",
165
+ "2026-09-04T19:00",
166
+ "2026-09-04T20:00",
167
+ "2026-09-04T21:00",
168
+ "2026-09-04T22:00",
169
+ "2026-09-04T23:00",
170
+ "2026-09-05T00:00",
171
+ "2026-09-05T01:00",
172
+ "2026-09-05T02:00",
173
+ "2026-09-05T03:00",
174
+ "2026-09-05T04:00",
175
+ "2026-09-05T05:00",
176
+ "2026-09-05T06:00",
177
+ "2026-09-05T07:00",
178
+ "2026-09-05T08:00",
179
+ "2026-09-05T09:00",
180
+ "2026-09-05T10:00",
181
+ "2026-09-05T11:00",
182
+ "2026-09-05T12:00",
183
+ "2026-09-05T13:00",
184
+ "2026-09-05T14:00",
185
+ "2026-09-05T15:00",
186
+ "2026-09-05T16:00",
187
+ "2026-09-05T17:00",
188
+ "2026-09-05T18:00",
189
+ "2026-09-05T19:00",
190
+ "2026-09-05T20:00",
191
+ "2026-09-05T21:00",
192
+ "2026-09-05T22:00",
193
+ "2026-09-05T23:00"
194
+ ],
195
+ "wave_height": [
196
+ 1.26,
197
+ 1.28,
198
+ 1.28,
199
+ 1.3,
200
+ 1.3,
201
+ 1.32,
202
+ 1.32,
203
+ 1.28,
204
+ 1.22,
205
+ 1.18,
206
+ 1.14,
207
+ 1.1,
208
+ 1.06,
209
+ 1.04,
210
+ 1.02,
211
+ 1.0,
212
+ 0.98,
213
+ 0.96,
214
+ 0.94,
215
+ 0.92,
216
+ 0.92,
217
+ 0.9,
218
+ 0.92,
219
+ 0.94,
220
+ 0.96,
221
+ 0.98,
222
+ 1.02,
223
+ 1.04,
224
+ 1.04,
225
+ 1.06,
226
+ 1.06,
227
+ 1.04,
228
+ 1.04,
229
+ 1.02,
230
+ 1.02,
231
+ 1.0,
232
+ 1.0,
233
+ 1.0,
234
+ 0.98,
235
+ 0.98,
236
+ 0.96,
237
+ 0.96,
238
+ 0.94,
239
+ 0.94,
240
+ 0.92,
241
+ 0.92,
242
+ 0.9,
243
+ 0.9,
244
+ 0.88,
245
+ 0.88,
246
+ 0.86,
247
+ 0.86,
248
+ 0.86,
249
+ 0.84,
250
+ 0.84,
251
+ 0.84,
252
+ 0.84,
253
+ 0.84,
254
+ 0.84,
255
+ 0.84,
256
+ 0.84,
257
+ 0.84,
258
+ 0.84,
259
+ 0.84,
260
+ 0.82,
261
+ 0.82,
262
+ 0.8,
263
+ 0.8,
264
+ 0.78,
265
+ 0.78,
266
+ 0.76,
267
+ 0.74,
268
+ 0.72,
269
+ 0.74,
270
+ 0.74,
271
+ 0.76,
272
+ 0.78,
273
+ 0.82,
274
+ 0.84,
275
+ 0.84,
276
+ 0.86,
277
+ 0.86,
278
+ 0.86,
279
+ 0.84,
280
+ 0.84,
281
+ 0.84,
282
+ 0.84,
283
+ 0.84,
284
+ 0.84,
285
+ 0.84,
286
+ 0.84,
287
+ 0.84,
288
+ 0.84,
289
+ 0.84,
290
+ 0.84,
291
+ 0.82,
292
+ 0.82,
293
+ 0.8,
294
+ 0.78,
295
+ 0.76,
296
+ 0.8,
297
+ 0.82,
298
+ 0.86,
299
+ 0.88,
300
+ 0.9,
301
+ 0.92,
302
+ 0.92,
303
+ 0.92,
304
+ 0.92,
305
+ 0.92,
306
+ 0.9,
307
+ 0.9,
308
+ 0.88,
309
+ 0.86,
310
+ 0.84,
311
+ 0.82,
312
+ 0.8,
313
+ 0.78,
314
+ 0.76,
315
+ 0.72,
316
+ 0.7,
317
+ 0.66,
318
+ 0.64,
319
+ 0.6,
320
+ 0.6,
321
+ 0.58,
322
+ 0.58,
323
+ 0.6,
324
+ 0.6,
325
+ 0.62,
326
+ 0.64,
327
+ 0.64,
328
+ 0.66,
329
+ 0.64,
330
+ 0.64,
331
+ 0.62,
332
+ 0.62,
333
+ 0.62,
334
+ 0.62,
335
+ 0.64,
336
+ 0.64,
337
+ 0.66,
338
+ 0.68,
339
+ 0.68,
340
+ 0.7,
341
+ 0.72,
342
+ 0.76,
343
+ 0.78,
344
+ 0.84,
345
+ 0.9,
346
+ 0.96,
347
+ 0.96,
348
+ 0.98,
349
+ 0.98,
350
+ 0.96,
351
+ 0.94,
352
+ 0.92,
353
+ 0.92,
354
+ 0.9,
355
+ 0.9,
356
+ 0.88,
357
+ 0.86,
358
+ 0.84,
359
+ 0.82,
360
+ 0.8,
361
+ 0.78,
362
+ 0.76,
363
+ 0.76
364
+ ],
365
+ "wave_period": [
366
+ 6.0,
367
+ 5.95,
368
+ 5.85,
369
+ 5.8,
370
+ 5.7,
371
+ 5.65,
372
+ 5.6,
373
+ 5.6,
374
+ 5.7,
375
+ 5.75,
376
+ 5.85,
377
+ 5.95,
378
+ 6.0,
379
+ 6.05,
380
+ 6.05,
381
+ 6.05,
382
+ 6.05,
383
+ 6.1,
384
+ 6.1,
385
+ 6.15,
386
+ 6.25,
387
+ 6.25,
388
+ 6.15,
389
+ 5.95,
390
+ 5.8,
391
+ 5.75,
392
+ 5.7,
393
+ 5.7,
394
+ 5.75,
395
+ 5.8,
396
+ 5.9,
397
+ 6.0,
398
+ 6.1,
399
+ 6.25,
400
+ 6.4,
401
+ 6.6,
402
+ 6.75,
403
+ 6.85,
404
+ 6.95,
405
+ 7.05,
406
+ 7.15,
407
+ 7.3,
408
+ 7.4,
409
+ 7.5,
410
+ 7.55,
411
+ 7.6,
412
+ 7.6,
413
+ 7.65,
414
+ 7.65,
415
+ 7.7,
416
+ 7.8,
417
+ 7.85,
418
+ 7.9,
419
+ 8.0,
420
+ 8.05,
421
+ 8.05,
422
+ 8.05,
423
+ 8.05,
424
+ 8.0,
425
+ 7.95,
426
+ 7.95,
427
+ 7.95,
428
+ 8.0,
429
+ 8.05,
430
+ 8.05,
431
+ 8.1,
432
+ 8.1,
433
+ 8.15,
434
+ 8.15,
435
+ 8.2,
436
+ 8.25,
437
+ 8.3,
438
+ 8.3,
439
+ 8.15,
440
+ 7.95,
441
+ 7.65,
442
+ 7.25,
443
+ 6.85,
444
+ 6.5,
445
+ 6.35,
446
+ 6.35,
447
+ 6.35,
448
+ 6.35,
449
+ 6.45,
450
+ 6.45,
451
+ 6.4,
452
+ 6.35,
453
+ 6.3,
454
+ 6.25,
455
+ 6.15,
456
+ 6.1,
457
+ 6.05,
458
+ 6.05,
459
+ 6.05,
460
+ 6.0,
461
+ 5.95,
462
+ 5.95,
463
+ 6.0,
464
+ 6.05,
465
+ 6.05,
466
+ 5.85,
467
+ 5.6,
468
+ 5.4,
469
+ 5.3,
470
+ 5.2,
471
+ 5.2,
472
+ 5.25,
473
+ 5.3,
474
+ 5.4,
475
+ 5.45,
476
+ 5.5,
477
+ 5.55,
478
+ 5.6,
479
+ 5.7,
480
+ 5.8,
481
+ 5.9,
482
+ 6.0,
483
+ 6.1,
484
+ 6.2,
485
+ 6.35,
486
+ 6.5,
487
+ 6.7,
488
+ 6.95,
489
+ 7.1,
490
+ 7.15,
491
+ 7.15,
492
+ 7.05,
493
+ 6.8,
494
+ 6.5,
495
+ 6.2,
496
+ 6.0,
497
+ 5.85,
498
+ 5.8,
499
+ 5.85,
500
+ 6.0,
501
+ 6.1,
502
+ 6.15,
503
+ 6.2,
504
+ 6.2,
505
+ 6.2,
506
+ 6.15,
507
+ 6.1,
508
+ 5.95,
509
+ 5.8,
510
+ 5.6,
511
+ 5.4,
512
+ 5.2,
513
+ 5.0,
514
+ 4.85,
515
+ 4.7,
516
+ 4.6,
517
+ 4.6,
518
+ 4.7,
519
+ 4.75,
520
+ 4.8,
521
+ 4.8,
522
+ 4.85,
523
+ 4.9,
524
+ 4.9,
525
+ 4.95,
526
+ 4.95,
527
+ 4.95,
528
+ 5.0,
529
+ 5.1,
530
+ 5.25,
531
+ 5.35,
532
+ 5.45,
533
+ 5.55
534
+ ],
535
+ "wave_direction": [
536
+ 137,
537
+ 137,
538
+ 138,
539
+ 138,
540
+ 139,
541
+ 139,
542
+ 140,
543
+ 139,
544
+ 138,
545
+ 137,
546
+ 135,
547
+ 134,
548
+ 132,
549
+ 131,
550
+ 131,
551
+ 130,
552
+ 129,
553
+ 129,
554
+ 128,
555
+ 128,
556
+ 127,
557
+ 127,
558
+ 129,
559
+ 131,
560
+ 133,
561
+ 133,
562
+ 133,
563
+ 133,
564
+ 134,
565
+ 134,
566
+ 135,
567
+ 134,
568
+ 134,
569
+ 133,
570
+ 132,
571
+ 131,
572
+ 130,
573
+ 129,
574
+ 128,
575
+ 127,
576
+ 126,
577
+ 126,
578
+ 125,
579
+ 125,
580
+ 124,
581
+ 124,
582
+ 123,
583
+ 123,
584
+ 122,
585
+ 121,
586
+ 121,
587
+ 120,
588
+ 120,
589
+ 119,
590
+ 119,
591
+ 119,
592
+ 118,
593
+ 118,
594
+ 117,
595
+ 116,
596
+ 115,
597
+ 115,
598
+ 115,
599
+ 115,
600
+ 114,
601
+ 114,
602
+ 113,
603
+ 113,
604
+ 113,
605
+ 113,
606
+ 113,
607
+ 112,
608
+ 112,
609
+ 108,
610
+ 104,
611
+ 100,
612
+ 93,
613
+ 85,
614
+ 78,
615
+ 76,
616
+ 73,
617
+ 71,
618
+ 70,
619
+ 70,
620
+ 69,
621
+ 67,
622
+ 66,
623
+ 64,
624
+ 60,
625
+ 57,
626
+ 53,
627
+ 51,
628
+ 50,
629
+ 48,
630
+ 47,
631
+ 45,
632
+ 44,
633
+ 45,
634
+ 47,
635
+ 48,
636
+ 45,
637
+ 42,
638
+ 39,
639
+ 37,
640
+ 36,
641
+ 34,
642
+ 35,
643
+ 37,
644
+ 38,
645
+ 38,
646
+ 39,
647
+ 39,
648
+ 40,
649
+ 40,
650
+ 41,
651
+ 42,
652
+ 44,
653
+ 45,
654
+ 47,
655
+ 50,
656
+ 52,
657
+ 54,
658
+ 56,
659
+ 58,
660
+ 57,
661
+ 57,
662
+ 56,
663
+ 53,
664
+ 49,
665
+ 46,
666
+ 43,
667
+ 40,
668
+ 37,
669
+ 39,
670
+ 42,
671
+ 44,
672
+ 44,
673
+ 45,
674
+ 45,
675
+ 43,
676
+ 42,
677
+ 40,
678
+ 36,
679
+ 33,
680
+ 29,
681
+ 25,
682
+ 21,
683
+ 17,
684
+ 16,
685
+ 16,
686
+ 15,
687
+ 15,
688
+ 15,
689
+ 15,
690
+ 15,
691
+ 15,
692
+ 15,
693
+ 15,
694
+ 15,
695
+ 15,
696
+ 15,
697
+ 15,
698
+ 15,
699
+ 17,
700
+ 18,
701
+ 20,
702
+ 21,
703
+ 21
704
+ ],
705
+ "wind_wave_height": [
706
+ 0.72,
707
+ 0.78,
708
+ 0.86,
709
+ 0.92,
710
+ 0.98,
711
+ 1.02,
712
+ 1.08,
713
+ 0.96,
714
+ 0.86,
715
+ 0.74,
716
+ 0.58,
717
+ 0.44,
718
+ 0.28,
719
+ 0.3,
720
+ 0.3,
721
+ 0.32,
722
+ 0.3,
723
+ 0.28,
724
+ 0.26,
725
+ 0.26,
726
+ 0.26,
727
+ 0.26,
728
+ 0.36,
729
+ 0.46,
730
+ 0.56,
731
+ 0.62,
732
+ 0.66,
733
+ 0.72,
734
+ 0.7,
735
+ 0.68,
736
+ 0.66,
737
+ 0.58,
738
+ 0.5,
739
+ 0.42,
740
+ 0.36,
741
+ 0.28,
742
+ 0.22,
743
+ 0.16,
744
+ 0.12,
745
+ 0.06,
746
+ 0.06,
747
+ 0.08,
748
+ 0.08,
749
+ 0.08,
750
+ 0.1,
751
+ 0.1,
752
+ 0.08,
753
+ 0.04,
754
+ 0.02,
755
+ 0.02,
756
+ 0.0,
757
+ 0.0,
758
+ 0.0,
759
+ 0.0,
760
+ 0.0,
761
+ 0.06,
762
+ 0.12,
763
+ 0.18,
764
+ 0.2,
765
+ 0.2,
766
+ 0.22,
767
+ 0.2,
768
+ 0.16,
769
+ 0.14,
770
+ 0.16,
771
+ 0.18,
772
+ 0.2,
773
+ 0.2,
774
+ 0.2,
775
+ 0.2,
776
+ 0.2,
777
+ 0.2,
778
+ 0.2,
779
+ 0.24,
780
+ 0.3,
781
+ 0.34,
782
+ 0.4,
783
+ 0.48,
784
+ 0.54,
785
+ 0.5,
786
+ 0.46,
787
+ 0.42,
788
+ 0.38,
789
+ 0.34,
790
+ 0.3,
791
+ 0.3,
792
+ 0.3,
793
+ 0.3,
794
+ 0.32,
795
+ 0.34,
796
+ 0.36,
797
+ 0.36,
798
+ 0.36,
799
+ 0.36,
800
+ 0.38,
801
+ 0.42,
802
+ 0.44,
803
+ 0.44,
804
+ 0.42,
805
+ 0.42,
806
+ 0.5,
807
+ 0.58,
808
+ 0.66,
809
+ 0.64,
810
+ 0.64,
811
+ 0.62,
812
+ 0.48,
813
+ 0.36,
814
+ 0.22,
815
+ 0.22,
816
+ 0.2,
817
+ 0.2,
818
+ 0.18,
819
+ 0.16,
820
+ 0.14,
821
+ 0.1,
822
+ 0.06,
823
+ 0.02,
824
+ 0.02,
825
+ 0.0,
826
+ 0.0,
827
+ 0.02,
828
+ 0.04,
829
+ 0.06,
830
+ 0.08,
831
+ 0.1,
832
+ 0.12,
833
+ 0.14,
834
+ 0.16,
835
+ 0.18,
836
+ 0.2,
837
+ 0.2,
838
+ 0.22,
839
+ 0.18,
840
+ 0.14,
841
+ 0.1,
842
+ 0.1,
843
+ 0.1,
844
+ 0.1,
845
+ 0.12,
846
+ 0.12,
847
+ 0.14,
848
+ 0.2,
849
+ 0.28,
850
+ 0.34,
851
+ 0.42,
852
+ 0.5,
853
+ 0.58,
854
+ 0.68,
855
+ 0.78,
856
+ 0.88,
857
+ 0.82,
858
+ 0.76,
859
+ 0.7,
860
+ 0.62,
861
+ 0.52,
862
+ 0.44,
863
+ 0.44,
864
+ 0.42,
865
+ 0.42,
866
+ 0.4,
867
+ 0.4,
868
+ 0.38,
869
+ 0.28,
870
+ 0.2,
871
+ 0.1,
872
+ 0.06,
873
+ 0.04
874
+ ],
875
+ "swell_wave_height": [
876
+ 1.0,
877
+ 0.9,
878
+ 0.82,
879
+ 0.72,
880
+ 0.62,
881
+ 0.54,
882
+ 0.44,
883
+ 0.56,
884
+ 0.68,
885
+ 0.8,
886
+ 0.86,
887
+ 0.94,
888
+ 1.0,
889
+ 0.98,
890
+ 0.94,
891
+ 0.92,
892
+ 0.86,
893
+ 0.78,
894
+ 0.72,
895
+ 0.72,
896
+ 0.72,
897
+ 0.72,
898
+ 0.68,
899
+ 0.66,
900
+ 0.62,
901
+ 0.62,
902
+ 0.62,
903
+ 0.62,
904
+ 0.66,
905
+ 0.72,
906
+ 0.76,
907
+ 0.8,
908
+ 0.84,
909
+ 0.88,
910
+ 0.86,
911
+ 0.82,
912
+ 0.8,
913
+ 0.7,
914
+ 0.6,
915
+ 0.5,
916
+ 0.52,
917
+ 0.54,
918
+ 0.56,
919
+ 0.56,
920
+ 0.56,
921
+ 0.56,
922
+ 0.56,
923
+ 0.54,
924
+ 0.54,
925
+ 0.54,
926
+ 0.56,
927
+ 0.56,
928
+ 0.58,
929
+ 0.62,
930
+ 0.64,
931
+ 0.64,
932
+ 0.62,
933
+ 0.62,
934
+ 0.62,
935
+ 0.6,
936
+ 0.6,
937
+ 0.6,
938
+ 0.6,
939
+ 0.6,
940
+ 0.58,
941
+ 0.58,
942
+ 0.56,
943
+ 0.56,
944
+ 0.54,
945
+ 0.54,
946
+ 0.54,
947
+ 0.52,
948
+ 0.52,
949
+ 0.5,
950
+ 0.5,
951
+ 0.48,
952
+ 0.48,
953
+ 0.48,
954
+ 0.48,
955
+ 0.48,
956
+ 0.46,
957
+ 0.46,
958
+ 0.46,
959
+ 0.46,
960
+ 0.46,
961
+ 0.46,
962
+ 0.46,
963
+ 0.46,
964
+ 0.5,
965
+ 0.54,
966
+ 0.58,
967
+ 0.56,
968
+ 0.54,
969
+ 0.52,
970
+ 0.52,
971
+ 0.5,
972
+ 0.5,
973
+ 0.46,
974
+ 0.4,
975
+ 0.36,
976
+ 0.36,
977
+ 0.34,
978
+ 0.34,
979
+ 0.4,
980
+ 0.44,
981
+ 0.5,
982
+ 0.6,
983
+ 0.7,
984
+ 0.8,
985
+ 0.78,
986
+ 0.78,
987
+ 0.76,
988
+ 0.76,
989
+ 0.74,
990
+ 0.74,
991
+ 0.72,
992
+ 0.68,
993
+ 0.66,
994
+ 0.64,
995
+ 0.6,
996
+ 0.58,
997
+ 0.54,
998
+ 0.5,
999
+ 0.46,
1000
+ 0.44,
1001
+ 0.4,
1002
+ 0.38,
1003
+ 0.38,
1004
+ 0.36,
1005
+ 0.36,
1006
+ 0.38,
1007
+ 0.42,
1008
+ 0.44,
1009
+ 0.46,
1010
+ 0.5,
1011
+ 0.52,
1012
+ 0.52,
1013
+ 0.52,
1014
+ 0.52,
1015
+ 0.52,
1016
+ 0.52,
1017
+ 0.52,
1018
+ 0.52,
1019
+ 0.52,
1020
+ 0.52,
1021
+ 0.48,
1022
+ 0.44,
1023
+ 0.4,
1024
+ 0.36,
1025
+ 0.3,
1026
+ 0.26,
1027
+ 0.36,
1028
+ 0.48,
1029
+ 0.58,
1030
+ 0.64,
1031
+ 0.7,
1032
+ 0.76,
1033
+ 0.76,
1034
+ 0.76,
1035
+ 0.76,
1036
+ 0.74,
1037
+ 0.74,
1038
+ 0.72,
1039
+ 0.72,
1040
+ 0.74,
1041
+ 0.74,
1042
+ 0.74,
1043
+ 0.72
1044
+ ]
1045
+ }
1046
+ },
1047
+ "weather": {
1048
+ "latitude": -28.224957,
1049
+ "longitude": 153.49944,
1050
+ "generationtime_ms": 0.06592273712158203,
1051
+ "utc_offset_seconds": 0,
1052
+ "timezone": "GMT",
1053
+ "timezone_abbreviation": "GMT",
1054
+ "elevation": 0.0,
1055
+ "hourly_units": {
1056
+ "time": "iso8601",
1057
+ "wind_speed_10m": "km/h",
1058
+ "wind_direction_10m": "\u00b0"
1059
+ },
1060
+ "hourly": {
1061
+ "time": [
1062
+ "2026-08-30T00:00",
1063
+ "2026-08-30T01:00",
1064
+ "2026-08-30T02:00",
1065
+ "2026-08-30T03:00",
1066
+ "2026-08-30T04:00",
1067
+ "2026-08-30T05:00",
1068
+ "2026-08-30T06:00",
1069
+ "2026-08-30T07:00",
1070
+ "2026-08-30T08:00",
1071
+ "2026-08-30T09:00",
1072
+ "2026-08-30T10:00",
1073
+ "2026-08-30T11:00",
1074
+ "2026-08-30T12:00",
1075
+ "2026-08-30T13:00",
1076
+ "2026-08-30T14:00",
1077
+ "2026-08-30T15:00",
1078
+ "2026-08-30T16:00",
1079
+ "2026-08-30T17:00",
1080
+ "2026-08-30T18:00",
1081
+ "2026-08-30T19:00",
1082
+ "2026-08-30T20:00",
1083
+ "2026-08-30T21:00",
1084
+ "2026-08-30T22:00",
1085
+ "2026-08-30T23:00",
1086
+ "2026-08-31T00:00",
1087
+ "2026-08-31T01:00",
1088
+ "2026-08-31T02:00",
1089
+ "2026-08-31T03:00",
1090
+ "2026-08-31T04:00",
1091
+ "2026-08-31T05:00",
1092
+ "2026-08-31T06:00",
1093
+ "2026-08-31T07:00",
1094
+ "2026-08-31T08:00",
1095
+ "2026-08-31T09:00",
1096
+ "2026-08-31T10:00",
1097
+ "2026-08-31T11:00",
1098
+ "2026-08-31T12:00",
1099
+ "2026-08-31T13:00",
1100
+ "2026-08-31T14:00",
1101
+ "2026-08-31T15:00",
1102
+ "2026-08-31T16:00",
1103
+ "2026-08-31T17:00",
1104
+ "2026-08-31T18:00",
1105
+ "2026-08-31T19:00",
1106
+ "2026-08-31T20:00",
1107
+ "2026-08-31T21:00",
1108
+ "2026-08-31T22:00",
1109
+ "2026-08-31T23:00",
1110
+ "2026-09-01T00:00",
1111
+ "2026-09-01T01:00",
1112
+ "2026-09-01T02:00",
1113
+ "2026-09-01T03:00",
1114
+ "2026-09-01T04:00",
1115
+ "2026-09-01T05:00",
1116
+ "2026-09-01T06:00",
1117
+ "2026-09-01T07:00",
1118
+ "2026-09-01T08:00",
1119
+ "2026-09-01T09:00",
1120
+ "2026-09-01T10:00",
1121
+ "2026-09-01T11:00",
1122
+ "2026-09-01T12:00",
1123
+ "2026-09-01T13:00",
1124
+ "2026-09-01T14:00",
1125
+ "2026-09-01T15:00",
1126
+ "2026-09-01T16:00",
1127
+ "2026-09-01T17:00",
1128
+ "2026-09-01T18:00",
1129
+ "2026-09-01T19:00",
1130
+ "2026-09-01T20:00",
1131
+ "2026-09-01T21:00",
1132
+ "2026-09-01T22:00",
1133
+ "2026-09-01T23:00",
1134
+ "2026-09-02T00:00",
1135
+ "2026-09-02T01:00",
1136
+ "2026-09-02T02:00",
1137
+ "2026-09-02T03:00",
1138
+ "2026-09-02T04:00",
1139
+ "2026-09-02T05:00",
1140
+ "2026-09-02T06:00",
1141
+ "2026-09-02T07:00",
1142
+ "2026-09-02T08:00",
1143
+ "2026-09-02T09:00",
1144
+ "2026-09-02T10:00",
1145
+ "2026-09-02T11:00",
1146
+ "2026-09-02T12:00",
1147
+ "2026-09-02T13:00",
1148
+ "2026-09-02T14:00",
1149
+ "2026-09-02T15:00",
1150
+ "2026-09-02T16:00",
1151
+ "2026-09-02T17:00",
1152
+ "2026-09-02T18:00",
1153
+ "2026-09-02T19:00",
1154
+ "2026-09-02T20:00",
1155
+ "2026-09-02T21:00",
1156
+ "2026-09-02T22:00",
1157
+ "2026-09-02T23:00",
1158
+ "2026-09-03T00:00",
1159
+ "2026-09-03T01:00",
1160
+ "2026-09-03T02:00",
1161
+ "2026-09-03T03:00",
1162
+ "2026-09-03T04:00",
1163
+ "2026-09-03T05:00",
1164
+ "2026-09-03T06:00",
1165
+ "2026-09-03T07:00",
1166
+ "2026-09-03T08:00",
1167
+ "2026-09-03T09:00",
1168
+ "2026-09-03T10:00",
1169
+ "2026-09-03T11:00",
1170
+ "2026-09-03T12:00",
1171
+ "2026-09-03T13:00",
1172
+ "2026-09-03T14:00",
1173
+ "2026-09-03T15:00",
1174
+ "2026-09-03T16:00",
1175
+ "2026-09-03T17:00",
1176
+ "2026-09-03T18:00",
1177
+ "2026-09-03T19:00",
1178
+ "2026-09-03T20:00",
1179
+ "2026-09-03T21:00",
1180
+ "2026-09-03T22:00",
1181
+ "2026-09-03T23:00",
1182
+ "2026-09-04T00:00",
1183
+ "2026-09-04T01:00",
1184
+ "2026-09-04T02:00",
1185
+ "2026-09-04T03:00",
1186
+ "2026-09-04T04:00",
1187
+ "2026-09-04T05:00",
1188
+ "2026-09-04T06:00",
1189
+ "2026-09-04T07:00",
1190
+ "2026-09-04T08:00",
1191
+ "2026-09-04T09:00",
1192
+ "2026-09-04T10:00",
1193
+ "2026-09-04T11:00",
1194
+ "2026-09-04T12:00",
1195
+ "2026-09-04T13:00",
1196
+ "2026-09-04T14:00",
1197
+ "2026-09-04T15:00",
1198
+ "2026-09-04T16:00",
1199
+ "2026-09-04T17:00",
1200
+ "2026-09-04T18:00",
1201
+ "2026-09-04T19:00",
1202
+ "2026-09-04T20:00",
1203
+ "2026-09-04T21:00",
1204
+ "2026-09-04T22:00",
1205
+ "2026-09-04T23:00",
1206
+ "2026-09-05T00:00",
1207
+ "2026-09-05T01:00",
1208
+ "2026-09-05T02:00",
1209
+ "2026-09-05T03:00",
1210
+ "2026-09-05T04:00",
1211
+ "2026-09-05T05:00",
1212
+ "2026-09-05T06:00",
1213
+ "2026-09-05T07:00",
1214
+ "2026-09-05T08:00",
1215
+ "2026-09-05T09:00",
1216
+ "2026-09-05T10:00",
1217
+ "2026-09-05T11:00",
1218
+ "2026-09-05T12:00",
1219
+ "2026-09-05T13:00",
1220
+ "2026-09-05T14:00",
1221
+ "2026-09-05T15:00",
1222
+ "2026-09-05T16:00",
1223
+ "2026-09-05T17:00",
1224
+ "2026-09-05T18:00",
1225
+ "2026-09-05T19:00",
1226
+ "2026-09-05T20:00",
1227
+ "2026-09-05T21:00",
1228
+ "2026-09-05T22:00",
1229
+ "2026-09-05T23:00"
1230
+ ],
1231
+ "wind_speed_10m": [
1232
+ 18.2,
1233
+ 18.8,
1234
+ 20.2,
1235
+ 21.6,
1236
+ 21.0,
1237
+ 19.2,
1238
+ 20.9,
1239
+ 18.2,
1240
+ 14.1,
1241
+ 12.3,
1242
+ 10.3,
1243
+ 11.2,
1244
+ 12.1,
1245
+ 12.2,
1246
+ 11.7,
1247
+ 12.1,
1248
+ 11.3,
1249
+ 11.1,
1250
+ 11.0,
1251
+ 11.4,
1252
+ 11.9,
1253
+ 11.9,
1254
+ 10.6,
1255
+ 12.4,
1256
+ 16.0,
1257
+ 18.2,
1258
+ 17.6,
1259
+ 16.9,
1260
+ 18.3,
1261
+ 17.1,
1262
+ 16.2,
1263
+ 13.8,
1264
+ 12.2,
1265
+ 9.3,
1266
+ 7.2,
1267
+ 7.7,
1268
+ 7.8,
1269
+ 8.4,
1270
+ 8.0,
1271
+ 8.4,
1272
+ 7.4,
1273
+ 7.8,
1274
+ 7.5,
1275
+ 7.8,
1276
+ 8.4,
1277
+ 7.9,
1278
+ 5.2,
1279
+ 5.8,
1280
+ 5.3,
1281
+ 7.0,
1282
+ 9.1,
1283
+ 9.3,
1284
+ 9.7,
1285
+ 9.2,
1286
+ 8.4,
1287
+ 7.4,
1288
+ 9.9,
1289
+ 11.4,
1290
+ 11.5,
1291
+ 12.7,
1292
+ 12.4,
1293
+ 12.7,
1294
+ 13.1,
1295
+ 13.3,
1296
+ 14.2,
1297
+ 15.2,
1298
+ 14.6,
1299
+ 13.5,
1300
+ 13.0,
1301
+ 12.0,
1302
+ 11.8,
1303
+ 14.3,
1304
+ 15.3,
1305
+ 15.0,
1306
+ 15.0,
1307
+ 15.6,
1308
+ 17.5,
1309
+ 19.8,
1310
+ 19.5,
1311
+ 16.3,
1312
+ 15.4,
1313
+ 14.3,
1314
+ 14.0,
1315
+ 14.0,
1316
+ 14.1,
1317
+ 15.6,
1318
+ 16.4,
1319
+ 16.1,
1320
+ 15.4,
1321
+ 13.5,
1322
+ 13.9,
1323
+ 14.0,
1324
+ 14.3,
1325
+ 13.6,
1326
+ 12.5,
1327
+ 13.7,
1328
+ 16.1,
1329
+ 16.0,
1330
+ 15.7,
1331
+ 15.4,
1332
+ 15.0,
1333
+ 15.7,
1334
+ 16.6,
1335
+ 16.0,
1336
+ 15.3,
1337
+ 15.1,
1338
+ 14.7,
1339
+ 13.9,
1340
+ 12.9,
1341
+ 11.5,
1342
+ 9.8,
1343
+ 8.4,
1344
+ 7.7,
1345
+ 8.0,
1346
+ 8.3,
1347
+ 8.6,
1348
+ 8.5,
1349
+ 7.6,
1350
+ 4.7,
1351
+ 1.8,
1352
+ 3.7,
1353
+ 6.3,
1354
+ 8.8,
1355
+ 9.9,
1356
+ 9.3,
1357
+ 9.0,
1358
+ 9.7,
1359
+ 9.9,
1360
+ 10.1,
1361
+ 10.8,
1362
+ 12.2,
1363
+ 13.4,
1364
+ 13.3,
1365
+ 10.7,
1366
+ 6.9,
1367
+ 4.1,
1368
+ 3.1,
1369
+ 2.6,
1370
+ 2.8,
1371
+ 3.9,
1372
+ 5.4,
1373
+ 7.3,
1374
+ 9.8,
1375
+ 13.1,
1376
+ 15.7,
1377
+ 17.1,
1378
+ 18.5,
1379
+ 18.5,
1380
+ 16.2,
1381
+ 15.1,
1382
+ 16.6,
1383
+ 19.3,
1384
+ 17.8,
1385
+ 16.2,
1386
+ 15.1,
1387
+ 14.4,
1388
+ 14.5,
1389
+ 15.4,
1390
+ 17.2,
1391
+ 19.1,
1392
+ 20.5,
1393
+ 21.1,
1394
+ 20.2,
1395
+ 17.4,
1396
+ 13.2,
1397
+ 8.2,
1398
+ 4.4,
1399
+ 5.2
1400
+ ],
1401
+ "wind_direction_10m": [
1402
+ 177,
1403
+ 166,
1404
+ 156,
1405
+ 155,
1406
+ 155,
1407
+ 146,
1408
+ 151,
1409
+ 157,
1410
+ 176,
1411
+ 192,
1412
+ 205,
1413
+ 222,
1414
+ 226,
1415
+ 225,
1416
+ 223,
1417
+ 220,
1418
+ 226,
1419
+ 233,
1420
+ 235,
1421
+ 229,
1422
+ 228,
1423
+ 222,
1424
+ 215,
1425
+ 199,
1426
+ 166,
1427
+ 152,
1428
+ 148,
1429
+ 142,
1430
+ 141,
1431
+ 142,
1432
+ 146,
1433
+ 153,
1434
+ 161,
1435
+ 172,
1436
+ 194,
1437
+ 213,
1438
+ 222,
1439
+ 222,
1440
+ 226,
1441
+ 227,
1442
+ 225,
1443
+ 226,
1444
+ 222,
1445
+ 228,
1446
+ 235,
1447
+ 235,
1448
+ 209,
1449
+ 182,
1450
+ 118,
1451
+ 87,
1452
+ 73,
1453
+ 62,
1454
+ 47,
1455
+ 31,
1456
+ 11,
1457
+ 3,
1458
+ 3,
1459
+ 7,
1460
+ 2,
1461
+ 349,
1462
+ 338,
1463
+ 321,
1464
+ 316,
1465
+ 318,
1466
+ 321,
1467
+ 320,
1468
+ 320,
1469
+ 323,
1470
+ 319,
1471
+ 320,
1472
+ 326,
1473
+ 329,
1474
+ 332,
1475
+ 337,
1476
+ 346,
1477
+ 7,
1478
+ 17,
1479
+ 20,
1480
+ 12,
1481
+ 352,
1482
+ 346,
1483
+ 341,
1484
+ 334,
1485
+ 329,
1486
+ 329,
1487
+ 329,
1488
+ 325,
1489
+ 329,
1490
+ 331,
1491
+ 335,
1492
+ 335,
1493
+ 336,
1494
+ 333,
1495
+ 334,
1496
+ 342,
1497
+ 349,
1498
+ 340,
1499
+ 335,
1500
+ 329,
1501
+ 328,
1502
+ 340,
1503
+ 356,
1504
+ 4,
1505
+ 358,
1506
+ 344,
1507
+ 333,
1508
+ 331,
1509
+ 331,
1510
+ 332,
1511
+ 330,
1512
+ 325,
1513
+ 318,
1514
+ 306,
1515
+ 288,
1516
+ 276,
1517
+ 269,
1518
+ 263,
1519
+ 258,
1520
+ 247,
1521
+ 186,
1522
+ 113,
1523
+ 97,
1524
+ 88,
1525
+ 80,
1526
+ 62,
1527
+ 37,
1528
+ 15,
1529
+ 359,
1530
+ 343,
1531
+ 332,
1532
+ 326,
1533
+ 323,
1534
+ 320,
1535
+ 316,
1536
+ 309,
1537
+ 293,
1538
+ 283,
1539
+ 282,
1540
+ 288,
1541
+ 292,
1542
+ 296,
1543
+ 301,
1544
+ 310,
1545
+ 317,
1546
+ 317,
1547
+ 310,
1548
+ 301,
1549
+ 299,
1550
+ 313,
1551
+ 340,
1552
+ 359,
1553
+ 356,
1554
+ 352,
1555
+ 345,
1556
+ 335,
1557
+ 326,
1558
+ 319,
1559
+ 315,
1560
+ 314,
1561
+ 313,
1562
+ 314,
1563
+ 314,
1564
+ 315,
1565
+ 317,
1566
+ 322,
1567
+ 334,
1568
+ 12,
1569
+ 76
1570
+ ]
1571
+ }
1572
+ }
1573
+ }
evals/golden_questions.json ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_meta": {
3
+ "status": "draft",
4
+ "note": "Pre-recording gate for the Sep 7 demo video. Harden expected_behaviors before evals day; a failing eval blocks the recording.",
5
+ "gate": "run against the deployed app before recording"
6
+ },
7
+ "questions": [
8
+ {
9
+ "id": "gq-01",
10
+ "question": "Which days this week are good for surfing near the Gold Coast?",
11
+ "expected_behaviors": [
12
+ "agent calls find_spots / rank_spots_this_week for the Gold Coast region",
13
+ "answer names specific days, backed by scores from scoring.py (no model-invented numbers)",
14
+ "forecast charts are rendered for surfaced data"
15
+ ]
16
+ },
17
+ {
18
+ "id": "gq-02",
19
+ "question": "I'm a beginner — which breaks near the Gold Coast should I try this week?",
20
+ "expected_behaviors": [
21
+ "skill filter (beginner) is applied to the ranking",
22
+ "returned spots are plausible beginner breaks for the region"
23
+ ]
24
+ },
25
+ {
26
+ "id": "gq-03",
27
+ "question": "What time should I surf Snapper Rocks today?",
28
+ "expected_behaviors": [
29
+ "agent calls get_forecast + score_week and reasons over hourly scores",
30
+ "answer gives a time window (best hours today) with wind/swell justification"
31
+ ]
32
+ },
33
+ {
34
+ "id": "gq-04",
35
+ "question": "Tell me about Margaret Bay — what swell and wind does it like?",
36
+ "expected_behaviors": [
37
+ "agent calls get_spot_knowledge and quotes the knowledge base (ideal_swell, ideal_wind, ideal_tide)",
38
+ "tide is described qualitatively only (no invented tide curves)"
39
+ ]
40
+ },
41
+ {
42
+ "id": "gq-05",
43
+ "question": "Best beach for an intermediate surfer near Byron Bay this weekend?",
44
+ "expected_behaviors": [
45
+ "ranking restricted to the Byron Bay region with skill=intermediate",
46
+ "weekend hours only are considered"
47
+ ]
48
+ },
49
+ {
50
+ "id": "gq-06",
51
+ "question": "How windy is it going to be at Bondi tomorrow morning?",
52
+ "expected_behaviors": [
53
+ "wind comes from the forecast data (wind_speed_10m/wind_direction_10m), not model guesses",
54
+ "answer includes wind strength and direction with an on/offshore interpretation for the break"
55
+ ]
56
+ },
57
+ {
58
+ "id": "gq-07",
59
+ "question": "Is Bells Beach worth a 2-hour drive on Saturday?",
60
+ "expected_behaviors": [
61
+ "score for Saturday hours at Bells is computed deterministically and cited",
62
+ "a clear go/no-go recommendation with reasoning"
63
+ ]
64
+ },
65
+ {
66
+ "id": "gq-08",
67
+ "question": "Show me the wave height forecast for Kirra for the next 3 days.",
68
+ "expected_behaviors": [
69
+ "interactive hourly wave-height chart is rendered (not just text)",
70
+ "numbers match the Open-Meteo Marine fetch (agent never fabricates values)"
71
+ ]
72
+ },
73
+ {
74
+ "id": "gq-09",
75
+ "question": "Where can I find a quiet weekday dawn patrol in Victoria?",
76
+ "expected_behaviors": [
77
+ "spot search constrained to Victoria region",
78
+ "answer balances scores with knowledge-base character (crowds, skill suitability)"
79
+ ]
80
+ },
81
+ {
82
+ "id": "gq-10",
83
+ "question": "Which is better this week: Noosa or Snapper Rocks?",
84
+ "expected_behaviors": [
85
+ "both spots scored over the same week and compared with cited numbers",
86
+ "one clear winner with the deciding factors explained"
87
+ ]
88
+ }
89
+ ]
90
+ }
main.py DELETED
@@ -1,7 +0,0 @@
1
- from fastapi import FastAPI
2
-
3
- app = FastAPI()
4
-
5
- @app.get("/")
6
- async def read_root():
7
- return {"Hello": "World"}
 
 
 
 
 
 
 
 
pyproject.toml CHANGED
@@ -1,13 +1,28 @@
1
  [project]
2
  name = "wavereader"
3
  version = "0.1.0"
4
- description = "Surf break data generator and API"
5
  readme = "README.md"
6
- requires-python = ">=3.14"
7
  dependencies = [
8
- "anthropic>=1.2.0",
9
  "ddgs>=9.16.0",
10
  "fastapi[standard]>=0.141.1",
 
 
11
  "huggingface-hub>=1.29.0",
12
- "langchain-community>=0.4.2",
 
13
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  [project]
2
  name = "wavereader"
3
  version = "0.1.0"
4
+ description = "Agentic surf forecaster for Australian breaks"
5
  readme = "README.md"
6
+ requires-python = ">=3.12"
7
  dependencies = [
8
+ "daggr",
9
  "ddgs>=9.16.0",
10
  "fastapi[standard]>=0.141.1",
11
+ "gradio",
12
+ "httpx",
13
  "huggingface-hub>=1.29.0",
14
+ "openai",
15
+ "pydantic",
16
  ]
17
+
18
+ [dependency-groups]
19
+ dev = [
20
+ "pytest",
21
+ ]
22
+
23
+ [build-system]
24
+ requires = ["hatchling"]
25
+ build-backend = "hatchling.build"
26
+
27
+ [tool.hatch.build.targets.wheel]
28
+ packages = ["wavereader"]
tests/test_smoke.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Smoke tests: package imports and pyproject sanity (structure milestone)."""
2
+
3
+ import tomllib
4
+ from pathlib import Path
5
+
6
+ import wavereader
7
+ from wavereader import agent, api, app, daggr_pipeline, forecasts, scoring, spots, tools
8
+
9
+ REPO = Path(__file__).resolve().parent.parent
10
+
11
+
12
+ def test_version():
13
+ assert wavereader.__version__ == "0.1.0"
14
+
15
+
16
+ def test_all_modules_importable():
17
+ for mod in (agent, api, app, daggr_pipeline, forecasts, scoring, spots, tools):
18
+ assert mod.__doc__
19
+
20
+
21
+ def test_pyproject_pinned():
22
+ cfg = tomllib.loads((REPO / "pyproject.toml").read_text())
23
+ assert cfg["project"]["requires-python"] == ">=3.12"
24
+ deps = " ".join(cfg["project"]["dependencies"])
25
+ assert "langchain" not in deps
26
+ assert "anthropic" not in deps
27
+ for required in ("gradio", "daggr", "openai", "httpx", "pydantic"):
28
+ assert required in deps
29
+
30
+
31
+ def test_api_app_exists():
32
+ routes = {r.path for r in api.app.routes}
33
+ assert {"/", "/spots", "/forecast", "/score"} <= routes
uv.lock CHANGED
The diff for this file is too large to render. See raw diff
 
wavereader/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """wavereader — agentic surf forecaster for Australian breaks."""
2
+
3
+ __version__ = "0.1.0"
wavereader/agent.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hand-rolled agent tool loop (~100 lines, no LangChain / no frameworks).
2
+
3
+ Provider-agnostic: one OpenAI-compatible client class; base URL + key come
4
+ from env (``HF_TOKEN`` -> https://router.huggingface.co/v1,
5
+ ``NVIDIA_API_KEY`` -> https://integrate.api.nvidia.com/v1). Default model:
6
+ ``Qwen/Qwen3-Next-80B-A3B-Instruct`` (only tested model supporting both
7
+ OpenAI-style tool calling and strict ``json_schema`` on the HF router — see
8
+ ``data/GENERATION.md`` before swapping models).
9
+
10
+ The agent interprets and explains only; all numbers come from scoring.py /
11
+ forecasts.py via the tools in tools.py. Streaming + trace capture for the UI
12
+ trace panel.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+
18
+ def run_agent(question: str, model: str | None = None) -> str:
19
+ """Run the tool loop to answer a surf question.
20
+
21
+ TODO: system prompt, tool dispatch loop (tools.py), streaming output,
22
+ trace capture, provider switch via env.
23
+ """
24
+ raise NotImplementedError
wavereader/api.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """FastAPI backend: data endpoints owned by the core modules.
2
+
3
+ Gradio (app.py) is the demo UI; this API exposes the same core for tests
4
+ and any non-Gradio consumer. Endpoints: /spots, /forecast, /score, /ask.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from fastapi import FastAPI
10
+
11
+ app = FastAPI(title="wavereader", version="0.1.0")
12
+
13
+
14
+ @app.get("/")
15
+ async def read_root():
16
+ return {"Hello": "World"}
17
+
18
+
19
+ @app.get("/spots")
20
+ async def list_spots(query: str = "", region: str | None = None, skill: str | None = None):
21
+ """Search/validate breaks via spots.py. TODO: wire up."""
22
+ raise NotImplementedError
23
+
24
+
25
+ @app.get("/forecast")
26
+ async def forecast(spot: str, days: int = 7):
27
+ """Hourly marine + wind forecast via forecasts.py. TODO: wire up."""
28
+ raise NotImplementedError
29
+
30
+
31
+ @app.get("/score")
32
+ async def score(spot: str, days: int = 7):
33
+ """Deterministic hourly surf scores via scoring.py. TODO: wire up."""
34
+ raise NotImplementedError
35
+
36
+
37
+ @app.post("/ask")
38
+ async def ask(question: str):
39
+ """Agent answer via agent.py tool loop. TODO: wire up."""
40
+ raise NotImplementedError
wavereader/app.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Gradio demo UI.
2
+
3
+ Home page is a map of Australia (select your break), plus chat with the
4
+ agent, interactive seaborn-styled charts (plotly/altair aesthetics) rendered
5
+ whenever the agent surfaces forecast/wind data, and the agent trace panel.
6
+ Calls the core modules (and FastAPI endpoints) — no custom frontend this
7
+ week.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+
13
+ def build_ui() -> object:
14
+ """Assemble the Blocks demo (map home, chat, charts, trace panel).
15
+
16
+ TODO: Australia map home page, chat pipeline against agent.run_agent,
17
+ chart rendering, trace panel.
18
+ """
19
+ raise NotImplementedError
20
+
21
+
22
+ if __name__ == "__main__":
23
+ demo = build_ui()
24
+ demo.launch()
wavereader/daggr_pipeline.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Fixed "Morning Surf Report" DAG visualised with daggr.
2
+
3
+ Daggr is beta and has NO conditional branching: this visualises the fixed
4
+ morning pipeline only, never the dynamic chat agent loop.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+
10
+ def build_pipeline() -> object:
11
+ """Assemble the fixed morning-report DAG.
12
+
13
+ TODO: nodes for pick-region -> fetch forecasts -> score -> summarise,
14
+ rendered on the daggr canvas.
15
+ """
16
+ raise NotImplementedError
wavereader/forecasts.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Open-Meteo Marine + weather forecast client.
2
+
3
+ Two endpoints, merged on ``time``:
4
+
5
+ * ``marine-api.open-meteo.com`` — hourly ``wave_height`` (m), ``wave_period``
6
+ (s), ``wave_direction`` (deg), ``swell_wave_height`` (m),
7
+ ``wind_wave_height`` (m). Verified 2026-08-30 at Snapper Rocks: 48 hourly
8
+ entries, no nulls, ~1.3 m @ ~6 s from ~SE. The marine endpoint does NOT
9
+ serve wind fields.
10
+ * ``api.open-meteo.com`` — hourly ``wind_speed_10m`` (km/h; convert to knots
11
+ for scoring) and ``wind_direction_10m`` (deg).
12
+
13
+ Grid points snap ~10 km offshore of a spot's coords (Snapper Rocks
14
+ -28.173,153.556 resolved to -28.04,153.63). Fetch slightly seaward and keep
15
+ the offset consistent across all spots.
16
+
17
+ Cache every response to disk: the demo must survive API flakiness and rate
18
+ limits. Horizon: hourly, 7 days.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ MARINE_URL = "https://marine-api.open-meteo.com/v1/marine"
24
+ WEATHER_URL = "https://api.open-meteo.com/v1/forecast"
25
+
26
+ # Marine hourly fields verified against the live API 2026-08-30.
27
+ MARINE_HOURLY_FIELDS = (
28
+ "wave_height",
29
+ "wave_period",
30
+ "wave_direction",
31
+ "wind_wave_height",
32
+ "swell_wave_height",
33
+ )
34
+ WEATHER_HOURLY_FIELDS = (
35
+ "wind_speed_10m",
36
+ "wind_direction_10m",
37
+ )
38
+
39
+
40
+ def get_forecast(lat: float, lon: float, days: int = 7) -> dict:
41
+ """Fetch marine + weather hourly data for a coordinate, merged on time.
42
+
43
+ TODO: httpx client, disk cache (keyed by rounded coords + day),
44
+ merge the two responses, return a normalized hourly frame.
45
+ """
46
+ raise NotImplementedError
wavereader/scoring.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Deterministic surf-quality scoring.
2
+
3
+ The LLM never owns numbers: every score and ranking comes from here.
4
+ Produces a 0-10 quality score per hour plus a component breakdown
5
+ (swell size vs ideal, period, wind strength/direction vs offshore, tide is
6
+ qualitative only — Open-Meteo has no tides, so it comes from the knowledge
7
+ base and never feeds the numeric score).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+
13
+ def score_hour(
14
+ wave_height_m: float,
15
+ wave_period_s: float,
16
+ wind_speed_kt: float,
17
+ wind_direction_deg: float,
18
+ spot: dict,
19
+ ) -> dict:
20
+ """Score one hour against a spot's ideal profile.
21
+
22
+ TODO: component subscores (swell size, period, wind strength,
23
+ wind alignment vs ideal direction) weighted into a 0-10 total plus a
24
+ breakdown dict for the UI.
25
+ """
26
+ raise NotImplementedError
27
+
28
+
29
+ def score_week(forecast: dict, spot: dict) -> list[dict]:
30
+ """Score every hour of a 7-day forecast for one spot.
31
+
32
+ TODO: map score_hour over the merged hourly frame.
33
+ """
34
+ raise NotImplementedError
wavereader/spots.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Load, validate and search surf breaks.
2
+
3
+ Source of truth is ``data/break_details.json`` (records keyed by
4
+ ``(name, region)``; identity fields anchored to ``data/breaks.json``).
5
+
6
+ Validation pass lives here rather than hand-edits: coords plausible for the
7
+ named region, ranges sane, spot-check ~10 records against local knowledge.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+
14
+ # TODO: pydantic models for a break record (identity, coordinates,
15
+ # ideal_swell, ideal_wind, ideal_tide, additional_details).
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class Spot:
20
+ name: str
21
+ region: str
22
+
23
+
24
+ def load_breaks() -> list[dict]:
25
+ """Load and validate ``data/break_details.json``.
26
+
27
+ TODO: parse into pydantic models, run validation (coords vs region,
28
+ sane ranges), raise on failure.
29
+ """
30
+ raise NotImplementedError
31
+
32
+
33
+ def find_spots(
34
+ query: str = "",
35
+ region: str | None = None,
36
+ skill: str | None = None,
37
+ limit: int = 10,
38
+ ) -> list[Spot]:
39
+ """Search breaks by name/city/region and optional skill filter.
40
+
41
+ TODO: case-insensitive substring match over name/region; skill filter
42
+ from knowledge-base suitability. Used by the find_spots tool and
43
+ /spots endpoint.
44
+ """
45
+ raise NotImplementedError
46
+
47
+
48
+ def get_spot(spot: Spot) -> dict:
49
+ """Return the full knowledge-base record for one break.
50
+
51
+ TODO: keyed lookup by (name, region).
52
+ """
53
+ raise NotImplementedError
wavereader/tools.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Agent tools: the function schemas handed to the LLM.
2
+
3
+ Tools are thin wrappers over spots.py / forecasts.py / scoring.py — the
4
+ model chooses them, the deterministic modules compute the values.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ TOOL_SCHEMAS = (
10
+ "get_forecast",
11
+ "score_week",
12
+ "find_spots",
13
+ "get_spot_knowledge",
14
+ "rank_spots_this_week",
15
+ )
16
+
17
+
18
+ def get_forecast(spot: str) -> dict:
19
+ """Hourly marine + wind forecast for a named spot. TODO."""
20
+ raise NotImplementedError
21
+
22
+
23
+ def score_week(spot: str) -> dict:
24
+ """Hour-by-hour surf scores for the next 7 days at a spot. TODO."""
25
+ raise NotImplementedError
26
+
27
+
28
+ def find_spots(query: str, skill: str | None = None) -> list[dict]:
29
+ """Search breaks by name/region with optional skill filter. TODO."""
30
+ raise NotImplementedError
31
+
32
+
33
+ def get_spot_knowledge(spot: str) -> dict:
34
+ """Knowledge-base profile for a spot (ideal swell/wind/tide). TODO."""
35
+ raise NotImplementedError
36
+
37
+
38
+ def rank_spots_this_week(region: str, skill: str | None = None) -> list[dict]:
39
+ """Rank spots in a region by their best score this week. TODO."""
40
+ raise NotImplementedError