Skip to content

Commit baf8d29

Browse files
author
Stew Alexander
committed
Fix winter weather advisory threat level and enhance detection coverage
Winter Weather Detection Fixes: - Fixed alert polling to detect winter weather advisory changes - Enhanced threat level update logic for winter weather advisories - Added debug logging for winter weather detection - Improved status tracking and comparison Enhanced Winter Weather Detection: - Expanded alert types from 13 to 26+ NWS products • Added: Winter Weather Warning, Freezing Rain Warning • Added: Snow Squall Warning, Lake Effect Snow Warning/Advisory • Added: Extreme Cold Warning/Watch, Hard Freeze Warning • Added: Freeze Watch, Freezing Rain Watch, and more - Expanded phenomena keywords from 24 to 40+ terms • Added: Blowing snow, drifting snow, snowdrift • Added: Lake effect snow, upslope snow, glaze ice • Added: Freezing fog, mixed precipitation • Added: Wind chill factor, extreme cold, bitter cold • Added: Near-zero visibility, hazardous winter weather - Improved detection logic for warnings vs advisories - Enhanced forecast discussion text analysis Test Suite Updates: - Updated all test files to match comprehensive detection - Expanded winter weather tests from 11 to 26 tests - Added tests for all new alert types (16 alert detection tests) - Enhanced detection scenario tests (9 scenarios) - Total test count: 49 tests (was 34), all passing Files Changed: - Severe-Weather-Dashboard.html: Enhanced detection + alert polling fix - test-dashboard.html: Expanded test coverage - run_tests.py: Updated comprehensive winter weather tests - run-tests.js: Updated comprehensive winter weather tests All 49 tests passing with 100% success rate.
1 parent d20be04 commit baf8d29

4 files changed

Lines changed: 378 additions & 66 deletions

File tree

Severe-Weather-Dashboard.html

Lines changed: 113 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -569,38 +569,65 @@ <h3 style="margin: 0 0 15px 0; color: #ff7043; font-size: 1.4em; font-weight: 60
569569
* @constant {Object<string, Array<string>>}
570570
*/
571571
const WINTER_WEATHER_SYNONYMS = {
572-
// Official NWS alert types
572+
// Official NWS alert types - comprehensive list
573573
alerts: [
574+
// Advisories
574575
'winter weather advisory',
576+
'freezing rain advisory',
577+
'snow advisory',
578+
'wind chill advisory',
579+
'frost advisory',
580+
'lake effect snow advisory',
581+
'winter weather statement',
582+
// Warnings
575583
'winter storm warning',
576-
'winter storm watch',
584+
'winter weather warning',
577585
'ice storm warning',
578-
'ice storm watch',
579586
'blizzard warning',
580-
'blizzard watch',
581-
'freezing rain advisory',
582-
'snow advisory',
583587
'wind chill warning',
584-
'wind chill advisory',
585588
'freeze warning',
586-
'frost advisory'
589+
'freezing rain warning',
590+
'snow squall warning',
591+
'lake effect snow warning',
592+
'extreme cold warning',
593+
'hard freeze warning',
594+
// Watches
595+
'winter storm watch',
596+
'ice storm watch',
597+
'blizzard watch',
598+
'wind chill watch',
599+
'extreme cold watch',
600+
'freeze watch',
601+
'freezing rain watch',
602+
'lake effect snow watch'
587603
],
588-
// Weather phenomena keywords
604+
// Weather phenomena keywords - comprehensive coverage
589605
phenomena: [
590-
'snow', 'snowfall', 'snowing', 'snowstorm',
591-
'freezing rain', 'freezing drizzle',
592-
'sleet', 'sleeting',
593-
'ice', 'icing', 'black ice',
606+
// Snow terms
607+
'snow', 'snowfall', 'snowing', 'snowstorm', 'snowfall',
608+
'snow squall', 'snow shower', 'snow flurries', 'flurries',
609+
'blowing snow', 'drifting snow', 'snowdrift',
610+
'lake effect snow', 'upslope snow',
611+
'accumulating snow', 'snow accumulation', 'snow totals',
612+
'snowfall rates', 'heavy snow', 'significant snow',
613+
// Ice terms
614+
'ice', 'icing', 'black ice', 'glaze ice',
615+
'freezing rain', 'freezing drizzle', 'freezing fog',
616+
'ice accumulation', 'ice buildup', 'ice storm',
617+
'ice formation', 'ice coating',
618+
// Mixed precipitation
619+
'sleet', 'sleeting', 'sleet pellets',
594620
'wintry', 'wintry mix', 'winter precipitation',
595-
'winter conditions', 'winter weather',
596-
'accumulating snow', 'snow accumulation',
597-
'ice accumulation', 'ice buildup',
598-
'snowfall rates', 'snow totals',
621+
'freezing precipitation', 'mixed precipitation',
622+
// Cold conditions
599623
'freezing temperatures', 'below freezing',
600-
'wind chill', 'windchill',
601-
'blizzard', 'whiteout',
602-
'snow squall', 'snow shower',
603-
'flurries', 'wintry precipitation'
624+
'wind chill', 'windchill', 'wind chill factor',
625+
'extreme cold', 'bitter cold', 'dangerous cold',
626+
'hard freeze', 'freeze', 'frost',
627+
// Visibility/conditions
628+
'blizzard', 'whiteout', 'near-zero visibility',
629+
'winter conditions', 'winter weather',
630+
'hazardous winter weather', 'winter storm'
604631
],
605632
// Timing/severity indicators
606633
severity: [
@@ -658,19 +685,30 @@ <h3 style="margin: 0 0 15px 0; color: #ff7043; font-size: 1.4em; font-weight: 60
658685
? alert.properties.severity.toLowerCase()
659686
: '';
660687

661-
// Check for warnings (highest priority)
662-
if (eventLower.includes('warning') && !eventLower.includes('watch') && !eventLower.includes('advisory')) {
688+
// Check for warnings (highest priority) - comprehensive detection
689+
// Look for "warning" keyword but exclude "watch" and "advisory" variations
690+
if (eventLower.includes('warning') &&
691+
!eventLower.includes('watch') &&
692+
!eventLower.includes('advisory') &&
693+
!eventLower.includes('statement')) {
663694
hasWarning = true;
695+
console.log(`[Winter Weather] WARNING detected: "${event}"`);
664696
}
665697
// Check for advisories (lower priority)
666-
else if (eventLower.includes('advisory') || (eventLower.includes('watch') && severity !== 'severe')) {
698+
// Includes advisory, watch (unless severe), and statements
699+
else if (eventLower.includes('advisory') ||
700+
eventLower.includes('statement') ||
701+
(eventLower.includes('watch') && severity !== 'severe')) {
667702
hasAdvisory = true;
703+
console.log(`[Winter Weather] ADVISORY/WATCH detected: "${event}"`);
668704
}
669705
}
670706

671707
if (hasWarning) {
708+
console.log(`[Winter Weather] Status determined: WARNING (has ${hasWarning ? 'warning' : ''}, ${hasAdvisory ? 'advisory' : ''})`);
672709
return { status: 'warning', hasAdvisory: true, hasWarning: true };
673710
} else if (hasAdvisory) {
711+
console.log(`[Winter Weather] Status determined: ADVISORY`);
674712
return { status: 'advisory', hasAdvisory: true, hasWarning: false };
675713
}
676714

@@ -711,25 +749,44 @@ <h3 style="margin: 0 0 15px 0; color: #ff7043; font-size: 1.4em; font-weight: 60
711749
}
712750
}
713751

714-
// Check for official alert terminology in text
752+
// Check for official alert terminology in text (higher weight for official terms)
715753
for (const alert of WINTER_WEATHER_SYNONYMS.alerts) {
716754
if (textLower.includes(alert)) {
717755
score += 3;
718-
if (alert.includes('warning')) {
756+
// Check for warning terms (higher priority)
757+
if (alert.includes('warning') && !alert.includes('watch') && !alert.includes('advisory')) {
719758
hasWarningTerms = true;
720-
} else if (alert.includes('advisory')) {
759+
}
760+
// Check for advisory/watch/statement terms
761+
else if (alert.includes('advisory') || alert.includes('watch') || alert.includes('statement')) {
721762
hasAdvisoryTerms = true;
722763
}
723764
}
724765
}
725766

726767
// Determine status based on score and indicators
727768
if (score >= 5) {
728-
if (hasWarningTerms || textLower.includes('winter storm warning') || textLower.includes('blizzard warning')) {
769+
// Check for winter weather warning terms (comprehensive detection)
770+
const warningPatterns = [
771+
'winter storm warning', 'winter weather warning', 'ice storm warning',
772+
'blizzard warning', 'freezing rain warning', 'snow squall warning',
773+
'wind chill warning', 'extreme cold warning'
774+
];
775+
const hasWarningPattern = warningPatterns.some(pattern => textLower.includes(pattern));
776+
777+
if (hasWarningTerms || hasWarningPattern) {
778+
console.log('[Winter Weather] Text detection: WARNING status');
729779
return { status: 'warning', confidence: Math.min(score, 10) };
730-
} else if (hasAdvisoryTerms || textLower.includes('winter weather advisory')) {
780+
}
781+
// Check for advisory/watch/statement terms
782+
else if (hasAdvisoryTerms ||
783+
textLower.includes('winter weather advisory') ||
784+
textLower.includes('winter storm watch') ||
785+
textLower.includes('winter weather statement')) {
786+
console.log('[Winter Weather] Text detection: ADVISORY status');
731787
return { status: 'advisory', confidence: Math.min(score, 8) };
732788
} else {
789+
console.log('[Winter Weather] Text detection: ADVISORY status (generic winter weather)');
733790
return { status: 'advisory', confidence: Math.min(score, 6) };
734791
}
735792
}
@@ -790,8 +847,14 @@ <h3 style="margin: 0 0 15px 0; color: #ff7043; font-size: 1.4em; font-weight: 60
790847
if (data && data.features && Array.isArray(data.features) && data.features.length > 0) {
791848
// Detect winter weather from alerts
792849
const winterWeather = detectWinterWeatherFromAlerts(data.features);
850+
const previousWinterStatus = winterWeatherStatus;
793851
winterWeatherStatus = winterWeather.status;
794852

853+
// Debug: Log winter weather detection
854+
if (winterWeatherStatus !== 'none') {
855+
console.log(`Winter weather detected: ${winterWeatherStatus} (was: ${previousWinterStatus})`);
856+
}
857+
795858
// Filter alerts to show only warnings/watches/advisories with valid severity
796859
// Excludes informational statements and other non-actionable alerts
797860
// Validates alert structure before processing
@@ -966,6 +1029,7 @@ <h3 style="margin: 0 0 15px 0; color: #ff7043; font-size: 1.4em; font-weight: 60
9661029
threatColor = '#f44336';
9671030
description = 'Winter Precipitation Imminent and/or Occurring';
9681031
icon = '⚠️';
1032+
console.log('[Threat Level] Set to WARNING due to winter weather warning');
9691033
} else {
9701034
threatLevel = 'WARNING';
9711035
threatColor = '#f44336';
@@ -979,6 +1043,7 @@ <h3 style="margin: 0 0 15px 0; color: #ff7043; font-size: 1.4em; font-weight: 60
9791043
threatColor = '#FFEB3B';
9801044
description = 'Monitor for Winter Conditions';
9811045
icon = '❄️';
1046+
console.log('Threat level set to MONITOR due to winter weather advisory');
9821047
}
9831048
// Priority 2: Enhanced/Moderate/High SPC risk levels
9841049
else if (spcRiskLevel && ['ENH', 'MDT', 'HIGH'].includes(spcRiskLevel)) {
@@ -1406,19 +1471,34 @@ <h3 style="margin: 0 0 15px 0; color: #ff7043; font-size: 1.4em; font-weight: 60
14061471
setInterval(updateDashboard, updateInterval);
14071472

14081473
// Independent 3-minute polling for alerts to keep them fresh
1409-
// This allows warnings to appear quickly without waiting for full refresh
1474+
// This allows warnings and winter weather advisories to appear quickly without waiting for full refresh
14101475
setInterval(async function() {
14111476
try {
1477+
const previousWinterStatus = winterWeatherStatus;
14121478
const hadWarnings = await updateLocalAlertsAndGetStatus();
1413-
// If warning state has changed, refresh threat card quickly
14141479
const currentThreat = document.getElementById('threat-status')?.textContent || '';
1415-
if ((hadWarnings && currentThreat !== 'WARNING') || (!hadWarnings && currentThreat === 'WARNING')) {
1480+
1481+
// Check if warning state has changed
1482+
const warningStateChanged = (hadWarnings && currentThreat !== 'WARNING') || (!hadWarnings && currentThreat === 'WARNING');
1483+
1484+
// Check if winter weather status has changed
1485+
const winterStatusChanged = previousWinterStatus !== winterWeatherStatus;
1486+
1487+
// Check if current threat level doesn't match the winter weather status
1488+
const threatMismatch = (winterWeatherStatus === 'advisory' && currentThreat !== 'MONITOR') ||
1489+
(winterWeatherStatus === 'warning' && currentThreat !== 'WARNING') ||
1490+
(winterWeatherStatus === 'none' && previousWinterStatus !== 'none' && currentThreat !== 'SAFE');
1491+
1492+
// Update if any of these conditions are true
1493+
if (warningStateChanged || winterStatusChanged || threatMismatch) {
14161494
const prevLast = lastUpdate;
14171495
lastUpdate = 0; // force refresh
14181496
await updateDashboard();
14191497
lastUpdate = prevLast || Date.now();
14201498
}
1421-
} catch (e) {}
1499+
} catch (e) {
1500+
console.error('Alert polling error:', e);
1501+
}
14221502
}, 180000);
14231503
document.addEventListener('visibilitychange', function() {
14241504
if (!document.hidden && Date.now() - lastUpdate > updateInterval) {

run-tests.js

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -264,13 +264,35 @@ function runTestsDirect() {
264264

265265
const WINTER_WEATHER_SYNONYMS = {
266266
alerts: [
267+
// Advisories
267268
'winter weather advisory',
269+
'freezing rain advisory',
270+
'snow advisory',
271+
'wind chill advisory',
272+
'frost advisory',
273+
'lake effect snow advisory',
274+
'winter weather statement',
275+
// Warnings
268276
'winter storm warning',
269-
'winter storm watch',
277+
'winter weather warning',
270278
'ice storm warning',
271279
'blizzard warning',
272-
'freezing rain advisory',
273-
'snow advisory'
280+
'wind chill warning',
281+
'freeze warning',
282+
'freezing rain warning',
283+
'snow squall warning',
284+
'lake effect snow warning',
285+
'extreme cold warning',
286+
'hard freeze warning',
287+
// Watches
288+
'winter storm watch',
289+
'ice storm watch',
290+
'blizzard watch',
291+
'wind chill watch',
292+
'extreme cold watch',
293+
'freeze watch',
294+
'freezing rain watch',
295+
'lake effect snow watch'
274296
]
275297
};
276298

@@ -283,10 +305,23 @@ function runTestsDirect() {
283305
}
284306

285307
const winterAlertTests = [
308+
// Advisories
286309
{ event: 'Winter Weather Advisory', expected: true },
310+
{ event: 'Freezing Rain Advisory', expected: true },
311+
{ event: 'Lake Effect Snow Advisory', expected: true },
312+
{ event: 'Winter Weather Statement', expected: true },
313+
// Warnings
287314
{ event: 'Winter Storm Warning', expected: true },
315+
{ event: 'Winter Weather Warning', expected: true },
288316
{ event: 'Ice Storm Warning', expected: true },
289-
{ event: 'Severe Thunderstorm Warning', expected: false }
317+
{ event: 'Blizzard Warning', expected: true },
318+
{ event: 'Snow Squall Warning', expected: true },
319+
{ event: 'Extreme Cold Warning', expected: true },
320+
{ event: 'Lake Effect Snow Warning', expected: true },
321+
{ event: 'Freezing Rain Warning', expected: true },
322+
// Non-winter (should be rejected)
323+
{ event: 'Severe Thunderstorm Warning', expected: false },
324+
{ event: 'Tornado Watch', expected: false }
290325
];
291326

292327
winterAlertTests.forEach(test => {
@@ -325,9 +360,17 @@ function runTestsDirect() {
325360
}
326361

327362
const eventLower = event.toLowerCase();
328-
if (eventLower.includes('warning') && !eventLower.includes('watch') && !eventLower.includes('advisory')) {
363+
// Check for warnings (highest priority) - must exclude watch/advisory/statement
364+
if (eventLower.includes('warning') &&
365+
!eventLower.includes('watch') &&
366+
!eventLower.includes('advisory') &&
367+
!eventLower.includes('statement')) {
329368
hasWarning = true;
330-
} else if (eventLower.includes('advisory') || eventLower.includes('watch')) {
369+
}
370+
// Check for advisories, watches, and statements (lower priority)
371+
else if (eventLower.includes('advisory') ||
372+
eventLower.includes('statement') ||
373+
eventLower.includes('watch')) {
331374
hasAdvisory = true;
332375
}
333376
}
@@ -352,6 +395,26 @@ function runTestsDirect() {
352395
expected: 'advisory',
353396
name: 'Winter Weather Advisory detection'
354397
},
398+
{
399+
alerts: [{ properties: { event: 'Blizzard Warning', severity: 'Severe' } }],
400+
expected: 'warning',
401+
name: 'Blizzard Warning detection'
402+
},
403+
{
404+
alerts: [{ properties: { event: 'Snow Squall Warning', severity: 'Severe' } }],
405+
expected: 'warning',
406+
name: 'Snow Squall Warning detection'
407+
},
408+
{
409+
alerts: [{ properties: { event: 'Extreme Cold Warning', severity: 'Severe' } }],
410+
expected: 'warning',
411+
name: 'Extreme Cold Warning detection'
412+
},
413+
{
414+
alerts: [{ properties: { event: 'Winter Weather Statement', severity: 'Minor' } }],
415+
expected: 'advisory',
416+
name: 'Winter Weather Statement detection'
417+
},
355418
{
356419
alerts: [{ properties: { event: 'Severe Thunderstorm Warning', severity: 'Severe' } }],
357420
expected: 'none',

0 commit comments

Comments
 (0)