weather-for-grown-ups 0.2.2 → 0.3.0

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.
Files changed (244) hide show
  1. package/README.md +122 -217
  2. package/dist/{cache/file-access-policy.d.ts → access/access-policy.d.ts} +5 -1
  3. package/dist/{cache/file-access-policy.js → access/access-policy.js} +16 -49
  4. package/dist/access/access-policy.js.map +1 -0
  5. package/dist/access/http-fetch.d.ts +13 -0
  6. package/dist/access/http-fetch.js +21 -0
  7. package/dist/access/http-fetch.js.map +1 -0
  8. package/dist/{sources → access}/http-retry.d.ts +9 -0
  9. package/dist/{sources → access}/http-retry.js +38 -0
  10. package/dist/access/http-retry.js.map +1 -0
  11. package/dist/cache/gefs-reforecast-s3-subset-cache.d.ts +30 -0
  12. package/dist/cache/gefs-reforecast-s3-subset-cache.js +182 -0
  13. package/dist/cache/gefs-reforecast-s3-subset-cache.js.map +1 -0
  14. package/dist/cache/gefs-s3-subset-cache.d.ts +1 -1
  15. package/dist/cache/gefs-s3-subset-cache.js +24 -57
  16. package/dist/cache/gefs-s3-subset-cache.js.map +1 -1
  17. package/dist/cache/ifs-open-data-cache.d.ts +1 -1
  18. package/dist/cache/ifs-open-data-cache.js +1 -1
  19. package/dist/cache/ifs-open-data-cache.js.map +1 -1
  20. package/dist/cache/nomads-cache.d.ts +6 -3
  21. package/dist/cache/nomads-cache.js +42 -12
  22. package/dist/cache/nomads-cache.js.map +1 -1
  23. package/dist/cache/s3-subset-cache.d.ts +1 -1
  24. package/dist/cache/s3-subset-cache.js +24 -57
  25. package/dist/cache/s3-subset-cache.js.map +1 -1
  26. package/dist/catalog/gefs-reforecast.d.ts +12 -0
  27. package/dist/catalog/gefs-reforecast.js +49 -0
  28. package/dist/catalog/gefs-reforecast.js.map +1 -0
  29. package/dist/catalog/unified-search.js +43 -4
  30. package/dist/catalog/unified-search.js.map +1 -1
  31. package/dist/cli/program.js +1 -1
  32. package/dist/cli/program.js.map +1 -1
  33. package/dist/cli/unified-atmosphere-command.d.ts +2 -0
  34. package/dist/cli/unified-atmosphere-command.js +78 -38
  35. package/dist/cli/unified-atmosphere-command.js.map +1 -1
  36. package/dist/cli/unified-catalog-command.js +2 -0
  37. package/dist/cli/unified-catalog-command.js.map +1 -1
  38. package/dist/core/archived-gfs-query.d.ts +4 -1
  39. package/dist/core/archived-gfs-query.js +9 -4
  40. package/dist/core/archived-gfs-query.js.map +1 -1
  41. package/dist/core/area-summary.d.ts +2 -1
  42. package/dist/core/area-summary.js +4 -3
  43. package/dist/core/area-summary.js.map +1 -1
  44. package/dist/core/diagnostic-adapters/gefs.d.ts +19 -0
  45. package/dist/core/diagnostic-adapters/gefs.js +102 -0
  46. package/dist/core/diagnostic-adapters/gefs.js.map +1 -0
  47. package/dist/core/diagnostic-adapters/gfs-analysis.d.ts +9 -0
  48. package/dist/core/diagnostic-adapters/gfs-analysis.js +33 -0
  49. package/dist/core/diagnostic-adapters/gfs-analysis.js.map +1 -0
  50. package/dist/core/diagnostic-adapters/gfs.d.ts +15 -0
  51. package/dist/core/diagnostic-adapters/gfs.js +43 -0
  52. package/dist/core/diagnostic-adapters/gfs.js.map +1 -0
  53. package/dist/core/diagnostic-adapters/helpers.d.ts +45 -0
  54. package/dist/core/diagnostic-adapters/helpers.js +45 -0
  55. package/dist/core/diagnostic-adapters/helpers.js.map +1 -0
  56. package/dist/core/diagnostic-adapters/ifs-ens.d.ts +16 -0
  57. package/dist/core/diagnostic-adapters/ifs-ens.js +55 -0
  58. package/dist/core/diagnostic-adapters/ifs-ens.js.map +1 -0
  59. package/dist/core/diagnostic-adapters/ifs.d.ts +9 -0
  60. package/dist/core/diagnostic-adapters/ifs.js +30 -0
  61. package/dist/core/diagnostic-adapters/ifs.js.map +1 -0
  62. package/dist/core/diagnostic-adapters/registry.d.ts +12 -0
  63. package/dist/core/diagnostic-adapters/registry.js +19 -0
  64. package/dist/core/diagnostic-adapters/registry.js.map +1 -0
  65. package/dist/core/diagnostic-adapters/types.d.ts +5 -0
  66. package/dist/core/diagnostic-adapters/types.js +2 -0
  67. package/dist/core/diagnostic-adapters/types.js.map +1 -0
  68. package/dist/core/gefs-reforecast-diagnostics.d.ts +34 -0
  69. package/dist/core/gefs-reforecast-diagnostics.js +306 -0
  70. package/dist/core/gefs-reforecast-diagnostics.js.map +1 -0
  71. package/dist/core/gefs-reforecast-mixed.d.ts +48 -0
  72. package/dist/core/gefs-reforecast-mixed.js +388 -0
  73. package/dist/core/gefs-reforecast-mixed.js.map +1 -0
  74. package/dist/core/gefs-reforecast-points-timeseries.d.ts +13 -0
  75. package/dist/core/gefs-reforecast-points-timeseries.js +206 -0
  76. package/dist/core/gefs-reforecast-points-timeseries.js.map +1 -0
  77. package/dist/core/gefs-reforecast-points.d.ts +16 -0
  78. package/dist/core/gefs-reforecast-points.js +169 -0
  79. package/dist/core/gefs-reforecast-points.js.map +1 -0
  80. package/dist/core/gefs-reforecast-profile.d.ts +17 -0
  81. package/dist/core/gefs-reforecast-profile.js +161 -0
  82. package/dist/core/gefs-reforecast-profile.js.map +1 -0
  83. package/dist/core/gefs-reforecast-timeseries.d.ts +16 -0
  84. package/dist/core/gefs-reforecast-timeseries.js +165 -0
  85. package/dist/core/gefs-reforecast-timeseries.js.map +1 -0
  86. package/dist/core/gefs-reforecast.d.ts +23 -0
  87. package/dist/core/gefs-reforecast.js +92 -0
  88. package/dist/core/gefs-reforecast.js.map +1 -0
  89. package/dist/core/history-area-summary.d.ts +2 -1
  90. package/dist/core/history-area-summary.js +4 -3
  91. package/dist/core/history-area-summary.js.map +1 -1
  92. package/dist/core/history-fields.d.ts +2 -1
  93. package/dist/core/history-fields.js +4 -3
  94. package/dist/core/history-fields.js.map +1 -1
  95. package/dist/core/history-forecast.d.ts +3 -1
  96. package/dist/core/history-forecast.js +5 -3
  97. package/dist/core/history-forecast.js.map +1 -1
  98. package/dist/core/history.d.ts +2 -1
  99. package/dist/core/history.js +4 -3
  100. package/dist/core/history.js.map +1 -1
  101. package/dist/core/ifs-ens-latest-run.js +1 -1
  102. package/dist/core/ifs-ens-latest-run.js.map +1 -1
  103. package/dist/core/ifs-ifs-ens-aligned-run.d.ts +20 -0
  104. package/dist/core/ifs-ifs-ens-aligned-run.js +51 -0
  105. package/dist/core/ifs-ifs-ens-aligned-run.js.map +1 -0
  106. package/dist/core/ifs-ifs-ens-comparison.d.ts +22 -0
  107. package/dist/core/ifs-ifs-ens-comparison.js +172 -0
  108. package/dist/core/ifs-ifs-ens-comparison.js.map +1 -0
  109. package/dist/core/ifs-latest-run.js +1 -1
  110. package/dist/core/ifs-latest-run.js.map +1 -1
  111. package/dist/core/igra-observation.d.ts +2 -1
  112. package/dist/core/igra-observation.js +4 -3
  113. package/dist/core/igra-observation.js.map +1 -1
  114. package/dist/core/profile.d.ts +2 -1
  115. package/dist/core/profile.js +5 -3
  116. package/dist/core/profile.js.map +1 -1
  117. package/dist/core/query-adapters/gefs.d.ts +56 -0
  118. package/dist/core/query-adapters/gefs.js +315 -0
  119. package/dist/core/query-adapters/gefs.js.map +1 -0
  120. package/dist/core/query-adapters/gfs-analysis.d.ts +38 -0
  121. package/dist/core/query-adapters/gfs-analysis.js +152 -0
  122. package/dist/core/query-adapters/gfs-analysis.js.map +1 -0
  123. package/dist/core/query-adapters/gfs.d.ts +39 -0
  124. package/dist/core/query-adapters/gfs.js +141 -0
  125. package/dist/core/query-adapters/gfs.js.map +1 -0
  126. package/dist/core/query-adapters/helpers.d.ts +33 -0
  127. package/dist/core/query-adapters/helpers.js +48 -0
  128. package/dist/core/query-adapters/helpers.js.map +1 -0
  129. package/dist/core/query-adapters/ifs-ens.d.ts +31 -0
  130. package/dist/core/query-adapters/ifs-ens.js +145 -0
  131. package/dist/core/query-adapters/ifs-ens.js.map +1 -0
  132. package/dist/core/query-adapters/ifs.d.ts +29 -0
  133. package/dist/core/query-adapters/ifs.js +119 -0
  134. package/dist/core/query-adapters/ifs.js.map +1 -0
  135. package/dist/core/query-adapters/registry.d.ts +12 -0
  136. package/dist/core/query-adapters/registry.js +19 -0
  137. package/dist/core/query-adapters/registry.js.map +1 -0
  138. package/dist/core/query-adapters/types.d.ts +5 -0
  139. package/dist/core/query-adapters/types.js +2 -0
  140. package/dist/core/query-adapters/types.js.map +1 -0
  141. package/dist/core/specialized-adapters/analogs.d.ts +8 -0
  142. package/dist/core/specialized-adapters/analogs.js +21 -0
  143. package/dist/core/specialized-adapters/analogs.js.map +1 -0
  144. package/dist/core/specialized-adapters/dataset-comparison.d.ts +26 -0
  145. package/dist/core/specialized-adapters/dataset-comparison.js +99 -0
  146. package/dist/core/specialized-adapters/dataset-comparison.js.map +1 -0
  147. package/dist/core/specialized-adapters/registry.d.ts +5 -0
  148. package/dist/core/specialized-adapters/registry.js +36 -0
  149. package/dist/core/specialized-adapters/registry.js.map +1 -0
  150. package/dist/core/specialized-adapters/run-comparison.d.ts +26 -0
  151. package/dist/core/specialized-adapters/run-comparison.js +111 -0
  152. package/dist/core/specialized-adapters/run-comparison.js.map +1 -0
  153. package/dist/core/specialized-adapters/types.d.ts +23 -0
  154. package/dist/core/specialized-adapters/types.js +4 -0
  155. package/dist/core/specialized-adapters/types.js.map +1 -0
  156. package/dist/core/specialized-adapters/verification.d.ts +18 -0
  157. package/dist/core/specialized-adapters/verification.js +91 -0
  158. package/dist/core/specialized-adapters/verification.js.map +1 -0
  159. package/dist/core/transect.d.ts +3 -1
  160. package/dist/core/transect.js +7 -4
  161. package/dist/core/transect.js.map +1 -1
  162. package/dist/core/unified-atmosphere-api.d.ts +3 -144
  163. package/dist/core/unified-atmosphere-api.js +3 -812
  164. package/dist/core/unified-atmosphere-api.js.map +1 -1
  165. package/dist/core/unified-atmosphere-diagnostics.d.ts +10 -0
  166. package/dist/core/unified-atmosphere-diagnostics.js +17 -0
  167. package/dist/core/unified-atmosphere-diagnostics.js.map +1 -0
  168. package/dist/core/unified-atmosphere-query.d.ts +12 -0
  169. package/dist/core/unified-atmosphere-query.js +18 -0
  170. package/dist/core/unified-atmosphere-query.js.map +1 -0
  171. package/dist/core/unified-atmosphere-result.d.ts +2 -0
  172. package/dist/core/unified-atmosphere-result.js +44 -0
  173. package/dist/core/unified-atmosphere-result.js.map +1 -0
  174. package/dist/core/unified-specialized-api.d.ts +21 -28
  175. package/dist/core/unified-specialized-api.js +23 -224
  176. package/dist/core/unified-specialized-api.js.map +1 -1
  177. package/dist/grib/index.d.ts +2 -0
  178. package/dist/grib/index.js +49 -0
  179. package/dist/grib/index.js.map +1 -1
  180. package/dist/mcp-server.js +2 -2
  181. package/dist/mcp-server.js.map +1 -1
  182. package/dist/mcp-unified-tool.js +3 -4
  183. package/dist/mcp-unified-tool.js.map +1 -1
  184. package/dist/schema/gefs-reforecast-diagnostics.d.ts +802 -0
  185. package/dist/schema/gefs-reforecast-diagnostics.js +311 -0
  186. package/dist/schema/gefs-reforecast-diagnostics.js.map +1 -0
  187. package/dist/schema/gefs-reforecast-mixed.d.ts +1033 -0
  188. package/dist/schema/gefs-reforecast-mixed.js +221 -0
  189. package/dist/schema/gefs-reforecast-mixed.js.map +1 -0
  190. package/dist/schema/gefs-reforecast.d.ts +1298 -0
  191. package/dist/schema/gefs-reforecast.js +605 -0
  192. package/dist/schema/gefs-reforecast.js.map +1 -0
  193. package/dist/schema/ifs-ifs-ens-comparison.d.ts +253 -0
  194. package/dist/schema/ifs-ifs-ens-comparison.js +110 -0
  195. package/dist/schema/ifs-ifs-ens-comparison.js.map +1 -0
  196. package/dist/schema/transect-result.d.ts +298 -1
  197. package/dist/schema/transect-result.js +25 -5
  198. package/dist/schema/transect-result.js.map +1 -1
  199. package/dist/schema/transect.d.ts +69 -3
  200. package/dist/schema/transect.js +21 -3
  201. package/dist/schema/transect.js.map +1 -1
  202. package/dist/schema/unified-api.d.ts +14 -1
  203. package/dist/schema/unified-api.js +92 -0
  204. package/dist/schema/unified-api.js.map +1 -1
  205. package/dist/schema/unified-catalog.d.ts +8 -0
  206. package/dist/schema/unified-catalog.js +9 -0
  207. package/dist/schema/unified-catalog.js.map +1 -1
  208. package/dist/schema/unified-specialized.d.ts +179 -0
  209. package/dist/schema/unified-specialized.js +39 -0
  210. package/dist/schema/unified-specialized.js.map +1 -1
  211. package/dist/sources/gefs-reforecast-s3.d.ts +23 -0
  212. package/dist/sources/gefs-reforecast-s3.js +138 -0
  213. package/dist/sources/gefs-reforecast-s3.js.map +1 -0
  214. package/dist/{cache → sources}/ifs-open-data-access-policy.d.ts +2 -2
  215. package/dist/{cache → sources}/ifs-open-data-access-policy.js +2 -2
  216. package/dist/sources/ifs-open-data-access-policy.js.map +1 -0
  217. package/dist/sources/ifs-open-data.js +6 -12
  218. package/dist/sources/ifs-open-data.js.map +1 -1
  219. package/dist/sources/ncei-gfs-forecast-history.d.ts +1 -1
  220. package/dist/sources/ncei-gfs-forecast-history.js +40 -50
  221. package/dist/sources/ncei-gfs-forecast-history.js.map +1 -1
  222. package/dist/sources/ncei-gfs-history.d.ts +1 -1
  223. package/dist/sources/ncei-gfs-history.js +40 -50
  224. package/dist/sources/ncei-gfs-history.js.map +1 -1
  225. package/dist/sources/ncei-igra.d.ts +11 -3
  226. package/dist/sources/ncei-igra.js +65 -77
  227. package/dist/sources/ncei-igra.js.map +1 -1
  228. package/dist/sources/rda-gfs-forecast-history.d.ts +1 -1
  229. package/dist/sources/rda-gfs-forecast-history.js +64 -85
  230. package/dist/sources/rda-gfs-forecast-history.js.map +1 -1
  231. package/docs/ARCHITECTURE.md +18 -0
  232. package/docs/GEFS_ENSEMBLE.md +89 -0
  233. package/docs/IFS_IFS_ENS_COMPARISON.md +71 -0
  234. package/docs/INSTALL.md +6 -12
  235. package/docs/README.md +56 -35
  236. package/docs/RELEASES.md +64 -0
  237. package/docs/UNIFIED_API.md +53 -7
  238. package/package.json +4 -3
  239. package/dist/cache/file-access-policy.js.map +0 -1
  240. package/dist/cache/file-rate-limiter.d.ts +0 -7
  241. package/dist/cache/file-rate-limiter.js +0 -17
  242. package/dist/cache/file-rate-limiter.js.map +0 -1
  243. package/dist/cache/ifs-open-data-access-policy.js.map +0 -1
  244. package/dist/sources/http-retry.js.map +0 -1
package/README.md CHANGED
@@ -1,288 +1,193 @@
1
1
  # Weather for Grown Ups
2
2
 
3
- **Everyone builds a weather tool for their first agent. This one grew up.**
3
+ **Weather is the hello-world of agent tools. This is the version for when “temperature tomorrow” stops being enough.**
4
4
 
5
- Weather is the canonical agent tutorial: give the model a tool, ask for tomorrow's forecast, celebrate when it tells you to bring an umbrella.
5
+ Weather for Grown Ups (WFG) gives agents direct, structured access to numerical weather prediction: NOAA **GFS**, **GEFS** and historical GFS data, plus ECMWF **IFS** and **IFS ENS**.
6
6
 
7
- **Weather for Grown Ups (WFG)** goes a bit further. It gives agents direct access to NOAA's GFS/GEFS and ECMWF's deterministic IFS / IFS ENS numerical weather models — pressure profiles, mixed fields, ensemble distributions, diagnostics, parcel physics, time series, transects, area statistics, run comparisons, and more.
7
+ The central idea is deliberately simple:
8
8
 
9
- One TypeScript core powers equal CLI and MCP surfaces. The public contract is deliberately model-neutral: **one query language over weather datasets**, with model-specific cadence, provenance and deterministic/ensemble semantics preserved behind it. No need to teach your agent how to wrestle GRIB files first.
9
+ > **One query language over weather datasets. Native model semantics stay intact.**
10
10
 
11
- ## Quick start
11
+ Ask for a point, a time range, several locations, a transect, an area, a pressure profile or a meteorological diagnostic. Change the dataset without learning another API. WFG handles source selection, GRIB message access, decoding, caching and shared physics; the result keeps the model's real cadence, grid, run, member and provenance semantics.
12
12
 
13
- Node.js 20+ is enough. The npm package includes its GRIB2 decoder; native `wgrib2` is optional.
13
+ No weather API key. No model-specific public namespaces. No need to teach the agent GRIB first.
14
+
15
+ ## 30-second start
16
+
17
+ Node.js 20+ is enough. The npm package includes its GRIB2 decoder.
14
18
 
15
19
  ```bash
16
20
  npx weather-for-grown-ups --help
17
- npx weather-for-grown-ups catalog --dataset gefs --search wind --json
21
+ npx weather-for-grown-ups catalog --dataset all --search wind --json
18
22
  ```
19
23
 
20
- Start either MCP transport from the same package:
24
+ Run the same core as MCP:
21
25
 
22
26
  ```bash
23
27
  npx weather-for-grown-ups mcp
28
+ # or
24
29
  npx weather-for-grown-ups mcp-http
25
30
  ```
26
31
 
27
- See [installation and deployment](docs/INSTALL.md) for global npm, Docker, stdio MCP, Streamable HTTP MCP, and hosting details.
28
-
29
- ## What can an agent do with it?
30
-
31
- WFG is deliberately a **tool**, not a forecast persona. It returns structured model data, diagnostics, provenance, and explicit ensemble semantics. The consuming agent decides what those data mean for the user's question.
32
+ For global npm, Docker and hosted MCP setup, see [Installation](docs/INSTALL.md).
32
33
 
33
- The examples below are illustrative agent interpretations, not live forecasts and not literal WFG output.
34
+ ## The query model
34
35
 
35
- ### Scientific meteorology — Prague, Czechia
36
+ Normal atmospheric access is expressed as four orthogonal choices:
36
37
 
37
- **You ask**
38
-
39
- > A cold front is forecast to cross Prague tomorrow. How does the vertical structure of the atmosphere change as it passes, and how confident is the ensemble about the timing?
38
+ ```text
39
+ dataset × geometry × time × selection
40
+ ```
40
41
 
41
- **The agent might use WFG**
42
+ For example:
43
+
44
+ ```json
45
+ {
46
+ "dataset": "gefs",
47
+ "geometry": {
48
+ "type": "point",
49
+ "latitude": 50.08,
50
+ "longitude": 14.43
51
+ },
52
+ "time": {
53
+ "at": "2026-08-30T12:00:00Z"
54
+ },
55
+ "selection": {
56
+ "variables": ["temperature", "wind"],
57
+ "pressureLevelsHpa": [850, 700, 500],
58
+ "fields": ["temperature_2m", "wind_10m"]
59
+ },
60
+ "ensemble": {
61
+ "quantiles": [0.1, 0.5, 0.9]
62
+ }
63
+ }
64
+ ```
42
65
 
43
- `diagnose_atmosphere` on `gfs` → `diagnose_atmosphere` on `gefs` → `compare_runs` on `gefs`
66
+ The same vocabulary is used for deterministic forecasts, ensembles, archived forecasts and historical analyses. Capability differences are explicit: unsupported combinations fail rather than being coerced into fake symmetry.
44
67
 
45
- **Example agent interpretation**
68
+ The full contract lives in [Unified atmospheric API](docs/UNIFIED_API.md).
46
69
 
47
- > The frontal signal is coherent through the lower and middle troposphere rather than being just a surface temperature change. The pre-frontal inversion erodes, the 850–700 hPa layer cools, and 850–500 hPa shear strengthens as the new air mass arrives. Most GEFS members show the same transition, but they spread it across several native forecast steps. Confidence is therefore higher in the air-mass change itself than in the exact hour it reaches Prague.
70
+ ## What can an agent ask?
48
71
 
49
- ### Aviation / paragliding — Bassano del Grappa, Italy
72
+ WFG is intentionally a **tool**, not a forecast persona. It supplies atmospheric evidence; the consuming agent supplies interpretation.
50
73
 
51
- **You ask**
74
+ ### Synoptic and profile meteorology
52
75
 
53
- > I'm considering an XC flight from Bassano del Grappa tomorrow. What does the atmospheric profile say about the usable convective window, and what is most likely to shut it down?
76
+ > A cold front is crossing Prague. How does the vertical structure change, and how confident is the ensemble about the timing?
54
77
 
55
- **The agent might use WFG**
78
+ An agent can combine GFS/GEFS pressure profiles, inversion and lapse-rate diagnostics, ensemble distributions and run-to-run changes. The useful answer is no longer just a surface temperature—it can reason about the structure and timing of the air-mass transition.
56
79
 
57
- `diagnose_atmosphere` on `gfs` → `diagnose_atmosphere` on `gefs` → `query_atmosphere` time series on `gefs`
80
+ ### Aviation and paragliding
58
81
 
59
- **Example agent interpretation**
82
+ > What does the Bassano profile say about tomorrow's usable convective window, and what is most likely to shut it down?
60
83
 
61
- > The profile becomes progressively more convective through late morning as inhibition weakens and the usable mixed layer deepens. The later constraint is not simply a lack of heating: winds through the lower and middle troposphere strengthen and shear increases into the afternoon. The ensemble agrees more strongly on that wind increase than on the exact instability magnitude, so the main robust signal is a narrowing window before the stronger flow arrives.
84
+ An agent can inspect parcel diagnostics, CAPE/CIN, freezing levels, wind shear and the member-by-member GEFS distribution through time. WFG deliberately does not turn those into a “go flying” score; activity-specific judgment stays outside the core.
62
85
 
63
86
  *Slightly more information than “Bassano: 21 °C, sunny.”*
64
87
 
65
- ### Mountaineering — Grossglockner, Austria
66
-
67
- **You ask**
68
-
69
- > I'm considering a Grossglockner summit attempt tomorrow morning. What conditions should I expect near summit altitude, and when does the weather start deteriorating?
70
-
71
- **The agent might use WFG**
72
-
73
- `query_atmosphere` on `gfs` → `query_atmosphere` on `gefs` → `diagnose_atmosphere` on `gefs`
74
-
75
- **Example agent interpretation**
76
-
77
- > The valley-level forecast understates the change aloft. Near the pressure levels representative of the upper mountain, temperatures remain well below freezing while winds strengthen through the morning. Moisture also increases higher in the column later in the period, raising the risk of cloud around the high terrain. Ensemble agreement is tighter on the wind increase than on the moisture timing, making stronger summit-level flow the more robust deterioration signal.
88
+ ### Wind energy
78
89
 
79
- WFG supplies atmospheric model evidence; a consuming agent should still treat mountaineering decisions as safety-critical and use appropriate local forecasts and observations.
90
+ > Is tomorrow afternoon's wind ramp near Esbjerg a local grid-point feature or a regional change, and how uncertain is the timing?
80
91
 
81
- ### Wind energy — Esbjerg, Denmark
92
+ An agent can combine multi-point queries, transects, area statistics and ensemble evolution to distinguish a spatially coherent flow change from a local signal.
82
93
 
83
- **You ask**
94
+ ### Forecast verification
84
95
 
85
- > A wind farm near Esbjerg expects a production ramp tomorrow afternoon. What is driving the change, how spatially uniform is it, and how confident is the forecast?
96
+ > What did the 48-hour GFS forecast predict for this event, and how wrong was it?
86
97
 
87
- **The agent might use WFG**
98
+ Old GFS initializations stay `dataset: "gfs"` and route to the matching archive. WFG can compare them with later GFS analysis or NOAA IGRA radiosondes, and can summarize bias, MAE and RMSE over bounded samples.
88
99
 
89
- `query_atmosphere` with GEFS points/time range → `query_atmosphere` with a GEFS transect → `compare_runs`
100
+ ## Datasets
90
101
 
91
- **Example agent interpretation**
102
+ | Public dataset | Meaning | Key semantics |
103
+ | --- | --- | --- |
104
+ | `gfs` | NOAA deterministic GFS | 0.25° default / 0.5° optional; operational data and archived forecasts share one public identity |
105
+ | `gefs` | NOAA GEFS | member-first operational ensemble; explicit `forecast.kind: "reforecast"` selects the GEFSv12 retrospective population |
106
+ | `ifs` | ECMWF deterministic IFS | 0.25° Open Data forecast with native ECMWF run/cadence semantics |
107
+ | `ifs-ens` | ECMWF IFS ENS | 50 perturbations `p01`–`p50`; deterministic IFS is the Cycle-50r1 unperturbed control |
108
+ | `gfs-analysis` | historical GFS Grid 4 analysis | deterministic analyzed state with analysis-time rather than forecast-lead semantics |
92
109
 
93
- > The ramp is associated with a broad strengthening of the low-level flow rather than one isolated grid point. Coastal samples strengthen first and most strongly, while points farther inland lag. Winds aloft increase at the same time, supporting a synoptic-scale interpretation rather than purely local mixing. GEFS spread is larger around the timing of the ramp than around the direction of change, so the bigger uncertainty is *when* the increase arrives rather than *whether* the regional flow strengthens.
110
+ NOAA IGRA is available as a **verification reference**, not as a fake gridded model dataset.
94
111
 
95
- WFG does not contain turbine power curves, wake models, availability assumptions, or a generation model. Converting atmospheric conditions into expected MW belongs to the consuming application.
112
+ For source inventories, cadence, member sets and archive details, start with [the documentation index](docs/README.md).
96
113
 
97
- ## Current model support
114
+ ## One core, two equal surfaces
98
115
 
99
- WFG exposes deterministic **GFS 0.25° and 0.5°** (`0p25` default), member-first **GEFS**, deterministic **ECMWF IFS 0.25°**, and **ECMWF IFS ENS 0.25°**. Deterministic IFS has the broad spatiotemporal/diagnostic surface; IFS ENS supports member-first point and multi-point distributions, native-cadence point/multi-point time series, member-first diagnostics, and native-cadence diagnostic time series across all 50 perturbations.
116
+ CLI and MCP are adapters over the same schemas and application services. Surface equivalence is tested explicitly.
100
117
 
101
- | Operation | GFS 0.25° / 0.5° | GEFS | IFS 0.25° | IFS ENS 0.25° |
102
- | --- | --- | --- | --- | --- |
103
- | Catalog and search | ✅ | ✅ | ✅ | ✅ |
104
- | Pressure profiles | ✅ deterministic | ✅ member distributions | ✅ deterministic | ✅ member distributions |
105
- | Mixed pressure/non-isobaric fields | ✅ | ✅ member-first bundles | ✅ point + instant | ✅ member-first point + multi-point bundles |
106
- | Raw and mixed-field time series | ✅ | ✅ | ✅ deterministic | ✅ member distributions |
107
- | Layer diagnostics | ✅ | ✅ per member → summarized | ✅ deterministic | ✅ per perturbation → summarized |
108
- | Whole-profile diagnostics | ✅ | ✅ per member → structural summaries | ✅ deterministic | ✅ structural member summaries |
109
- | Parcel / LCL / LFC / EL / CAPE / CIN | ✅ | ✅ per member → summarized | ✅ deterministic | ✅ per perturbation → summarized |
110
- | Diagnostic time series | ✅ layer/profile/parcel | ✅ layer/profile/parcel | ✅ layer/profile/parcel | ✅ compact member-first |
111
- | Multi-point queries | ✅ | ✅ | ✅ | ✅ member distributions |
112
- | Multi-point time series | ✅ | ✅ | ✅ | ✅ native 3h/6h cadence |
113
- | Transects | ✅ deterministic | ✅ ensemble-native mixed fields | ✅ deterministic mixed fields | ✅ member-first mixed fields |
114
- | Area statistics | ✅ deterministic | ✅ member-first spatial statistics | ✅ deterministic raw scalar | ✅ member-first spatial statistics |
115
- | Run-to-run comparison | ✅ deterministic deltas | ✅ distribution shifts | ✅ deterministic deltas | ✅ distribution shifts, 6h/12h stride |
116
- | Scalar ensemble distribution | — | ✅ | — | ✅ 50 perturbations |
117
- | Aligned GFS-vs-GEFS comparison | ✅ | ✅ | — | — |
118
- | Aligned GFS-vs-IFS comparison | ✅ deterministic deltas | — | ✅ deterministic deltas | — |
119
- | Aligned GEFS-vs-IFS ENS comparison | — | ✅ distribution shifts | — | ✅ distribution shifts |
118
+ | Operation | CLI | MCP |
119
+ | --- | --- | --- |
120
+ | Discover capabilities | `catalog` | `search_catalog` |
121
+ | Query atmospheric state | `query` | `query_atmosphere` |
122
+ | Derive meteorology | `diagnose` | `diagnose_atmosphere` |
123
+ | Compare forecast cycles | `compare-runs` | `compare_runs` |
124
+ | Compare datasets | `compare-datasets` | `compare_datasets` |
125
+ | Verify forecasts | `verify` | `verify_forecast` |
126
+ | Search historical analogs | `analogs` | `find_analogs` |
120
127
 
121
- GEFS also supports control `c00` plus perturbed members `p01`–`p30`, native three-hour output through `f384`, mixed pressure/non-isobaric field bundles, and opt-in member payloads for auditability. Field-only requests automatically use NOAA's `pgrb2s` 0.25° selected-field product through `f240`; pressure-level and mixed requests use `pgrb2a` 0.5°, and field ranges extending beyond `f240` stay on 0.5° for the whole range. Result provenance reports the actual product and horizontal grid.
128
+ Administrative index build/backfill remains CLI-only on purpose; it is not part of the normal weather-query surface.
122
129
 
123
- ECMWF **IFS 0.25°** is exposed as `ifs`; **IFS ENS 0.25°** is `ifs-ens`. Both use official Open Data indexed byte-range access with bounded mirror retry/failover. Under Cycle 50r1 the products deliberately have different horizons: deterministic `oper/fc` runs to `f240` at 00/12Z and `f90` at 06/18Z, while perturbed `enfo/ef` runs to `f360` and `f144` respectively. `ifs-ens` represents the 50 perturbed members `p01`–`p50`; the unperturbed control is now identical to deterministic `ifs`, so WFG does not invent a 51st ENS member. See [IFS access](docs/IFS.md).
130
+ ## What the engine covers
124
131
 
125
- Historical **GFS Grid 4 0.5° analysis** is exposed as `gfs-analysis`, through the same `query` / `diagnose` CLI operations and `query_atmosphere` / `diagnose_atmosphere` MCP tools. It covers profiles, time series, diagnostics, parcels, multi-point queries, multi-point time series, transects and native bbox area statistics while preserving analysis-time semantics and NCEI provenance.
132
+ The common language composes a fairly broad atmospheric surface:
126
133
 
127
- Historical **GFS forecasts** do not add another public dataset. Keep `dataset: "gfs"`, provide an explicit old forecast run, and select `forecast.grid` when needed. `0p25` routes to the NCAR/GDEX d084001 0.25° archive (`gfs_0p25_forecast_archive`), available from 2015-01-15 with native 3-hour output through +240 h and 12-hour output from +252 h through +384 h. `0p50` routes to NOAA NCEI Grid 4 (`gfs_grid4_forecast_0p5_archive`), beginning 2006-10-10 with native 3-hour output through +192 h. Direct online archive availability can vary. Both paths preserve run, valid time, lead, grid and archive provenance across unified state queries and layer/profile/parcel diagnostics.
134
+ - pressure profiles and mixed pressure/non-isobaric fields;
135
+ - native-cadence time ranges;
136
+ - multi-point queries and multi-point ranges;
137
+ - great-circle transects;
138
+ - bounded area statistics;
139
+ - layer diagnostics such as lapse rate, wind shear and potential-temperature gradient;
140
+ - whole-profile freezing-level and inversion diagnostics;
141
+ - parcel/LCL/LFC/EL/CAPE/CIN diagnostics where the source fields support them;
142
+ - deterministic run deltas and ensemble distribution shifts;
143
+ - aligned GFS↔GEFS, GFS↔IFS, GEFS↔IFS ENS and IFS↔IFS ENS comparisons;
144
+ - historical analog search and archived-forecast verification.
128
145
 
129
- Forecast verification can use either the later `gfs-analysis` model state or a real **NOAA IGRA v2.2 radiosonde** reference. IGRA remains a verification reference rather than a public gridded atmospheric dataset: WFG selects a nearby or explicit sounding station, samples the archived GFS forecast at the sounding location, and compares only pressure levels that were actually observed. No vertical interpolation is hidden inside the verification result.
146
+ Not every dataset implements every line. `catalog` / `search_catalog` is the source of truth for what a particular dataset and forecast population supports.
130
147
 
131
- The same `verify_forecast` operation also supports **bounded forecast-skill summaries** against either reference. Use a time range plus up to three lead times; WFG samples at most eight nominal verification times (24 forecast evaluations maximum), keeps failed cases explicit, and returns count, signed bias, MAE and RMSE by lead × pressure × field. `gfs-analysis` summaries remain same-grid 0.5° analysis-minus-forecast comparisons; IGRA summaries remain observation-minus-forecast and report the radiosonde stations used. Wind-direction errors use shortest signed angular differences.
148
+ ## Semantics are part of the API
132
149
 
133
- For larger verification studies, `wfg index verification-backfill` materializes atomic cases into a local JSONL corpus and `wfg index verification-summary` aggregates multi-year or month-filtered seasonal skill with **zero NOAA requests**. Coverage is reported explicitly, so partially materialized periods stay distinguishable from complete samples. This remains CLI-side administration; it does not expand the seven-tool MCP surface.
150
+ WFG is opinionated about a few things that are easy to get subtly wrong:
134
151
 
135
- ## The design rule
152
+ - **Ensemble physics is member-first.** Nonlinear diagnostics are computed inside each GEFS/IFS ENS member before aggregation.
153
+ - **Spread is not calibrated uncertainty.** Member fractions and ensemble spread are reported as raw model evidence unless a dedicated calibrated layer says otherwise.
154
+ - **History is not relabeled as “current”.** Archived GFS forecasts retain their old initialization and lead; GFS analysis retains analysis-time semantics; GEFSv12 reforecasts are explicitly retrospective forecasts, not archived operational GEFS.
155
+ - **Provenance stays visible.** Results keep run, valid time, sampled grid, source product and archive/backend information.
156
+ - **Unsupported means unsupported.** The engine fails at capability boundaries instead of silently substituting another model, grid or physical meaning.
136
157
 
137
- > **Unify operations and physics; preserve model semantics.**
158
+ The deeper reasoning is documented in [Architecture](docs/ARCHITECTURE.md).
138
159
 
139
- The same physical kernels are reused where that is scientifically valid. But deterministic GFS values are not forced into ensemble-shaped objects, and GEFS member distributions are not flattened into fake confidence scores.
160
+ ## Data access without the plumbing leaking into the query
140
161
 
141
- In particular:
162
+ WFG selects only the upstream messages needed for a request, caches immutable slices and decodes locally.
142
163
 
143
- - nonlinear GEFS diagnostics are calculated **member by member before aggregation**;
144
- - member fractions and spread are labeled as raw ensemble evidence, not calibrated probability or uncertainty;
145
- - pressure levels, temporal semantics, valid times, cycles, sampling and provenance stay explicit;
146
- - unsupported combinations fail explicitly rather than being guessed;
147
- - WFG does not own activity-specific scores or safety judgments.
164
+ Operational point/profile access favors indexed byte-range reads from NOAA AWS Open Data and ECMWF Open Data. Bounded GFS areas use NOMADS geographic subsetting where that is materially better. Historical products route to NCEI or NCAR/GDEX as appropriate.
148
165
 
149
- The [architecture guide](docs/ARCHITECTURE.md) goes deeper into the shared core, model adapters, member-first computation, source strategy, caching, and public surfaces.
166
+ Provider etiquette is also source-specific: NOMADS retains its courtesy pacing, while AWS, ECMWF, NCEI, GDEX and IGRA use independent bounded-concurrency policies with transient retry/backoff. A slow provider does not impose its policy on unrelated sources.
150
167
 
151
- ## Public API
152
-
153
- WFG exposes the dataset-oriented query vocabulary documented in [UNIFIED_API.md](docs/UNIFIED_API.md).
154
-
155
- Normal atmospheric access is expressed as:
156
-
157
- ```text
158
- dataset × geometry × time × selection
159
- ```
160
-
161
- with short dataset IDs `gfs`, `gefs`, `ifs`, `ifs-ens`, and `gfs-analysis`. The same query structure therefore works for a deterministic forecast, an ensemble forecast, an archived GFS forecast, or an archived analysis while each result preserves its native deterministic/ensemble and forecast/analysis semantics. An archived forecast remains `gfs`; the explicit old `forecast.run` selects the historical state.
162
-
163
- The MCP vocabulary is intentionally small:
164
-
165
- - `search_catalog`
166
- - `query_atmosphere`
167
- - `diagnose_atmosphere`
168
- - `compare_runs`
169
- - `compare_datasets`
170
- - `verify_forecast`
171
- - `find_analogs`
172
-
173
- The CLI mirrors the same concepts with `catalog --dataset ...`, `query`, `diagnose`, `compare-runs`, `compare-datasets`, `verify`, and `analogs`.
174
-
175
- ## CLI
176
-
177
- The CLI mirrors the same operation vocabulary. Dataset choice is always explicit through `--dataset`.
178
-
179
- A deterministic GFS profile:
180
-
181
- ```bash
182
- wfg query \
183
- --dataset gfs \
184
- --lat 50.08 --lon 14.43 \
185
- --at 2026-08-24T12:00:00Z \
186
- --vars temperature,relative_humidity,wind \
187
- --levels 1000,925,850,700,500 \
188
- --json
189
- ```
190
-
191
- An old forecast keeps `gfs` and selects the historical initialization:
192
-
193
- ```bash
194
- wfg query \
195
- --dataset gfs \
196
- --grid 0p25 \
197
- --run 2019-12-24T12:00:00Z \
198
- --lat 50.08 --lon 14.43 \
199
- --at 2019-12-26T18:00:00Z \
200
- --vars temperature,relative_humidity,wind \
201
- --levels 1000,925,850,700,500 \
202
- --json
203
- ```
204
-
205
- The same query against historical analysis only changes the dataset and time:
206
-
207
- ```bash
208
- wfg query \
209
- --dataset gfs-analysis \
210
- --lat 50.08 --lon 14.43 \
211
- --at 2019-12-26T18:00:00Z \
212
- --vars temperature,relative_humidity,wind \
213
- --levels 1000,925,850,700,500 \
214
- --json
215
- ```
216
-
217
- An ensemble parcel diagnostic:
218
-
219
- ```bash
220
- wfg diagnose \
221
- --dataset gefs \
222
- --lat 45.80 --lon 11.77 \
223
- --at 2026-08-24T12:00:00Z \
224
- --kind parcel \
225
- --parcel surface_2m \
226
- --levels 1000,925,850,700,500,250,200 \
227
- --quantiles 0.1,0.5,0.9 \
228
- --json
229
- ```
230
-
231
- Local analog-index maintenance is intentionally separate from weather queries:
232
-
233
- ```bash
234
- wfg index build --dataset gfs-analysis ...
235
- wfg index backfill --dataset gfs-analysis ...
236
- ```
237
-
238
- Run `wfg --help` or `npx weather-for-grown-ups --help` for the complete command surface.
239
-
240
- ## MCP
241
-
242
- MCP exposes exactly seven atmospheric tools:
243
-
244
- - `search_catalog`
245
- - `query_atmosphere`
246
- - `diagnose_atmosphere`
247
- - `compare_runs`
248
- - `compare_datasets`
249
- - `verify_forecast`
250
- - `find_analogs`
251
-
252
- Both transports expose exactly the same tool set:
253
-
254
- - **stdio** — `npx weather-for-grown-ups mcp`
255
- - **Streamable HTTP** — `npx weather-for-grown-ups mcp-http`
256
-
257
- The HTTP server defaults to `127.0.0.1:3000`, serves MCP at `/mcp`, and exposes `/healthz`. Read [INSTALL.md](docs/INSTALL.md) before exposing it remotely.
258
-
259
- ## Data access
260
-
261
- WFG selects only the GRIB messages needed for a query, caches immutable upstream slices, decodes locally, and performs physical transforms and aggregation in the TypeScript core.
262
-
263
- - GFS source routing is automatic by default: point/profile, time-series, multi-point, transect, and run-comparison access uses NOAA AWS Open Data selected-message byte ranges; bounded area queries use NOAA NOMADS geographic subsetting. `--source` remains an explicit override where that geometry supports it, and result provenance reports the resolved backend.
264
- - GEFS uses NOAA AWS Open Data `.idx` inventories and byte-range access per member. Pressure/mixed selections use `pgrb2a` 0.5°; eligible field-only selections use `pgrb2s` 0.25° through `f240`.
265
- - The npm package ships with a GRIB2 decoder; native `wgrib2` remains an optional compatibility/debug path.
266
- - Historical GFS analysis uses NOAA NCEI THREDDS/NCSS: grid-as-point for point/profile operations and native bbox/grid subsets for area statistics.
267
- - Historical GFS forecasts keep the public `gfs` identity and route by grid: 0.25° uses NCAR/GDEX d084001 through THREDDS/NCSS, while 0.5° uses NOAA NCEI Grid 4 through THREDDS/NCSS. Direct online availability varies; older Grid 4 files may require NCEI HAS retrieval.
268
- - Upstream etiquette is provider-specific: NOMADS keeps an 11-second cross-process courtesy interval, NCEI THREDDS/NCSS is bounded to 2 concurrent requests, NCAR/GDEX to 4, and IGRA downloads to 4. NOAA AWS and ECMWF cloud/direct access are bounded separately without inheriting the NOMADS delay. Transient 429/5xx responses use exponential backoff with jitter and honor `Retry-After`.
168
+ Those are implementation and provenance concerns—not new public query dimensions.
269
169
 
270
170
  ## Documentation
271
171
 
272
- The repository root intentionally stays small. Detailed documentation lives in [`docs/`](docs/README.md).
172
+ The root README is the product overview. Detailed reference material lives under [`docs/`](docs/README.md).
273
173
 
274
- Start with:
174
+ Start here:
275
175
 
276
- - [Installation and distribution](docs/INSTALL.md)
176
+ - [Installation and deployment](docs/INSTALL.md)
177
+ - [Unified atmospheric API](docs/UNIFIED_API.md)
277
178
  - [Architecture](docs/ARCHITECTURE.md)
278
- - [GEFS ensemble access](docs/GEFS_ENSEMBLE.md)
279
- - [ECMWF IFS and ENS access](docs/IFS.md)
280
- - [Catalog search](docs/CATALOG_SEARCH.md)
281
- - [Testing](docs/TESTING.md)
282
- - [Meteorology validation](docs/METEOROLOGY_VALIDATION.md)
179
+ - [GEFS and GEFSv12 reforecast semantics](docs/GEFS_ENSEMBLE.md)
180
+ - [ECMWF IFS / IFS ENS semantics](docs/IFS.md)
181
+ - [Historical GFS, archives and verification](docs/HISTORY.md)
182
+ - [Testing](docs/TESTING.md) and [meteorology validation](docs/METEOROLOGY_VALIDATION.md)
183
+ - [Release notes](docs/RELEASES.md)
184
+
185
+ ## Scope
186
+
187
+ WFG exposes numerical-model evidence and meteorological diagnostics. It does **not** own activity-specific scores, turbine power curves, route decisions, flight/summit safety judgments or calibrated probabilities unless such a layer is explicitly designed and validated.
283
188
 
284
- Feature-specific implementation and semantics notes are indexed in [docs/README.md](docs/README.md).
189
+ That separation is intentional: the tool should be reusable by many agents and applications without smuggling one application's judgment into the weather core.
285
190
 
286
191
  ## License
287
192
 
288
- MIT. NOAA model data and separately distributed decoder components retain their own upstream terms and licenses; see [installation and distribution](docs/INSTALL.md) for packaging details.
193
+ MIT. See [LICENSE](LICENSE).
@@ -8,6 +8,11 @@ export interface UpstreamAccessPolicyDefinition {
8
8
  minIntervalMs: number;
9
9
  staleLockMs?: number;
10
10
  }
11
+ /**
12
+ * Provider etiquette and concurrency limits live here, independently of cache
13
+ * implementation. A source/cache client may reuse these policies, but it must
14
+ * not invent provider pacing of its own.
15
+ */
11
16
  export declare const UPSTREAM_ACCESS_POLICIES: {
12
17
  readonly nomads: {
13
18
  readonly id: "nomads";
@@ -58,4 +63,3 @@ export declare class FileAccessPolicy implements UpstreamAccessPolicy {
58
63
  private statePath;
59
64
  private readState;
60
65
  }
61
- export declare function withLegacyCooldown(definition: UpstreamAccessPolicyDefinition, cooldownMs: number | undefined): UpstreamAccessPolicyDefinition;
@@ -3,42 +3,19 @@ import { join } from "node:path";
3
3
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4
4
  export const DEFAULT_ACCESS_POLICY_STALE_LOCK_MS = 120_000;
5
5
  const DEFAULT_POLL_MS = 100;
6
+ /**
7
+ * Provider etiquette and concurrency limits live here, independently of cache
8
+ * implementation. A source/cache client may reuse these policies, but it must
9
+ * not invent provider pacing of its own.
10
+ */
6
11
  export const UPSTREAM_ACCESS_POLICIES = {
7
- nomads: {
8
- id: "nomads",
9
- maxConcurrency: 1,
10
- minIntervalMs: 11_000,
11
- },
12
- noaaAws: {
13
- id: "noaa-aws",
14
- maxConcurrency: 8,
15
- minIntervalMs: 0,
16
- },
17
- ecmwfCloud: {
18
- id: "ecmwf-cloud",
19
- maxConcurrency: 8,
20
- minIntervalMs: 0,
21
- },
22
- ecmwfDirect: {
23
- id: "ecmwf-direct",
24
- maxConcurrency: 4,
25
- minIntervalMs: 0,
26
- },
27
- nceiThredds: {
28
- id: "ncei-thredds",
29
- maxConcurrency: 2,
30
- minIntervalMs: 0,
31
- },
32
- gdex: {
33
- id: "gdex",
34
- maxConcurrency: 4,
35
- minIntervalMs: 0,
36
- },
37
- nceiIgra: {
38
- id: "ncei-igra",
39
- maxConcurrency: 4,
40
- minIntervalMs: 0,
41
- },
12
+ nomads: { id: "nomads", maxConcurrency: 1, minIntervalMs: 11_000 },
13
+ noaaAws: { id: "noaa-aws", maxConcurrency: 8, minIntervalMs: 0 },
14
+ ecmwfCloud: { id: "ecmwf-cloud", maxConcurrency: 8, minIntervalMs: 0 },
15
+ ecmwfDirect: { id: "ecmwf-direct", maxConcurrency: 4, minIntervalMs: 0 },
16
+ nceiThredds: { id: "ncei-thredds", maxConcurrency: 2, minIntervalMs: 0 },
17
+ gdex: { id: "gdex", maxConcurrency: 4, minIntervalMs: 0 },
18
+ nceiIgra: { id: "ncei-igra", maxConcurrency: 4, minIntervalMs: 0 },
42
19
  };
43
20
  export class FileAccessPolicy {
44
21
  rootDir;
@@ -124,10 +101,9 @@ export class FileAccessPolicy {
124
101
  }
125
102
  }
126
103
  slotPath(slot) {
127
- if (this.definition.maxConcurrency === 1) {
128
- return join(this.rootDir, `${this.definition.id}.lock`);
129
- }
130
- return join(this.rootDir, `${this.definition.id}.slot-${slot}.lock`);
104
+ return this.definition.maxConcurrency === 1
105
+ ? join(this.rootDir, `${this.definition.id}.lock`)
106
+ : join(this.rootDir, `${this.definition.id}.slot-${slot}.lock`);
131
107
  }
132
108
  statePath() {
133
109
  return join(this.rootDir, `${this.definition.id}-state.json`);
@@ -141,16 +117,7 @@ export class FileAccessPolicy {
141
117
  }
142
118
  }
143
119
  }
144
- export function withLegacyCooldown(definition, cooldownMs) {
145
- if (cooldownMs === undefined)
146
- return definition;
147
- return {
148
- ...definition,
149
- maxConcurrency: 1,
150
- minIntervalMs: cooldownMs,
151
- };
152
- }
153
120
  function isAlreadyExists(error) {
154
121
  return error instanceof Error && "code" in error && error.code === "EEXIST";
155
122
  }
156
- //# sourceMappingURL=file-access-policy.js.map
123
+ //# sourceMappingURL=access-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-policy.js","sourceRoot":"","sources":["../../src/access/access-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { UpstreamAccessPolicy } from "./access-policy.js";
2
+ import { type HttpRetryExecutionOptions } from "./http-retry.js";
3
+ export interface RetryableFetchOptions extends HttpRetryExecutionOptions {
4
+ fetchFn?: typeof fetch;
5
+ accessPolicy?: UpstreamAccessPolicy;
6
+ }
7
+ /**
8
+ * Shared HTTP execution policy: provider concurrency/pacing is applied to every
9
+ * attempt, then transient HTTP/transport failures use the common retry policy.
10
+ * Callers retain responsibility for provider-specific success validation and
11
+ * response decoding.
12
+ */
13
+ export declare function fetchWithRetry(input: string | URL, init: RequestInit | undefined, options?: RetryableFetchOptions): Promise<Response>;
@@ -0,0 +1,21 @@
1
+ import { runWithHttpRetry, } from "./http-retry.js";
2
+ /**
3
+ * Shared HTTP execution policy: provider concurrency/pacing is applied to every
4
+ * attempt, then transient HTTP/transport failures use the common retry policy.
5
+ * Callers retain responsibility for provider-specific success validation and
6
+ * response decoding.
7
+ */
8
+ export async function fetchWithRetry(input, init, options = {}) {
9
+ const fetchFn = options.fetchFn ?? globalThis.fetch;
10
+ const run = (operation) => options.accessPolicy?.run(operation) ?? operation();
11
+ const result = await runWithHttpRetry(async () => {
12
+ const response = await run(() => fetchFn(input, init));
13
+ return {
14
+ status: response.status,
15
+ retryAfter: response.headers.get("retry-after"),
16
+ response,
17
+ };
18
+ }, options);
19
+ return result.response;
20
+ }
21
+ //# sourceMappingURL=http-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-fetch.js","sourceRoot":"","sources":["../../src/access/http-fetch.ts"],"names":[],"mappings":"AACA,OAAO,EACL,gBAAgB,GAEjB,MAAM,iBAAiB,CAAC;AAOzB;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,KAAmB,EACnB,IAA6B,EAC7B,UAAiC,EAAE;IAEnC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC;IACpD,MAAM,GAAG,GAAG,CAAI,SAA2B,EAAE,EAAE,CAC7C,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,SAAS,CAAC,IAAI,SAAS,EAAE,CAAC;IAEtD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,KAAK,IAAI,EAAE;QAC/C,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;QACvD,OAAO;YACL,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;YAC/C,QAAQ;SACT,CAAC;IACJ,CAAC,EAAE,OAAO,CAAC,CAAC;IAEZ,OAAO,MAAM,CAAC,QAAQ,CAAC;AACzB,CAAC"}
@@ -2,6 +2,13 @@ export declare const DEFAULT_HTTP_RETRY_BASE_DELAY_MS = 500;
2
2
  export declare const DEFAULT_HTTP_RETRY_MAX_DELAY_MS = 10000;
3
3
  export declare const DEFAULT_HTTP_RETRY_MAX_ATTEMPTS = 3;
4
4
  export declare const DEFAULT_HTTP_RETRY_JITTER_RATIO = 0.2;
5
+ export interface HttpRetryAttemptResult {
6
+ status: number;
7
+ retryAfter: string | null;
8
+ }
9
+ export interface HttpRetryExecutionOptions extends HttpRetryWaitOptions {
10
+ maxAttempts?: number;
11
+ }
5
12
  export interface HttpRetryWaitOptions {
6
13
  baseDelayMs?: number;
7
14
  maxDelayMs?: number;
@@ -10,6 +17,8 @@ export interface HttpRetryWaitOptions {
10
17
  randomFn?: () => number;
11
18
  }
12
19
  export declare function isRetryableHttpStatus(status: number): boolean;
20
+ export declare function isRetryableHttpTransportError(error: unknown): boolean;
13
21
  export declare function retryAfterMilliseconds(value: string | null, now?: number): number | undefined;
14
22
  export declare function exponentialBackoffMilliseconds(attempt: number, options?: Omit<HttpRetryWaitOptions, "sleepFn">): number;
23
+ export declare function runWithHttpRetry<T extends HttpRetryAttemptResult>(operation: () => Promise<T>, options?: HttpRetryExecutionOptions): Promise<T>;
15
24
  export declare function waitBeforeHttpRetry(attempt: number, retryAfterHeader: string | null, options?: HttpRetryWaitOptions): Promise<void>;