@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,223 @@
1
+ /**
2
+ * Utility functions for computing climate normals from historical data
3
+ *
4
+ * Climate normals are 30-year averages (1991-2020) used to provide
5
+ * context for current weather conditions.
6
+ *
7
+ * Hybrid Strategy:
8
+ * 1. Try NCEI (US only, requires token) for official normals
9
+ * 2. Fall back to Open-Meteo (global, no token) computed normals
10
+ */
11
+ import { celsiusToFahrenheit } from './units.js';
12
+ import { logger } from './logger.js';
13
+ /**
14
+ * Convert millimeters to inches
15
+ */
16
+ function millimetersToInches(mm) {
17
+ return mm / 25.4;
18
+ }
19
+ /**
20
+ * Compute climate normals from 30 years of historical data
21
+ *
22
+ * @param historicalData - 30 years of daily historical weather data from Open-Meteo
23
+ * @param targetMonth - Month (1-12) to compute normals for
24
+ * @param targetDay - Day of month (1-31) to compute normals for
25
+ * @returns Climate normals (30-year averages) for the specified date
26
+ */
27
+ export function computeNormalsFrom30YearData(historicalData, targetMonth, targetDay) {
28
+ if (!historicalData.daily || !historicalData.daily.time) {
29
+ throw new Error('Historical data does not contain daily data');
30
+ }
31
+ const daily = historicalData.daily;
32
+ const targetMonthDay = `${String(targetMonth).padStart(2, '0')}-${String(targetDay).padStart(2, '0')}`;
33
+ // Find all occurrences of this month/day across the 30-year period
34
+ const matchingDays = [];
35
+ for (let i = 0; i < daily.time.length; i++) {
36
+ const date = daily.time[i]; // Format: "YYYY-MM-DD"
37
+ const monthDay = date.substring(5); // Extract "MM-DD"
38
+ if (monthDay === targetMonthDay) {
39
+ matchingDays.push({
40
+ tempHigh: daily.temperature_2m_max?.[i],
41
+ tempLow: daily.temperature_2m_min?.[i],
42
+ precipitation: daily.precipitation_sum?.[i]
43
+ });
44
+ }
45
+ }
46
+ if (matchingDays.length === 0) {
47
+ throw new Error(`No historical data found for ${targetMonth}/${targetDay}`);
48
+ }
49
+ // Calculate averages
50
+ const tempHighValues = matchingDays.filter(d => d.tempHigh !== undefined).map(d => d.tempHigh);
51
+ const tempLowValues = matchingDays.filter(d => d.tempLow !== undefined).map(d => d.tempLow);
52
+ const precipitationValues = matchingDays.filter(d => d.precipitation !== undefined).map(d => d.precipitation);
53
+ const avgTempHighC = tempHighValues.reduce((sum, val) => sum + val, 0) / tempHighValues.length;
54
+ const avgTempLowC = tempLowValues.reduce((sum, val) => sum + val, 0) / tempLowValues.length;
55
+ const avgPrecipitationMm = precipitationValues.reduce((sum, val) => sum + val, 0) / precipitationValues.length;
56
+ // Convert to US units (Fahrenheit, inches)
57
+ const tempHigh = Math.round(celsiusToFahrenheit(avgTempHighC));
58
+ const tempLow = Math.round(celsiusToFahrenheit(avgTempLowC));
59
+ const precipitation = Math.round(millimetersToInches(avgPrecipitationMm) * 100) / 100; // Round to 2 decimals
60
+ return {
61
+ tempHigh,
62
+ tempLow,
63
+ precipitation,
64
+ source: 'Open-Meteo',
65
+ month: targetMonth,
66
+ day: targetDay
67
+ };
68
+ }
69
+ /**
70
+ * Generate a cache key for climate normals
71
+ *
72
+ * @param latitude - Latitude (rounded to 2 decimals)
73
+ * @param longitude - Longitude (rounded to 2 decimals)
74
+ * @param month - Month (1-12)
75
+ * @param day - Day of month (1-31)
76
+ * @returns Cache key string
77
+ */
78
+ export function getNormalsCacheKey(latitude, longitude, month, day) {
79
+ // Round coordinates to 2 decimals to increase cache hit rate
80
+ const lat = Math.round(latitude * 100) / 100;
81
+ const lon = Math.round(longitude * 100) / 100;
82
+ return `normals:${lat}:${lon}:${month}:${day}`;
83
+ }
84
+ /**
85
+ * Calculate departure from normal
86
+ *
87
+ * @param actual - Actual temperature value
88
+ * @param normal - Normal (average) temperature value
89
+ * @returns Departure with sign (e.g., +10, -5)
90
+ */
91
+ export function calculateDeparture(actual, normal) {
92
+ const departure = Math.round(actual - normal);
93
+ return departure >= 0 ? `+${departure}` : `${departure}`;
94
+ }
95
+ /**
96
+ * Format climate normals for display
97
+ *
98
+ * @param normals - Climate normals data
99
+ * @param currentTemp - Optional current temperature for comparison
100
+ * @returns Formatted markdown string
101
+ */
102
+ export function formatNormals(normals, currentTemp) {
103
+ let output = `\n## 📊 Climate Context\n\n`;
104
+ output += `**Normal High:** ${normals.tempHigh}°F\n`;
105
+ output += `**Normal Low:** ${normals.tempLow}°F\n`;
106
+ output += `**Normal Precipitation:** ${normals.precipitation}" \n`;
107
+ if (currentTemp) {
108
+ if (currentTemp.high !== undefined) {
109
+ const departure = calculateDeparture(currentTemp.high, normals.tempHigh);
110
+ output += `**High Departure:** ${departure}°F`;
111
+ if (departure.startsWith('+')) {
112
+ output += ` (warmer than normal)`;
113
+ }
114
+ else if (departure.startsWith('-')) {
115
+ output += ` (cooler than normal)`;
116
+ }
117
+ output += `\n`;
118
+ }
119
+ if (currentTemp.low !== undefined) {
120
+ const departure = calculateDeparture(currentTemp.low, normals.tempLow);
121
+ output += `**Low Departure:** ${departure}°F`;
122
+ if (departure.startsWith('+')) {
123
+ output += ` (warmer than normal)`;
124
+ }
125
+ else if (departure.startsWith('-')) {
126
+ output += ` (cooler than normal)`;
127
+ }
128
+ output += `\n`;
129
+ }
130
+ }
131
+ output += `\n*Climate normals based on 1991-2020 data*\n`;
132
+ output += `*Source: ${normals.source}*\n`;
133
+ return output;
134
+ }
135
+ /**
136
+ * Get date components from Date object or ISO string
137
+ *
138
+ * @param date - Date object or ISO string
139
+ * @returns Object with month (1-12) and day (1-31)
140
+ */
141
+ export function getDateComponents(date) {
142
+ const d = typeof date === 'string' ? new Date(date) : date;
143
+ return {
144
+ month: d.getMonth() + 1, // JavaScript months are 0-indexed
145
+ day: d.getDate()
146
+ };
147
+ }
148
+ /**
149
+ * Check if coordinates are within the contiguous United States
150
+ *
151
+ * @param latitude - Latitude
152
+ * @param longitude - Longitude
153
+ * @returns true if location is in contiguous US
154
+ */
155
+ export function isLocationInUS(latitude, longitude) {
156
+ // Contiguous US bounding box (approximate)
157
+ // Latitude: ~25°N (Florida) to ~49°N (Canada border)
158
+ // Longitude: ~-125°W (Pacific) to ~-66°W (Atlantic)
159
+ return (latitude >= 24 &&
160
+ latitude <= 50 &&
161
+ longitude >= -125 &&
162
+ longitude <= -66);
163
+ }
164
+ /**
165
+ * Get climate normals using hybrid strategy
166
+ *
167
+ * Strategy:
168
+ * 1. If location is in US and NCEI token available, try NCEI first
169
+ * 2. Fall back to Open-Meteo computed normals (always works)
170
+ *
171
+ * @param openMeteoService - Open-Meteo service instance
172
+ * @param nceiService - NCEI service instance (optional)
173
+ * @param latitude - Latitude
174
+ * @param longitude - Longitude
175
+ * @param month - Month (1-12)
176
+ * @param day - Day of month (1-31)
177
+ * @returns Climate normals with source indication
178
+ */
179
+ export async function getClimateNormals(openMeteoService, nceiService, latitude, longitude, month, day) {
180
+ // Try NCEI if available and location is in US
181
+ if (nceiService && nceiService.isAvailable() && isLocationInUS(latitude, longitude)) {
182
+ try {
183
+ logger.info('Attempting to fetch climate normals from NCEI', {
184
+ latitude,
185
+ longitude,
186
+ month,
187
+ day
188
+ });
189
+ const normals = await nceiService.getClimateNormals(latitude, longitude, month, day);
190
+ logger.info('Successfully retrieved climate normals from NCEI', {
191
+ latitude,
192
+ longitude,
193
+ month,
194
+ day
195
+ });
196
+ return normals;
197
+ }
198
+ catch (error) {
199
+ // NCEI failed, fall back to Open-Meteo
200
+ logger.warn('NCEI climate normals failed, falling back to Open-Meteo', {
201
+ latitude,
202
+ longitude,
203
+ month,
204
+ day,
205
+ error: error.message
206
+ });
207
+ }
208
+ }
209
+ // Use Open-Meteo (default, always works)
210
+ logger.info('Using Open-Meteo for climate normals', {
211
+ latitude,
212
+ longitude,
213
+ month,
214
+ day,
215
+ reason: nceiService && nceiService.isAvailable()
216
+ ? 'NCEI fallback'
217
+ : !isLocationInUS(latitude, longitude)
218
+ ? 'Location outside US'
219
+ : 'NCEI token not configured'
220
+ });
221
+ return await openMeteoService.getClimateNormals(latitude, longitude, month, day);
222
+ }
223
+ //# sourceMappingURL=normals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normals.js","sourceRoot":"","sources":["../../src/utils/normals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Utility functions for extracting and formatting snow data
3
+ * from NOAA API responses
4
+ */
5
+ import { GridpointProperties, ObservationProperties } from '../types/noaa.js';
6
+ /**
7
+ * Snow data extracted from observations or gridpoint forecasts
8
+ */
9
+ export interface SnowData {
10
+ snowDepth?: {
11
+ value: number;
12
+ unit: string;
13
+ };
14
+ snowfallAmount?: {
15
+ value: number;
16
+ unit: string;
17
+ period?: string;
18
+ };
19
+ iceAccumulation?: {
20
+ value: number;
21
+ unit: string;
22
+ period?: string;
23
+ };
24
+ }
25
+ /**
26
+ * Extract current snow depth from observation data
27
+ */
28
+ export declare function extractSnowDepth(observation: ObservationProperties): SnowData['snowDepth'] | undefined;
29
+ /**
30
+ * Extract snowfall forecast from gridpoint data for a specific time period
31
+ */
32
+ export declare function extractSnowfallForecast(gridpoint: GridpointProperties, startTime?: Date, endTime?: Date): SnowData['snowfallAmount'] | undefined;
33
+ /**
34
+ * Extract ice accumulation forecast from gridpoint data
35
+ */
36
+ export declare function extractIceAccumulation(gridpoint: GridpointProperties, startTime?: Date, endTime?: Date): SnowData['iceAccumulation'] | undefined;
37
+ /**
38
+ * Format snow data for display
39
+ */
40
+ export declare function formatSnowData(snowData: SnowData): string;
41
+ /**
42
+ * Check if there's any significant winter weather to report
43
+ */
44
+ export declare function hasWinterWeather(snowData: SnowData): boolean;
45
+ //# sourceMappingURL=snow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snow.d.ts","sourceRoot":"","sources":["../../src/utils/snow.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,SAAS,CAAC,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,cAAc,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,eAAe,CAAC,EAAE;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AASD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,qBAAqB,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,SAAS,CA8BtG;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,mBAAmB,EAC9B,SAAS,CAAC,EAAE,IAAI,EAChB,OAAO,CAAC,EAAE,IAAI,GACb,QAAQ,CAAC,gBAAgB,CAAC,GAAG,SAAS,CA2CxC;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,mBAAmB,EAC9B,SAAS,CAAC,EAAE,IAAI,EAChB,OAAO,CAAC,EAAE,IAAI,GACb,QAAQ,CAAC,iBAAiB,CAAC,GAAG,SAAS,CAyCzC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAkBzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAE5D"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Utility functions for extracting and formatting snow data
3
+ * from NOAA API responses
4
+ */
5
+ /**
6
+ * Convert millimeters to inches
7
+ */
8
+ function mmToInches(mm) {
9
+ return mm / 25.4;
10
+ }
11
+ /**
12
+ * Extract current snow depth from observation data
13
+ */
14
+ export function extractSnowDepth(observation) {
15
+ if (!observation.snowDepth || observation.snowDepth.value === null) {
16
+ return undefined;
17
+ }
18
+ // NOAA returns snow depth in various units, but typically centimeters or meters
19
+ const value = observation.snowDepth.value;
20
+ const unit = observation.snowDepth.unitCode;
21
+ let inches;
22
+ if (unit?.includes('cm') || unit?.includes('centimeter')) {
23
+ inches = value / 2.54; // cm to inches
24
+ }
25
+ else if (unit?.includes('m') && !unit?.includes('mm')) {
26
+ inches = value * 39.3701; // meters to inches
27
+ }
28
+ else if (unit?.includes('mm') || unit?.includes('millimeter')) {
29
+ inches = mmToInches(value);
30
+ }
31
+ else {
32
+ // Assume centimeters if unit unclear
33
+ inches = value / 2.54;
34
+ }
35
+ // Only return if there's actually snow on the ground
36
+ if (inches < 0.1) {
37
+ return undefined;
38
+ }
39
+ return {
40
+ value: Math.round(inches * 10) / 10, // Round to 1 decimal
41
+ unit: 'in'
42
+ };
43
+ }
44
+ /**
45
+ * Extract snowfall forecast from gridpoint data for a specific time period
46
+ */
47
+ export function extractSnowfallForecast(gridpoint, startTime, endTime) {
48
+ if (!gridpoint.snowfallAmount || gridpoint.snowfallAmount.values.length === 0) {
49
+ return undefined;
50
+ }
51
+ let totalSnowfall = 0;
52
+ const snowfallSeries = gridpoint.snowfallAmount;
53
+ for (const entry of snowfallSeries.values) {
54
+ if (entry.value === null || entry.value === undefined) {
55
+ continue;
56
+ }
57
+ // Parse the time interval (e.g., "2025-11-07T12:00:00+00:00/PT6H")
58
+ const [validTimeStr] = entry.validTime.split('/');
59
+ const validTime = new Date(validTimeStr);
60
+ // If we have a time range filter, check if this entry is within it
61
+ if (startTime && validTime < startTime) {
62
+ continue;
63
+ }
64
+ if (endTime && validTime > endTime) {
65
+ continue;
66
+ }
67
+ totalSnowfall += entry.value;
68
+ }
69
+ // NOAA returns snowfall in millimeters
70
+ const inches = mmToInches(totalSnowfall);
71
+ // Only return if there's meaningful snowfall
72
+ if (inches < 0.1) {
73
+ return undefined;
74
+ }
75
+ return {
76
+ value: Math.round(inches * 10) / 10, // Round to 1 decimal
77
+ unit: 'in',
78
+ period: startTime && endTime ?
79
+ `${startTime.toLocaleDateString()} - ${endTime.toLocaleDateString()}` :
80
+ undefined
81
+ };
82
+ }
83
+ /**
84
+ * Extract ice accumulation forecast from gridpoint data
85
+ */
86
+ export function extractIceAccumulation(gridpoint, startTime, endTime) {
87
+ if (!gridpoint.iceAccumulation || gridpoint.iceAccumulation.values.length === 0) {
88
+ return undefined;
89
+ }
90
+ let totalIce = 0;
91
+ const iceSeries = gridpoint.iceAccumulation;
92
+ for (const entry of iceSeries.values) {
93
+ if (entry.value === null || entry.value === undefined) {
94
+ continue;
95
+ }
96
+ const [validTimeStr] = entry.validTime.split('/');
97
+ const validTime = new Date(validTimeStr);
98
+ if (startTime && validTime < startTime) {
99
+ continue;
100
+ }
101
+ if (endTime && validTime > endTime) {
102
+ continue;
103
+ }
104
+ totalIce += entry.value;
105
+ }
106
+ // NOAA returns ice accumulation in millimeters
107
+ const inches = mmToInches(totalIce);
108
+ // Only return if there's meaningful ice accumulation
109
+ if (inches < 0.05) {
110
+ return undefined;
111
+ }
112
+ return {
113
+ value: Math.round(inches * 100) / 100, // Round to 2 decimals (ice is often small amounts)
114
+ unit: 'in',
115
+ period: startTime && endTime ?
116
+ `${startTime.toLocaleDateString()} - ${endTime.toLocaleDateString()}` :
117
+ undefined
118
+ };
119
+ }
120
+ /**
121
+ * Format snow data for display
122
+ */
123
+ export function formatSnowData(snowData) {
124
+ const parts = [];
125
+ if (snowData.snowDepth) {
126
+ parts.push(`**Snow Depth:** ${snowData.snowDepth.value}${snowData.snowDepth.unit} on ground`);
127
+ }
128
+ if (snowData.snowfallAmount) {
129
+ const period = snowData.snowfallAmount.period ? ` (${snowData.snowfallAmount.period})` : '';
130
+ parts.push(`**Snowfall Forecast:** ${snowData.snowfallAmount.value}${snowData.snowfallAmount.unit}${period}`);
131
+ }
132
+ if (snowData.iceAccumulation) {
133
+ const period = snowData.iceAccumulation.period ? ` (${snowData.iceAccumulation.period})` : '';
134
+ parts.push(`**Ice Accumulation:** ${snowData.iceAccumulation.value}${snowData.iceAccumulation.unit}${period}`);
135
+ }
136
+ return parts.length > 0 ? `\n## ❄️ Winter Weather\n\n${parts.join('\n')}\n` : '';
137
+ }
138
+ /**
139
+ * Check if there's any significant winter weather to report
140
+ */
141
+ export function hasWinterWeather(snowData) {
142
+ return !!(snowData.snowDepth || snowData.snowfallAmount || snowData.iceAccumulation);
143
+ }
144
+ //# sourceMappingURL=snow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snow.js","sourceRoot":"","sources":["../../src/utils/snow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Temperature conversion utilities
3
+ * Centralized to avoid duplication across handlers
4
+ */
5
+ /**
6
+ * Convert temperature value to Fahrenheit based on unit code
7
+ * @param value - Temperature value (can be null)
8
+ * @param unitCode - Unit code from NOAA API (e.g., "wmoUnit:degC", "wmoUnit:degF")
9
+ * @returns Temperature in Fahrenheit, or null if value is null
10
+ */
11
+ export declare function convertToFahrenheit(value: number | null, unitCode: string): number | null;
12
+ //# sourceMappingURL=temperatureConversion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temperatureConversion.d.ts","sourceRoot":"","sources":["../../src/utils/temperatureConversion.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGzF"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Temperature conversion utilities
3
+ * Centralized to avoid duplication across handlers
4
+ */
5
+ import { celsiusToFahrenheit } from './units.js';
6
+ /**
7
+ * Convert temperature value to Fahrenheit based on unit code
8
+ * @param value - Temperature value (can be null)
9
+ * @param unitCode - Unit code from NOAA API (e.g., "wmoUnit:degC", "wmoUnit:degF")
10
+ * @returns Temperature in Fahrenheit, or null if value is null
11
+ */
12
+ export function convertToFahrenheit(value, unitCode) {
13
+ if (value === null)
14
+ return null;
15
+ return unitCode.includes('degC') ? celsiusToFahrenheit(value) : value;
16
+ }
17
+ //# sourceMappingURL=temperatureConversion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temperatureConversion.js","sourceRoot":"","sources":["../../src/utils/temperatureConversion.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAoB,EAAE,QAAgB;IACxE,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,OAAO,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACxE,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Utility functions for timezone-aware time formatting
3
+ * Uses Luxon for timezone support with native Intl API
4
+ */
5
+ /**
6
+ * Format an ISO 8601 datetime string in the specified timezone
7
+ * @param isoString - ISO 8601 datetime string (e.g., "2025-11-07T14:30:00+00:00")
8
+ * @param timezone - IANA timezone identifier (e.g., "America/New_York", "Asia/Tokyo")
9
+ * @param format - Optional format style ('full', 'long', 'medium', 'short')
10
+ * @returns Formatted datetime string in local timezone
11
+ */
12
+ export declare function formatInTimezone(isoString: string, timezone: string, format?: 'full' | 'long' | 'medium' | 'short'): string;
13
+ /**
14
+ * Format a date-only ISO string in the specified timezone
15
+ * @param isoString - ISO 8601 datetime string
16
+ * @param timezone - IANA timezone identifier
17
+ * @returns Formatted date string (e.g., "Nov 7, 2025")
18
+ */
19
+ export declare function formatDateInTimezone(isoString: string, timezone: string): string;
20
+ /**
21
+ * Format a time-only ISO string in the specified timezone
22
+ * @param isoString - ISO 8601 datetime string
23
+ * @param timezone - IANA timezone identifier
24
+ * @returns Formatted time string (e.g., "2:30 PM EST")
25
+ */
26
+ export declare function formatTimeInTimezone(isoString: string, timezone: string): string;
27
+ /**
28
+ * Get timezone abbreviation from IANA timezone identifier
29
+ * @param timezone - IANA timezone identifier (e.g., "America/New_York")
30
+ * @param datetime - Optional datetime to determine DST status
31
+ * @returns Timezone abbreviation (e.g., "EST" or "EDT")
32
+ */
33
+ export declare function getTimezoneAbbreviation(timezone: string, datetime?: Date): string;
34
+ /**
35
+ * Detect timezone from coordinates using a simple heuristic
36
+ * This is a fallback when timezone is not provided by APIs
37
+ * @param latitude - Latitude
38
+ * @param longitude - Longitude
39
+ * @returns Best-guess IANA timezone identifier
40
+ */
41
+ export declare function guessTimezoneFromCoords(latitude: number, longitude: number): string;
42
+ /**
43
+ * Format a time range with timezone context
44
+ * @param startTime - Start time ISO string
45
+ * @param endTime - End time ISO string
46
+ * @param timezone - IANA timezone identifier
47
+ * @returns Formatted range (e.g., "Nov 7, 2:00 PM - 5:00 PM EST")
48
+ */
49
+ export declare function formatTimeRangeInTimezone(startTime: string, endTime: string, timezone: string): string;
50
+ /**
51
+ * Check if a timezone is valid IANA timezone identifier
52
+ * @param timezone - Timezone string to validate
53
+ * @returns true if valid, false otherwise
54
+ */
55
+ export declare function isValidTimezone(timezone: string): boolean;
56
+ //# sourceMappingURL=timezone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timezone.d.ts","sourceRoot":"","sources":["../../src/utils/timezone.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAkB,GACtD,MAAM,CA2BR;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAahF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAmBhF;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,GAAG,MAAM,CAUjF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAkBnF;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,MAAM,CAmBR;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOzD"}