|
| 1 | +//! # List of SAME Events Codes Known to `sameplace` |
| 2 | +//! |
| 3 | +//! | `XYZ` | Description | |
| 4 | +//! |-------|----------------------------------------| |
| 5 | +//! | `ADR` | Administrative Message | |
| 6 | +//! | `AVA` | Avalanche Watch | |
| 7 | +//! | `AVW` | Avalanche Warning | |
| 8 | +//! | `BLU` | Blue Alert | |
| 9 | +//! | `BZW` | Blizzard Warning | |
| 10 | +//! | `CAE` | Child Abduction Emergency | |
| 11 | +//! | `CDW` | Civil Danger Warning | |
| 12 | +//! | `CEM` | Civil Emergency Message | |
| 13 | +//! | `CFA` | Coastal Flood Watch | |
| 14 | +//! | `CFW` | Coastal Flood Warning | |
| 15 | +//! | `DMO` | Practice/Demo Warning | |
| 16 | +//! | `DSW` | Dust Storm Warning | |
| 17 | +//! | `EAN` | National Emergency Message | |
| 18 | +//! | `EQW` | Earthquake Warning | |
| 19 | +//! | `EVI` | Evacuation Immediate | |
| 20 | +//! | `EWW` | Extreme Wind Warning | |
| 21 | +//! | `FFA` | Flash Flood Watch | |
| 22 | +//! | `FFS` | Flash Flood Statement | |
| 23 | +//! | `FFW` | Flash Flood Warning | |
| 24 | +//! | `FLA` | Flood Watch | |
| 25 | +//! | `FLS` | Flood Statement | |
| 26 | +//! | `FLW` | Flood Warning | |
| 27 | +//! | `FRW` | Fire Warning | |
| 28 | +//! | `FSW` | Flash Freeze Warning | |
| 29 | +//! | `FZW` | Freeze Warning | |
| 30 | +//! | `HLS` | Hurricane Local Statement | |
| 31 | +//! | `HMW` | Hazardous Materials Warning | |
| 32 | +//! | `HUA` | Hurricane Watch | |
| 33 | +//! | `HUW` | Hurricane Warning | |
| 34 | +//! | `HWA` | High Wind Watch | |
| 35 | +//! | `HWW` | High Wind Warning | |
| 36 | +//! | `LAE` | Local Area Emergency | |
| 37 | +//! | `LEW` | Law Enforcement Warning | |
| 38 | +//! | `NAT` | National Audible Test | |
| 39 | +//! | `NIC` | National Information Center | |
| 40 | +//! | `NMN` | Network Notification Message | |
| 41 | +//! | `NPT` | National Periodic Test | |
| 42 | +//! | `NST` | National Silent Test | |
| 43 | +//! | `NUW` | Nuclear Power Plant Warning | |
| 44 | +//! | `RHW` | Radiological Hazard Warning | |
| 45 | +//! | `RMT` | Required Monthly Test | |
| 46 | +//! | `RWT` | Required Weekly Test | |
| 47 | +//! | `SMW` | Special Marine Warning | |
| 48 | +//! | `SPS` | Special Weather Statement | |
| 49 | +//! | `SPW` | Shelter In-Place warning | |
| 50 | +//! | `SQW` | Snow Squall Warning | |
| 51 | +//! | `SSA` | Storm Surge Watch | |
| 52 | +//! | `SSW` | Storm Surge Warning | |
| 53 | +//! | `SVA` | Severe Thunderstorm Watch | |
| 54 | +//! | `SVR` | Severe Thunderstorm Warning | |
| 55 | +//! | `SVS` | Severe Weather Statement | |
| 56 | +//! | `TOA` | Tornado Watch | |
| 57 | +//! | `TOE` | 911 Telephone Outage Emergency | |
| 58 | +//! | `TOR` | Tornado Warning | |
| 59 | +//! | `TRA` | Tropical Storm Watch | |
| 60 | +//! | `TRW` | Tropical Storm Warning | |
| 61 | +//! | `TSA` | Tsunami Watch | |
| 62 | +//! | `TSW` | Tsunami Warning | |
| 63 | +//! | `VOW` | Volcano Warning | |
| 64 | +//! | `WSA` | Winter Storm Watch | |
| 65 | +//! | `WSW` | Winter Storm Warning | |
| 66 | +//! |
| 67 | +//! SAME event codes for the United States are given in |
| 68 | +//! [NWSI 10-1712](https://www.nws.noaa.gov/directives/sym/pd01017012curr.pdf). |
| 69 | +//! |
| 70 | +//! ## See Also |
| 71 | +//! |
| 72 | +//! * [`EventCode`](crate::EventCode) |
| 73 | +//! * [`MessageHeader::event()`](crate::MessageHeader::event) |
| 74 | +
|
| 75 | +use phf::phf_map; |
| 76 | + |
| 77 | +use crate::{Phenomenon, SignificanceLevel}; |
| 78 | + |
| 79 | +/// An entry in [`CODEBOOK`]. |
| 80 | +pub(crate) type CodeEntry = (Phenomenon, SignificanceLevel); |
| 81 | + |
| 82 | +/// Lookup a three-character SAME event code in the database |
| 83 | +/// |
| 84 | +/// If the input `code` matches a `CodeEntry` that is known to |
| 85 | +/// sameplace, returns it. If no exact match could be found, the |
| 86 | +/// third character is matched as a significance level only. If |
| 87 | +/// even that does not match, returns `None`. |
| 88 | +pub(crate) fn parse_event<S>(code: S) -> Option<CodeEntry> |
| 89 | +where |
| 90 | + S: AsRef<str>, |
| 91 | +{ |
| 92 | + let code = code.as_ref(); |
| 93 | + if code.len() != 3 { |
| 94 | + // invalid |
| 95 | + return None; |
| 96 | + } |
| 97 | + |
| 98 | + // try the full three-character code first |
| 99 | + lookup_threecharacter(code) |
| 100 | + // if not, lookup the two-character code + significance |
| 101 | + .or_else(|| lookup_twocharacter(code)) |
| 102 | + // if not, is the last character a known SignificanceLevel? |
| 103 | + .or_else(|| lookup_onecharacter(code)) |
| 104 | + // otherwise → None |
| 105 | +} |
| 106 | + |
| 107 | +/// Database of three-character SAME event codes. |
| 108 | +/// |
| 109 | +/// All three-character codes imply a significance level: |
| 110 | +/// the `RWT` will always have a significance of `Test`. |
| 111 | +static CODEBOOK3: phf::Map<&'static str, CodeEntry> = phf_map! { |
| 112 | + // national activations |
| 113 | + "EAN" => (Phenomenon::NationalEmergency, SignificanceLevel::Warning), |
| 114 | + "NIC" => (Phenomenon::NationalInformationCenter, SignificanceLevel::Statement), |
| 115 | + |
| 116 | + // tests |
| 117 | + "DMO" => (Phenomenon::PracticeDemoWarning, SignificanceLevel::Warning), |
| 118 | + "NAT" => (Phenomenon::NationalAudibleTest, SignificanceLevel::Test), |
| 119 | + "NPT" => (Phenomenon::NationalPeriodicTest, SignificanceLevel::Test), |
| 120 | + "NST" => (Phenomenon::NationalSilentTest, SignificanceLevel::Test), |
| 121 | + "RMT" => (Phenomenon::RequiredMonthlyTest, SignificanceLevel::Test), |
| 122 | + "RWT" => (Phenomenon::RequiredWeeklyTest, SignificanceLevel::Test), |
| 123 | + |
| 124 | + // civil authority codes |
| 125 | + "ADR" => (Phenomenon::AdministrativeMessage, SignificanceLevel::Statement), |
| 126 | + "BLU" => (Phenomenon::BlueAlert, SignificanceLevel::Warning), |
| 127 | + "CAE" => (Phenomenon::ChildAbduction, SignificanceLevel::Emergency), |
| 128 | + "CDW" => (Phenomenon::CivilDanger, SignificanceLevel::Warning), |
| 129 | + "CEM" => (Phenomenon::CivilEmergency, SignificanceLevel::Warning), |
| 130 | + "EQW" => (Phenomenon::Earthquake, SignificanceLevel::Warning), |
| 131 | + "EVI" => (Phenomenon::Evacuation, SignificanceLevel::Warning), |
| 132 | + "FRW" => (Phenomenon::Fire, SignificanceLevel::Warning), |
| 133 | + "HMW" => (Phenomenon::HazardousMaterials, SignificanceLevel::Warning), |
| 134 | + "LAE" => (Phenomenon::LocalAreaEmergency, SignificanceLevel::Emergency), |
| 135 | + "LEW" => (Phenomenon::LawEnforcementWarning, SignificanceLevel::Warning), |
| 136 | + "NMN" => (Phenomenon::NetworkMessageNotification, SignificanceLevel::Statement), |
| 137 | + "NUW" => (Phenomenon::NuclearPowerPlant, SignificanceLevel::Warning), |
| 138 | + "RHW" => (Phenomenon::RadiologicalHazard, SignificanceLevel::Warning), |
| 139 | + "SPW" => (Phenomenon::ShelterInPlace, SignificanceLevel::Warning), |
| 140 | + "TOE" => (Phenomenon::TelephoneOutage, SignificanceLevel::Emergency), |
| 141 | + "VOW" => (Phenomenon::Volcano, SignificanceLevel::Warning), |
| 142 | + |
| 143 | + // weather codes, three-character |
| 144 | + "HLS" => (Phenomenon::HurricaneLocalStatement, SignificanceLevel::Statement), |
| 145 | + "SPS" => (Phenomenon::SpecialWeatherStatement, SignificanceLevel::Statement), |
| 146 | + "SVR" => (Phenomenon::SevereThunderstorm, SignificanceLevel::Warning), |
| 147 | + "SVS" => (Phenomenon::SevereWeather, SignificanceLevel::Statement), |
| 148 | + "TOR" => (Phenomenon::Tornado, SignificanceLevel::Warning), |
| 149 | + |
| 150 | + // "flash freeze warning" is Canada-only and not a NWS VTEC code |
| 151 | + "FSW" => (Phenomenon::FlashFreeze, SignificanceLevel::Warning), |
| 152 | +}; |
| 153 | + |
| 154 | +/// Database of two-character (plus significance) SAME codes |
| 155 | +/// |
| 156 | +/// Two-character codes follow a standard convention set by |
| 157 | +/// the National Weather Service: the last character is the |
| 158 | +/// significance level. |
| 159 | +static CODEBOOK2: phf::Map<&'static str, Phenomenon> = phf_map! { |
| 160 | + // civil authority codes, two-character with standard significance |
| 161 | + "AV" => Phenomenon::Avalanche, |
| 162 | + |
| 163 | + // weather codes, two-character with standard significance |
| 164 | + "BZ" => Phenomenon::Blizzard, |
| 165 | + "CF" => Phenomenon::CoastalFlood, |
| 166 | + "DS" => Phenomenon::DustStorm, |
| 167 | + "EW" => Phenomenon::ExtremeWind, |
| 168 | + "FF" => Phenomenon::FlashFlood, |
| 169 | + "FL" => Phenomenon::Flood, |
| 170 | + "FZ" => Phenomenon::Freeze, |
| 171 | + "HU" => Phenomenon::Hurricane, |
| 172 | + "HW" => Phenomenon::HighWind, |
| 173 | + "SM" => Phenomenon::SpecialMarine, |
| 174 | + "SQ" => Phenomenon::SnowSquall, |
| 175 | + "SS" => Phenomenon::StormSurge, |
| 176 | + "SV" => Phenomenon::SevereThunderstorm, |
| 177 | + "TO" => Phenomenon::Tornado, |
| 178 | + "TR" => Phenomenon::TropicalStorm, |
| 179 | + "TS" => Phenomenon::Tsunami, |
| 180 | + "WS" => Phenomenon::WinterStorm, |
| 181 | +}; |
| 182 | + |
| 183 | +/// Get codebook entry for full code like "`RWT`" |
| 184 | +fn lookup_threecharacter(code: &str) -> Option<CodeEntry> { |
| 185 | + CODEBOOK3.get(code.get(0..3)?).cloned() |
| 186 | +} |
| 187 | + |
| 188 | +/// Convert `BZx` → `CodeEntry` with proper significance |
| 189 | +fn lookup_twocharacter(code: &str) -> Option<CodeEntry> { |
| 190 | + let phenom = CODEBOOK2.get(code.get(0..2)?).cloned()?; |
| 191 | + Some((phenom, code.get(2..3)?.into())) |
| 192 | +} |
| 193 | + |
| 194 | +/// Convert `??x` → Unrecognized event with parsed significance |
| 195 | +fn lookup_onecharacter(code: &str) -> Option<CodeEntry> { |
| 196 | + Some((Phenomenon::Unrecognized, code.get(2..3)?.into())) |
| 197 | +} |
| 198 | + |
| 199 | +#[cfg(test)] |
| 200 | +mod tests { |
| 201 | + use super::*; |
| 202 | + |
| 203 | + use std::collections::HashSet; |
| 204 | + |
| 205 | + use lazy_static::lazy_static; |
| 206 | + use regex::Regex; |
| 207 | + use strum::IntoEnumIterator; |
| 208 | + |
| 209 | + /// ensure we have populated our codebooks correctly |
| 210 | + #[test] |
| 211 | + fn check_codebooks() { |
| 212 | + lazy_static! { |
| 213 | + static ref ASCII_UPPER: Regex = Regex::new(r"^[[A-Z]]{2,3}$").expect("bad test regexp"); |
| 214 | + } |
| 215 | + |
| 216 | + let mut codebook_phenomenon = HashSet::new(); |
| 217 | + |
| 218 | + for (key, val) in CODEBOOK3.entries() { |
| 219 | + assert!(key.is_ascii()); |
| 220 | + assert_eq!(key.len(), 3); |
| 221 | + ASCII_UPPER.is_match(key); |
| 222 | + assert_ne!(Phenomenon::Unrecognized, val.0); |
| 223 | + assert_ne!(SignificanceLevel::Unknown, val.1); |
| 224 | + codebook_phenomenon.insert(val.0); |
| 225 | + } |
| 226 | + |
| 227 | + for (key, val) in CODEBOOK2.entries() { |
| 228 | + assert!(key.is_ascii()); |
| 229 | + assert_eq!(key.len(), 2); |
| 230 | + ASCII_UPPER.is_match(key); |
| 231 | + assert_ne!(&Phenomenon::Unrecognized, val); |
| 232 | + codebook_phenomenon.insert(*val); |
| 233 | + } |
| 234 | + |
| 235 | + // check that every Phenomenon is covered by at least one codebook entry |
| 236 | + for phen in Phenomenon::iter() { |
| 237 | + if phen.is_unrecognized() { |
| 238 | + continue; |
| 239 | + } |
| 240 | + |
| 241 | + assert!( |
| 242 | + codebook_phenomenon.contains(&phen), |
| 243 | + "phenomenon {} not covered by any codebook entries", |
| 244 | + phen |
| 245 | + ); |
| 246 | + } |
| 247 | + } |
| 248 | +} |
0 commit comments