@jablum/weather-mcp 1.7.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 (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1319 -0
  3. package/dist/analytics/anonymizer.d.ts +37 -0
  4. package/dist/analytics/anonymizer.d.ts.map +1 -0
  5. package/dist/analytics/anonymizer.js +112 -0
  6. package/dist/analytics/anonymizer.js.map +1 -0
  7. package/dist/analytics/collector.d.ts +72 -0
  8. package/dist/analytics/collector.d.ts.map +1 -0
  9. package/dist/analytics/collector.js +282 -0
  10. package/dist/analytics/collector.js.map +1 -0
  11. package/dist/analytics/config.d.ts +15 -0
  12. package/dist/analytics/config.d.ts.map +1 -0
  13. package/dist/analytics/config.js +172 -0
  14. package/dist/analytics/config.js.map +1 -0
  15. package/dist/analytics/index.d.ts +8 -0
  16. package/dist/analytics/index.d.ts.map +1 -0
  17. package/dist/analytics/index.js +7 -0
  18. package/dist/analytics/index.js.map +1 -0
  19. package/dist/analytics/middleware.d.ts +33 -0
  20. package/dist/analytics/middleware.d.ts.map +1 -0
  21. package/dist/analytics/middleware.js +99 -0
  22. package/dist/analytics/middleware.js.map +1 -0
  23. package/dist/analytics/transport.d.ts +11 -0
  24. package/dist/analytics/transport.d.ts.map +1 -0
  25. package/dist/analytics/transport.js +92 -0
  26. package/dist/analytics/transport.js.map +1 -0
  27. package/dist/analytics/types.d.ts +74 -0
  28. package/dist/analytics/types.d.ts.map +1 -0
  29. package/dist/analytics/types.js +6 -0
  30. package/dist/analytics/types.js.map +1 -0
  31. package/dist/config/api.d.ts +30 -0
  32. package/dist/config/api.d.ts.map +1 -0
  33. package/dist/config/api.js +32 -0
  34. package/dist/config/api.js.map +1 -0
  35. package/dist/config/cache.d.ts +31 -0
  36. package/dist/config/cache.d.ts.map +1 -0
  37. package/dist/config/cache.js +108 -0
  38. package/dist/config/cache.js.map +1 -0
  39. package/dist/config/displayThresholds.d.ts +83 -0
  40. package/dist/config/displayThresholds.d.ts.map +1 -0
  41. package/dist/config/displayThresholds.js +83 -0
  42. package/dist/config/displayThresholds.js.map +1 -0
  43. package/dist/config/tools.d.ts +44 -0
  44. package/dist/config/tools.d.ts.map +1 -0
  45. package/dist/config/tools.js +269 -0
  46. package/dist/config/tools.js.map +1 -0
  47. package/dist/errors/ApiError.d.ts +62 -0
  48. package/dist/errors/ApiError.d.ts.map +1 -0
  49. package/dist/errors/ApiError.js +171 -0
  50. package/dist/errors/ApiError.js.map +1 -0
  51. package/dist/handlers/airQualityHandler.d.ts +11 -0
  52. package/dist/handlers/airQualityHandler.d.ts.map +1 -0
  53. package/dist/handlers/airQualityHandler.js +154 -0
  54. package/dist/handlers/airQualityHandler.js.map +1 -0
  55. package/dist/handlers/alertsHandler.d.ts +11 -0
  56. package/dist/handlers/alertsHandler.d.ts.map +1 -0
  57. package/dist/handlers/alertsHandler.js +98 -0
  58. package/dist/handlers/alertsHandler.js.map +1 -0
  59. package/dist/handlers/currentConditionsHandler.d.ts +13 -0
  60. package/dist/handlers/currentConditionsHandler.d.ts.map +1 -0
  61. package/dist/handlers/currentConditionsHandler.js +296 -0
  62. package/dist/handlers/currentConditionsHandler.js.map +1 -0
  63. package/dist/handlers/forecastHandler.d.ts +16 -0
  64. package/dist/handlers/forecastHandler.d.ts.map +1 -0
  65. package/dist/handlers/forecastHandler.js +454 -0
  66. package/dist/handlers/forecastHandler.js.map +1 -0
  67. package/dist/handlers/historicalWeatherHandler.d.ts +12 -0
  68. package/dist/handlers/historicalWeatherHandler.d.ts.map +1 -0
  69. package/dist/handlers/historicalWeatherHandler.js +188 -0
  70. package/dist/handlers/historicalWeatherHandler.js.map +1 -0
  71. package/dist/handlers/lightningHandler.d.ts +14 -0
  72. package/dist/handlers/lightningHandler.d.ts.map +1 -0
  73. package/dist/handlers/lightningHandler.js +258 -0
  74. package/dist/handlers/lightningHandler.js.map +1 -0
  75. package/dist/handlers/locationHandler.d.ts +12 -0
  76. package/dist/handlers/locationHandler.d.ts.map +1 -0
  77. package/dist/handlers/locationHandler.js +149 -0
  78. package/dist/handlers/locationHandler.js.map +1 -0
  79. package/dist/handlers/marineConditionsHandler.d.ts +13 -0
  80. package/dist/handlers/marineConditionsHandler.d.ts.map +1 -0
  81. package/dist/handlers/marineConditionsHandler.js +270 -0
  82. package/dist/handlers/marineConditionsHandler.js.map +1 -0
  83. package/dist/handlers/riverConditionsHandler.d.ts +11 -0
  84. package/dist/handlers/riverConditionsHandler.d.ts.map +1 -0
  85. package/dist/handlers/riverConditionsHandler.js +176 -0
  86. package/dist/handlers/riverConditionsHandler.js.map +1 -0
  87. package/dist/handlers/savedLocationsHandler.d.ts +50 -0
  88. package/dist/handlers/savedLocationsHandler.d.ts.map +1 -0
  89. package/dist/handlers/savedLocationsHandler.js +397 -0
  90. package/dist/handlers/savedLocationsHandler.js.map +1 -0
  91. package/dist/handlers/statusHandler.d.ts +12 -0
  92. package/dist/handlers/statusHandler.d.ts.map +1 -0
  93. package/dist/handlers/statusHandler.js +115 -0
  94. package/dist/handlers/statusHandler.js.map +1 -0
  95. package/dist/handlers/weatherImageryHandler.d.ts +14 -0
  96. package/dist/handlers/weatherImageryHandler.d.ts.map +1 -0
  97. package/dist/handlers/weatherImageryHandler.js +143 -0
  98. package/dist/handlers/weatherImageryHandler.js.map +1 -0
  99. package/dist/handlers/wildfireHandler.d.ts +11 -0
  100. package/dist/handlers/wildfireHandler.d.ts.map +1 -0
  101. package/dist/handlers/wildfireHandler.js +186 -0
  102. package/dist/handlers/wildfireHandler.js.map +1 -0
  103. package/dist/index.d.ts +7 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +735 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/services/blitzortung.d.ts +67 -0
  108. package/dist/services/blitzortung.d.ts.map +1 -0
  109. package/dist/services/blitzortung.js +475 -0
  110. package/dist/services/blitzortung.js.map +1 -0
  111. package/dist/services/geocoding.d.ts +57 -0
  112. package/dist/services/geocoding.d.ts.map +1 -0
  113. package/dist/services/geocoding.js +393 -0
  114. package/dist/services/geocoding.js.map +1 -0
  115. package/dist/services/locationStore.d.ts +62 -0
  116. package/dist/services/locationStore.d.ts.map +1 -0
  117. package/dist/services/locationStore.js +201 -0
  118. package/dist/services/locationStore.js.map +1 -0
  119. package/dist/services/ncei.d.ts +61 -0
  120. package/dist/services/ncei.d.ts.map +1 -0
  121. package/dist/services/ncei.js +126 -0
  122. package/dist/services/ncei.js.map +1 -0
  123. package/dist/services/nifc.d.ts +44 -0
  124. package/dist/services/nifc.d.ts.map +1 -0
  125. package/dist/services/nifc.js +159 -0
  126. package/dist/services/nifc.js.map +1 -0
  127. package/dist/services/noaa.d.ts +161 -0
  128. package/dist/services/noaa.d.ts.map +1 -0
  129. package/dist/services/noaa.js +681 -0
  130. package/dist/services/noaa.js.map +1 -0
  131. package/dist/services/nominatim.d.ts +62 -0
  132. package/dist/services/nominatim.d.ts.map +1 -0
  133. package/dist/services/nominatim.js +254 -0
  134. package/dist/services/nominatim.js.map +1 -0
  135. package/dist/services/openmeteo.d.ts +189 -0
  136. package/dist/services/openmeteo.d.ts.map +1 -0
  137. package/dist/services/openmeteo.js +936 -0
  138. package/dist/services/openmeteo.js.map +1 -0
  139. package/dist/services/rainviewer.d.ts +37 -0
  140. package/dist/services/rainviewer.d.ts.map +1 -0
  141. package/dist/services/rainviewer.js +115 -0
  142. package/dist/services/rainviewer.js.map +1 -0
  143. package/dist/types/imagery.d.ts +82 -0
  144. package/dist/types/imagery.d.ts.map +1 -0
  145. package/dist/types/imagery.js +6 -0
  146. package/dist/types/imagery.js.map +1 -0
  147. package/dist/types/lightning.d.ts +89 -0
  148. package/dist/types/lightning.d.ts.map +1 -0
  149. package/dist/types/lightning.js +6 -0
  150. package/dist/types/lightning.js.map +1 -0
  151. package/dist/types/noaa.d.ts +535 -0
  152. package/dist/types/noaa.d.ts.map +1 -0
  153. package/dist/types/noaa.js +5 -0
  154. package/dist/types/noaa.js.map +1 -0
  155. package/dist/types/nominatim.d.ts +72 -0
  156. package/dist/types/nominatim.d.ts.map +1 -0
  157. package/dist/types/nominatim.js +6 -0
  158. package/dist/types/nominatim.js.map +1 -0
  159. package/dist/types/openmeteo.d.ts +583 -0
  160. package/dist/types/openmeteo.d.ts.map +1 -0
  161. package/dist/types/openmeteo.js +6 -0
  162. package/dist/types/openmeteo.js.map +1 -0
  163. package/dist/types/savedLocations.d.ts +58 -0
  164. package/dist/types/savedLocations.d.ts.map +1 -0
  165. package/dist/types/savedLocations.js +5 -0
  166. package/dist/types/savedLocations.js.map +1 -0
  167. package/dist/types/wildfire.d.ts +83 -0
  168. package/dist/types/wildfire.d.ts.map +1 -0
  169. package/dist/types/wildfire.js +5 -0
  170. package/dist/types/wildfire.js.map +1 -0
  171. package/dist/utils/airQuality.d.ts +54 -0
  172. package/dist/utils/airQuality.d.ts.map +1 -0
  173. package/dist/utils/airQuality.js +251 -0
  174. package/dist/utils/airQuality.js.map +1 -0
  175. package/dist/utils/cache.d.ts +69 -0
  176. package/dist/utils/cache.d.ts.map +1 -0
  177. package/dist/utils/cache.js +164 -0
  178. package/dist/utils/cache.js.map +1 -0
  179. package/dist/utils/distance.d.ts +25 -0
  180. package/dist/utils/distance.d.ts.map +1 -0
  181. package/dist/utils/distance.js +40 -0
  182. package/dist/utils/distance.js.map +1 -0
  183. package/dist/utils/fireWeather.d.ts +76 -0
  184. package/dist/utils/fireWeather.d.ts.map +1 -0
  185. package/dist/utils/fireWeather.js +243 -0
  186. package/dist/utils/fireWeather.js.map +1 -0
  187. package/dist/utils/geography.d.ts +79 -0
  188. package/dist/utils/geography.d.ts.map +1 -0
  189. package/dist/utils/geography.js +266 -0
  190. package/dist/utils/geography.js.map +1 -0
  191. package/dist/utils/geohash.d.ts +62 -0
  192. package/dist/utils/geohash.d.ts.map +1 -0
  193. package/dist/utils/geohash.js +146 -0
  194. package/dist/utils/geohash.js.map +1 -0
  195. package/dist/utils/locationResolver.d.ts +34 -0
  196. package/dist/utils/locationResolver.d.ts.map +1 -0
  197. package/dist/utils/locationResolver.js +120 -0
  198. package/dist/utils/locationResolver.js.map +1 -0
  199. package/dist/utils/logger.d.ts +75 -0
  200. package/dist/utils/logger.d.ts.map +1 -0
  201. package/dist/utils/logger.js +153 -0
  202. package/dist/utils/logger.js.map +1 -0
  203. package/dist/utils/marine.d.ts +59 -0
  204. package/dist/utils/marine.d.ts.map +1 -0
  205. package/dist/utils/marine.js +215 -0
  206. package/dist/utils/marine.js.map +1 -0
  207. package/dist/utils/normals.d.ts +86 -0
  208. package/dist/utils/normals.d.ts.map +1 -0
  209. package/dist/utils/normals.js +223 -0
  210. package/dist/utils/normals.js.map +1 -0
  211. package/dist/utils/snow.d.ts +45 -0
  212. package/dist/utils/snow.d.ts.map +1 -0
  213. package/dist/utils/snow.js +144 -0
  214. package/dist/utils/snow.js.map +1 -0
  215. package/dist/utils/temperatureConversion.d.ts +12 -0
  216. package/dist/utils/temperatureConversion.d.ts.map +1 -0
  217. package/dist/utils/temperatureConversion.js +17 -0
  218. package/dist/utils/temperatureConversion.js.map +1 -0
  219. package/dist/utils/timezone.d.ts +56 -0
  220. package/dist/utils/timezone.d.ts.map +1 -0
  221. package/dist/utils/timezone.js +167 -0
  222. package/dist/utils/timezone.js.map +1 -0
  223. package/dist/utils/units.d.ts +69 -0
  224. package/dist/utils/units.d.ts.map +1 -0
  225. package/dist/utils/units.js +158 -0
  226. package/dist/utils/units.js.map +1 -0
  227. package/dist/utils/validation.d.ts +89 -0
  228. package/dist/utils/validation.d.ts.map +1 -0
  229. package/dist/utils/validation.js +177 -0
  230. package/dist/utils/validation.js.map +1 -0
  231. package/dist/utils/version.d.ts +15 -0
  232. package/dist/utils/version.d.ts.map +1 -0
  233. package/dist/utils/version.js +24 -0
  234. package/dist/utils/version.js.map +1 -0
  235. package/package.json +74 -0
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Service for managing saved/favorite locations
3
+ * Stores locations in ~/.weather-mcp/locations.json
4
+ */
5
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
6
+ import { homedir } from 'os';
7
+ import { join } from 'path';
8
+ import { logger } from '../utils/logger.js';
9
+ import { validateLatitude, validateLongitude } from '../utils/validation.js';
10
+ export class LocationStore {
11
+ storePath;
12
+ storeDir;
13
+ cache = null;
14
+ constructor(customPath) {
15
+ if (customPath) {
16
+ this.storePath = customPath;
17
+ this.storeDir = join(customPath, '..');
18
+ }
19
+ else {
20
+ this.storeDir = join(homedir(), '.weather-mcp');
21
+ this.storePath = join(this.storeDir, 'locations.json');
22
+ }
23
+ }
24
+ /**
25
+ * Ensure the storage directory exists
26
+ * @private
27
+ */
28
+ ensureDirectoryExists() {
29
+ if (!existsSync(this.storeDir)) {
30
+ try {
31
+ mkdirSync(this.storeDir, { recursive: true });
32
+ logger.info('Created locations storage directory', { path: this.storeDir });
33
+ }
34
+ catch (error) {
35
+ logger.error('Failed to create storage directory', error, {
36
+ path: this.storeDir
37
+ });
38
+ throw new Error(`Failed to create storage directory at ${this.storeDir}`);
39
+ }
40
+ }
41
+ }
42
+ /**
43
+ * Load all saved locations from disk
44
+ */
45
+ load() {
46
+ // Return cached data if available
47
+ if (this.cache !== null) {
48
+ return this.cache;
49
+ }
50
+ // If file doesn't exist, return empty store
51
+ if (!existsSync(this.storePath)) {
52
+ logger.info('No saved locations file found, starting fresh', {
53
+ path: this.storePath
54
+ });
55
+ this.cache = {};
56
+ return this.cache;
57
+ }
58
+ try {
59
+ const data = readFileSync(this.storePath, 'utf-8');
60
+ const parsed = JSON.parse(data);
61
+ // Validate the structure
62
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
63
+ logger.warn('Invalid locations file format, resetting to empty', {
64
+ path: this.storePath
65
+ });
66
+ this.cache = {};
67
+ return this.cache;
68
+ }
69
+ logger.info('Loaded saved locations', {
70
+ count: Object.keys(parsed).length,
71
+ path: this.storePath
72
+ });
73
+ this.cache = parsed;
74
+ return this.cache;
75
+ }
76
+ catch (error) {
77
+ logger.error('Failed to load saved locations', error, {
78
+ path: this.storePath
79
+ });
80
+ // Return empty store on error rather than failing
81
+ this.cache = {};
82
+ return this.cache;
83
+ }
84
+ }
85
+ /**
86
+ * Save all locations to disk
87
+ * @private
88
+ */
89
+ save(locations) {
90
+ this.ensureDirectoryExists();
91
+ try {
92
+ const data = JSON.stringify(locations, null, 2);
93
+ writeFileSync(this.storePath, data, 'utf-8');
94
+ this.cache = locations; // Update cache
95
+ logger.info('Saved locations to disk', {
96
+ count: Object.keys(locations).length,
97
+ path: this.storePath
98
+ });
99
+ }
100
+ catch (error) {
101
+ logger.error('Failed to save locations', error, {
102
+ path: this.storePath
103
+ });
104
+ throw new Error(`Failed to save locations to ${this.storePath}`);
105
+ }
106
+ }
107
+ /**
108
+ * Get a saved location by alias
109
+ */
110
+ get(alias) {
111
+ const locations = this.load();
112
+ const normalized = alias.toLowerCase().trim();
113
+ return locations[normalized];
114
+ }
115
+ /**
116
+ * Get all saved locations
117
+ */
118
+ getAll() {
119
+ return this.load();
120
+ }
121
+ /**
122
+ * Save or update a location
123
+ */
124
+ set(alias, location) {
125
+ // Validate alias
126
+ const normalized = alias.toLowerCase().trim();
127
+ if (!normalized || normalized.length === 0) {
128
+ throw new Error('Location alias cannot be empty');
129
+ }
130
+ if (normalized.length > 50) {
131
+ throw new Error('Location alias must be 50 characters or less');
132
+ }
133
+ // Validate coordinates
134
+ validateLatitude(location.latitude);
135
+ validateLongitude(location.longitude);
136
+ const locations = this.load();
137
+ const isUpdate = normalized in locations;
138
+ const now = new Date().toISOString();
139
+ const savedLocation = {
140
+ ...location,
141
+ saved_at: isUpdate ? locations[normalized].saved_at : now,
142
+ updated_at: now
143
+ };
144
+ locations[normalized] = savedLocation;
145
+ this.save(locations);
146
+ logger.info(isUpdate ? 'Updated saved location' : 'Created new saved location', {
147
+ alias: normalized,
148
+ name: location.name
149
+ });
150
+ return savedLocation;
151
+ }
152
+ /**
153
+ * Remove a saved location
154
+ */
155
+ remove(alias) {
156
+ const locations = this.load();
157
+ const normalized = alias.toLowerCase().trim();
158
+ if (!(normalized in locations)) {
159
+ return false;
160
+ }
161
+ delete locations[normalized];
162
+ this.save(locations);
163
+ logger.info('Removed saved location', { alias: normalized });
164
+ return true;
165
+ }
166
+ /**
167
+ * Check if a location exists
168
+ */
169
+ has(alias) {
170
+ const locations = this.load();
171
+ const normalized = alias.toLowerCase().trim();
172
+ return normalized in locations;
173
+ }
174
+ /**
175
+ * Get the number of saved locations
176
+ */
177
+ count() {
178
+ const locations = this.load();
179
+ return Object.keys(locations).length;
180
+ }
181
+ /**
182
+ * Clear all saved locations
183
+ */
184
+ clear() {
185
+ this.save({});
186
+ logger.info('Cleared all saved locations');
187
+ }
188
+ /**
189
+ * Get the storage file path
190
+ */
191
+ getStorePath() {
192
+ return this.storePath;
193
+ }
194
+ /**
195
+ * Invalidate the cache, forcing reload on next access
196
+ */
197
+ invalidateCache() {
198
+ this.cache = null;
199
+ }
200
+ }
201
+ //# sourceMappingURL=locationStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locationStore.js","sourceRoot":"","sources":["../../src/services/locationStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Service for interacting with NOAA NCEI (National Centers for Environmental Information)
3
+ * Climate Data Online (CDO) API
4
+ *
5
+ * Documentation: https://www.ncei.noaa.gov/support/access-data-service-api-user-documentation
6
+ *
7
+ * NOTE: This is a simplified implementation. The NCEI API is station-based (not coordinate-based),
8
+ * which adds complexity:
9
+ * 1. Must find nearby weather stations from coordinates
10
+ * 2. Stations may not have all climate normals data
11
+ * 3. Need to handle multiple stations and aggregate data
12
+ *
13
+ * For v1.2.0, we implement a basic structure that falls back to Open-Meteo.
14
+ * Future enhancement: Full NCEI climate normals integration.
15
+ */
16
+ import type { ClimateNormals } from '../types/openmeteo.js';
17
+ export interface NCEIServiceConfig {
18
+ baseURL?: string;
19
+ timeout?: number;
20
+ token?: string;
21
+ }
22
+ /**
23
+ * NCEI Climate Data Online (CDO) Service
24
+ *
25
+ * Provides access to official NOAA climate normals for US locations.
26
+ * Requires API token (free from https://www.ncdc.noaa.gov/cdo-web/token)
27
+ */
28
+ export declare class NCEIService {
29
+ private client;
30
+ private token;
31
+ constructor(config?: NCEIServiceConfig);
32
+ /**
33
+ * Check if NCEI service is available (token configured)
34
+ */
35
+ isAvailable(): boolean;
36
+ /**
37
+ * Get climate normals for a US location
38
+ *
39
+ * NOTE: This is a placeholder implementation. The NCEI API requires:
40
+ * 1. Station lookup from coordinates
41
+ * 2. Querying climate normals datasets by station
42
+ * 3. Data transformation to our format
43
+ *
44
+ * For v1.2.0, this throws DataNotFoundError to trigger fallback to Open-Meteo.
45
+ * Future enhancement: Implement full NCEI normals retrieval.
46
+ *
47
+ * @param latitude - Latitude (US only)
48
+ * @param longitude - Longitude (US only)
49
+ * @param month - Month (1-12)
50
+ * @param day - Day of month (1-31)
51
+ * @returns Climate normals
52
+ * @throws {DataNotFoundError} - NCEI integration not yet complete
53
+ */
54
+ getClimateNormals(latitude: number, longitude: number, month: number, day: number): Promise<ClimateNormals>;
55
+ /**
56
+ * Handle API errors
57
+ * @private
58
+ */
59
+ private handleError;
60
+ }
61
+ //# sourceMappingURL=ncei.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ncei.d.ts","sourceRoot":"","sources":["../../src/services/ncei.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAM5D,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,KAAK,CAAqB;gBAEtB,MAAM,GAAE,iBAAsB;IA8B1C;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;;;;;;;;;;;;;;;;OAiBG;IACG,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC;IAiC1B;;;OAGG;YACW,WAAW;CAyC1B"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Service for interacting with NOAA NCEI (National Centers for Environmental Information)
3
+ * Climate Data Online (CDO) API
4
+ *
5
+ * Documentation: https://www.ncei.noaa.gov/support/access-data-service-api-user-documentation
6
+ *
7
+ * NOTE: This is a simplified implementation. The NCEI API is station-based (not coordinate-based),
8
+ * which adds complexity:
9
+ * 1. Must find nearby weather stations from coordinates
10
+ * 2. Stations may not have all climate normals data
11
+ * 3. Need to handle multiple stations and aggregate data
12
+ *
13
+ * For v1.2.0, we implement a basic structure that falls back to Open-Meteo.
14
+ * Future enhancement: Full NCEI climate normals integration.
15
+ */
16
+ import axios from 'axios';
17
+ import { NCEI_API_TOKEN } from '../config/api.js';
18
+ import { logger } from '../utils/logger.js';
19
+ import { DataNotFoundError, RateLimitError, ServiceUnavailableError } from '../errors/ApiError.js';
20
+ import { getUserAgent } from '../utils/version.js';
21
+ /**
22
+ * NCEI Climate Data Online (CDO) Service
23
+ *
24
+ * Provides access to official NOAA climate normals for US locations.
25
+ * Requires API token (free from https://www.ncdc.noaa.gov/cdo-web/token)
26
+ */
27
+ export class NCEIService {
28
+ client;
29
+ token;
30
+ constructor(config = {}) {
31
+ const { baseURL = 'https://www.ncei.noaa.gov/cdo-web/api/v2', timeout = 30000, token = NCEI_API_TOKEN } = config;
32
+ this.token = token;
33
+ this.client = axios.create({
34
+ baseURL,
35
+ timeout,
36
+ headers: {
37
+ 'Accept': 'application/json',
38
+ 'User-Agent': getUserAgent()
39
+ }
40
+ });
41
+ // Add token to requests if available
42
+ if (this.token) {
43
+ this.client.defaults.headers.common['token'] = this.token;
44
+ }
45
+ // Add response interceptor for error handling
46
+ this.client.interceptors.response.use(response => response, error => this.handleError(error));
47
+ }
48
+ /**
49
+ * Check if NCEI service is available (token configured)
50
+ */
51
+ isAvailable() {
52
+ return !!this.token && this.token.trim().length > 0;
53
+ }
54
+ /**
55
+ * Get climate normals for a US location
56
+ *
57
+ * NOTE: This is a placeholder implementation. The NCEI API requires:
58
+ * 1. Station lookup from coordinates
59
+ * 2. Querying climate normals datasets by station
60
+ * 3. Data transformation to our format
61
+ *
62
+ * For v1.2.0, this throws DataNotFoundError to trigger fallback to Open-Meteo.
63
+ * Future enhancement: Implement full NCEI normals retrieval.
64
+ *
65
+ * @param latitude - Latitude (US only)
66
+ * @param longitude - Longitude (US only)
67
+ * @param month - Month (1-12)
68
+ * @param day - Day of month (1-31)
69
+ * @returns Climate normals
70
+ * @throws {DataNotFoundError} - NCEI integration not yet complete
71
+ */
72
+ async getClimateNormals(latitude, longitude, month, day) {
73
+ if (!this.isAvailable()) {
74
+ throw new DataNotFoundError('NCEI', 'NCEI API token not configured. Set NCEI_API_TOKEN environment variable.');
75
+ }
76
+ logger.info('NCEI climate normals requested (not yet implemented)', {
77
+ latitude,
78
+ longitude,
79
+ month,
80
+ day
81
+ });
82
+ // TODO: Implement NCEI climate normals retrieval
83
+ // Steps needed:
84
+ // 1. Find nearby weather stations using /stations endpoint
85
+ // 2. Query climate normals datasets (NORMAL_DLY, NORMAL_MLY) for station
86
+ // 3. Extract and transform data to ClimateNormals format
87
+ // 4. Handle missing data and station selection logic
88
+ //
89
+ // Complexity: High - requires multiple API calls and data processing
90
+ // Estimated implementation: 1-2 days
91
+ //
92
+ // For now, throw DataNotFoundError to trigger fallback to Open-Meteo
93
+ throw new DataNotFoundError('NCEI', 'NCEI climate normals integration is planned for a future release. Using Open-Meteo fallback.');
94
+ }
95
+ /**
96
+ * Handle API errors
97
+ * @private
98
+ */
99
+ async handleError(error) {
100
+ if (error.response) {
101
+ const status = error.response.status;
102
+ const message = error.response.data?.message || error.message;
103
+ if (status === 429) {
104
+ throw new RateLimitError('NCEI', 'Rate limit exceeded (5 requests/second or 10,000/day)', 60);
105
+ }
106
+ if (status === 401 || status === 403) {
107
+ throw new ServiceUnavailableError('NCEI', 'Invalid or missing API token. Get a free token at https://www.ncdc.noaa.gov/cdo-web/token');
108
+ }
109
+ if (status === 404) {
110
+ throw new DataNotFoundError('NCEI', message || 'Data not found');
111
+ }
112
+ if (status >= 500) {
113
+ throw new ServiceUnavailableError('NCEI', message || 'Service temporarily unavailable');
114
+ }
115
+ throw new ServiceUnavailableError('NCEI', message);
116
+ }
117
+ if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
118
+ throw new ServiceUnavailableError('NCEI', 'Request timed out');
119
+ }
120
+ if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
121
+ throw new ServiceUnavailableError('NCEI', 'Unable to connect to NCEI API');
122
+ }
123
+ throw new ServiceUnavailableError('NCEI', error.message || 'Unknown error occurred');
124
+ }
125
+ }
126
+ //# sourceMappingURL=ncei.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ncei.js","sourceRoot":"","sources":["../../src/services/ncei.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Service for interacting with NIFC (National Interagency Fire Center) ArcGIS REST API
3
+ */
4
+ import type { NIFCQueryResponse } from '../types/wildfire.js';
5
+ export interface NIFCServiceConfig {
6
+ baseURL?: string;
7
+ timeout?: number;
8
+ }
9
+ export declare class NIFCService {
10
+ private client;
11
+ private cache;
12
+ private readonly featureServerUrl;
13
+ constructor(config?: NIFCServiceConfig);
14
+ /**
15
+ * Get cache statistics
16
+ */
17
+ getCacheStats(): import("../utils/cache.js").CacheStats;
18
+ /**
19
+ * Clear the cache
20
+ */
21
+ clearCache(): void;
22
+ /**
23
+ * Query fire perimeters within a bounding box
24
+ * @param west Western longitude boundary
25
+ * @param south Southern latitude boundary
26
+ * @param east Eastern longitude boundary
27
+ * @param north Northern latitude boundary
28
+ * @returns Fire perimeter features within the bounding box
29
+ */
30
+ queryFirePerimeters(west: number, south: number, east: number, north: number): Promise<NIFCQueryResponse>;
31
+ /**
32
+ * Query the NIFC ArcGIS Feature Server
33
+ */
34
+ private queryFeatureServer;
35
+ /**
36
+ * Check if the NIFC service is operational
37
+ */
38
+ checkServiceStatus(): Promise<{
39
+ operational: boolean;
40
+ message: string;
41
+ timestamp: string;
42
+ }>;
43
+ }
44
+ //# sourceMappingURL=nifc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nifc.d.ts","sourceRoot":"","sources":["../../src/services/nifc.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAM9D,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,KAAK,CAAQ;IAGrB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6H;gBAElJ,MAAM,GAAE,iBAAsB;IAe1C;;OAEG;IACH,aAAa;IAIb;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;;;;;;OAOG;IACG,mBAAmB,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,iBAAiB,CAAC;IAuC7B;;OAEG;YACW,kBAAkB;IA2DhC;;OAEG;IACG,kBAAkB,IAAI,OAAO,CAAC;QAClC,WAAW,EAAE,OAAO,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CA8BH"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Service for interacting with NIFC (National Interagency Fire Center) ArcGIS REST API
3
+ */
4
+ import axios from 'axios';
5
+ import { Cache } from '../utils/cache.js';
6
+ import { CacheConfig } from '../config/cache.js';
7
+ import { validateLatitude, validateLongitude } from '../utils/validation.js';
8
+ import { logger, redactCoordinatesForLogging } from '../utils/logger.js';
9
+ export class NIFCService {
10
+ client;
11
+ cache;
12
+ // NIFC WFIGS Feature Server URL for current fire perimeters
13
+ featureServerUrl = 'https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/WFIGS_Interagency_Perimeters_Current/FeatureServer/0';
14
+ constructor(config = {}) {
15
+ const { timeout = CacheConfig.apiTimeoutMs } = config;
16
+ this.cache = new Cache(CacheConfig.maxSize);
17
+ this.client = axios.create({
18
+ timeout,
19
+ headers: {
20
+ 'Accept': 'application/json'
21
+ }
22
+ });
23
+ }
24
+ /**
25
+ * Get cache statistics
26
+ */
27
+ getCacheStats() {
28
+ return this.cache.getStats();
29
+ }
30
+ /**
31
+ * Clear the cache
32
+ */
33
+ clearCache() {
34
+ this.cache.clear();
35
+ }
36
+ /**
37
+ * Query fire perimeters within a bounding box
38
+ * @param west Western longitude boundary
39
+ * @param south Southern latitude boundary
40
+ * @param east Eastern longitude boundary
41
+ * @param north Northern latitude boundary
42
+ * @returns Fire perimeter features within the bounding box
43
+ */
44
+ async queryFirePerimeters(west, south, east, north) {
45
+ // Validate bounding box
46
+ validateLongitude(west);
47
+ validateLongitude(east);
48
+ validateLatitude(south);
49
+ validateLatitude(north);
50
+ if (west >= east) {
51
+ throw new Error('Invalid bounding box: west longitude must be less than east longitude');
52
+ }
53
+ if (south >= north) {
54
+ throw new Error('Invalid bounding box: south latitude must be less than north latitude');
55
+ }
56
+ // Check cache first (if enabled)
57
+ const bboxKey = `${west.toFixed(4)},${south.toFixed(4)},${east.toFixed(4)},${north.toFixed(4)}`;
58
+ if (CacheConfig.enabled) {
59
+ const cacheKey = Cache.generateKey('nifc-fire-perimeters', bboxKey);
60
+ const cached = this.cache.get(cacheKey);
61
+ if (cached) {
62
+ // Redact bounding box coordinates for privacy
63
+ const sw = redactCoordinatesForLogging(south, west);
64
+ const ne = redactCoordinatesForLogging(north, east);
65
+ logger.info('NIFC cache hit', {
66
+ bbox: `${sw.lon},${sw.lat},${ne.lon},${ne.lat}`
67
+ });
68
+ return cached;
69
+ }
70
+ const result = await this.queryFeatureServer(west, south, east, north);
71
+ // Cache for 30 minutes (fire data updates frequently)
72
+ this.cache.set(cacheKey, result, 1800000);
73
+ return result;
74
+ }
75
+ return this.queryFeatureServer(west, south, east, north);
76
+ }
77
+ /**
78
+ * Query the NIFC ArcGIS Feature Server
79
+ */
80
+ async queryFeatureServer(west, south, east, north) {
81
+ try {
82
+ // Build ArcGIS REST query
83
+ const params = new URLSearchParams({
84
+ f: 'json', // Response format
85
+ geometry: `${west},${south},${east},${north}`, // Bounding box
86
+ geometryType: 'esriGeometryEnvelope',
87
+ spatialRel: 'esriSpatialRelIntersects',
88
+ outFields: '*', // Return all fields
89
+ returnGeometry: 'true',
90
+ returnCentroid: 'false',
91
+ returnExceededLimitFeatures: 'false',
92
+ maxAllowableOffset: '0.0001', // Simplify geometry slightly for performance
93
+ where: '1=1' // No additional filters (query all active fires)
94
+ });
95
+ const url = `${this.featureServerUrl}/query?${params.toString()}`;
96
+ // Redact bounding box coordinates for privacy
97
+ const sw = redactCoordinatesForLogging(south, west);
98
+ const ne = redactCoordinatesForLogging(north, east);
99
+ logger.info('Querying NIFC fire perimeters', {
100
+ bbox: `${sw.lon},${sw.lat},${ne.lon},${ne.lat}`,
101
+ url: this.featureServerUrl
102
+ });
103
+ const response = await this.client.get(url);
104
+ logger.info('NIFC query complete', {
105
+ featureCount: response.data.features?.length || 0,
106
+ exceeded: response.data.exceededTransferLimit || false
107
+ });
108
+ return response.data;
109
+ }
110
+ catch (error) {
111
+ logger.error('NIFC query failed', error);
112
+ // Check for specific error types
113
+ if (axios.isAxiosError(error)) {
114
+ if (error.code === 'ECONNABORTED') {
115
+ throw new Error('NIFC service request timed out. The service may be temporarily unavailable.');
116
+ }
117
+ if (error.response?.status === 400) {
118
+ throw new Error('Invalid query parameters for NIFC service.');
119
+ }
120
+ if (error.response?.status === 503) {
121
+ throw new Error('NIFC service is temporarily unavailable. Please try again later.');
122
+ }
123
+ }
124
+ throw new Error(`Failed to query NIFC fire perimeters: ${error instanceof Error ? error.message : String(error)}`);
125
+ }
126
+ }
127
+ /**
128
+ * Check if the NIFC service is operational
129
+ */
130
+ async checkServiceStatus() {
131
+ try {
132
+ // Query a small bounding box to test service availability
133
+ const response = await this.client.get(`${this.featureServerUrl}?f=json`, {
134
+ timeout: 10000
135
+ });
136
+ if (response.status === 200 && response.data) {
137
+ return {
138
+ operational: true,
139
+ message: 'NIFC ArcGIS service is operational',
140
+ timestamp: new Date().toISOString()
141
+ };
142
+ }
143
+ return {
144
+ operational: false,
145
+ message: 'NIFC service returned unexpected response',
146
+ timestamp: new Date().toISOString()
147
+ };
148
+ }
149
+ catch (error) {
150
+ logger.warn('NIFC service check failed', { error: error instanceof Error ? error.message : String(error) });
151
+ return {
152
+ operational: false,
153
+ message: `NIFC service is unavailable: ${error instanceof Error ? error.message : 'Unknown error'}`,
154
+ timestamp: new Date().toISOString()
155
+ };
156
+ }
157
+ }
158
+ }
159
+ //# sourceMappingURL=nifc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nifc.js","sourceRoot":"","sources":["../../src/services/nifc.ts"],"names":[],"mappings":"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"}