@mostlyrightmd/core 0.1.0-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/discovery/index.cjs +1646 -0
- package/dist/discovery/index.cjs.map +1 -0
- package/dist/discovery/index.d.cts +313 -0
- package/dist/discovery/index.d.ts +313 -0
- package/dist/discovery/index.mjs +1609 -0
- package/dist/discovery/index.mjs.map +1 -0
- package/dist/formats/index.cjs +498 -0
- package/dist/formats/index.cjs.map +1 -0
- package/dist/formats/index.d.cts +97 -0
- package/dist/formats/index.d.ts +97 -0
- package/dist/formats/index.mjs +465 -0
- package/dist/formats/index.mjs.map +1 -0
- package/dist/index.cjs +1624 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +559 -0
- package/dist/index.d.ts +559 -0
- package/dist/index.global.js +1582 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.mjs +1557 -0
- package/dist/index.mjs.map +1 -0
- package/dist/internal/bounds.cjs +125 -0
- package/dist/internal/bounds.cjs.map +1 -0
- package/dist/internal/bounds.d.cts +36 -0
- package/dist/internal/bounds.d.ts +36 -0
- package/dist/internal/bounds.mjs +81 -0
- package/dist/internal/bounds.mjs.map +1 -0
- package/dist/internal/cache/fs.cjs +217 -0
- package/dist/internal/cache/fs.cjs.map +1 -0
- package/dist/internal/cache/fs.d.cts +57 -0
- package/dist/internal/cache/fs.d.ts +57 -0
- package/dist/internal/cache/fs.mjs +179 -0
- package/dist/internal/cache/fs.mjs.map +1 -0
- package/dist/internal/cache/index.browser.cjs +1184 -0
- package/dist/internal/cache/index.browser.cjs.map +1 -0
- package/dist/internal/cache/index.browser.d.cts +20 -0
- package/dist/internal/cache/index.browser.d.ts +20 -0
- package/dist/internal/cache/index.browser.mjs +36 -0
- package/dist/internal/cache/index.browser.mjs.map +1 -0
- package/dist/internal/cache/index.cjs +1389 -0
- package/dist/internal/cache/index.cjs.map +1 -0
- package/dist/internal/cache/index.d.cts +16 -0
- package/dist/internal/cache/index.d.ts +16 -0
- package/dist/internal/cache/index.mjs +40 -0
- package/dist/internal/cache/index.mjs.map +1 -0
- package/dist/internal/chunk-PKJXHY27.mjs +1137 -0
- package/dist/internal/chunk-PKJXHY27.mjs.map +1 -0
- package/dist/internal/convert.cjs +161 -0
- package/dist/internal/convert.cjs.map +1 -0
- package/dist/internal/convert.d.cts +44 -0
- package/dist/internal/convert.d.ts +44 -0
- package/dist/internal/convert.mjs +117 -0
- package/dist/internal/convert.mjs.map +1 -0
- package/dist/internal/fs-O6XR4WWW.mjs +183 -0
- package/dist/internal/fs-O6XR4WWW.mjs.map +1 -0
- package/dist/internal/keys-B7C8C88N.d.cts +191 -0
- package/dist/internal/keys-B7C8C88N.d.ts +191 -0
- package/dist/internal/merge/index.cjs +75 -0
- package/dist/internal/merge/index.cjs.map +1 -0
- package/dist/internal/merge/index.d.cts +74 -0
- package/dist/internal/merge/index.d.ts +74 -0
- package/dist/internal/merge/index.mjs +46 -0
- package/dist/internal/merge/index.mjs.map +1 -0
- package/dist/internal/pairs.cjs +328 -0
- package/dist/internal/pairs.cjs.map +1 -0
- package/dist/internal/pairs.d.cts +105 -0
- package/dist/internal/pairs.d.ts +105 -0
- package/dist/internal/pairs.mjs +298 -0
- package/dist/internal/pairs.mjs.map +1 -0
- package/dist/qc/index.cjs +247 -0
- package/dist/qc/index.cjs.map +1 -0
- package/dist/qc/index.d.cts +140 -0
- package/dist/qc/index.d.ts +140 -0
- package/dist/qc/index.mjs +212 -0
- package/dist/qc/index.mjs.map +1 -0
- package/dist/temporal/index.cjs +504 -0
- package/dist/temporal/index.cjs.map +1 -0
- package/dist/temporal/index.d.cts +121 -0
- package/dist/temporal/index.d.ts +121 -0
- package/dist/temporal/index.mjs +474 -0
- package/dist/temporal/index.mjs.map +1 -0
- package/dist/transforms/index.cjs +399 -0
- package/dist/transforms/index.cjs.map +1 -0
- package/dist/transforms/index.d.cts +193 -0
- package/dist/transforms/index.d.ts +193 -0
- package/dist/transforms/index.mjs +362 -0
- package/dist/transforms/index.mjs.map +1 -0
- package/dist/validator.cjs +1870 -0
- package/dist/validator.cjs.map +1 -0
- package/dist/validator.d.cts +30 -0
- package/dist/validator.d.ts +30 -0
- package/dist/validator.mjs +1843 -0
- package/dist/validator.mjs.map +1 -0
- package/package.json +115 -0
|
@@ -0,0 +1,1609 @@
|
|
|
1
|
+
// src/data/generated/stations.ts
|
|
2
|
+
var STATIONS = [
|
|
3
|
+
{
|
|
4
|
+
code: "EDDB",
|
|
5
|
+
country: "DE",
|
|
6
|
+
ghcnh_id: null,
|
|
7
|
+
icao: "EDDB",
|
|
8
|
+
latitude: 52.3667,
|
|
9
|
+
longitude: 13.5033,
|
|
10
|
+
name: "Berlin Brandenburg",
|
|
11
|
+
tz: "Europe/Berlin"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
code: "EDDF",
|
|
15
|
+
country: "DE",
|
|
16
|
+
ghcnh_id: null,
|
|
17
|
+
icao: "EDDF",
|
|
18
|
+
latitude: 50.0379,
|
|
19
|
+
longitude: 8.5622,
|
|
20
|
+
name: "Frankfurt am Main",
|
|
21
|
+
tz: "Europe/Berlin"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
code: "EDDM",
|
|
25
|
+
country: "DE",
|
|
26
|
+
ghcnh_id: null,
|
|
27
|
+
icao: "EDDM",
|
|
28
|
+
latitude: 48.3538,
|
|
29
|
+
longitude: 11.7861,
|
|
30
|
+
name: "Munich Franz Josef Strauss",
|
|
31
|
+
tz: "Europe/Berlin"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
code: "EFHK",
|
|
35
|
+
country: "FI",
|
|
36
|
+
ghcnh_id: null,
|
|
37
|
+
icao: "EFHK",
|
|
38
|
+
latitude: 60.3172,
|
|
39
|
+
longitude: 24.9633,
|
|
40
|
+
name: "Helsinki-Vantaa",
|
|
41
|
+
tz: "Europe/Helsinki"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
code: "EGKK",
|
|
45
|
+
country: "GB",
|
|
46
|
+
ghcnh_id: null,
|
|
47
|
+
icao: "EGKK",
|
|
48
|
+
latitude: 51.1481,
|
|
49
|
+
longitude: -0.1903,
|
|
50
|
+
name: "London Gatwick",
|
|
51
|
+
tz: "Europe/London"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
code: "EGLL",
|
|
55
|
+
country: "GB",
|
|
56
|
+
ghcnh_id: null,
|
|
57
|
+
icao: "EGLL",
|
|
58
|
+
latitude: 51.4706,
|
|
59
|
+
longitude: -0.4619,
|
|
60
|
+
name: "London Heathrow",
|
|
61
|
+
tz: "Europe/London"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
code: "EHAM",
|
|
65
|
+
country: "NL",
|
|
66
|
+
ghcnh_id: null,
|
|
67
|
+
icao: "EHAM",
|
|
68
|
+
latitude: 52.3086,
|
|
69
|
+
longitude: 4.7639,
|
|
70
|
+
name: "Amsterdam Schiphol",
|
|
71
|
+
tz: "Europe/Amsterdam"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
code: "EKCH",
|
|
75
|
+
country: "DK",
|
|
76
|
+
ghcnh_id: null,
|
|
77
|
+
icao: "EKCH",
|
|
78
|
+
latitude: 55.6181,
|
|
79
|
+
longitude: 12.6561,
|
|
80
|
+
name: "Copenhagen Kastrup",
|
|
81
|
+
tz: "Europe/Copenhagen"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
code: "EPWA",
|
|
85
|
+
country: "PL",
|
|
86
|
+
ghcnh_id: null,
|
|
87
|
+
icao: "EPWA",
|
|
88
|
+
latitude: 52.1657,
|
|
89
|
+
longitude: 20.9671,
|
|
90
|
+
name: "Warsaw Chopin",
|
|
91
|
+
tz: "Europe/Warsaw"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
code: "ESSA",
|
|
95
|
+
country: "SE",
|
|
96
|
+
ghcnh_id: null,
|
|
97
|
+
icao: "ESSA",
|
|
98
|
+
latitude: 59.6519,
|
|
99
|
+
longitude: 17.9186,
|
|
100
|
+
name: "Stockholm Arlanda",
|
|
101
|
+
tz: "Europe/Stockholm"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
code: "ATL",
|
|
105
|
+
country: "US",
|
|
106
|
+
ghcnh_id: "USW00013874",
|
|
107
|
+
icao: "KATL",
|
|
108
|
+
latitude: 33.6407,
|
|
109
|
+
longitude: -84.4277,
|
|
110
|
+
name: "Hartsfield-Jackson Atlanta International",
|
|
111
|
+
tz: "America/New_York"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
code: "AUS",
|
|
115
|
+
country: "US",
|
|
116
|
+
ghcnh_id: "USW00013904",
|
|
117
|
+
icao: "KAUS",
|
|
118
|
+
latitude: 30.1975,
|
|
119
|
+
longitude: -97.6664,
|
|
120
|
+
name: "Austin-Bergstrom International",
|
|
121
|
+
tz: "America/Chicago"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
code: "BOS",
|
|
125
|
+
country: "US",
|
|
126
|
+
ghcnh_id: "USW00014739",
|
|
127
|
+
icao: "KBOS",
|
|
128
|
+
latitude: 42.3656,
|
|
129
|
+
longitude: -71.0096,
|
|
130
|
+
name: "Boston Logan International",
|
|
131
|
+
tz: "America/New_York"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
code: "DCA",
|
|
135
|
+
country: "US",
|
|
136
|
+
ghcnh_id: "USW00013743",
|
|
137
|
+
icao: "KDCA",
|
|
138
|
+
latitude: 38.8512,
|
|
139
|
+
longitude: -77.0402,
|
|
140
|
+
name: "Washington Reagan National",
|
|
141
|
+
tz: "America/New_York"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
code: "DEN",
|
|
145
|
+
country: "US",
|
|
146
|
+
ghcnh_id: "USW00003017",
|
|
147
|
+
icao: "KDEN",
|
|
148
|
+
latitude: 39.8561,
|
|
149
|
+
longitude: -104.6737,
|
|
150
|
+
name: "Denver International",
|
|
151
|
+
tz: "America/Denver"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
code: "DFW",
|
|
155
|
+
country: "US",
|
|
156
|
+
ghcnh_id: "USW00003927",
|
|
157
|
+
icao: "KDFW",
|
|
158
|
+
latitude: 32.8998,
|
|
159
|
+
longitude: -97.0403,
|
|
160
|
+
name: "Dallas-Fort Worth International",
|
|
161
|
+
tz: "America/Chicago"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
code: "HOU",
|
|
165
|
+
country: "US",
|
|
166
|
+
ghcnh_id: "USW00012918",
|
|
167
|
+
icao: "KHOU",
|
|
168
|
+
latitude: 29.6454,
|
|
169
|
+
longitude: -95.2789,
|
|
170
|
+
name: "Houston Hobby",
|
|
171
|
+
tz: "America/Chicago"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
code: "LAS",
|
|
175
|
+
country: "US",
|
|
176
|
+
ghcnh_id: "USW00023169",
|
|
177
|
+
icao: "KLAS",
|
|
178
|
+
latitude: 36.084,
|
|
179
|
+
longitude: -115.1537,
|
|
180
|
+
name: "Harry Reid (McCarran) International",
|
|
181
|
+
tz: "America/Los_Angeles"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
code: "LAX",
|
|
185
|
+
country: "US",
|
|
186
|
+
ghcnh_id: "USW00023174",
|
|
187
|
+
icao: "KLAX",
|
|
188
|
+
latitude: 33.9425,
|
|
189
|
+
longitude: -118.4081,
|
|
190
|
+
name: "Los Angeles International",
|
|
191
|
+
tz: "America/Los_Angeles"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
code: "MDW",
|
|
195
|
+
country: "US",
|
|
196
|
+
ghcnh_id: "USW00014819",
|
|
197
|
+
icao: "KMDW",
|
|
198
|
+
latitude: 41.7868,
|
|
199
|
+
longitude: -87.7522,
|
|
200
|
+
name: "Chicago Midway International",
|
|
201
|
+
tz: "America/Chicago"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
code: "MIA",
|
|
205
|
+
country: "US",
|
|
206
|
+
ghcnh_id: "USW00012839",
|
|
207
|
+
icao: "KMIA",
|
|
208
|
+
latitude: 25.7959,
|
|
209
|
+
longitude: -80.287,
|
|
210
|
+
name: "Miami International",
|
|
211
|
+
tz: "America/New_York"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
code: "MSP",
|
|
215
|
+
country: "US",
|
|
216
|
+
ghcnh_id: "USW00014922",
|
|
217
|
+
icao: "KMSP",
|
|
218
|
+
latitude: 44.8848,
|
|
219
|
+
longitude: -93.2223,
|
|
220
|
+
name: "Minneapolis-St Paul International",
|
|
221
|
+
tz: "America/Chicago"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
code: "MSY",
|
|
225
|
+
country: "US",
|
|
226
|
+
ghcnh_id: "USW00012916",
|
|
227
|
+
icao: "KMSY",
|
|
228
|
+
latitude: 29.9934,
|
|
229
|
+
longitude: -90.258,
|
|
230
|
+
name: "New Orleans Louis Armstrong International",
|
|
231
|
+
tz: "America/Chicago"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
code: "NYC",
|
|
235
|
+
country: "US",
|
|
236
|
+
ghcnh_id: "USW00094728",
|
|
237
|
+
icao: "KNYC",
|
|
238
|
+
latitude: 40.7789,
|
|
239
|
+
longitude: -73.9692,
|
|
240
|
+
name: "Central Park, New York",
|
|
241
|
+
tz: "America/New_York"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
code: "OKC",
|
|
245
|
+
country: "US",
|
|
246
|
+
ghcnh_id: "USW00013967",
|
|
247
|
+
icao: "KOKC",
|
|
248
|
+
latitude: 35.3931,
|
|
249
|
+
longitude: -97.6007,
|
|
250
|
+
name: "Oklahoma City Will Rogers World",
|
|
251
|
+
tz: "America/Chicago"
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
code: "PHL",
|
|
255
|
+
country: "US",
|
|
256
|
+
ghcnh_id: "USW00013739",
|
|
257
|
+
icao: "KPHL",
|
|
258
|
+
latitude: 39.8721,
|
|
259
|
+
longitude: -75.2411,
|
|
260
|
+
name: "Philadelphia International",
|
|
261
|
+
tz: "America/New_York"
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
code: "PHX",
|
|
265
|
+
country: "US",
|
|
266
|
+
ghcnh_id: "USW00023183",
|
|
267
|
+
icao: "KPHX",
|
|
268
|
+
latitude: 33.4373,
|
|
269
|
+
longitude: -112.0078,
|
|
270
|
+
name: "Phoenix Sky Harbor International",
|
|
271
|
+
tz: "America/Phoenix"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
code: "SAT",
|
|
275
|
+
country: "US",
|
|
276
|
+
ghcnh_id: "USW00012921",
|
|
277
|
+
icao: "KSAT",
|
|
278
|
+
latitude: 29.5337,
|
|
279
|
+
longitude: -98.4698,
|
|
280
|
+
name: "San Antonio International",
|
|
281
|
+
tz: "America/Chicago"
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
code: "SEA",
|
|
285
|
+
country: "US",
|
|
286
|
+
ghcnh_id: "USW00024233",
|
|
287
|
+
icao: "KSEA",
|
|
288
|
+
latitude: 47.4502,
|
|
289
|
+
longitude: -122.3088,
|
|
290
|
+
name: "Seattle-Tacoma International",
|
|
291
|
+
tz: "America/Los_Angeles"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
code: "SFO",
|
|
295
|
+
country: "US",
|
|
296
|
+
ghcnh_id: "USW00023234",
|
|
297
|
+
icao: "KSFO",
|
|
298
|
+
latitude: 37.6213,
|
|
299
|
+
longitude: -122.379,
|
|
300
|
+
name: "San Francisco International",
|
|
301
|
+
tz: "America/Los_Angeles"
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
code: "LEBL",
|
|
305
|
+
country: "ES",
|
|
306
|
+
ghcnh_id: null,
|
|
307
|
+
icao: "LEBL",
|
|
308
|
+
latitude: 41.2974,
|
|
309
|
+
longitude: 2.0833,
|
|
310
|
+
name: "Barcelona El Prat",
|
|
311
|
+
tz: "Europe/Madrid"
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
code: "LEMD",
|
|
315
|
+
country: "ES",
|
|
316
|
+
ghcnh_id: null,
|
|
317
|
+
icao: "LEMD",
|
|
318
|
+
latitude: 40.4719,
|
|
319
|
+
longitude: -3.5626,
|
|
320
|
+
name: "Madrid Barajas",
|
|
321
|
+
tz: "Europe/Madrid"
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
code: "LFPB",
|
|
325
|
+
country: "FR",
|
|
326
|
+
ghcnh_id: null,
|
|
327
|
+
icao: "LFPB",
|
|
328
|
+
latitude: 48.9694,
|
|
329
|
+
longitude: 2.4414,
|
|
330
|
+
name: "Paris Le Bourget",
|
|
331
|
+
tz: "Europe/Paris"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
code: "LFPG",
|
|
335
|
+
country: "FR",
|
|
336
|
+
ghcnh_id: null,
|
|
337
|
+
icao: "LFPG",
|
|
338
|
+
latitude: 49.0097,
|
|
339
|
+
longitude: 2.5479,
|
|
340
|
+
name: "Paris Charles de Gaulle",
|
|
341
|
+
tz: "Europe/Paris"
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
code: "LFPO",
|
|
345
|
+
country: "FR",
|
|
346
|
+
ghcnh_id: null,
|
|
347
|
+
icao: "LFPO",
|
|
348
|
+
latitude: 48.7233,
|
|
349
|
+
longitude: 2.3794,
|
|
350
|
+
name: "Paris Orly",
|
|
351
|
+
tz: "Europe/Paris"
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
code: "LIMC",
|
|
355
|
+
country: "IT",
|
|
356
|
+
ghcnh_id: null,
|
|
357
|
+
icao: "LIMC",
|
|
358
|
+
latitude: 45.6306,
|
|
359
|
+
longitude: 8.7281,
|
|
360
|
+
name: "Milan Malpensa",
|
|
361
|
+
tz: "Europe/Rome"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
code: "LIRF",
|
|
365
|
+
country: "IT",
|
|
366
|
+
ghcnh_id: null,
|
|
367
|
+
icao: "LIRF",
|
|
368
|
+
latitude: 41.8003,
|
|
369
|
+
longitude: 12.2389,
|
|
370
|
+
name: "Rome Fiumicino",
|
|
371
|
+
tz: "Europe/Rome"
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
code: "LOWW",
|
|
375
|
+
country: "AT",
|
|
376
|
+
ghcnh_id: null,
|
|
377
|
+
icao: "LOWW",
|
|
378
|
+
latitude: 48.1103,
|
|
379
|
+
longitude: 16.5697,
|
|
380
|
+
name: "Vienna International",
|
|
381
|
+
tz: "Europe/Vienna"
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
code: "LSZH",
|
|
385
|
+
country: "CH",
|
|
386
|
+
ghcnh_id: null,
|
|
387
|
+
icao: "LSZH",
|
|
388
|
+
latitude: 47.4647,
|
|
389
|
+
longitude: 8.5492,
|
|
390
|
+
name: "Zurich",
|
|
391
|
+
tz: "Europe/Zurich"
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
code: "NZAA",
|
|
395
|
+
country: "NZ",
|
|
396
|
+
ghcnh_id: null,
|
|
397
|
+
icao: "NZAA",
|
|
398
|
+
latitude: -37.0081,
|
|
399
|
+
longitude: 174.7917,
|
|
400
|
+
name: "Auckland",
|
|
401
|
+
tz: "Pacific/Auckland"
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
code: "NZWN",
|
|
405
|
+
country: "NZ",
|
|
406
|
+
ghcnh_id: null,
|
|
407
|
+
icao: "NZWN",
|
|
408
|
+
latitude: -41.3272,
|
|
409
|
+
longitude: 174.8053,
|
|
410
|
+
name: "Wellington",
|
|
411
|
+
tz: "Pacific/Auckland"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
code: "OERK",
|
|
415
|
+
country: "SA",
|
|
416
|
+
ghcnh_id: null,
|
|
417
|
+
icao: "OERK",
|
|
418
|
+
latitude: 24.9576,
|
|
419
|
+
longitude: 46.6988,
|
|
420
|
+
name: "Riyadh King Khalid International",
|
|
421
|
+
tz: "Asia/Riyadh"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
code: "OMDB",
|
|
425
|
+
country: "AE",
|
|
426
|
+
ghcnh_id: null,
|
|
427
|
+
icao: "OMDB",
|
|
428
|
+
latitude: 25.2532,
|
|
429
|
+
longitude: 55.3657,
|
|
430
|
+
name: "Dubai International",
|
|
431
|
+
tz: "Asia/Dubai"
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
code: "OTHH",
|
|
435
|
+
country: "QA",
|
|
436
|
+
ghcnh_id: null,
|
|
437
|
+
icao: "OTHH",
|
|
438
|
+
latitude: 25.2731,
|
|
439
|
+
longitude: 51.608,
|
|
440
|
+
name: "Doha Hamad International",
|
|
441
|
+
tz: "Asia/Qatar"
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
code: "RCTP",
|
|
445
|
+
country: "TW",
|
|
446
|
+
ghcnh_id: null,
|
|
447
|
+
icao: "RCTP",
|
|
448
|
+
latitude: 25.0777,
|
|
449
|
+
longitude: 121.2328,
|
|
450
|
+
name: "Taipei Taoyuan",
|
|
451
|
+
tz: "Asia/Taipei"
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
code: "RJAA",
|
|
455
|
+
country: "JP",
|
|
456
|
+
ghcnh_id: null,
|
|
457
|
+
icao: "RJAA",
|
|
458
|
+
latitude: 35.7647,
|
|
459
|
+
longitude: 140.3864,
|
|
460
|
+
name: "Tokyo Narita",
|
|
461
|
+
tz: "Asia/Tokyo"
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
code: "RJTT",
|
|
465
|
+
country: "JP",
|
|
466
|
+
ghcnh_id: null,
|
|
467
|
+
icao: "RJTT",
|
|
468
|
+
latitude: 35.5522,
|
|
469
|
+
longitude: 139.78,
|
|
470
|
+
name: "Tokyo Haneda",
|
|
471
|
+
tz: "Asia/Tokyo"
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
code: "RKSI",
|
|
475
|
+
country: "KR",
|
|
476
|
+
ghcnh_id: null,
|
|
477
|
+
icao: "RKSI",
|
|
478
|
+
latitude: 37.4691,
|
|
479
|
+
longitude: 126.4505,
|
|
480
|
+
name: "Seoul Incheon",
|
|
481
|
+
tz: "Asia/Seoul"
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
code: "SAEZ",
|
|
485
|
+
country: "AR",
|
|
486
|
+
ghcnh_id: null,
|
|
487
|
+
icao: "SAEZ",
|
|
488
|
+
latitude: -34.8222,
|
|
489
|
+
longitude: -58.5358,
|
|
490
|
+
name: "Buenos Aires Ezeiza",
|
|
491
|
+
tz: "America/Argentina/Buenos_Aires"
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
code: "SBGR",
|
|
495
|
+
country: "BR",
|
|
496
|
+
ghcnh_id: null,
|
|
497
|
+
icao: "SBGR",
|
|
498
|
+
latitude: -23.4356,
|
|
499
|
+
longitude: -46.4731,
|
|
500
|
+
name: "S\xE3o Paulo Guarulhos",
|
|
501
|
+
tz: "America/Sao_Paulo"
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
code: "UUEE",
|
|
505
|
+
country: "RU",
|
|
506
|
+
ghcnh_id: null,
|
|
507
|
+
icao: "UUEE",
|
|
508
|
+
latitude: 55.9728,
|
|
509
|
+
longitude: 37.4147,
|
|
510
|
+
name: "Moscow Sheremetyevo",
|
|
511
|
+
tz: "Europe/Moscow"
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
code: "VABB",
|
|
515
|
+
country: "IN",
|
|
516
|
+
ghcnh_id: null,
|
|
517
|
+
icao: "VABB",
|
|
518
|
+
latitude: 19.0887,
|
|
519
|
+
longitude: 72.8679,
|
|
520
|
+
name: "Mumbai Chhatrapati Shivaji",
|
|
521
|
+
tz: "Asia/Kolkata"
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
code: "VHHH",
|
|
525
|
+
country: "HK",
|
|
526
|
+
ghcnh_id: null,
|
|
527
|
+
icao: "VHHH",
|
|
528
|
+
latitude: 22.308,
|
|
529
|
+
longitude: 113.9185,
|
|
530
|
+
name: "Hong Kong International",
|
|
531
|
+
tz: "Asia/Hong_Kong"
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
code: "VIDP",
|
|
535
|
+
country: "IN",
|
|
536
|
+
ghcnh_id: null,
|
|
537
|
+
icao: "VIDP",
|
|
538
|
+
latitude: 28.5562,
|
|
539
|
+
longitude: 77.1,
|
|
540
|
+
name: "Delhi Indira Gandhi",
|
|
541
|
+
tz: "Asia/Kolkata"
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
code: "VTBS",
|
|
545
|
+
country: "TH",
|
|
546
|
+
ghcnh_id: null,
|
|
547
|
+
icao: "VTBS",
|
|
548
|
+
latitude: 13.69,
|
|
549
|
+
longitude: 100.7501,
|
|
550
|
+
name: "Bangkok Suvarnabhumi",
|
|
551
|
+
tz: "Asia/Bangkok"
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
code: "WSSS",
|
|
555
|
+
country: "SG",
|
|
556
|
+
ghcnh_id: null,
|
|
557
|
+
icao: "WSSS",
|
|
558
|
+
latitude: 1.3644,
|
|
559
|
+
longitude: 103.9915,
|
|
560
|
+
name: "Singapore Changi",
|
|
561
|
+
tz: "Asia/Singapore"
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
code: "YBBN",
|
|
565
|
+
country: "AU",
|
|
566
|
+
ghcnh_id: null,
|
|
567
|
+
icao: "YBBN",
|
|
568
|
+
latitude: -27.3842,
|
|
569
|
+
longitude: 153.1175,
|
|
570
|
+
name: "Brisbane",
|
|
571
|
+
tz: "Australia/Brisbane"
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
code: "YMML",
|
|
575
|
+
country: "AU",
|
|
576
|
+
ghcnh_id: null,
|
|
577
|
+
icao: "YMML",
|
|
578
|
+
latitude: -37.6733,
|
|
579
|
+
longitude: 144.8433,
|
|
580
|
+
name: "Melbourne Tullamarine",
|
|
581
|
+
tz: "Australia/Melbourne"
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
code: "YSSY",
|
|
585
|
+
country: "AU",
|
|
586
|
+
ghcnh_id: null,
|
|
587
|
+
icao: "YSSY",
|
|
588
|
+
latitude: -33.9461,
|
|
589
|
+
longitude: 151.1772,
|
|
590
|
+
name: "Sydney Kingsford Smith",
|
|
591
|
+
tz: "Australia/Sydney"
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
code: "ZBAA",
|
|
595
|
+
country: "CN",
|
|
596
|
+
ghcnh_id: null,
|
|
597
|
+
icao: "ZBAA",
|
|
598
|
+
latitude: 40.0801,
|
|
599
|
+
longitude: 116.5846,
|
|
600
|
+
name: "Beijing Capital",
|
|
601
|
+
tz: "Asia/Shanghai"
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
code: "ZSPD",
|
|
605
|
+
country: "CN",
|
|
606
|
+
ghcnh_id: null,
|
|
607
|
+
icao: "ZSPD",
|
|
608
|
+
latitude: 31.1443,
|
|
609
|
+
longitude: 121.8083,
|
|
610
|
+
name: "Shanghai Pudong",
|
|
611
|
+
tz: "Asia/Shanghai"
|
|
612
|
+
}
|
|
613
|
+
];
|
|
614
|
+
var STATION_BY_CODE = /* @__PURE__ */ new Map([
|
|
615
|
+
["ATL", STATIONS[10]],
|
|
616
|
+
["AUS", STATIONS[11]],
|
|
617
|
+
["BOS", STATIONS[12]],
|
|
618
|
+
["DCA", STATIONS[13]],
|
|
619
|
+
["DEN", STATIONS[14]],
|
|
620
|
+
["DFW", STATIONS[15]],
|
|
621
|
+
["EDDB", STATIONS[0]],
|
|
622
|
+
["EDDF", STATIONS[1]],
|
|
623
|
+
["EDDM", STATIONS[2]],
|
|
624
|
+
["EFHK", STATIONS[3]],
|
|
625
|
+
["EGKK", STATIONS[4]],
|
|
626
|
+
["EGLL", STATIONS[5]],
|
|
627
|
+
["EHAM", STATIONS[6]],
|
|
628
|
+
["EKCH", STATIONS[7]],
|
|
629
|
+
["EPWA", STATIONS[8]],
|
|
630
|
+
["ESSA", STATIONS[9]],
|
|
631
|
+
["HOU", STATIONS[16]],
|
|
632
|
+
["LAS", STATIONS[17]],
|
|
633
|
+
["LAX", STATIONS[18]],
|
|
634
|
+
["LEBL", STATIONS[30]],
|
|
635
|
+
["LEMD", STATIONS[31]],
|
|
636
|
+
["LFPB", STATIONS[32]],
|
|
637
|
+
["LFPG", STATIONS[33]],
|
|
638
|
+
["LFPO", STATIONS[34]],
|
|
639
|
+
["LIMC", STATIONS[35]],
|
|
640
|
+
["LIRF", STATIONS[36]],
|
|
641
|
+
["LOWW", STATIONS[37]],
|
|
642
|
+
["LSZH", STATIONS[38]],
|
|
643
|
+
["MDW", STATIONS[19]],
|
|
644
|
+
["MIA", STATIONS[20]],
|
|
645
|
+
["MSP", STATIONS[21]],
|
|
646
|
+
["MSY", STATIONS[22]],
|
|
647
|
+
["NYC", STATIONS[23]],
|
|
648
|
+
["NZAA", STATIONS[39]],
|
|
649
|
+
["NZWN", STATIONS[40]],
|
|
650
|
+
["OERK", STATIONS[41]],
|
|
651
|
+
["OKC", STATIONS[24]],
|
|
652
|
+
["OMDB", STATIONS[42]],
|
|
653
|
+
["OTHH", STATIONS[43]],
|
|
654
|
+
["PHL", STATIONS[25]],
|
|
655
|
+
["PHX", STATIONS[26]],
|
|
656
|
+
["RCTP", STATIONS[44]],
|
|
657
|
+
["RJAA", STATIONS[45]],
|
|
658
|
+
["RJTT", STATIONS[46]],
|
|
659
|
+
["RKSI", STATIONS[47]],
|
|
660
|
+
["SAEZ", STATIONS[48]],
|
|
661
|
+
["SAT", STATIONS[27]],
|
|
662
|
+
["SBGR", STATIONS[49]],
|
|
663
|
+
["SEA", STATIONS[28]],
|
|
664
|
+
["SFO", STATIONS[29]],
|
|
665
|
+
["UUEE", STATIONS[50]],
|
|
666
|
+
["VABB", STATIONS[51]],
|
|
667
|
+
["VHHH", STATIONS[52]],
|
|
668
|
+
["VIDP", STATIONS[53]],
|
|
669
|
+
["VTBS", STATIONS[54]],
|
|
670
|
+
["WSSS", STATIONS[55]],
|
|
671
|
+
["YBBN", STATIONS[56]],
|
|
672
|
+
["YMML", STATIONS[57]],
|
|
673
|
+
["YSSY", STATIONS[58]],
|
|
674
|
+
["ZBAA", STATIONS[59]],
|
|
675
|
+
["ZSPD", STATIONS[60]]
|
|
676
|
+
]);
|
|
677
|
+
var STATION_BY_ICAO = /* @__PURE__ */ new Map([
|
|
678
|
+
["EDDB", STATIONS[0]],
|
|
679
|
+
["EDDF", STATIONS[1]],
|
|
680
|
+
["EDDM", STATIONS[2]],
|
|
681
|
+
["EFHK", STATIONS[3]],
|
|
682
|
+
["EGKK", STATIONS[4]],
|
|
683
|
+
["EGLL", STATIONS[5]],
|
|
684
|
+
["EHAM", STATIONS[6]],
|
|
685
|
+
["EKCH", STATIONS[7]],
|
|
686
|
+
["EPWA", STATIONS[8]],
|
|
687
|
+
["ESSA", STATIONS[9]],
|
|
688
|
+
["KATL", STATIONS[10]],
|
|
689
|
+
["KAUS", STATIONS[11]],
|
|
690
|
+
["KBOS", STATIONS[12]],
|
|
691
|
+
["KDCA", STATIONS[13]],
|
|
692
|
+
["KDEN", STATIONS[14]],
|
|
693
|
+
["KDFW", STATIONS[15]],
|
|
694
|
+
["KHOU", STATIONS[16]],
|
|
695
|
+
["KLAS", STATIONS[17]],
|
|
696
|
+
["KLAX", STATIONS[18]],
|
|
697
|
+
["KMDW", STATIONS[19]],
|
|
698
|
+
["KMIA", STATIONS[20]],
|
|
699
|
+
["KMSP", STATIONS[21]],
|
|
700
|
+
["KMSY", STATIONS[22]],
|
|
701
|
+
["KNYC", STATIONS[23]],
|
|
702
|
+
["KOKC", STATIONS[24]],
|
|
703
|
+
["KPHL", STATIONS[25]],
|
|
704
|
+
["KPHX", STATIONS[26]],
|
|
705
|
+
["KSAT", STATIONS[27]],
|
|
706
|
+
["KSEA", STATIONS[28]],
|
|
707
|
+
["KSFO", STATIONS[29]],
|
|
708
|
+
["LEBL", STATIONS[30]],
|
|
709
|
+
["LEMD", STATIONS[31]],
|
|
710
|
+
["LFPB", STATIONS[32]],
|
|
711
|
+
["LFPG", STATIONS[33]],
|
|
712
|
+
["LFPO", STATIONS[34]],
|
|
713
|
+
["LIMC", STATIONS[35]],
|
|
714
|
+
["LIRF", STATIONS[36]],
|
|
715
|
+
["LOWW", STATIONS[37]],
|
|
716
|
+
["LSZH", STATIONS[38]],
|
|
717
|
+
["NZAA", STATIONS[39]],
|
|
718
|
+
["NZWN", STATIONS[40]],
|
|
719
|
+
["OERK", STATIONS[41]],
|
|
720
|
+
["OMDB", STATIONS[42]],
|
|
721
|
+
["OTHH", STATIONS[43]],
|
|
722
|
+
["RCTP", STATIONS[44]],
|
|
723
|
+
["RJAA", STATIONS[45]],
|
|
724
|
+
["RJTT", STATIONS[46]],
|
|
725
|
+
["RKSI", STATIONS[47]],
|
|
726
|
+
["SAEZ", STATIONS[48]],
|
|
727
|
+
["SBGR", STATIONS[49]],
|
|
728
|
+
["UUEE", STATIONS[50]],
|
|
729
|
+
["VABB", STATIONS[51]],
|
|
730
|
+
["VHHH", STATIONS[52]],
|
|
731
|
+
["VIDP", STATIONS[53]],
|
|
732
|
+
["VTBS", STATIONS[54]],
|
|
733
|
+
["WSSS", STATIONS[55]],
|
|
734
|
+
["YBBN", STATIONS[56]],
|
|
735
|
+
["YMML", STATIONS[57]],
|
|
736
|
+
["YSSY", STATIONS[58]],
|
|
737
|
+
["ZBAA", STATIONS[59]],
|
|
738
|
+
["ZSPD", STATIONS[60]]
|
|
739
|
+
]);
|
|
740
|
+
|
|
741
|
+
// src/discovery/availability.ts
|
|
742
|
+
function hasListKeys(store) {
|
|
743
|
+
return typeof store.listKeys === "function";
|
|
744
|
+
}
|
|
745
|
+
var STATION_RE = /^[A-Z0-9]{3,5}$/;
|
|
746
|
+
function normalizeStation(station) {
|
|
747
|
+
const upper = station.toUpperCase();
|
|
748
|
+
if (!STATION_RE.test(upper)) {
|
|
749
|
+
throw new RangeError(
|
|
750
|
+
`availability: station must be a 3-5 char alphanumeric code; got ${JSON.stringify(station)}`
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
const byIcao = STATION_BY_ICAO.get(upper);
|
|
754
|
+
if (byIcao !== void 0) {
|
|
755
|
+
return byIcao.code ?? byIcao.icao;
|
|
756
|
+
}
|
|
757
|
+
const byCode = STATION_BY_CODE.get(upper);
|
|
758
|
+
if (byCode !== void 0) {
|
|
759
|
+
return byCode.code ?? byCode.icao;
|
|
760
|
+
}
|
|
761
|
+
return upper;
|
|
762
|
+
}
|
|
763
|
+
var OBS_KEY_RE = /^mostlyright:v1:observations:([A-Z0-9]+):(\d{4}):(\d{2})(?::[a-z0-9_-]+)?$/;
|
|
764
|
+
var CLIMATE_KEY_RE = /^mostlyright:v1:climate:([A-Z0-9]+):(\d{4})$/;
|
|
765
|
+
async function availability(station, cache, opts = {}) {
|
|
766
|
+
const stationCode = normalizeStation(station);
|
|
767
|
+
const empty = Object.freeze({
|
|
768
|
+
station: stationCode,
|
|
769
|
+
monthsCached: 0,
|
|
770
|
+
firstMonth: null,
|
|
771
|
+
lastMonth: null,
|
|
772
|
+
climateYears: 0,
|
|
773
|
+
firstClimateYear: null,
|
|
774
|
+
lastClimateYear: null
|
|
775
|
+
});
|
|
776
|
+
if (!hasListKeys(cache)) {
|
|
777
|
+
return empty;
|
|
778
|
+
}
|
|
779
|
+
const upperInput = station.toUpperCase();
|
|
780
|
+
const scanCodes = upperInput === stationCode ? [stationCode] : [stationCode, upperInput];
|
|
781
|
+
const obsPrefixes = scanCodes.map((c) => `mostlyright:v1:observations:${c}:`);
|
|
782
|
+
const climatePrefixes = scanCodes.map((c) => `mostlyright:v1:climate:${c}:`);
|
|
783
|
+
const obsKeySets = await Promise.all(obsPrefixes.map((p) => cache.listKeys(p)));
|
|
784
|
+
const climateKeySets = await Promise.all(climatePrefixes.map((p) => cache.listKeys(p)));
|
|
785
|
+
const obsKeys = obsKeySets.flat();
|
|
786
|
+
const climateKeys = climateKeySets.flat();
|
|
787
|
+
const monthCandidates = /* @__PURE__ */ new Map();
|
|
788
|
+
for (const key of obsKeys) {
|
|
789
|
+
const m = OBS_KEY_RE.exec(key);
|
|
790
|
+
if (m === null) continue;
|
|
791
|
+
const keyStation = m[1];
|
|
792
|
+
if (!scanCodes.includes(keyStation)) continue;
|
|
793
|
+
const ym = `${m[2]}-${m[3]}`;
|
|
794
|
+
const arr = monthCandidates.get(ym) ?? [];
|
|
795
|
+
arr.push(key);
|
|
796
|
+
monthCandidates.set(ym, arr);
|
|
797
|
+
}
|
|
798
|
+
const yearCandidates = /* @__PURE__ */ new Map();
|
|
799
|
+
for (const key of climateKeys) {
|
|
800
|
+
const m = CLIMATE_KEY_RE.exec(key);
|
|
801
|
+
if (m === null) continue;
|
|
802
|
+
const keyStation = m[1];
|
|
803
|
+
if (!scanCodes.includes(keyStation)) continue;
|
|
804
|
+
const y = m[2];
|
|
805
|
+
const arr = yearCandidates.get(y) ?? [];
|
|
806
|
+
arr.push(key);
|
|
807
|
+
yearCandidates.set(y, arr);
|
|
808
|
+
}
|
|
809
|
+
let months;
|
|
810
|
+
let years;
|
|
811
|
+
if (opts.validate === true) {
|
|
812
|
+
const monthChecks = await Promise.all(
|
|
813
|
+
[...monthCandidates.entries()].map(async ([ym, keys]) => {
|
|
814
|
+
for (const k of keys) {
|
|
815
|
+
if (await cache.get(k) !== null) return ym;
|
|
816
|
+
}
|
|
817
|
+
return null;
|
|
818
|
+
})
|
|
819
|
+
);
|
|
820
|
+
months = monthChecks.filter((v) => v !== null).sort();
|
|
821
|
+
const yearChecks = await Promise.all(
|
|
822
|
+
[...yearCandidates.entries()].map(async ([y, keys]) => {
|
|
823
|
+
for (const k of keys) {
|
|
824
|
+
if (await cache.get(k) !== null) return y;
|
|
825
|
+
}
|
|
826
|
+
return null;
|
|
827
|
+
})
|
|
828
|
+
);
|
|
829
|
+
years = yearChecks.filter((v) => v !== null).sort();
|
|
830
|
+
} else {
|
|
831
|
+
months = [...monthCandidates.keys()].sort();
|
|
832
|
+
years = [...yearCandidates.keys()].sort();
|
|
833
|
+
}
|
|
834
|
+
return Object.freeze({
|
|
835
|
+
station: stationCode,
|
|
836
|
+
monthsCached: months.length,
|
|
837
|
+
firstMonth: months[0] ?? null,
|
|
838
|
+
lastMonth: months.at(-1) ?? null,
|
|
839
|
+
climateYears: years.length,
|
|
840
|
+
firstClimateYear: years[0] ?? null,
|
|
841
|
+
lastClimateYear: years.at(-1) ?? null
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/discovery/international.ts
|
|
846
|
+
var LOW_COVERAGE_THRESHOLD = 12;
|
|
847
|
+
var PARTS_CACHE = /* @__PURE__ */ new Map();
|
|
848
|
+
function getDateFormatter(tz) {
|
|
849
|
+
let f = PARTS_CACHE.get(tz);
|
|
850
|
+
if (f === void 0) {
|
|
851
|
+
f = new Intl.DateTimeFormat("en-US", {
|
|
852
|
+
timeZone: tz,
|
|
853
|
+
year: "numeric",
|
|
854
|
+
month: "2-digit",
|
|
855
|
+
day: "2-digit"
|
|
856
|
+
});
|
|
857
|
+
PARTS_CACHE.set(tz, f);
|
|
858
|
+
}
|
|
859
|
+
return f;
|
|
860
|
+
}
|
|
861
|
+
function localDateFor(instant, tz) {
|
|
862
|
+
const parts = getDateFormatter(tz).formatToParts(instant);
|
|
863
|
+
let y = "";
|
|
864
|
+
let m = "";
|
|
865
|
+
let d = "";
|
|
866
|
+
for (const p of parts) {
|
|
867
|
+
if (p.type === "year") y = p.value;
|
|
868
|
+
else if (p.type === "month") m = p.value;
|
|
869
|
+
else if (p.type === "day") d = p.value;
|
|
870
|
+
}
|
|
871
|
+
return `${y}-${m}-${d}`;
|
|
872
|
+
}
|
|
873
|
+
function parseInstant(observed) {
|
|
874
|
+
if (observed === void 0 || observed === null || observed.length === 0) {
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
const ms = Date.parse(observed);
|
|
878
|
+
if (Number.isNaN(ms)) return null;
|
|
879
|
+
return new Date(ms);
|
|
880
|
+
}
|
|
881
|
+
function roundHalfUp(value, places) {
|
|
882
|
+
if (!Number.isFinite(value)) return value;
|
|
883
|
+
const scale = 10 ** places;
|
|
884
|
+
const sign = value < 0 ? -1 : 1;
|
|
885
|
+
const abs = Math.abs(value);
|
|
886
|
+
const rounded = Math.floor(abs * scale + 0.5 + abs * 1e-12) / scale;
|
|
887
|
+
return sign * rounded;
|
|
888
|
+
}
|
|
889
|
+
function cToF(c) {
|
|
890
|
+
return c * 1.8 + 32;
|
|
891
|
+
}
|
|
892
|
+
function internationalDailyExtremes(rows, opts) {
|
|
893
|
+
const tz = opts.stationTz;
|
|
894
|
+
if (typeof tz !== "string" || tz.length === 0) {
|
|
895
|
+
throw new RangeError("internationalDailyExtremes: stationTz is required (non-empty string)");
|
|
896
|
+
}
|
|
897
|
+
const precision = opts.precision ?? 0;
|
|
898
|
+
const minObs = opts.minObs ?? LOW_COVERAGE_THRESHOLD;
|
|
899
|
+
try {
|
|
900
|
+
getDateFormatter(tz);
|
|
901
|
+
} catch (e) {
|
|
902
|
+
throw new RangeError(
|
|
903
|
+
`internationalDailyExtremes: invalid stationTz ${JSON.stringify(tz)}: ${e.message}`
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
const byLocalDate = /* @__PURE__ */ new Map();
|
|
907
|
+
for (const row of rows) {
|
|
908
|
+
const instant = parseInstant(row.observed_at);
|
|
909
|
+
if (instant === null) continue;
|
|
910
|
+
const localDate = localDateFor(instant, tz);
|
|
911
|
+
let bucket = byLocalDate.get(localDate);
|
|
912
|
+
if (bucket === void 0) {
|
|
913
|
+
bucket = { temps: [], precipMm: 0 };
|
|
914
|
+
byLocalDate.set(localDate, bucket);
|
|
915
|
+
}
|
|
916
|
+
const t = row.temp_c;
|
|
917
|
+
if (typeof t === "number" && Number.isFinite(t)) {
|
|
918
|
+
bucket.temps.push({ value: t, source: row.source ?? null });
|
|
919
|
+
}
|
|
920
|
+
const p = row.precip_mm_1h;
|
|
921
|
+
if (typeof p === "number" && Number.isFinite(p)) {
|
|
922
|
+
bucket.precipMm += p;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const out = [];
|
|
926
|
+
const sortedDates = [...byLocalDate.keys()].sort();
|
|
927
|
+
for (const localDate of sortedDates) {
|
|
928
|
+
const bucket = byLocalDate.get(localDate);
|
|
929
|
+
if (bucket === void 0) continue;
|
|
930
|
+
const nObs = bucket.temps.length;
|
|
931
|
+
let tempMinC = null;
|
|
932
|
+
let tempMaxC = null;
|
|
933
|
+
let tempMeanC = null;
|
|
934
|
+
let sourceTmin = null;
|
|
935
|
+
let sourceTmax = null;
|
|
936
|
+
if (nObs > 0 && nObs >= minObs) {
|
|
937
|
+
let minIdx = 0;
|
|
938
|
+
let maxIdx = 0;
|
|
939
|
+
let sum = 0;
|
|
940
|
+
for (let i = 0; i < bucket.temps.length; i += 1) {
|
|
941
|
+
const v = bucket.temps[i];
|
|
942
|
+
sum += v.value;
|
|
943
|
+
const minRow2 = bucket.temps[minIdx];
|
|
944
|
+
const maxRow2 = bucket.temps[maxIdx];
|
|
945
|
+
if (v.value < minRow2.value) minIdx = i;
|
|
946
|
+
if (v.value > maxRow2.value) maxIdx = i;
|
|
947
|
+
}
|
|
948
|
+
const mean = sum / nObs;
|
|
949
|
+
const minRow = bucket.temps[minIdx];
|
|
950
|
+
const maxRow = bucket.temps[maxIdx];
|
|
951
|
+
tempMinC = roundHalfUp(minRow.value, precision);
|
|
952
|
+
tempMaxC = roundHalfUp(maxRow.value, precision);
|
|
953
|
+
tempMeanC = roundHalfUp(mean, precision);
|
|
954
|
+
sourceTmin = minRow.source;
|
|
955
|
+
sourceTmax = maxRow.source;
|
|
956
|
+
}
|
|
957
|
+
out.push(
|
|
958
|
+
Object.freeze({
|
|
959
|
+
localDate,
|
|
960
|
+
nObs,
|
|
961
|
+
tempMinC,
|
|
962
|
+
tempMaxC,
|
|
963
|
+
tempMeanC,
|
|
964
|
+
tempMinF: tempMinC === null ? null : roundHalfUp(cToF(tempMinC), precision),
|
|
965
|
+
tempMaxF: tempMaxC === null ? null : roundHalfUp(cToF(tempMaxC), precision),
|
|
966
|
+
precipMm: roundHalfUp(bucket.precipMm, 4),
|
|
967
|
+
sourceTmin,
|
|
968
|
+
sourceTmax
|
|
969
|
+
})
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
return out;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// src/exceptions/index.ts
|
|
976
|
+
function toJsonSafe(value, seen) {
|
|
977
|
+
const visited = seen ?? /* @__PURE__ */ new WeakSet();
|
|
978
|
+
if (value === null || value === void 0) {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
if (typeof value === "boolean") {
|
|
982
|
+
return value;
|
|
983
|
+
}
|
|
984
|
+
if (typeof value === "string") {
|
|
985
|
+
return value;
|
|
986
|
+
}
|
|
987
|
+
if (typeof value === "number") {
|
|
988
|
+
return Number.isFinite(value) ? value : null;
|
|
989
|
+
}
|
|
990
|
+
if (typeof value === "bigint") {
|
|
991
|
+
if (value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
992
|
+
return Number(value);
|
|
993
|
+
}
|
|
994
|
+
return { _repr_only: true, value: value.toString() };
|
|
995
|
+
}
|
|
996
|
+
if (value instanceof Date) {
|
|
997
|
+
if (Number.isNaN(value.getTime())) {
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
return value.toISOString();
|
|
1001
|
+
}
|
|
1002
|
+
if (Array.isArray(value)) {
|
|
1003
|
+
if (visited.has(value)) {
|
|
1004
|
+
return { _cycle: true, value: String(value) };
|
|
1005
|
+
}
|
|
1006
|
+
visited.add(value);
|
|
1007
|
+
try {
|
|
1008
|
+
return value.map((item) => toJsonSafe(item, visited));
|
|
1009
|
+
} finally {
|
|
1010
|
+
visited.delete(value);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (typeof value === "object") {
|
|
1014
|
+
if (visited.has(value)) {
|
|
1015
|
+
return { _cycle: true, value: String(value) };
|
|
1016
|
+
}
|
|
1017
|
+
visited.add(value);
|
|
1018
|
+
try {
|
|
1019
|
+
const out = {};
|
|
1020
|
+
for (const key of Object.keys(value)) {
|
|
1021
|
+
if (typeof key !== "string") {
|
|
1022
|
+
throw new TypeError(`toJsonSafe dict keys must be string; got ${typeof key}`);
|
|
1023
|
+
}
|
|
1024
|
+
out[key] = toJsonSafe(value[key], visited);
|
|
1025
|
+
}
|
|
1026
|
+
return out;
|
|
1027
|
+
} finally {
|
|
1028
|
+
visited.delete(value);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return { _repr_only: true, value: String(value) };
|
|
1032
|
+
}
|
|
1033
|
+
var TradewindsError = class extends Error {
|
|
1034
|
+
/** Subclass override — the stable string enum surfaced via `errorCode`. */
|
|
1035
|
+
static defaultErrorCode = "TRADEWINDS_ERROR";
|
|
1036
|
+
errorCode;
|
|
1037
|
+
source;
|
|
1038
|
+
requestId;
|
|
1039
|
+
constructor(message = "", options = {}) {
|
|
1040
|
+
super(message);
|
|
1041
|
+
this.name = new.target.name;
|
|
1042
|
+
const ctor = this.constructor;
|
|
1043
|
+
this.errorCode = options.errorCode ?? ctor.defaultErrorCode;
|
|
1044
|
+
this.source = options.source ?? null;
|
|
1045
|
+
this.requestId = options.requestId ?? null;
|
|
1046
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Subclass hook returning the structured attributes for `toDict`.
|
|
1050
|
+
* Values are passed through `toJsonSafe` by `toDict()`, so subclasses
|
|
1051
|
+
* don't need to coerce values themselves.
|
|
1052
|
+
*/
|
|
1053
|
+
payload() {
|
|
1054
|
+
return {
|
|
1055
|
+
error_code: this.errorCode,
|
|
1056
|
+
message: this.message,
|
|
1057
|
+
source: this.source,
|
|
1058
|
+
request_id: this.requestId
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
/** Return a JSON-safe dict suitable for MCP `error.data`. */
|
|
1062
|
+
toDict() {
|
|
1063
|
+
const safe = toJsonSafe(this.payload());
|
|
1064
|
+
return safe;
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
// src/formats/toon.ts
|
|
1069
|
+
var SAFE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_.]*$/;
|
|
1070
|
+
var NUMERIC_LIKE_RE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
1071
|
+
var NEEDS_QUOTE_CHARS_RE = /[:\\\"'\[\]{}\x00-\x1f\x7f\x85\u2028\u2029]/;
|
|
1072
|
+
var UNSUPPORTED_CTRL_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f\x85\u2028\u2029]/g;
|
|
1073
|
+
function formatNumber(n) {
|
|
1074
|
+
if (!Number.isFinite(n)) return "null";
|
|
1075
|
+
if (n === 0) return "0";
|
|
1076
|
+
if (Number.isInteger(n) && Math.abs(n) <= 2 ** 53) return String(n);
|
|
1077
|
+
let s = String(n);
|
|
1078
|
+
if (/[eE]/.test(s)) {
|
|
1079
|
+
s = expandExponent(s);
|
|
1080
|
+
}
|
|
1081
|
+
return s;
|
|
1082
|
+
}
|
|
1083
|
+
function expandExponent(s) {
|
|
1084
|
+
const m = /^(-?)(\d+(?:\.\d+)?)[eE]([+-]?\d+)$/.exec(s);
|
|
1085
|
+
if (!m) return s;
|
|
1086
|
+
const sign = m[1] ?? "";
|
|
1087
|
+
const mantissa = m[2] ?? "";
|
|
1088
|
+
const exp = Number(m[3]);
|
|
1089
|
+
const [intPart, fracPart = ""] = mantissa.split(".");
|
|
1090
|
+
const digits = (intPart ?? "") + fracPart;
|
|
1091
|
+
const pointPos = (intPart ?? "").length + exp;
|
|
1092
|
+
let out;
|
|
1093
|
+
if (pointPos <= 0) {
|
|
1094
|
+
out = `0.${"0".repeat(-pointPos)}${digits}`.replace(/0+$/, "");
|
|
1095
|
+
if (out.endsWith(".")) out = out.slice(0, -1);
|
|
1096
|
+
} else if (pointPos >= digits.length) {
|
|
1097
|
+
out = digits + "0".repeat(pointPos - digits.length);
|
|
1098
|
+
} else {
|
|
1099
|
+
out = `${digits.slice(0, pointPos)}.${digits.slice(pointPos)}`.replace(/0+$/, "");
|
|
1100
|
+
if (out.endsWith(".")) out = out.slice(0, -1);
|
|
1101
|
+
}
|
|
1102
|
+
return sign + out;
|
|
1103
|
+
}
|
|
1104
|
+
function needsQuoting(s, delimiter) {
|
|
1105
|
+
if (s.length === 0) return true;
|
|
1106
|
+
const first = s.charAt(0);
|
|
1107
|
+
const last = s.charAt(s.length - 1);
|
|
1108
|
+
if (first === " " || first === " ") return true;
|
|
1109
|
+
if (last === " " || last === " ") return true;
|
|
1110
|
+
if (s === "true" || s === "false" || s === "null") return true;
|
|
1111
|
+
if (first === "-" || first === "+") return true;
|
|
1112
|
+
if (first >= "0" && first <= "9") return true;
|
|
1113
|
+
if (NUMERIC_LIKE_RE.test(s)) return true;
|
|
1114
|
+
if (s.includes(delimiter)) return true;
|
|
1115
|
+
if (NEEDS_QUOTE_CHARS_RE.test(s)) return true;
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
function quoteString(s) {
|
|
1119
|
+
let out = s.replace(UNSUPPORTED_CTRL_RE, "");
|
|
1120
|
+
out = out.replace(/\\/g, "\\\\");
|
|
1121
|
+
out = out.replace(/"/g, '\\"');
|
|
1122
|
+
out = out.replace(/\n/g, "\\n");
|
|
1123
|
+
out = out.replace(/\r/g, "\\r");
|
|
1124
|
+
out = out.replace(/\t/g, "\\t");
|
|
1125
|
+
return `"${out}"`;
|
|
1126
|
+
}
|
|
1127
|
+
function formatKey(key) {
|
|
1128
|
+
if (typeof key !== "string") {
|
|
1129
|
+
throw new TypeError(`TOON keys must be strings; got ${typeof key}`);
|
|
1130
|
+
}
|
|
1131
|
+
if (SAFE_KEY_RE.test(key)) return key;
|
|
1132
|
+
return quoteString(key);
|
|
1133
|
+
}
|
|
1134
|
+
function encodeScalar(value, delimiter) {
|
|
1135
|
+
if (value === null || value === void 0) return "null";
|
|
1136
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
1137
|
+
if (typeof value === "number") return formatNumber(value);
|
|
1138
|
+
if (typeof value === "string") {
|
|
1139
|
+
if (needsQuoting(value, delimiter)) return quoteString(value);
|
|
1140
|
+
return value;
|
|
1141
|
+
}
|
|
1142
|
+
if (typeof value === "object") {
|
|
1143
|
+
if (Array.isArray(value)) {
|
|
1144
|
+
return quoteString(JSON.stringify(value));
|
|
1145
|
+
}
|
|
1146
|
+
const sorted = sortedJson(value);
|
|
1147
|
+
return quoteString(sorted);
|
|
1148
|
+
}
|
|
1149
|
+
return quoteString(String(value));
|
|
1150
|
+
}
|
|
1151
|
+
function sortedJson(obj) {
|
|
1152
|
+
const keys = Object.keys(obj).sort();
|
|
1153
|
+
const parts = keys.map((k) => `${JSON.stringify(k)}:${JSON.stringify(obj[k])}`);
|
|
1154
|
+
return `{${parts.join(",")}}`;
|
|
1155
|
+
}
|
|
1156
|
+
var ToonTabularError = class extends RangeError {
|
|
1157
|
+
name = "ToonTabularError";
|
|
1158
|
+
};
|
|
1159
|
+
function isToonPrimitive(v) {
|
|
1160
|
+
if (v === null || v === void 0) return true;
|
|
1161
|
+
const t = typeof v;
|
|
1162
|
+
return t === "string" || t === "number" || t === "boolean";
|
|
1163
|
+
}
|
|
1164
|
+
function assertTabular(rows) {
|
|
1165
|
+
if (rows.length === 0) return;
|
|
1166
|
+
const first = rows[0];
|
|
1167
|
+
const expectedKeys = Object.keys(first);
|
|
1168
|
+
if (expectedKeys.length === 0) {
|
|
1169
|
+
throw new ToonTabularError(
|
|
1170
|
+
"toonDumps requires non-empty rows; first row has no keys (Python parity: encode_tabular rejects empty key set)"
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
const expectedKeySet = new Set(expectedKeys);
|
|
1174
|
+
for (let i = 0; i < rows.length; i++) {
|
|
1175
|
+
const row = rows[i];
|
|
1176
|
+
const rowKeys = Object.keys(row);
|
|
1177
|
+
if (rowKeys.length !== expectedKeySet.size) {
|
|
1178
|
+
throw new ToonTabularError(
|
|
1179
|
+
`toonDumps requires uniform rows; row ${i} has ${rowKeys.length} key(s) vs row 0's ${expectedKeySet.size}. Python encode_tabular rejects rows whose key sets differ.`
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
for (const k of rowKeys) {
|
|
1183
|
+
if (!expectedKeySet.has(k)) {
|
|
1184
|
+
throw new ToonTabularError(
|
|
1185
|
+
`toonDumps requires uniform rows; row ${i} has key ${JSON.stringify(k)} not present in row 0. Python encode_tabular rejects rows whose key sets differ.`
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
for (const k of expectedKeys) {
|
|
1190
|
+
const v = row[k];
|
|
1191
|
+
if (!isToonPrimitive(v)) {
|
|
1192
|
+
throw new ToonTabularError(
|
|
1193
|
+
`toonDumps requires primitive cell values; row ${i} column ${JSON.stringify(k)} has non-primitive value of type ${typeof v}. Python encode_tabular rejects nested objects/arrays at the cell level.`
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
function toonDumps(rows, columns) {
|
|
1200
|
+
if (rows.length === 0) {
|
|
1201
|
+
if (columns !== void 0) {
|
|
1202
|
+
const cols2 = columns.map((c) => formatKey(String(c))).join(",");
|
|
1203
|
+
return `rows[0]{${cols2}}:`;
|
|
1204
|
+
}
|
|
1205
|
+
return "rows[0]:";
|
|
1206
|
+
}
|
|
1207
|
+
assertTabular(rows);
|
|
1208
|
+
const cols = Object.keys(rows[0]);
|
|
1209
|
+
const colHeader = cols.map((c) => formatKey(c)).join(",");
|
|
1210
|
+
const header = `rows[${rows.length}]{${colHeader}}:`;
|
|
1211
|
+
const dataLines = rows.map((r) => {
|
|
1212
|
+
const vals = cols.map((c) => encodeScalar(r[c], ","));
|
|
1213
|
+
return ` ${vals.join(",")}`;
|
|
1214
|
+
});
|
|
1215
|
+
return `${header}
|
|
1216
|
+
${dataLines.join("\n")}`;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// src/discovery/snapshot.ts
|
|
1220
|
+
function normalizeKnowledgeTime(value) {
|
|
1221
|
+
if (value === void 0) {
|
|
1222
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1223
|
+
}
|
|
1224
|
+
if (value instanceof Date) {
|
|
1225
|
+
if (Number.isNaN(value.getTime())) {
|
|
1226
|
+
throw new RangeError("buildSnapshot: knowledgeTime is an invalid Date");
|
|
1227
|
+
}
|
|
1228
|
+
return value.toISOString();
|
|
1229
|
+
}
|
|
1230
|
+
const ms = Date.parse(value);
|
|
1231
|
+
if (Number.isNaN(ms)) {
|
|
1232
|
+
throw new RangeError(
|
|
1233
|
+
`buildSnapshot: knowledgeTime ${JSON.stringify(value)} is not a parseable ISO 8601 timestamp`
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
return new Date(ms).toISOString();
|
|
1237
|
+
}
|
|
1238
|
+
function freezeRows(rows) {
|
|
1239
|
+
const out = [];
|
|
1240
|
+
for (const r of rows) {
|
|
1241
|
+
out.push(Object.freeze({ ...r }));
|
|
1242
|
+
}
|
|
1243
|
+
return Object.freeze(out);
|
|
1244
|
+
}
|
|
1245
|
+
function buildSnapshot(opts) {
|
|
1246
|
+
if (typeof opts.schemaId !== "string" || opts.schemaId.length === 0) {
|
|
1247
|
+
throw new RangeError("buildSnapshot: schemaId must be a non-empty string");
|
|
1248
|
+
}
|
|
1249
|
+
if (typeof opts.source !== "string" || opts.source.length === 0) {
|
|
1250
|
+
throw new RangeError("buildSnapshot: source must be a non-empty string");
|
|
1251
|
+
}
|
|
1252
|
+
if (!Array.isArray(opts.rows)) {
|
|
1253
|
+
throw new RangeError("buildSnapshot: rows must be an array");
|
|
1254
|
+
}
|
|
1255
|
+
const knowledgeTime = normalizeKnowledgeTime(opts.knowledgeTime);
|
|
1256
|
+
const schemaId = opts.schemaId;
|
|
1257
|
+
const source = opts.source;
|
|
1258
|
+
const rows = freezeRows(opts.rows);
|
|
1259
|
+
const dataVersion = opts.dataVersion ?? null;
|
|
1260
|
+
const metadata = Object.freeze({ ...opts.metadata ?? {} });
|
|
1261
|
+
const snapshot = {
|
|
1262
|
+
knowledgeTime,
|
|
1263
|
+
schemaId,
|
|
1264
|
+
source,
|
|
1265
|
+
rows,
|
|
1266
|
+
dataVersion,
|
|
1267
|
+
metadata,
|
|
1268
|
+
toDict() {
|
|
1269
|
+
return toJsonSafe({
|
|
1270
|
+
knowledge_time: knowledgeTime,
|
|
1271
|
+
schema_id: schemaId,
|
|
1272
|
+
source,
|
|
1273
|
+
rows,
|
|
1274
|
+
data_version: dataVersion,
|
|
1275
|
+
metadata
|
|
1276
|
+
});
|
|
1277
|
+
},
|
|
1278
|
+
toToon() {
|
|
1279
|
+
return toonDumps(rows);
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
return Object.freeze(snapshot);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/discovery/data-version.ts
|
|
1286
|
+
var HEX = "0123456789abcdef";
|
|
1287
|
+
function bytesToHex(bytes) {
|
|
1288
|
+
let out = "";
|
|
1289
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
1290
|
+
const b = bytes[i];
|
|
1291
|
+
out += HEX[b >> 4 & 15];
|
|
1292
|
+
out += HEX[b & 15];
|
|
1293
|
+
}
|
|
1294
|
+
return out;
|
|
1295
|
+
}
|
|
1296
|
+
async function dataVersionFromComponents(components) {
|
|
1297
|
+
const sortedSchemaIds = [...components.schemaIds].sort();
|
|
1298
|
+
const sortedSources = [...components.sources].sort();
|
|
1299
|
+
const canonical = [
|
|
1300
|
+
components.sdkVersion,
|
|
1301
|
+
sortedSchemaIds.join(","),
|
|
1302
|
+
sortedSources.join(","),
|
|
1303
|
+
components.codeSha,
|
|
1304
|
+
components.dataSha
|
|
1305
|
+
].join("|");
|
|
1306
|
+
const encoded = new TextEncoder().encode(canonical);
|
|
1307
|
+
const digest = await crypto.subtle.digest("SHA-256", encoded);
|
|
1308
|
+
const token = bytesToHex(new Uint8Array(digest));
|
|
1309
|
+
return Object.freeze({
|
|
1310
|
+
sdkVersion: components.sdkVersion,
|
|
1311
|
+
schemaIds: Object.freeze([...components.schemaIds]),
|
|
1312
|
+
sources: Object.freeze([...components.sources]),
|
|
1313
|
+
codeSha: components.codeSha,
|
|
1314
|
+
dataSha: components.dataSha,
|
|
1315
|
+
token
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
async function dataVersionForResearch(args) {
|
|
1319
|
+
return dataVersionFromComponents({
|
|
1320
|
+
sdkVersion: args.sdkVersion,
|
|
1321
|
+
schemaIds: ["schema.observation.v1", "schema.forecast.iem_mos.v1", "schema.settlement.cli.v1"],
|
|
1322
|
+
sources: ["iem.archive", "iem.live", "awc.live", "ghcnh", "nws.cli"],
|
|
1323
|
+
codeSha: `research:${args.station}:${args.fromDate}:${args.toDate}`,
|
|
1324
|
+
dataSha: args.dataSha
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// src/discovery/describe.ts
|
|
1329
|
+
var BUILT_IN_SCHEMAS = Object.freeze([
|
|
1330
|
+
{
|
|
1331
|
+
id: "schema.observation.v1",
|
|
1332
|
+
title: "schema.observation.v1",
|
|
1333
|
+
columnCount: 20,
|
|
1334
|
+
columns: [
|
|
1335
|
+
{ name: "dew_point_c", description: "units: celsius \u2014 bounded", nullable: true },
|
|
1336
|
+
{ name: "event_time", description: "observation valid time", nullable: false },
|
|
1337
|
+
{
|
|
1338
|
+
name: "metar_raw",
|
|
1339
|
+
description: "raw METAR text if source has it; null for AWC JSON (structured-only)",
|
|
1340
|
+
nullable: true
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
name: "observation_type",
|
|
1344
|
+
description: "METAR | SPECI; defaults METAR when source can't distinguish (e.g. AWC JSON)",
|
|
1345
|
+
nullable: false
|
|
1346
|
+
},
|
|
1347
|
+
{
|
|
1348
|
+
name: "precip_mm_1h",
|
|
1349
|
+
description: "units: mm \u2014 hourly precip (METAR p01i, converted from inches)",
|
|
1350
|
+
nullable: true
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
name: "sky_base_1_m",
|
|
1354
|
+
description: "units: meters \u2014 first cloud layer base height (converted from feet)",
|
|
1355
|
+
nullable: true
|
|
1356
|
+
},
|
|
1357
|
+
{ name: "sky_base_2_m", description: "units: meters", nullable: true },
|
|
1358
|
+
{ name: "sky_base_3_m", description: "units: meters", nullable: true },
|
|
1359
|
+
{ name: "sky_base_4_m", description: "units: meters", nullable: true },
|
|
1360
|
+
{ name: "sky_cover_1", description: "first cloud layer cover code", nullable: true },
|
|
1361
|
+
{ name: "sky_cover_2", description: "second layer; null if not present", nullable: true },
|
|
1362
|
+
{ name: "sky_cover_3", description: "third layer; null if not present", nullable: true },
|
|
1363
|
+
{ name: "sky_cover_4", description: "fourth layer; null if not present", nullable: true },
|
|
1364
|
+
{
|
|
1365
|
+
name: "slp_hpa",
|
|
1366
|
+
description: "units: hPa \u2014 sea-level pressure (canonical aviation unit, not converted across modes)",
|
|
1367
|
+
nullable: true
|
|
1368
|
+
},
|
|
1369
|
+
{ name: "station", description: "ICAO/ASOS station ID (e.g. KORD)", nullable: false },
|
|
1370
|
+
{
|
|
1371
|
+
name: "temp_c",
|
|
1372
|
+
description: "units: celsius \u2014 bounded TEMP_MIN_C..TEMP_MAX_C",
|
|
1373
|
+
nullable: true
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
name: "visibility_m",
|
|
1377
|
+
description: "units: meters \u2014 converted from statute miles",
|
|
1378
|
+
nullable: true
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
name: "wind_dir_deg",
|
|
1382
|
+
description: "units: degrees \u2014 0-360, bounded",
|
|
1383
|
+
nullable: true
|
|
1384
|
+
},
|
|
1385
|
+
{ name: "wind_gust_ms", description: "units: m/s \u2014 converted from kt", nullable: true },
|
|
1386
|
+
{ name: "wind_speed_ms", description: "units: m/s \u2014 converted from kt", nullable: true }
|
|
1387
|
+
]
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
id: "schema.forecast.iem_mos.v1",
|
|
1391
|
+
title: "schema.forecast.iem_mos.v1",
|
|
1392
|
+
columnCount: 11,
|
|
1393
|
+
columns: [
|
|
1394
|
+
{ name: "dew_point_c", description: "units: celsius", nullable: true },
|
|
1395
|
+
{
|
|
1396
|
+
name: "forecast_hour",
|
|
1397
|
+
description: "units: hours \u2014 (valid_at - issued_at).total_seconds() / 3600",
|
|
1398
|
+
nullable: false
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
name: "issued_at",
|
|
1402
|
+
description: "model run time (from source `runtime` field)",
|
|
1403
|
+
nullable: false
|
|
1404
|
+
},
|
|
1405
|
+
{ name: "model", description: "e.g. NBE, GFS, LAV, MET", nullable: false },
|
|
1406
|
+
{
|
|
1407
|
+
name: "precip_probability",
|
|
1408
|
+
description: "units: probability \u2014 bounded [0, 1]",
|
|
1409
|
+
nullable: true
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
name: "sky_cover_pct",
|
|
1413
|
+
description: "units: percent \u2014 bounded [0, 100]",
|
|
1414
|
+
nullable: true
|
|
1415
|
+
},
|
|
1416
|
+
{ name: "station", description: "", nullable: false },
|
|
1417
|
+
{ name: "temp_c", description: "units: celsius", nullable: true },
|
|
1418
|
+
{
|
|
1419
|
+
name: "valid_at",
|
|
1420
|
+
description: "forecast target time (from source `ftime`)",
|
|
1421
|
+
nullable: false
|
|
1422
|
+
},
|
|
1423
|
+
{ name: "wind_dir_deg", description: "units: degrees", nullable: true },
|
|
1424
|
+
{ name: "wind_speed_ms", description: "units: m/s", nullable: true }
|
|
1425
|
+
]
|
|
1426
|
+
},
|
|
1427
|
+
{
|
|
1428
|
+
id: "schema.settlement.cli.v1",
|
|
1429
|
+
title: "schema.settlement.cli.v1",
|
|
1430
|
+
columnCount: 12,
|
|
1431
|
+
columns: [
|
|
1432
|
+
{
|
|
1433
|
+
name: "cli_data_quality",
|
|
1434
|
+
description: "NWS CLI data-quality marker (Pitfall 6/16). Allows downstream code to filter or weight settlement rows by issuer quality without re-parsing the product header.",
|
|
1435
|
+
nullable: false
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
name: "event_time",
|
|
1439
|
+
description: "00:00 local time on observation_date converted to UTC; for sort/join only",
|
|
1440
|
+
nullable: false
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
name: "observation_date",
|
|
1444
|
+
description: "local climate day per NWS convention (no timezone applied to the date itself)",
|
|
1445
|
+
nullable: false
|
|
1446
|
+
},
|
|
1447
|
+
{ name: "precipitation_in", description: "units: inches", nullable: true },
|
|
1448
|
+
{
|
|
1449
|
+
name: "product_release_time",
|
|
1450
|
+
description: "parsed from CLI product header (_climate.py::_parse_product_timestamp)",
|
|
1451
|
+
nullable: false
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
name: "report_type",
|
|
1455
|
+
description: "preliminary | final | correction; dedup priority preliminary < final < correction",
|
|
1456
|
+
nullable: false
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
name: "settlement_finality",
|
|
1460
|
+
description: "provisional | final | superseded. Kalshi NHIGH/NLOW settlement contractually requires 'final'; 'provisional' values are kept for early-look research only.",
|
|
1461
|
+
nullable: false
|
|
1462
|
+
},
|
|
1463
|
+
{ name: "snowfall_in", description: "units: inches", nullable: true },
|
|
1464
|
+
{ name: "station", description: "ICAO/ASOS station ID", nullable: false },
|
|
1465
|
+
{
|
|
1466
|
+
name: "station_tz",
|
|
1467
|
+
description: "IANA timezone for the station (e.g. America/Chicago for KORD). Required for local-climate-day semantics; see \xA7U.",
|
|
1468
|
+
nullable: false
|
|
1469
|
+
},
|
|
1470
|
+
{
|
|
1471
|
+
name: "temp_max_F",
|
|
1472
|
+
description: "units: fahrenheit \u2014 daily high (uppercase F for consistency with obs imperial mode)",
|
|
1473
|
+
nullable: true
|
|
1474
|
+
},
|
|
1475
|
+
{ name: "temp_min_F", description: "units: fahrenheit \u2014 daily low", nullable: true }
|
|
1476
|
+
]
|
|
1477
|
+
},
|
|
1478
|
+
{
|
|
1479
|
+
id: "schema.observation_ledger.v1",
|
|
1480
|
+
title: "schema.observation_ledger.v1",
|
|
1481
|
+
columnCount: 15,
|
|
1482
|
+
columns: [
|
|
1483
|
+
{ name: "as_of_time", description: "", nullable: true },
|
|
1484
|
+
{ name: "dewpoint_c", description: "units: celsius", nullable: true },
|
|
1485
|
+
{ name: "ingestion_id", description: "", nullable: true },
|
|
1486
|
+
{ name: "observation_kind", description: "", nullable: true },
|
|
1487
|
+
{
|
|
1488
|
+
name: "observation_quality",
|
|
1489
|
+
description: "Lineage row-quality flag per LINEAGE-01; distinct from qc_status enum slot AND distinct from the obs_qc_status bitmask column per QC-05.",
|
|
1490
|
+
nullable: true
|
|
1491
|
+
},
|
|
1492
|
+
{ name: "observation_type", description: "", nullable: false },
|
|
1493
|
+
{ name: "observed_at", description: "", nullable: false },
|
|
1494
|
+
{ name: "parser_name", description: "", nullable: true },
|
|
1495
|
+
{ name: "parser_version", description: "", nullable: true },
|
|
1496
|
+
{ name: "provenance", description: "", nullable: true },
|
|
1497
|
+
{ name: "qc_status", description: "", nullable: true },
|
|
1498
|
+
{
|
|
1499
|
+
name: "source",
|
|
1500
|
+
description: "ncei reserved per D-2.1-09; never written in v0.1.0.",
|
|
1501
|
+
nullable: false
|
|
1502
|
+
},
|
|
1503
|
+
{ name: "source_received_at", description: "", nullable: true },
|
|
1504
|
+
{ name: "station_code", description: "", nullable: false },
|
|
1505
|
+
{ name: "temp_c", description: "units: celsius", nullable: true }
|
|
1506
|
+
]
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
id: "schema.observation_qc.v1",
|
|
1510
|
+
title: "schema.observation_qc.v1",
|
|
1511
|
+
columnCount: 13,
|
|
1512
|
+
columns: [
|
|
1513
|
+
{ name: "as_of_time", description: "", nullable: true },
|
|
1514
|
+
{
|
|
1515
|
+
name: "detector_metadata",
|
|
1516
|
+
description: "JSON-serialized detector payload; shape per qc_system.",
|
|
1517
|
+
nullable: true
|
|
1518
|
+
},
|
|
1519
|
+
{
|
|
1520
|
+
name: "field",
|
|
1521
|
+
description: "Observation column the rule evaluated (e.g. temp_c).",
|
|
1522
|
+
nullable: false
|
|
1523
|
+
},
|
|
1524
|
+
{ name: "flag", description: "", nullable: false },
|
|
1525
|
+
{ name: "ingestion_id", description: "", nullable: true },
|
|
1526
|
+
{ name: "observation_kind", description: "", nullable: true },
|
|
1527
|
+
{ name: "observed_at", description: "", nullable: false },
|
|
1528
|
+
{ name: "parser_name", description: "", nullable: true },
|
|
1529
|
+
{ name: "qc_system", description: "", nullable: false },
|
|
1530
|
+
{ name: "qc_version", description: "", nullable: false },
|
|
1531
|
+
{ name: "rule_id", description: "", nullable: false },
|
|
1532
|
+
{ name: "source", description: "", nullable: false },
|
|
1533
|
+
{ name: "station_code", description: "", nullable: false }
|
|
1534
|
+
]
|
|
1535
|
+
}
|
|
1536
|
+
]);
|
|
1537
|
+
function deepFreezeSchema(info) {
|
|
1538
|
+
const frozenCols = Object.freeze(info.columns.map((c) => Object.freeze({ ...c })));
|
|
1539
|
+
return Object.freeze({ ...info, columns: frozenCols });
|
|
1540
|
+
}
|
|
1541
|
+
var REGISTRY = new Map(
|
|
1542
|
+
BUILT_IN_SCHEMAS.map((info) => [info.id, deepFreezeSchema(info)])
|
|
1543
|
+
);
|
|
1544
|
+
function registerSchema(info) {
|
|
1545
|
+
REGISTRY.set(info.id, deepFreezeSchema(info));
|
|
1546
|
+
}
|
|
1547
|
+
function describe(schemaId) {
|
|
1548
|
+
const info = REGISTRY.get(schemaId);
|
|
1549
|
+
if (info === void 0) {
|
|
1550
|
+
const known = [...REGISTRY.keys()].sort();
|
|
1551
|
+
throw new UnknownSchemaError(
|
|
1552
|
+
`Unknown schemaId ${JSON.stringify(schemaId)}; registered: ${known.length === 0 ? "<none>" : known.join(", ")}`
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
const lines = [`Schema: ${info.id}`, ` Title: ${info.title}`, ` Columns: ${info.columnCount}`];
|
|
1556
|
+
for (const col of info.columns) {
|
|
1557
|
+
const nullable = col.nullable ? "?" : "";
|
|
1558
|
+
const desc = col.description.length === 0 ? "" : ` \u2014 ${col.description}`;
|
|
1559
|
+
lines.push(` - ${col.name}${nullable}${desc}`);
|
|
1560
|
+
}
|
|
1561
|
+
return lines.join("\n");
|
|
1562
|
+
}
|
|
1563
|
+
var UnknownSchemaError = class extends TradewindsError {
|
|
1564
|
+
constructor(message) {
|
|
1565
|
+
super(message);
|
|
1566
|
+
this.name = "UnknownSchemaError";
|
|
1567
|
+
}
|
|
1568
|
+
static defaultErrorCode = "UNKNOWN_SCHEMA";
|
|
1569
|
+
};
|
|
1570
|
+
var FEATURE_NAMES = Object.freeze([
|
|
1571
|
+
"calendarFeatures",
|
|
1572
|
+
"clipOutliers",
|
|
1573
|
+
"diff",
|
|
1574
|
+
"diff2",
|
|
1575
|
+
"heatIndex",
|
|
1576
|
+
"lag",
|
|
1577
|
+
"rolling",
|
|
1578
|
+
"spread",
|
|
1579
|
+
"windChill"
|
|
1580
|
+
]);
|
|
1581
|
+
function featureCatalog() {
|
|
1582
|
+
return FEATURE_NAMES;
|
|
1583
|
+
}
|
|
1584
|
+
function climateGaps(_station, _fromDate, _toDate) {
|
|
1585
|
+
throw new ClimateGapsNotImplementedError(
|
|
1586
|
+
"climateGaps is Python-only in v0.1.0; the TS climate cache lands in v0.2"
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
var ClimateGapsNotImplementedError = class extends TradewindsError {
|
|
1590
|
+
constructor(message) {
|
|
1591
|
+
super(message);
|
|
1592
|
+
this.name = "ClimateGapsNotImplementedError";
|
|
1593
|
+
}
|
|
1594
|
+
static defaultErrorCode = "NOT_IMPLEMENTED";
|
|
1595
|
+
};
|
|
1596
|
+
export {
|
|
1597
|
+
ClimateGapsNotImplementedError,
|
|
1598
|
+
UnknownSchemaError,
|
|
1599
|
+
availability,
|
|
1600
|
+
buildSnapshot,
|
|
1601
|
+
climateGaps,
|
|
1602
|
+
dataVersionForResearch,
|
|
1603
|
+
dataVersionFromComponents,
|
|
1604
|
+
describe,
|
|
1605
|
+
featureCatalog,
|
|
1606
|
+
internationalDailyExtremes,
|
|
1607
|
+
registerSchema
|
|
1608
|
+
};
|
|
1609
|
+
//# sourceMappingURL=index.mjs.map
|