<?php
define("IEM_APPID", 107);
require_once "../../config/settings.inc.php";
require_once "../../include/myview.php";
require_once "../../include/database.inc.php";
require_once "../../include/network.php";
require_once "../../include/forms.php";
$t = new MyView();
$t->iemselect2 = true;
$t->iem_resource = "COW";
$t->headextra = <<<EOM
<link rel="stylesheet" href="/cow/index.css" />
EOM;

$wtype = isset($_GET["wtype"]) ? xssafe($_GET["wtype"]) : array();
$ltype = isset($_GET["ltype"]) ? xssafe($_GET["ltype"]) : array();
$syear = get_int404("syear", date("Y"));
$smonth = get_int404("smonth", 1);
$sday = get_int404("sday", 1);
$shour = get_int404("shour", 12);
$sts = new DateTime("{$syear}-{$smonth}-{$sday} {$shour}:00");

$tomorrow = new DateTime('now', new DateTimeZone("UTC"));
$tomorrow->add(new DateInterval("PT24H"));

$eyear = get_int404("eyear", $tomorrow->format("Y"));
$emonth = get_int404("emonth", $tomorrow->format("m"));
$eday = get_int404("eday", $tomorrow->format("d"));
$ehour = get_int404("ehour", 12);
$ets = new DateTime("{$eyear}-{$emonth}-{$eday} {$ehour}:00");

$hail = get_float404("hail", 1.0);
$wind = get_float404("wind", 58);
$wfo = get_str404("wfo", "MPX", 4);
$lsrbuffer = get_int404("lsrbuffer", 15);
$warnbuffer = get_float404("warnbuffer", 0.01);
$useWindHailTag = get_str404("windhailtag", 'N', 1);
$limitwarns = get_str404("limitwarns", 'N', 1);
$fcster = get_str404("fcster", '', 255);

$abegin = new DateTime("2005-08-06");
if ($sts < $abegin) {
    $sts = $abegin;
}
if ($ets < $sts) {
    $n = $sts;
    $sts = $ets;
    $ets = $n;
}

if (in_array("FF", $wtype) && $syear < 2008) {
    $wtype = array_diff($wtype, array("FF"));
}
if (in_array("FF", $ltype) && $syear < 2008) {
    $ltype = array_diff($ltype, array("FF"));
}

if (in_array("DS", $wtype) && $syear < 2018) {
    $wtype = array_diff($wtype, array("FF"));
}
if (in_array("DS", $ltype) && $syear < 2018) {
    $ltype = array_diff($ltype, array("DS"));
}

$t->title = "Cow (Storm Based Warning Verification)";
$nt = new NetworkTable("WFO");

$y1 = yearSelect(2005, $syear, "syear");
$m1 = monthSelect($smonth, "smonth");
$d1 = daySelect($sday, "sday");
$h1 = gmtHourSelect($shour, "shour");

$y2 = yearSelect(2005, $eyear, "eyear");
$m2 = monthSelect($emonth, "emonth");
$d2 = daySelect($eday, "eday");
$h2 = gmtHourSelect($ehour, "ehour");

$wselect = '<select name="wfo" class="iemselect2">';
foreach ($nt->table as $key => $value) {
    $wselect .= "<option value=\"$key\" ";
    if ($wfo == $key) $wselect .= "SELECTED";
    $wselect .= ">" . $nt->table[$key]["name"] . " [" . $key . "]</option>\n";
}
$wselect .= "</select>";

$ar = array(
    "TO" => "Tornado",
    "SV" => "Svr T'storm",
    "MA" => "Marine",
    "FF" => "Flash Flood (2008+)",
    "DS" => "Dust Storm (2018+)",
    "SQ" => "Snow Squall (2018+)",
);
$tselect = "";
foreach ($ar as $k => $v) {
    $tselect .= sprintf(
        '<div class="form-check form-check-inline">'.
        '<input class="form-check-input" name="wtype[]" type="checkbox" id="%s" value="%s"%s> ' .
            '<label class="form-check-label" for="%s">%s</label>'.
        '</div>',
        $k,
        $k,
        (in_array($k, $wtype) ? " CHECKED" : ""),
        $k,
        $v
    );
}

$ar = array(
    "T" => "Tornado",
    "D" => "Wind Damage",
    "H" => "Hail",
    "G" => "Wind Gust",
    "MA" => "Marine",
    "FF" => "Flash Flood (2008+)",
    "DS" => "Dust Storm (2018+)",
    "SQ" => "Snow Squall (Oct 2023+)",
);
$ltselect = "";
foreach ($ar as $k => $v) {
    if ($k == "MA2") $ltselect .= "<br />";
    $ltselect .= sprintf(
        '<div class="form-check form-check-inline">'.
        '<input class="form-check-input" name="ltype[]" type="checkbox" id="%s%s" value="%s"%s> ' .
            '<label class="form-check-label" for="%s%s">%s</label>'.
        '</div>',
        $k,
        (strlen($k) == 2) ? "2" : "",
        $k,
        (in_array($k, $ltype) ? " CHECKED" : ""),
        $k,
        (strlen($k) == 2) ? "2" : "",
        $v
    );
}


$ar = array(
    "0.75" => "0.75",
    "1.00" => "1.00",
    "1.75" => "1.75",
    "2.00" => "2.00",
    "2.75" => "2.75",
    "7.00" => "7.00"
);

$hselect = make_select("hail", $hail, $ar);

$ar = array("5" => "5", "15" => "15", "30" => "30");
$lbselect = make_select("lsrbuffer", $lsrbuffer, $ar);

$ar = array("0" => "0", "0.01" => "0.01");
$wbuffer = make_select("warnbuffer", $warnbuffer, $ar);

$ar = array(58 => "58 mph", 70 => "70 mph", 75 => "75 mph", 80 => "80 mph", 150 => "150 mph");
$wiselect = make_select("wind", $wind, $ar);

$tagselect = '<input id="windhailtag" name="windhailtag" type="checkbox" value="Y" ';
$tagselect .= (($useWindHailTag == 'Y') ? " CHECKED" : "");
$tagselect .= <<<EOM
> <label for="windhailtag">When possible, verify SVR warnings against
issuance WIND...HAIL tag</label>
 <a href="/nws/list_tags.php" target="_new"><i class="bi bi-link-45deg" aria-hidden="true"></i> App listing per-WFO tags</a>
<br /><input id="limitwarns" name="limitwarns" type="checkbox" value="Y"
EOM;
$tagselect .= (($limitwarns == 'Y') ? " CHECKED" : "");
$tagselect .= '> <label for="limitwarns">When possible, limit considered warnings to your hail/wind threshold</label>';

$forecaster_form = "";
$resetlink = "/cow/";
// Allow for a special bypass to expose this configuration setting
if (isset($_GET["f"])) {
    $resetlink = "/cow/?f";
    $forecaster_form = <<<EOM
    <input type="hidden" name="f" />
<br />Filter by Product Signature (exact match):<input type="text" name="fcster" size="15" value="{$fcster}" />
EOM;
}

$content = <<<EOM
<a href="{$resetlink}">Reset Application</a> (All timestamps are displayed in UTC)
<form method="GET" name="cow" rel="nofollow">

<div class="row">
    <div class="col-sm-6 mb-3 mb-sm-0">

<div class="card shadow-sm h-100 cow-filter-card">
<div class="card-body p-0">

<table class="table table-sm table-bordered align-middle mb-0">
<tr><th>Start:</th><td>{$y1} {$m1} {$d1} {$h1}</td></tr>
<tr><th>End:</th><td>{$y2} {$m2} {$d2} {$h2}</td></tr>
<tr><th>WFO:</th><td colspan="3">{$wselect}</td></tr>
<tr><th>Hail Size:</th><td>{$hselect}</td></tr>
<tr><th>LSR Buffer:</th><td>{$lbselect} km</td></tr>
<tr><th>Warning Buffer:</th><td>{$wbuffer} degrees</td></tr>
<tr><th>Wind:</th><td>{$wiselect}</td></tr>
</table>

</div>
</div>

    </div>
    <div class="col-sm-6">

<div class="card shadow-sm h-100 cow-filter-card">
<div class="card-body p-0">

<table class="table table-sm table-bordered align-middle mb-0">
<tr><th>Warning Type:</th>
<td>
<button type="button" class="btn btn-outline-secondary btn-sm mb-2">Select All</button> {$tselect}
</td></tr>

<tr><th>LSR Type:</th><td>

<button type="button" class="btn btn-outline-secondary btn-sm mb-2">Select All</button> {$ltselect}
</td></tr>

 <tr><td colspan="2">
 {$tagselect}
 {$forecaster_form}
 </td></tr>

</table>

</div>
</div>

    </div>
</div>

<input type="submit" value="Go Cow Go">
</form>
EOM;
if (isset($_GET["hail"])) {
    require_once "../../include/cow_worker.php";
} else {
    $content .= <<<EOM

<h3>IEM Cow (NWS Storm Based Warning Verification)</h3>

<p><a href="#api" class="btn btn-primary"><i class="bi bi-download" aria-hidden="true"></i> IEM Cow API Access</a></p>

<p><img src="cow.jpg" align="right" style="margin: 5px;">Data presented here is <b>unofficial</b> and should be used for
educational purposes only.  This application allows you to view warnings
and storm reports issued by a Weather Forecast Office (WFO) for a period
of time of your choice.  The application attempts to automatically provide
verification numbers for the warnings issued.

<p>This application works for dates after 8 June 2005.

<h3>Storm Based Warnings</h3>
<p>On the first of October 2007, the National Weather
Service began issuing warnings for tornados, severe weather, floods, and
marine hazards on a storm by storm basis.  Previously, the warnings
were issued for an entire county. This application attempts to provide
verification statistics for these storm based warnings.

<p>Related links:
<ul>
 <li><a href="sbwsum.phtml">Graphical Summaries of Daily SBW</a></li>
 <li><a href="sbwstats.phtml">SBW Size Statistics</a></li>
</ul>

<br clear="all"/>

<h3>Verification Methodology</h3>

<p><img src="verif_example.png" style="float: left; margin-right: 20px;" />

<p>The map on the left illustrates some of the spatial statistics the Cow
produces.  The following is a brief description of these values.

<ul>
<li><strong>SBW Area: (P)</strong> (<i>black/white outline polygon</i>) This is
the areal size of the polygon expressed in square kilometers.  The computation
is done in the projection "US National Atlas Equal Area" (EPSG 9311).</li>
<li><strong>County Area: (C)</strong> (<i>red outline polygon</i>) Each storm
based warning is associated with one or more counties.  This value is the total
area of the associated counties.  Same projected used as above.</li>
<li><strong>Size % (C-P)/C</strong> This is the size <strong>reduction</strong>
percentage of the storm based warning versus the counties. Prior to SBW,
the warning would have included the entire area of the county.  So larger
reduction values are desired.</li>
<li><strong>Perimeter Ratio:</strong> (<i>purple line</i>) This is an attempt
to determine how much of the storm based warning perimeter is being influenced
by a county political boundary.  The ratio is simply the portion of the SBW
perimeter that closely coincides with a political boundary.  A 100% value
implies the warning is simply a retracement of county borders.</li>
<li><strong>Verification Area %:</strong> (<i>filled ovals</i>) Local Storm
Reports are buffered out in a circular fashion and combined to create a
coverage of impacted areas.  The area of the buffered LSRs is then compared
with the area of the storm based warning to yield a percentage.</li>
</ul>

<h3><a name="api"></a> API for IEM Cow Data</h3>

<p>
<strong>6 April 2026</strong>:  The backend/database computation of a polygon
shared-border was too intensive and causing large requests to time-out. Code
was added to pre-compute the values and store it within the database, but there
is up to about five minutes of lag for this value to be available for near
real-time warnings.  Rewording, for warnings issued within the past five minutes,
the shared border value will be missing.
</p>

<p><strong>29 May 2024</strong>: A few updates were made to provide some
accounting of tornado <code>POSSIBLE</code> tags within Severe Thunderstorm
Warnings.</p>

<p><strong>28 Nov 2023</strong>: The option to do verification for Snow Squall
Warnings was added, but note that verifying LSRs only became an option after
1 October 2023.  So no prior warnings will show as verified prior to that date.
</p>

<p>The JSON emitting service endpoint is:
<br /><code>{$EXTERNAL_BASEURL}/api/1/cow.json</code></p>

<p>This endpoint accepts a large number of CGI parameters via HTTP GET. None of
the parameters are required.</p>

<table class="table table-striped">
<thead>
<tr><th>Parameter</th><th>Default</th><th>Description</th></tr>
</thead>
<tbody>

<tr>
 <td>callback=func</td>
 <td>not used</td>
 <td>This supports JSON-P style requests with the resulting JSON data begin
 encapsulated by a javascript function call. i.e. <code>callback=gotdata</code></td>
 </tr>

<tr>
 <td>wfo=XXX</td>
 <td>All WFOs considered</td>
 <td>This specifies the three character WFO identifier that you wish to get
 statistics for.  If none are specified, then you get the entire NWS.  You can
 provide this parameter one or more times, i.e. <code>wfo=XXX&amp;wfo=YYY</code></td>
 </tr>
<tr><th colspan="3">Time Specification Option 1</th></tr>
<tr>
 <td>begints=ISO8601<br />endts=ISO8601</td>
 <td>Last 4 Hour Period</td>
 <td>This start time and end time sets the window to look for NWS Storm Based
 Warnings and Local Storm Reports.  For the case of warnings, the warning must
 have been issued after the start time and have an expiration prior to the end
 time.  The tricky issue issue is when warnings cross either the start or end
 time, this can lead to incomplete statistics (ie a storm report was actually
 covered by a warning, but that warning was outside your time domain. <strong>These
 timestamps are in UTC.</strong>, i.e. <code>begints=2018-06-18T12:00Z&amp;endts=2018-06-19T12:00Z</code>
</td></tr>

<tr><th colspan="3">Time Specification Option 2</th></tr>
<tr>
<td>syear=YYYY smonth=MM sday=DD shour=HH24
<br />&nbsp;<br />eyear=YYYY emonth=MM eday=DD ehour=HH24
</td>
<td>Last 4 Hour Period</td>
<td>Same time details above as with Option 1, but here you are just specifying manually
each part of the date, i.e. <code>syear=2018&amp;smonth=6&amp;sday=18&amp;shour=12&amp;eyear=2018&amp;emonth=6&amp;eday=19&amp;ehour=12&amp;</code></td>
</tr>

<tr>
<td>phenomena=XX</td>
<td>All TO, SV, FF, MA, DS</td>
<td>These are the VTEC phenomena codes that you want the data and stats for.  These
are two character and the currently supported Storm Based Warning types are
Tornado (TO), Severe Thunderstorm (SV), Flash Flood (FF), Marine (MA), and
Dust Storm (DS). You can
specify more than one phenomena, i.e. <code>phenomena=TO&amp;phenomena=SV</code>
</td></tr>

<tr>
<td>lsrtype=XX</td>
<td>All TO, SV, FF, MA, DS</td>
<td>So like in the case of phenomena above, here you are specifying which
category of Local Storm Reports to consider.  This gets to be a bit difficult
to fully explain. In general, the codes reflect with Local Storm Report types
should be used that can potentially verify the warning. i.e. <code>lsrtype=TO&amp;lsrtype=SV</code>.
The two letter identifiers are aliases to explicit LSR types.  You can specify
the explicit types of <code>T</code>: Tornado, <code>G</code>: TStorm Wind Gust,
<code>D</code>: TStorm Wind Damanage, <code>H</code>: Hail.
</td></tr>

<tr>
<td>hailsize=SIZE_IN_INCHES</td>
<td>1.00 inches</td>
<td>What hail size in inches should be considered when verifying the warnings.  The
present day standard is one inch, but previously it was 0.75 inches.  This parameter
only considers one value, i.e. <code>hailsize=1.50</code></td></tr>

<tr>
<td>lsrbuffer=DIST_IN_KM</td>
<td>15 km</td>
<td>IEM Cow attempts to provide an areal verification percentage within the polygons,
this areal value is computed by buffering out the point LSR reports by the given radius
in kilometers.  The GIS operation is done in USGS Albers (EPSG:9311).
example, <code>lsrbuffer=15</code></td></tr>

<tr>
<td>warningbuffer=DIST_IN_KM</td>
<td>1 km</td>
<td>So this is kind of a bug-a-boo and dirty little secret, but the NWS Storm Based
Warnings are not necessarily exact in latitude/longitude space.  The basic data
provides polygon points with two places of decimal precision.  Given political boundaries
and other lame limitations, there are places in the country that would never receive
a warning if not for allowing the polygons to buffer out slightly for folks implementing
workflows with this data.  So we default to buffer out the warning 1km, which is used
in verification, but not in size calculations.  example, <code>warningbuffer=1</code></td>
</tr>

<tr>
<td>wind=SPEED_IN_MPH</td>
<td>58 MPH</td>
<td>For wind Local Storm Reports, what minimum speed should be considered for
verifying a warning.  This value is not used in the case of Marine Warnings.
example, <code>wind=58</code></td></tr>

<tr>
<td>windhailtag=N_or_Y</td>
<td>No</td>
<td>For Severe Thunderstorm Warnings, the tags used to denote the wind speed
and hail size at issuance are used to verify the warning.  For example, if a hail
tag of 2 inches was used at issuance, any reports below that would not be considered
as verifying the report.</td></tr>

<tr>
<td>limitwarns=N_or_Y</td>
<td>No</td>
<td>Use the <code>wind</code> and <code>hailsize</code> parameters to filter
considered warnings for the verification.  For example, if you set <code>wind=70</code>
then any warnings issued with a wind tag below 70 MPH would be ignored.</td></tr>

<tr>
<td>fcster=string</td>
<td>Not considered</td>
<td><strong>With this enabled, resulting stats should not be used</strong> This
will limit considered warnings to those signed by the exact string provided. This
creates a problematic situation with all storm reports considered, but only a subset
of warnings provided for verification.  The actual verification of individual warnings
will be accurate, but the bulk stats are not correct.
example, <code>fcster=forecaster10</code></td></tr>

</tbody>
</table>

<h4>Resulting JSON Schema.</h4>

<pre>
{"generated_at": "ISO8601",
 "params": {dictionary of how API was called},
 "stats": {dictionary of generated statistics},
   "area_verify%": float # percent of polygon area verified
   "avg_leadtime[min]": int # Average leadtime in minutes
   "avg_size[sq km]": float # Average polygon size in square kilometers
   "CSI[1]": float # Critical Success Index 0-1
   "events_verified": int # Number of warning events verified
   "events_total": int # Number of warning events considered for report
   "FAR[1]": float # False Alarm Rate 0-1
   "max_leadtime[min]": int # Longest leadtime in minutes
   "min_leadtime[min]": int # Shortest leadtime in minutes
   "POD[1]": float # Probability of Detection 0-1
   "reports_total: int # Number of LSRs considered for report
   "shared_border%": float # percent of polygon border coincident with political bounds
   "size_poly_vs_county[%s]" : float # percent of polygon size compared to county size
   "tdq_stormreports": int # number of non-verifying storm reports in a TOR but outside SVR
   "unwarned_reports": int # number of LSRs without a warning
   "svr_with_torpossible_total": int # number of SVRs with a TORNADO POSSIBLE at issuance
   "svr_with_torpossible_verified": int # number of SVRs with a TORNADO POSSIBLE at issuance that got Tornado LSR

 "events": GeoJSON-style object of warnings,
   "features": []
     "id": str # unique id for this warning
     "properties":
       "ar_ugc": [] # list of ugc codes for this warning
       "ar_ugcname": [] # list of ugc names for this warning
       "areaverify": float # percent of polygon area verified
       "carea": float # area of associated counties/parishes in square kilometers
       "eventid": int # VTEC Event Identifier
       "expire": "ISO8601" # expiration time of warning
       "fcster": str # Forecaster/Product Signature of the warning
       "hailtag": float # IBW Hail Size (inch) at issuance
       "issue": "ISO8601" # issue time of warning
       "lat0": float # latitude of polygon centroid
       "lead0": int # leadtime in minutes of the first verifying LSR
       "lon0": float # longitude of polygon centroid
       "parea": float # sq km area of polygon computed in EPSG:9311
       "perimeter": float # perimeter of polygon computed in EPSG:9311
       "phenomena": str # VTEC Phenomena Two-Letter Code
       "sharedborder": float # perimeter of polygon shared with political border
       "significance": str # VTEC Significance One-Letter Code
       "status": str # VTEC Status of last event product update
       "statuses": str # typo of status, but kept for backwards compatibility
       "stormreports": str # comma seperated list of storm report IDs that verified warning
       "stormreports_all": str # comma seperated list of all storm reports within space/time bounds
       "verify": bool # true if warning verified
       "wfo": str # WFO that issued warning
       "windtag": float # IBW Wind Speed (mph) at issuance
       "year": int # year of warning
       "tor_in_svrtorpossible": bool # did the SVR warning have a tornado POSSIBLE at issuance
 "stormreports": GeoJSON-style object on LSRS
   "features": []
     "id": int # Sequential LSR identifier used for cross-references
     "properties":
       "city": str # City name of LSR
       "county": str # County name of LSR
       "lat0": float # latitude of LSR
       "leadtime": int # leadtime in minutes to first verifying warning
       "lon0": float # longitude of LSR
       "lsrtype": str # LSR type (SV, TO, FF, MA)
       "magnitude": float # LSR magnitude
       "remark": str # LSR remark
       "source": str # LSR source
       "state": str # State identifier of LSR
       "tdq": bool # Was this LSR covered only by a TOR warning, but not of a
                   # TOR LSR type?  Such events are not counted against verif.
       "type": str # IEM internal LSR type code (1 char)
       "typetext": str # LSR type found in NWS Product Text
       "valid": "ISO8601" # LSR valid time
       "warned": bool # Was this LSR warned for?
       "wfo": str # WFO that issued warning
}
</pre>

<p>There is a <a href="https://github.com/akrherz/DEV/blob/main/cow/api2shapefile.py">Python based example</a>
that uses this API to generate shapefiles of the verification data.</p>

EOM;
}
$t->jsextra = <<<EOM
<script type="module" src="index.module.js"></script>
EOM;
$t->content = $content;
$t->render('full.phtml');
