"""Parsing of Storm Prediction Center WWP Product."""
import re
from pyiem.models.wwp import WWPModel
from pyiem.nws.product import TextProduct
# The product format has been remarkably consistent over 16+ years!
WS_RE = re.compile(r"W(?P<typ>[ST])\s+(?P<num>\d\d\d\d)\s*P?D?S?\n")
PROB_RE = re.compile(
r"PROB OF 2 OR MORE TORNADOES\s+:\s+(?P<tornadoes_2m>[\<\>\d]+)%\n"
r"PROB OF 1 OR MORE STRONG /E?F2-E?F5/ TORNADOES\s+:"
r"\s+(?P<tornadoes_1m_strong>[\<\>\d]+)%\n"
r"PROB OF 10 OR MORE SEVERE WIND EVENTS\s+:\s+(?P<wind_10m>[\<\>\d]+)%\n"
r"PROB OF 1 OR MORE WIND EVENTS >= 65 KNOTS\s+:"
r"\s+(?P<wind_1m_65kt>[\<\>\d]+)%\n"
r"PROB OF 10 OR MORE SEVERE HAIL EVENTS\s+:\s+(?P<hail_10m>[\<\>\d]+)%\n"
r"PROB OF 1 OR MORE HAIL EVENTS >= 2 INCHES\s+:"
r"\s+(?P<hail_1m_2inch>[\<\>\d]+)%\n"
r"PROB OF 6 OR MORE COMBINED SEVERE HAIL/WIND EVENTS\s+:"
r"\s+(?P<wind_hail_6m>[\<\>\d]+)%\n"
)
ATTR_RE = re.compile(
r"MAX HAIL /INCHES/\s+:\s+(?P<max_hail_size>[\<\d\.]+)\n"
r"MAX WIND GUSTS SURFACE /KNOTS/\s+:\s+(?P<max_wind_gust_knots>[\<\d]+)\n"
r"MAX TOPS /X 100 FEET/\s+:\s+(?P<tops>\d*)\n"
r"MEAN STORM MOTION VECTOR /DEGREES AND KNOTS/\s+:"
r"\s+(?P<drct>\d\d\d)(?P<sknt>\d\d)\n"
r"PARTICULARLY DANGEROUS SITUATION\s+:\s+(?P<is_pds>NO|YES)"
)
def _convprob(val):
"""Safe conversion."""
# appears currently that these values are always static
return int(val.replace(">", "").replace("<", ""))
def _parse_data(tp):
"""Fill out the data model."""
ws = WS_RE.search(tp.unixtext).groupdict()
prob = PROB_RE.search(tp.unixtext).groupdict()
attr = ATTR_RE.search(tp.unixtext).groupdict()
return WWPModel(
typ="TOR" if ws["typ"] == "T" else "SVR",
num=int(ws["num"]),
tornadoes_2m=_convprob(prob["tornadoes_2m"]),
tornadoes_1m_strong=_convprob(prob["tornadoes_1m_strong"]),
wind_10m=_convprob(prob["wind_10m"]),
wind_1m_65kt=_convprob(prob["wind_1m_65kt"]),
hail_10m=_convprob(prob["hail_10m"]),
hail_1m_2inch=_convprob(prob["hail_1m_2inch"]),
hail_wind_6m=_convprob(prob["wind_hail_6m"]),
max_hail_size=float(attr["max_hail_size"]),
max_wind_gust_knots=int(attr["max_wind_gust_knots"]),
max_tops_feet=int(attr["tops"]) * 100.0,
storm_motion_drct=int(attr["drct"]),
storm_motion_sknt=int(attr["sknt"]),
is_pds=(attr["is_pds"] == "YES"),
)
[docs]
class WWPProduct(TextProduct):
"""Class representing a WWP Product"""
def __init__(self, text, utcnow=None):
"""Constructor
Args:
text (str): text to parse
"""
super().__init__(text, utcnow=utcnow, ugc_provider={})
self.data = _parse_data(self)
[docs]
def is_test(self):
"""Is this a test product?"""
return self.data.num > 9000 or self.unixtext.find("...TEST") > 0
[docs]
def sql(self, txn):
"""Do the necessary database work
Args:
(psycopg.transaction): a database transaction
"""
# First, check to see if we already have this num
txn.execute(
"SELECT num from watches where "
"extract(year from issued at time zone 'UTC') = %s and num = %s "
"and type = %s",
(self.valid.year, self.data.num, self.data.typ),
)
if txn.rowcount == 0:
# Insert an entry
txn.execute(
"INSERT into watches (num, issued, type) VALUES (%s, %s, %s)",
(self.data.num, self.valid, self.data.typ),
)
# Now, update the data
txn.execute(
"UPDATE watches SET "
"tornadoes_2m = %s, "
"tornadoes_1m_strong = %s, "
"wind_10m = %s, "
"wind_1m_65kt = %s, "
"hail_10m = %s, "
"hail_1m_2inch = %s, "
"hail_wind_6m = %s, "
"max_hail_size = %s, "
"max_wind_gust_knots = %s, "
"max_tops_feet = %s, "
"storm_motion_drct = %s, "
"storm_motion_sknt = %s, "
"is_pds = %s, "
"product_id_wwp = %s "
"WHERE extract(year from issued at time zone 'UTC') = %s "
"and num = %s",
(
self.data.tornadoes_2m,
self.data.tornadoes_1m_strong,
self.data.wind_10m,
self.data.wind_1m_65kt,
self.data.hail_10m,
self.data.hail_1m_2inch,
self.data.hail_wind_6m,
self.data.max_hail_size,
self.data.max_wind_gust_knots,
self.data.max_tops_feet,
self.data.storm_motion_drct,
self.data.storm_motion_sknt,
self.data.is_pds,
self.get_product_id(),
self.valid.year,
self.data.num,
),
)
[docs]
def parser(text, utcnow=None, _ugc_provider=None, _nwsli_provider=None):
"""Parse SPC WWP Product.
Args:
text (str): the raw text to parse
utcnow (datetime): the current datetime with timezone set!
Returns:
WWPProduct instance
"""
return WWPProduct(text, utcnow=utcnow)