awips2/cave/com.raytheon.viz.avnconfig/localization/aviation/python/MetarMonitorP.py
Max Schenkelberg 6f60751ec6 Issue #2033 moved avnfps and text workstation files into respective plugins.
Change-Id: If95cb839ad81ca2a842ff7f6926847ac3928d8f2

Former-commit-id: 77e1a4d8f5237e5fae930c1e00589c752f8b3738
2013-08-15 12:21:43 -05:00

1828 lines
68 KiB
Python

##
# This software was developed and / or modified by Raytheon Company,
# pursuant to Contract DG133W-05-CQ-1067 with the US Government.
#
# U.S. EXPORT CONTROLLED TECHNICAL DATA
# This software product contains export-restricted data whose
# export/transfer/disclosure is restricted by U.S. law. Dissemination
# to non-U.S. persons whether in the United States or abroad requires
# an export license or other authorization.
#
# Contractor Name: Raytheon Company
# Contractor Address: 6825 Pine Street, Suite 340
# Mail Stop B8
# Omaha, NE 68106
# 402.291.0100
#
# See the AWIPS II Master Rights File ("Master Rights File.pdf") for
# further licensing information.
##
#
# Name:
# MetarMonitorP.py
# GFS1-NHD:A7808.0000-SCRIPT;1.38
#
# Status:
# DELIVERED
#
# History:
# Revision 1.38 (DELIVERED)
# Created: 05-NOV-2009 20:41:00 OBERFIEL
# Removed superfluous try/except cases when TEMPO conditions
# are being evaluated.
#
# Revision 1.37 (DELIVERED)
# Created: 21-OCT-2009 13:12:40 OBERFIEL
# Revised default severity of FuelAlternate and FltCatDelta
# rules upward.
# Removed dead code.
#
# Revision 1.36 (DELIVERED)
# Created: 09-OCT-2009 14:50:00 OBERFIEL
# Removal of much cruft. Whew!
#
# Revision 1.35 (DELIVERED)
# Created: 28-SEP-2009 08:12:44 OBERFIEL
# Final implementation of CAC rules.
#
# Revision 1.34 (DELIVERED)
# Created: 24-AUG-2009 14:38:06 OBERFIEL
# Fixed head/tailwind computations. Minor changes to balloon
# messages.
#
# Revision 1.33 (DELIVERED)
# Created: 11-AUG-2009 10:41:18 OBERFIEL
# Minor changes to balloon messages. Made sure that cig/vis
# thresholds have the same number of breakpoints.
#
# Revision 1.32 (DELIVERED)
# Created: 17-APR-2009 12:05:51 OBERFIEL
# For 'wx' rules raise AvnUnknwnPcp when UP is detected in
# the observation.
#
# Revision 1.31 (DELIVERED)
# Created: 12-NOV-2008 22:45:07 OBERFIEL
# Added 'strict' flag to FltCatDelta rule.
#
# Revision 1.30 (DELIVERED)
# Created: 14-MAR-2008 10:06:38 OBERFIEL
# Added code to make the use of variability in the METAR
# remarks section optional.
#
# Revision 1.29 (DELIVERED)
# Created: 19-NOV-2007 20:31:26 OBERFIEL
# Removed carriage return characters in files
#
# Revision 1.28 (INITIALIZE)
# Created: 19-NOV-2007 11:51:11 GILMOREDM
# Backed out previous changes to WxMetar class
#
# Revision 1.27 (REVIEW)
# Created: 16-NOV-2007 14:11:26 GILMOREDM
# added code to WxMetar rule that fixes problem of message
# appearing incorrectly
#
# Revision 1.26 (DELIVERED)
# Created: 03-OCT-2007 13:56:13 OBERFIEL
# Change logic on FuelAlternate and AirportOpsThresh to use
# LT instead of LTE.
# Changed slightly the logic to calculate differences btw
# observed and forecasted flight category.
#
# Revision 1.25 (DELIVERED)
# Created: 21-SEP-2007 17:26:08 OBERFIEL
# Reorganized alert messages for flight category alerts in
# balloon popup. Changed wording and added additional
# dynamic info flt cat msgs.
#
# Revision 1.24 (DELIVERED)
# Created: 18-SEP-2007 10:55:04 OBERFIEL
# Change logic so that flight category conditions forecasted
# in the TAF can bracket the observed flight category.
#
# Revision 1.23 (DELIVERED)
# Created: 27-JUN-2007 13:14:15 OBERFIEL
# MetarMonitorP.py revamped logic and created new rule for
# Alaska region.
# TafDecoder.py changed to allow variable AMD LTD element
# list
#
# Revision 1.22 (DELIVERED)
# Created: 29-MAY-2007 12:19:40 OBERFIEL
# Updated colophon. Fixed logic within MetarMonitorP.py to
# produce same behavior as 3.4 for
# for vis and cig monitoring. Fixed AvnWatch to better
# detect flight category type messages.
#
# Revision 1.21 (DELIVERED)
# Created: 25-MAY-2007 14:27:10 OBERFIEL
# Update to support additional information in remarks
#
# Revision 1.20 (DELIVERED)
# Created: 15-MAY-2007 08:28:06 GILMOREDM
# Changed SKC to CIGNO
#
# Revision 1.19 (UNDER WORK)
# Created: 21-MAR-2007 09:54:48 OBERFIEL
# For AvnClimate.py: Updated the Help text; MetarMonitorP.py:
# fix potential permission problem;
# WindRose.py: Added Auto Update feature, by default always
# on.
#
# Revision 1.18 (DELIVERED)
# Created: 02-MAR-2007 13:31:25 OBERFIEL
# Removed carriage returns from code.
# No code changes in this revision.
#
# Revision 1.17 (REVIEW)
# Created: 02-MAR-2007 12:18:45 OBERFIEL
# Added head/tail wind monitoring rule.
#
# Revision 1.16 (DELIVERED)
# Created: 19-NOV-2006 09:25:25 OBERFIEL
# Change runway indexing to be consistent with 3.3 and prior
# code.
#
# Revision 1.15 (DELIVERED)
# Created: 19-NOV-2006 09:19:49 OBERFIEL
# Corrected indexing of runways to be consistent with 3.2 and
# previous versions.
#
# Revision 1.14 (DELIVERED)
# Created: 29-AUG-2006 08:59:30 OBERFIEL
# Corrected arguments for Crosswind monitoring
#
# Revision 1.13 (DELIVERED)
# Created: 02-JUN-2006 10:35:29 TROJAN
# spr 7161: changed logic in rules FltCatDelta() and
# FuelAlternate()
#
# Revision 1.12 (DELIVERED)
# Created: 02-JUN-2006 08:57:30 TROJAN
# spr 7159:changed logic in rules FltCatDelta() and
# FuelAlternate()
#
# Revision 1.11 (DELIVERED)
# Created: 23-APR-2006 11:54:00 TROJAN
# spr 7125 - changes to TEMPO and category monitoring
#
# Revision 1.10 (DELIVERED)
# Created: 23-APR-2006 10:52:19 TROJAN
# spr 7126 - changes to tempo and category alerts
#
# Revision 1.9 (DELIVERED)
# Created: 23-JAN-2006 08:23:14 TROJAN
# stdr 956
#
# Revision 1.8 (DELIVERED)
# Created: 07-JUL-2005 13:01:13 TROJAN
# spr 6904
#
# Revision 1.7 (DELIVERED)
# Created: 07-MAY-2005 11:35:50 OBERFIEL
# Added Item Header Block
#
# Revision 1.6 (DELIVERED)
# Created: 04-APR-2005 15:51:08 TROJAN
# spr 6775
#
# Revision 1.5 (APPROVED)
# Created: 25-MAR-2005 12:00:46 TROJAN
# spr 6749
#
# Revision 1.4 (DELIVERED)
# Created: 24-JAN-2005 15:51:13 TROJAN
# spr 6259
#
# Revision 1.3 (APPROVED)
# Created: 30-SEP-2004 18:56:02 TROJAN
# stdr 874
#
# Revision 1.2 (APPROVED)
# Created: 09-JUL-2004 18:09:23 OBERFIEL
# Updated to fix problem with VCTS
#
# Revision 1.1 (APPROVED)
# Created: 01-JUL-2004 14:42:18 OBERFIEL
# date and time created -2147483647/-2147483648/-2147481748
# -2147483648:-2147483648:-2147483648 by oberfiel
#
# Change Document History:
# 1:
# Change Document: GFS1-NHD_SPR_7432
# Action Date: 06-NOV-2009 08:30:19
# Relationship Type: In Response to
# Status: NEXTRELEASE
# Title: OB9.2 AvnFPS - TPO/FuelAlternate Rule Doesn't work
#
#################################
# Date DR. # Engineer Description
# Dec. 27, 2012 15583 zhao Fixed a bug with Wind Dir. when wind is calm
#
import copy, logging, math, sets
import Avn, MonitorP
_Logger = logging.getLogger(__name__)
###############################################################################
class Monitor(MonitorP.Monitor):
Namespace = globals()
###############################################################################
def _WX(s, wx):
"""Returns TRUE if s contains string wx"""
if wx == '':
return False
# special case for blowing/drifting snow
ix = s.find(wx)
if wx == 'SN' and ix > 1:
if s[ix-2:ix] in ('BL', 'DR'):
return False
return ix >= 0
def _fmt(cig, vsby):
if vsby < 0.01:
vstr = '0SM'
elif vsby < 0.07:
vstr = '1/16SM'
elif vsby < 0.14:
vstr = '1/8SM'
elif vsby < 0.28:
vstr = '1/4SM'
elif vsby < 0.56:
vstr = '1/2SM'
elif vsby < 0.9:
vstr = '3/4SM'
elif vsby < 1.1:
vstr = '1SM'
elif vsby < 1.3:
vstr = '1 1/4SM'
elif vsby < 1.6:
vstr = '1 1/2SM'
elif vsby < 1.8:
vstr = '1 3/4SM'
elif vsby < 2.1:
vstr = '2SM'
elif vsby < 2.7:
vstr = '2 1/2SM'
elif vsby < 80.0:
vstr = '%.0fSM' % vsby
else:
vstr = 'P6SM'
if cig >= Avn.CLEAR:
return 'CIGNO|%s' % vstr
else:
return '%03d|%s' % (cig/100, vstr)
def format_msg(taf, mtr):
#
# Print the observation first
cats = [':METAR: %s, TAF:' % _fmt(mtr['sky']['cig'], mtr['vsby']['vsby'])]
#
# If temporary conditions are present, print that out too.
#
if taf['vsby']['lo'] < taf['vsby']['hi'] or \
taf['sky']['lo'] < taf['sky']['hi']:
cats.append('%s /' % _fmt(taf['sky'].get('ocnl',taf['sky']['prev']),
taf['vsby'].get('ocnl',taf['vsby']['prev'])))
#
# Append the prevailing conditions in the TAF.
cats.append(_fmt(taf['sky']['prev'], taf['vsby']['prev']))
return ' '.join(cats)
###############################################################################
# Section containing editable rules
###############################################################################
class DDDelta(MonitorP.Rule):
"""TAF and METAR winds directions differ by "dd" with either wind speed >= "ff1."
Arguments: dd ff1"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'wind'
self.unique = True
self.severity = 3
self.args = {'ff': 10, 'dd': 30}
def method(self, taf, mtr):
try:
mw, tw = mtr['wind'], taf['wind']
mdd = mw['dd']
tddp = tw['dd'].get('prev', None)
tddo = tw['dd'].get('ocnl', None)
# variable wind: always matches
if 'VRB' in (mdd, tddp, tddo):
return False
if mw['ff']['lo'] == 0:
return False
if tddp is None:
delta1= 999
else:
delta1 = abs(tddp - mdd)
if delta1 > 180:
delta1 = 360 - delta1
if tddo is None:
delta2= 999
else:
delta2 = abs(tddo - mdd)
if delta2 > 180:
delta2 = 360 - delta2
delta = min(delta1, delta2)
if delta == 999:
raise Avn.AvnMissing
if delta < self.args['dd']:
return False
if mw['ff']['lo'] >= tw['ff']['lo']:
if mw['ff']['lo'] >= self.args['ff']:
self.setmsg('Wind directions differ by %d deg, METAR wind' \
' >= %d KTS', delta, self.args['ff'])
return True
else:
if tw['ff']['lo'] >= self.args['ff']:
self.setmsg('Wind directions differ by %d deg, TAF wind' \
' >= %d KTS', delta, self.args['ff'])
return True
return False
except KeyError:
raise Avn.AvnMissing
class FFDelta(MonitorP.Rule):
"""TAF and METAR wind speeds/gusts differ by "ff" with either wind speed >= "ff1."
Arguments: ff ff1"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'wind'
self.unique = True
self.severity = 3
self.args = {'ff': 10, 'ff1': 15}
def method(self, taf, mtr):
try:
mff = mtr['wind']['ff']['hi']
tfflo = taf['wind']['ff']['lo']
tffhi = taf['wind']['ff']['hi']
if mff < tfflo:
self.setmsg('Wind speeds differ by %d, TAF wind >= %d KTS',
self.args['ff'], self.args['ff1'])
return tfflo-mff >= int(self.args['ff']) and \
tfflo >= self.args['ff1']
elif mff > tffhi:
self.setmsg('Wind speeds differ by %d KTS, METAR wind >= %d KTS',
self.args['ff'], self.args['ff1'])
return mff-tffhi >= int(self.args['ff']) and \
mff >= self.args['ff1']
else:
return False
except KeyError:
raise Avn.AvnMissing
class XFFMetar(MonitorP.Rule):
"""METAR runway cross wind speed >= ff kts
Arguments: runway - index to runway array in site config file
ff - crosswind speed"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'wind'
self.unique = False
self.severity = 3
self.args = {'runway': -1, 'ff': 15}
def method(self, taf, mtr):
rway = int(self.args['runway'])
try:
angle = self.sitedata['geography']['runway'][rway-1]
except IndexError:
angle = 0
if angle <= 0: # don't bother
return False
try:
mdd = mtr['wind']['dd']
mff = mtr['wind']['ff']['hi']
if mdd == 'VRB':
return mff >= self.args['ff']
# add 0.1 for roundoff error
xff = mff * abs(math.sin(math.radians(angle-mdd))) + 0.1
if xff >= self.args['ff']:
self.setmsg('Crosswind %.0f KTS on runway %02d >= %d KTS',
xff, angle/10, self.args['ff'])
return True
return False
except KeyError:
raise Avn.AvnMissing
class LFFMetar(MonitorP.Rule):
"""METAR runway head or tail wind meets or exceeds "ff" KT
Arguments: runway = index to runways[] in info.cfg;
+ff = tailwind, -ff = headwind"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'wind'
self.unique = False
self.severity = 3
self.args = {'runway': -1, 'ff': 0}
def method(self, taf, mtr):
rway = int(self.args['runway'])
try:
angle = self.sitedata['geography']['runway'][rway-1]
except IndexError:
angle = 0
if angle <= 0: # don't bother
return False
try:
mdd = mtr['wind']['dd']
mff = mtr['wind']['ff']['hi']
if mdd == 'VRB':
return mff >= self.args['ff']
#
# Negative sign is there because runway headings point _towards_ a direction
# METAR report winds coming _from_ a direction.
#
lff = -mff * math.cos(math.radians(angle-mdd)) + 0.1
if self.args['ff'] > 0:
if lff >= self.args['ff']:
self.setmsg('Tailwind %.0f KTS on runway %02d >= %d KTS',
lff, angle/10, self.args['ff'])
return True
elif self.args['ff'] < 0:
if lff <= self.args['ff']:
self.setmsg('Headwind %.0f KTS on runway %02d >= %d KTS',
-lff, angle/10, -(self.args['ff']))
return True
return False
except KeyError:
raise Avn.AvnMissing
class CigCatDelta(MonitorP.Rule):
"""TAF and METAR ceiling differ by #categories.
Arguments: ncat - number of category differences;
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'sky'
self.unique = True
self.severity = 3
self.args = {'ncat': 1, 'remarks': 'Y'}
def method(self, taf, mtr):
try:
thresholds = self.sitedata['thresholds']['cig']
msg = 'ncat,thresholds = (%d,%s)' % (self.args['ncat'],thresholds)
_Logger.debug(msg)
#
# Use METAR RMK information only if its turned on and there's variability
# in the TAF for the given hour
#
use_rmks = False
try:
if taf['sky'].has_key('ocnl'):
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
_Logger.debug({ True: 'Using remarks',False:'Ignoring remarks'}.get(use_rmks))
#
# Gather the lowest ceilings
tclo = Avn.category(taf['sky']['lo'], thresholds)
mclo = [Avn.category(mtr['sky']['cig'], thresholds)]
if use_rmks:
try:
mclo.append(Avn.category(mtr['vcig']['lo'], thresholds))
except KeyError:
pass
#
# Now gather the highest ceilings
tchi = Avn.category(taf['sky']['hi'], thresholds)
mchi = [Avn.category(mtr['sky']['cig'], thresholds)]
if use_rmks:
try:
mchi.append(Avn.category(mtr['vcig']['hi'], thresholds))
except KeyError:
pass
#
# If the same category is found between both observation and
# forecast, return early.
#
msg='metar sky categories',mchi,mclo
_Logger.debug(msg)
msg='taf sky categories',tchi,tclo
_Logger.debug(msg)
for mcig in mchi+mclo:
if mcig == tchi or mcig == tclo:
return False
result1 = [(x-tchi)>=self.args['ncat'] for x in mchi if x > tchi]
msg='result1',result1
_Logger.debug(msg)
result2 = [(tclo-x)>=self.args['ncat'] for x in mclo if x < tclo]
msg='result2',result2
_Logger.debug(msg)
for result in result1+result2:
if result:
self.setmsg('TAF and METAR differ by %s', { 1:"1 category",
2:"2 categories",
3:"3 categories",
4:"4 categories"}.get(int(self.args['ncat']),
">4 categories"))
return True
return False
except KeyError:
raise Avn.AvnMissing
class VsbyCatDelta(MonitorP.Rule):
"""TAF and METAR visibilities differ by #categories,
Arguments: ncat - number of category differences;
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'vsby'
self.unique = True
self.severity = 3
self.args = {'ncat': 1, 'remarks':'Y'}
def method(self, taf, mtr):
try:
thresholds = self.sitedata['thresholds']['vsby']
#
# Use METAR RMK information only if its turned on and there's variability
# in the TAF for the given hour
#
use_rmks = False
try:
if taf['vsby'].has_key('ocnl'):
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
#
# Gather the lowest visibilities first
tvlo = Avn.category(taf['vsby']['lo'], thresholds)
mvlo = [Avn.category(mtr['vsby']['vsby'], thresholds)]
if use_rmks:
try:
mvlo.append(Avn.category(mtr['vvsby']['lo'], thresholds))
except KeyError:
pass
#
# Now gather the highest visibilities
tvhi = Avn.category(taf['vsby']['hi'], thresholds)
mvhi = [Avn.category(mtr['vsby']['vsby'], thresholds)]
if use_rmks:
try:
mvhi.append(Avn.category(mtr['vvsby']['hi'], thresholds))
except KeyError:
pass
#
# If the same category is found between both observation and
# forecast, return early.
#
for mvis in mvhi+mvlo:
if mvis == tvhi or mvis == tvlo:
return False
#
# See if differences between forecast and observation differ
# less than the threshold allowed.
#
result1 = [(x-tvhi)>=self.args['ncat'] for x in mvhi if x > tvhi]
result2 = [(tvlo-x)>=self.args['ncat'] for x in mvlo if x < tvlo]
for result in result1+result2:
if result:
self.setmsg('TAF and METAR differ by %s',{ 1:"1 category",
2:"2 categories",
3:"3 categories",
4:"4 categories"}.get(int(self.args['ncat']),
">4 categories"))
return True
return False
except KeyError:
raise Avn.AvnMissing
class VsbyTafThresh(MonitorP.Rule):
"""TAF visibility <= vsby1 and METAR visibility > vsby2
Arguments: vsby1 vsby2
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'vsby'
self.unique = False
self.severity = 3
self.args = {'vsby1': 3.0, 'vsby2': 3.0, 'remarks':'Y'}
def method(self, taf, mtr):
try:
#
# Use METAR RMK information only if its turned on and there's variability
# in the TAF for the given hour
#
use_rmks = False
try:
if taf['vsby'].has_key('ocnl'):
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
if taf['vsby']['hi'] <= self.args['vsby1']:
vsbys = [mtr['vsby']['vsby']]
if use_rmks:
try:
vsbys.append(mtr['vvsby']['lo'])
except KeyError:
pass
for v in vsbys:
if v <= self.args['vsby2']:
return False
self.setmsg('TAF visibility <= %.1f and METAR visibility > %.1f',
self.args['vsby1'], self.args['vsby2'])
return True
return False
except KeyError:
raise Avn.AvnMissing
class VsbyMetarThresh(MonitorP.Rule):
"""METAR visibility <= vsby1 and TAF visibility > vsby2
Arguments: vsby1 vsby2
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'vsby'
self.unique = False
self.severity = 3
self.args = {'vsby1': 3.0, 'vsby2': 3.0, 'remarks': 'Y'}
def method(self, taf, mtr):
#
# Use METAR RMK information only if its turned on
use_rmks = False
try:
if taf['vsby'].has_key('ocnl'):
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
try:
if taf['vsby']['lo'] > self.args['vsby2']:
vsbys = [mtr['vsby']['vsby']]
if use_rmks:
try:
vsbys.append(mtr['vvsby']['hi'])
except KeyError:
pass
for v in vsbys:
if v > self.args['vsby1']:
return False
self.setmsg('METAR visibility <= %.1f and TAF visibility > %.1f',
self.args['vsby1'], self.args['vsby2'])
return True
return False
except KeyError:
raise Avn.AvnMissing
class CigTafThresh(MonitorP.Rule):
"""TAF ceiling <= cig1 and METAR ceiling > cig2
Arguments: cig1 cig2
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'sky'
self.use_remarks = True
self.unique = False
self.severity = 3
self.args = {'cig1': 3100, 'cig2': 3100, 'remarks':'Y'}
def method(self, taf, mtr):
#
# Use METAR RMK information only if its turned on and there's variability
# in the TAF for the given hour
#
use_rmks = False
try:
if taf['sky'].has_key('ocnl'):
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
try:
if taf['sky']['hi'] <= self.args['cig1']:
cigs = [mtr['sky']['cig']]
if use_rmks:
try:
cigs.append(mtr['vcig']['lo'])
except KeyError:
pass
try:
if max(mtr['vsky']['cvr1'], mtr['vsky']['cvr2']) > 2:
cigs.append(mtr['vsky']['cig'])
except KeyError:
pass
for c in cigs:
if c <= self.args['cig2']:
return False
self.setmsg('TAF ceiling <= %d and METAR ceiling > %d',
self.args['cig1'], self.args['cig2'])
return True
return False
except KeyError:
raise Avn.AvnMissing
class CigMetarThresh(MonitorP.Rule):
"""METAR ceiling <= cig1 and TAF ceiling > cig2
Arguments: cig1 cig2
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'sky'
self.unique = False
self.severity = 3
self.args = {'cig1': 3100, 'cig2': 3100, 'remarks':'Y'}
def method(self, taf, mtr):
#
# Use METAR RMK information only if its turned on and there's variability
# in the TAF for the given hour
#
use_rmks = False
try:
if taf['sky'].has_key('ocnl'):
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
try:
if taf['sky']['lo'] > self.args['cig2']:
cigs = [mtr['sky']['cig']]
if use_rmks:
try:
cigs.append(mtr['vcig']['hi'])
cigs.append(mtr['vcig']['lo'])
except KeyError:
pass
try:
if max(mtr['vsky']['cvr1'], mtr['vsky']['cvr2']) > 2:
cigs.append(mtr['vsky']['cig'])
except KeyError:
pass
for c in cigs:
if c > self.args['cig1']:
return False
self.setmsg('METAR ceiling <= %d and TAF ceiling > %d',
self.args['cig1'], self.args['cig2'])
return True
return False
except KeyError:
raise Avn.AvnMissing
class WxTafDelta(MonitorP.Rule):
"""Weather (any of the list) occurs in TAF and not in METAR.
Arguments: wx (list)"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'wx'
self.unique = False
self.severity = 3
self.args = {'wx': ['FZRA', 'FZDZ', 'PL']}
def method(self, taf, mtr):
if 'wx' in mtr and mtr['wx']['str'][:2] == 'UP':
raise Avn.AvnUnknwnPcp
if 'wx' not in taf or 'pstr' not in taf['wx']:
return False
wx = taf['wx']['pstr'] # only prevailing conditions
if not Avn.any(self.args['wx'], Avn.curry(_WX, wx)):
return False
self.setmsg('%s forecasted but does not occurs in METAR',
' or '.join(self.args['wx']))
if 'wx' not in mtr:
return True
wx = mtr['wx']['str']
return not Avn.any(self.args['wx'], Avn.curry(_WX, wx))
class WxMetarDelta(MonitorP.Rule):
"""Weather (any of the list) occurs in METAR and not in TAF.
Arguments: wx (list)"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'wx'
self.unique = False
self.severity = 3
self.args = {'wx': ['FZRA', 'FZDZ', 'PL']}
def method(self, taf, mtr):
if 'wx' not in mtr:
return False
wx = mtr['wx']['str']
if wx[:2] == 'UP':
raise Avn.AvnUnknwnPcp
if not Avn.any(self.args['wx'], Avn.curry(_WX, wx)):
return False
# special case: VCTS
if 'TS' in self.args['wx'] and 'ts' in taf:
return False
self.setmsg('%s occurred in METAR but not forecasted',
' or '.join(self.args['wx']))
if 'wx' not in taf:
return True
if 'pstr' in taf['wx']:
wx = taf['wx']['pstr']
if Avn.any(self.args['wx'], Avn.curry(_WX, wx)):
return False
if 'ostr' not in taf['wx']:
return True
wx = taf['wx']['ostr']
return not Avn.any(self.args['wx'], Avn.curry(_WX, wx))
class WxMetar(MonitorP.Rule):
"""Checks for occurrence of weather in METAR.
Arguments: WX (list)"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'wx'
self.unique = False
self.severity = 3
self.args = {'wx': ['TS']}
def method(self, taf, mtr):
if 'wx' not in mtr:
return False
self.setmsg('%s occurred in METAR', ' or '.join(self.args['wx']))
wx = mtr['wx']['str']
if self.wx_not_in_mtr(wx):
return False
return Avn.any(self.args['wx'], Avn.curry(_WX, wx))
def wx_not_in_mtr(self,wx):
for el in self.args['wx']:
if el not in wx:
continue
else:
return False #weather found in wx
return True #weather not found in wx
class WxVsbyDelta(MonitorP.Rule):
"""Checks for occurrence of weather in METAR while not in TAF,
with visibility <= vsby.
Arguments: vsby, wx (list)
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'wx'
self.unique = False
self.severity = 3
self.args = {'vsby': 3.0, 'wx': ['DZ'], 'remarks':'Y'}
def method(self, taf, mtr):
#
# Use METAR RMK information only if its turned on
use_rmks = False
try:
if taf['vsby'].has_key('ocnl'):
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
try:
vsbys = [mtr['vsby']['vsby']]
if use_rmks:
try:
vsbys.append(mtr['vvsby']['hi'])
vsbys.append(mtr['vvsby']['lo'])
except KeyError:
pass
for v in vsbys:
if v > self.args['vsby']:
return False
except KeyError:
raise Avn.AvnMissing
if 'wx' not in mtr:
return False
wx = mtr['wx']['str']
if not Avn.any(self.args['wx'], Avn.curry(_WX, wx)):
return False
self.setmsg('%s occurred in METAR but not forecasted\n'
'visibility <= %.1f', ' or '.join(self.args['wx']),
self.args['vsby'])
if 'wx' not in taf:
return True
if 'pstr' in taf['wx']:
wx = taf['wx']['pstr']
if Avn.any(self.args['wx'], Avn.curry(_WX, wx)):
return False
if 'ostr' not in taf['wx']:
return True
wx = taf['wx']['ostr']
return not Avn.any(self.args['wx'], Avn.curry(_WX, wx))
class FltCatDelta(MonitorP.Rule):
"""Comparing TAF and observations with respect to aviation flight categories. Severity and
message determined by algorithm
Arguments: remarks - yes to use variability information, if given in the METAR remarks
strict - yes to alert if forecasted and observed flight category do not match exactly"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'cat'
self.unique = True
self.args = {'remarks':'True','strict':'True'}
self.severity = 3
def method(self, taf, mtr):
try:
#
# Use METAR RMK information only if its turned on and there's variability
# in the TAF for the given hour
#
use_rmks = False
try:
if taf['vsby'].has_key('ocnl') or taf['sky'].has_key('ocnl'):
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
#
# Check to see if exact matching is desired.
try:
if type(self.args['strict']) == type(' '):
strict = self.args['strict'].lower()[0] in ['a','y','t','1']
else:
strict = self.args['strict'] == 1
except KeyError:
strict = True
cig_thresholds = self.sitedata['thresholds']['cig']
vis_thresholds = self.sitedata['thresholds']['vsby']
c=len(cig_thresholds)
v=len(vis_thresholds)
if v > c:
cig_thresholds=[-1]*(v-c)+self.sitedata['thresholds']['cig']
elif v < c:
vis_thresholds=[-1]*(c-v)+self.sitedata['thresholds']['vis']
mCset = sets.Set()
mVset = sets.Set()
mVset.add(Avn.category(mtr['vsby']['vsby'], vis_thresholds))
if use_rmks:
try:
mVset.add(Avn.category(mtr['vvsby']['lo'], vis_thresholds))
mVset.add(Avn.category(mtr['vvsby']['hi'], vis_thresholds))
except KeyError:
pass
mCset.add(Avn.category(mtr['sky']['cig'], cig_thresholds))
if use_rmks:
try:
mCset.add(Avn.category(mtr['vcig']['lo'], cig_thresholds))
mCset.add(Avn.category(mtr['vcig']['hi'], cig_thresholds))
except KeyError:
pass
try:
if max(mtr['vsky']['cvr1'], mtr['vsky']['cvr2']) > 2:
mCset.add(Avn.category(mtr['vsky']['cig'], cig_thresholds))
except KeyError:
pass
#
# Observation can span a range of categories
lo_mtrFltCat = min(min(mCset),min(mVset))
hi_mtrFltCat = min(max(mCset),max(mVset))
#
prev_list = [Avn.category(taf['vsby']['prev'],vis_thresholds),
Avn.category(taf['sky']['prev'],cig_thresholds)]
t_p_cat = min(prev_list)
ocnl_list = []
try:
ocnl_list.append(Avn.category(taf['vsby']['ocnl'],vis_thresholds))
except KeyError:
ocnl_list.append(prev_list[0])
try:
ocnl_list.append(Avn.category(taf['sky']['ocnl'],cig_thresholds))
except KeyError:
ocnl_list.append(prev_list[1])
t_o_cat = min(ocnl_list)
#
# If prevailing or temporary forecast brackets the observation
# w.r.t flight category
#
if not strict and (min(t_p_cat,t_o_cat) <= lo_mtrFltCat <= max(t_p_cat,t_o_cat) or \
min(t_p_cat,t_o_cat) <= hi_mtrFltCat <= max(t_p_cat,t_o_cat)):
if prev_list[0] in mVset and prev_list[1] in mCset:
return False
if ocnl_list[0] in mVset and ocnl_list[1] in mCset:
return False
tVis, tCig = prev_list[0],prev_list[1]
group = 'Prevailing'
pmin = min([abs(prev_list[0]-m) for m in mVset]) + \
min([abs(prev_list[1]-m) for m in mCset])
omin = min([abs(ocnl_list[0]-m) for m in mVset]) + \
min([abs(ocnl_list[1]-m) for m in mCset])
if pmin > omin:
tVis, tCig = ocnl_list[0],ocnl_list[1]
group = 'Occasional'
items = []
if tVis not in mVset:
items.append('visibility')
if tCig not in mCset:
items.append('ceiling')
#
# If there was a disagreement on ceiling or visibility
# category, raise a minor flag
#
if items:
if len(items) == 1:
self.severity = 2 #Light green
self.setmsg('%s %s category differs %s',
group, items[0],
format_msg(taf, mtr))
else:
self.severity = 3 #Yellow
self.setmsg('%s %s categories differ %s',
group, ' and '.join(items),
format_msg(taf, mtr))
return True
#
# For those situations where flight categories of forecast and
# observations don't agree
#
# Get the lowest (worse) flight category forecasted
taf_lo = min(t_p_cat,t_o_cat)
if hi_mtrFltCat > taf_lo > lo_mtrFltCat:
delta_lo = taf_lo - lo_mtrFltCat
else:
delta_lo = min(abs(lo_mtrFltCat-taf_lo),
abs(hi_mtrFltCat-taf_lo))
#
# A generic message first
self.setmsg('Flight categories differ %s', format_msg(taf, mtr))
#
# The severity color is dependent on the difference between what
# is observed and forecasted.
#
if delta_lo > 3:
self.severity = 6 #Purple
elif delta_lo == 3:
self.severity = 5 #Red
elif delta_lo == 2:
self.severity = 4 #Orange
elif delta_lo == 1:
self.severity = 3 #Yellow
else:
t_p_vis, t_p_cig = prev_list[0], prev_list[1]
t_o_vis, t_o_cig = ocnl_list[0], ocnl_list[1]
t_vis = min(t_p_vis, t_o_vis)
t_cig = min(t_p_cig, t_o_cig)
items = []
if t_cig not in mCset:
items.append('ceiling')
if t_vis not in mVset:
items.append('visibility')
#
# If there was a disagreement on ceiling or visibility
# category, raise a minor flag
#
if items:
self.severity = 2 # Light green
items[0] = items[0].capitalize()
if len(items) == 1:
self.setmsg('%s category differs %s',
items[0], format_msg(taf, mtr))
else:
self.setmsg('%s categories differ %s',
' and '.join(items),
format_msg(taf, mtr))
return True
return False
return True
except KeyError:
raise Avn.AvnMissing
class FuelAlternate(MonitorP.Rule):
"""Either TAF and METAR weather falls below alternate fuel requirements.
Arguments: vsby, cig - ceiling and visibility thresholds for alternate fuel loading for aircaft
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'cat'
self.unique = True
self.severity = 3
self.args = {'vsby': 3, 'cig': 2000, 'remarks':'Y'}
def method(self, taf, mtr):
try:
#
# Use METAR RMK information only if its turned on and there's variability
# in the TAF for the given hour
#
use_rmks = False
try:
if taf['vsby'].has_key('ocnl') or taf['sky'].has_key('ocnl'):
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
mvlist = [mtr['vsby']['vsby']]
if use_rmks:
try:
mvlist.append(mtr['vvsby']['lo'])
except KeyError:
pass
mclist = [mtr['sky']['cig']]
if use_rmks:
try:
mclist.append(mtr['vcig']['lo'])
except KeyError:
pass
if use_rmks:
try:
if max(mtr['vsky']['cvr1'], mtr['vsky']['cvr2']) > 2:
mclist.append(mtr['vsky']['cig'])
except KeyError:
pass
metar = min(mvlist) < self.args['vsby'] or min(mclist) < self.args['cig']
tvlist = [taf['vsby']['prev']]
try:
tvlist.append(taf['vsby']['ocnl'])
except KeyError:
pass
tclist = [taf['sky']['prev']]
try:
tclist.append(taf['sky']['ocnl'])
except KeyError:
pass
fcst = min(tvlist) < self.args['vsby'] or min(tclist) < self.args['cig']
if metar and fcst:
return False
if metar:
self.setmsg('METAR LT %03d|%sSM requires additional fuel & alternate airport %s',
int(self.args['cig'])/100,self.args['vsby'],format_msg(taf,mtr))
return True
if fcst:
self.setmsg('TAF LT %03d|%sSM requires additional fuel & alternate airport %s',
int(self.args['cig'])/100,self.args['vsby'],format_msg(taf,mtr))
return True
return False
except KeyError:
raise Avn.AvnMissing
class AirportOpsThresh(MonitorP.Rule):
"""Alert when either TAF or METAR cig/vis falls below an airport
operations criteria and obs and forecast disagree.
Arguments: vsby, cig - visibility/ceiling thresholds that affect operations.
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'cat'
self.unique = True
self.severity = 5
self.args = {'vsby': 1, 'cig': 500, 'remarks': 'Y'}
def method(self, taf, mtr):
try:
#
# Use METAR RMK information only if its turned on and there's variability
# in the TAF for the given hour a==affirmative,y=yes,t=true
#
use_rmks = False
try:
if taf['vsby'].has_key('ocnl') or taf['sky'].has_key('ocnl'):
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
mvlist = [mtr['vsby']['vsby']]
if use_rmks:
try:
mvlist.append(mtr['vvsby']['lo'])
except KeyError:
pass
mclist = [mtr['sky']['cig']]
if use_rmks:
try:
mclist.append(mtr['vcig']['lo'])
except KeyError:
pass
if use_rmks:
try:
if max(mtr['vsky']['cvr1'], mtr['vsky']['cvr2']) > 2:
mclist.append(mtr['vsky']['cig'])
except KeyError:
pass
metar = min(mvlist) < self.args['vsby'] or min(mclist) < self.args['cig']
tvlist = [taf['vsby']['prev']]
try:
tvlist.append(taf['vsby']['ocnl'])
except KeyError:
pass
tclist = [taf['sky']['prev']]
try:
tclist.append(taf['sky']['ocnl'])
except KeyError:
pass
fcst = min(tvlist) < self.args['vsby'] or min(tclist) < self.args['cig']
#
if metar and fcst:
return False
elif metar or fcst:
if metar:
self.setmsg('METAR LT airport operations criteria (%03d|%sSM) %s',
int(self.args['cig'])/100,self.args['vsby'],format_msg(taf,mtr))
if fcst:
self.setmsg('TAF LT airport operations criteria (%03d|%sSM) %s',
int(self.args['cig'])/100,self.args['vsby'],format_msg(taf,mtr))
return True
else:
return False
except KeyError:
raise Avn.AvnMissing
class CAC_FltCatDelta(MonitorP.Rule):
"""Comparing TAF and observations with respect to ceiling and visibility categories.
Severity and message returned determined by algorithm. This rule satisfies CAC
requirements.
Arguments: remarks - yes to use variability information, if given in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'cat'
self.unique = True
self.args = {'remarks':'True'}
self.severity = 3
def method(self, taf, mtr):
try:
tempoCheck = taf.get('tempoCheck', False)
use_rmks = [False, False]
try:
if type(self.args['remarks']) == type(' '):
use_rmks[0] = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks[0] = self.args['remarks'] == 1
if taf['vsby'].has_key('ocnl') or taf['sky'].has_key('ocnl'):
use_rmks[1] = use_rmks[0]
pass
except KeyError:
pass
cig_thresholds = self.sitedata['thresholds']['cig']
vis_thresholds = self.sitedata['thresholds']['vsby']
c=len(cig_thresholds)
v=len(vis_thresholds)
if v > c:
cig_thresholds=[-1]*(v-c)+self.sitedata['thresholds']['cig']
elif v < c:
vis_thresholds=[-1]*(c-v)+self.sitedata['thresholds']['vis']
mCset = sets.Set()
mVset = sets.Set()
mVset.add(Avn.category(mtr['vsby']['vsby'], vis_thresholds))
mCset.add(Avn.category(mtr['sky']['cig'], cig_thresholds))
mCset1 = copy.deepcopy(mCset)
mVset1 = copy.deepcopy(mVset)
mVset1.add(
Avn.category(
mtr.get('vvsby', {}).get('lo', 10.0), vis_thresholds))
mVset1.add(
Avn.category(
mtr.get('vvsby', {}).get('hi', 10.0), vis_thresholds))
mCset1.add(
Avn.category(
mtr.get('vcig', {}).get('lo', 99999), cig_thresholds))
mCset1.add(
Avn.category(
mtr.get('vcig', {}).get('hi', 99999), cig_thresholds))
try:
if max(mtr['vsky']['cvr1'], mtr['vsky']['cvr2']) > 2:
mCset1.add(
Avn.category(mtr['vsky']['cig'], cig_thresholds))
except KeyError:
pass
# Observation can span a range of categories
lo_mtrFltCat = min(min(mCset),min(mVset))
hi_mtrFltCat = min(max(mCset),max(mVset))
#
try:
tafVisPrev = Avn.category(taf['vsby']['prev'], vis_thresholds)
except KeyError:
raise Avn.AvnMissing
try:
tafCigPrev = Avn.category(taf['sky']['prev'], cig_thresholds)
except KeyError:
raise Avn.AvnMissing
prev_list = [tafVisPrev, tafCigPrev]
t_p_cat = min(prev_list)
ocnl_list = []
try:
ocnl_list.append(Avn.category(taf['vsby']['ocnl'],vis_thresholds))
except KeyError:
ocnl_list.append(prev_list[0])
try:
ocnl_list.append(Avn.category(taf['sky']['ocnl'],cig_thresholds))
except KeyError:
ocnl_list.append(prev_list[1])
t_o_cat = min(ocnl_list)
#
# For those situations where flight categories of forecast and
# observations don't agree
#
# Get the lowest (worse) flight category forecasted
taf_lo = min(t_p_cat,t_o_cat)
if hi_mtrFltCat > taf_lo > lo_mtrFltCat:
delta_lo = taf_lo - lo_mtrFltCat
else:
delta_lo = min(abs(lo_mtrFltCat-taf_lo),
abs(hi_mtrFltCat-taf_lo))
#
if use_rmks[1]:
if delta_lo > 0:
lo_mtrFltCat = min(min(mCset1),min(mVset1))
hi_mtrFltCat = min(max(mCset1),max(mVset1))
# Get the lowest (worse) flight category forecasted
if hi_mtrFltCat > taf_lo > lo_mtrFltCat:
if taf_lo - lo_mtrFltCat == 0:
if tempoCheck:
return False
self.severity = 2
self.setmsg('METAR variable remarks verified the TEMPO flight category')
return True
else:
if min(abs(lo_mtrFltCat-taf_lo),
abs(hi_mtrFltCat-taf_lo)) == 0:
if tempoCheck:
return False
self.severity = 2
self.setmsg('METAR variable remarks verified the TEMPO flight category')
return True
# The severity color is dependent on the difference between what
# is observed and forecasted.
#
if delta_lo > 3:
self.severity = 6 #Purple
elif delta_lo == 3:
self.severity = 5 #Red
elif delta_lo == 2:
self.severity = 4 #Orange
elif delta_lo == 1:
self.severity = 3 #Yellow
else:
if use_rmks[0]:
if min(min(mCset1), min(mVset1)) < \
min(min(mCset), min(mVset)):
if tempoCheck:
return False
self.severity = 2
self.setmsg('METAR variable remarks are in a lower flight category '\
'than the lowest TAF category')
return True
t_p_vis, t_p_cig = prev_list[0], prev_list[1]
t_o_vis, t_o_cig = ocnl_list[0], ocnl_list[1]
t_vis = min(t_p_vis, t_o_vis)
t_cig = min(t_p_cig, t_o_cig)
items = []
if t_cig not in mCset:
items.append('ceiling')
if t_vis not in mVset:
items.append('visibility')
#
# If there was a disagreement on ceiling or visibility
# category, raise a minor flag
#
if items:
if tempoCheck:
return False
self.severity = 2 # Light green
items[0] = items[0].capitalize()
if len(items) == 1:
self.setmsg('%s category differs %s',
items[0],format_msg(taf, mtr))
else:
self.setmsg('%s categories differ %s',
' and '.join(items),format_msg(taf, mtr))
return True
return False
# A generic message
if not tempoCheck:
self.setmsg('Categories differ %s ',format_msg(taf, mtr))
return True
except KeyError:
raise Avn.AvnMissing
class CAC_AirportOpsThresh(MonitorP.Rule):
"""Alert when either TAF or METAR cig/vis falls below an airport
operations criteria. This rule satisfies CAC requirements.
Arguments: vsby, cig - visibility/ceiling thresholds that affect operations.
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'cat'
self.unique = True
self.severity = 5
self.args = {'vsby': 1, 'cig': 500, 'remarks': 'Y'}
def method(self, taf, mtr):
try:
cigThresh = self.args['cig']
visThresh = self.args['vsby']
cigVisThreshStr = _fmt(cigThresh, visThresh)
if (cigThresh == 600 or cigThresh == 800) and \
visThresh == 2:
msg = 'below use as an alternate airfield minimums'
elif cigThresh == self.sitedata['thresholds']['cig'][0] and \
visThresh == self.sitedata['thresholds']['vsby'][0]:
msg = 'below airfield minimums'
else:
msg = ''
tempoCheck = taf.get('tempoCheck', False)
use_rmks = [False, False]
try:
if type(self.args['remarks']) == type(' '):
use_rmks[0] = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks[0] = self.args['remarks'] == 1
if taf['vsby'].has_key('ocnl') or taf['sky'].has_key('ocnl'):
use_rmks[1] = use_rmks[0]
except KeyError:
pass
except IndexError:
pass
vVis = mtr.get('vvsby', {}).get('lo', 10.0)
vCig = mtr.get('vcig', {}).get('lo', 99999)
vCvr1 = mtr.get('vsky', {}).get('cvr1', -1)
vCvr2 = mtr.get('vsky', {}).get('cvr2', -1)
if max(vCvr1, vCvr2) > 2:
vCig = min(
vCig, mtr.get('vsky', {}).get('cig', 99999))
mvlist = [mtr['vsby']['vsby']]
mclist = [mtr['sky']['cig']]
metar = min(mvlist) < visThresh or min(mclist) < cigThresh
try:
tvlist = [taf['vsby']['prev']]
except KeyError:
raise Avn.AvnMissing
try:
tvlist.append(taf['vsby']['ocnl'])
except KeyError:
pass
try:
tclist = [taf['sky']['prev']]
except KeyError:
raise Avn.AvnMissing
try:
tclist.append(taf['sky']['ocnl'])
except KeyError:
pass
fcst = min(tvlist) < visThresh or min(tclist) < cigThresh
#
# 4 cases:
# fcst and metar are True: Good, no need to check remarks
# fcst and metar are False: Good, but check remarks to see if they
# they are below threshold
# fcst False and metar True: Bad, no need to check remarks
# fcst True and metar False: Bad, but check remarks to see if they
# save the TAF.
if metar and fcst:
return False
if not metar and not fcst:
if use_rmks[0]:
# Always check to see if the variable remarks, if present,
# put the METAR in a lower category than the lowest TAF
# category.
if vVis < visThresh or vCig < cigThresh:
if tempoCheck:
# "cat" box alerts and messages not appropriate for
# "tpo" box checks.
return False
self.severity = 2
self.setmsg('METAR variable remarks LT %s (%s)',
cigVisThreshStr,msg)
return True
return False
if metar:
if not tempoCheck:
# Create "cat" box messages only when doing "cat" box
# checking.
self.setmsg('METAR LT %s %s %s',cigVisThreshStr,msg,
format_msg(taf, mtr))
return True
if fcst:
if use_rmks[1]:
mvlist.append(vVis)
mclist.append(vCig)
metar1 = min(mvlist) < visThresh or min(mclist) < cigThresh
if metar1 and fcst:
if tempoCheck:
return False
self.severity = 2
self.setmsg('METAR variable remarks verified the TEMPO LT %s (%s)',
cigVisThreshStr, msg)
return True
if not tempoCheck:
self.setmsg('TAF LT %s %s %s', cigVisThreshStr,msg,
format_msg(taf, mtr))
return True
return False
except KeyError:
raise Avn.AvnMissing
class CAC_VsbyMetarThresh(MonitorP.Rule):
"""METAR visibility < vsby1 and TAF visibility >= vsby2. This rule satisfies
CAC requirements.
Arguments: vsby1 vsby2
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'cat'
self.unique = False
self.severity = 3
self.args = {'vsby1': 3.0, 'vsby2': 3.0, 'remarks': 'Y'}
def method(self, taf, mtr):
tempoCheck = taf.get('tempoCheck', False)
use_rmks = False
try:
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
except IndexError:
pass
# 4 cases:
# Both below: Good, remarks do not need to be checked.
# METAR above, TAF below: Good, remarks do not need to be checked.
# METAR below, TAF above: Bad, remarks do no need to be checked.
# Both above: Good, but check remarks to see if that makes METAR
# below. If so, light green.
try:
tempoCheck = taf.get('tempoCheck', False)
fcst = taf['vsby']['lo']
metar = mtr['vsby']['vsby']
tafThresh = self.args['vsby2']
metarThresh = self.args['vsby1']
tafThreshStr = _fmt(99999, tafThresh).split('|')[1]
metarThreshStr = _fmt(99999, metarThresh).split('|')[1]
if (fcst < tafThresh and metar < metarThresh) or \
(fcst < tafThresh and metar >= metarThresh):
return False
if fcst >= tafThresh and metar < metarThresh:
if not tempoCheck:
self.setmsg('METAR visibility < %s and TAF visibility >= %s %s',
metarThreshStr, tafThreshStr,format_msg(taf, mtr))
return True
if fcst >= tafThresh and metar >= metarThresh:
if use_rmks:
if tempoCheck:
return False
metar1 = mtr['vsby'].get('vvsby', 99999.0)
if metar1 < metarThresh:
self.severity = 2
self.setmsg('METAR variable remarks < %s while TEMPO > %s %s',
metarThreshStr,tafThreshStr,format_msg(taf, mtr))
return True
return False
except KeyError:
raise Avn.AvnMissing
class CAC_VsbyTafThresh(MonitorP.Rule):
"""TAF visibility < vsby1 and METAR visibility >= vsby2. This rule satisfies
CAC requirements.
Arguments: vsby1 vsby2
remarks - use variability information, if given, in the METAR remarks"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'cat'
self.unique = False
self.severity = 3
self.args = {'vsby1': 3.0, 'vsby2': 3.0, 'remarks':'Y'}
def method(self, taf, mtr):
tempoCheck = taf.get('tempoCheck', False)
# Use METAR RMK information only if it's turned on and there is
# variablility in the TAF.
use_rmks = False
try:
if taf['vsby'].has_key('ocnl'):
if type(self.args['remarks']) == type(' '):
use_rmks = self.args['remarks'].lower()[0] in ['a','y','t','1']
else:
use_rmks = self.args['remarks'] == 1
except KeyError:
pass
except IndexError:
pass
# 4 cases:
# Both below: Good, no need to check remarks
# TAF above, METAR below: Good, no need to check remarks
# Both above: Good, no need to check remarks
# TAF below, METAR above: Bad, but check remarks to see if METAR "saves"
# the TAF
try:
fcst = taf['vsby']['lo']
metar = mtr['vsby']['vsby']
tafThresh = self.args['vsby1']
metarThresh = self.args['vsby2']
tafThreshStr = _fmt(99999, tafThresh).split('|')[1]
metarThreshStr = _fmt(99999, metarThresh).split('|')[1]
if (fcst < tafThresh and metar < metarThresh) or \
(fcst >= tafThresh and metar < metarThresh) or \
(fcst >= tafThresh and metar >= metarThresh):
return False
# Only the bad case is left
if use_rmks:
metar = mtr['vsby'].get('vvsby', 99999.0)
if metar < metarThresh:
self.severity = 2
self.setmsg('METAR variable remarks < %s verified TEMPO visibility < %s %s',
metarThreshStr,tafThreshStr,format_msg(taf, mtr))
return True
if not tempoCheck:
self.setmsg('TAF visibility < %s and METAR visibility >= %s %s',
tafThreshStr,metarThreshStr,format_msg(taf, mtr))
return True
except KeyError:
raise Avn.AvnMissing
class CAC_WxTafDelta(MonitorP.Rule):
"""Weather (any of the list) occurs in the TAF, conditional group included, but not in METAR.
Arguments: wx (list)"""
def __init__(self):
# defaults, can be overwritten by configuration file
MonitorP.Rule.__init__(self)
self.type = 'wx'
self.unique = False
self.severity = 3
self.args = {'wx': ['FZRA', 'FZDZ', 'PL']}
def method(self, taf, mtr):
if 'wx' in mtr and mtr['wx']['str'][:2] == 'UP':
raise Avn.AvnUnknwnPcp
if 'wx' not in taf:
return False
wx = taf['wx'].get('pstr', '')
if not Avn.any(self.args['wx'], Avn.curry(_WX, wx)):
wx = taf['wx'].get('ostr', '')
if not Avn.any(self.args['wx'], Avn.curry(_WX, wx)):
return False
self.setmsg('%s forecasted but does not occurs in METAR',
' or '.join(self.args['wx']))
if 'wx' not in mtr:
return True
wx = mtr['wx']['str']
return not Avn.any(self.args['wx'], Avn.curry(_WX, wx))