Source code for pyiem.nws.products.wwp

"""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)