2747 lines
110 KiB
Python
2747 lines
110 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.
|
|
##
|
|
# ----------------------------------------------------------------------------
|
|
# This software is in the public domain, furnished "as is", without technical
|
|
# support, and with no warranty, express or implied, as to its usefulness for
|
|
# any purpose.
|
|
#
|
|
# ScalarPhrases.py
|
|
# Methods for producing text forecast from SampleAnalysis statistics.
|
|
#
|
|
# Author: hansen
|
|
# ----------------------------------------------------------------------------
|
|
|
|
##
|
|
# This is a base file that is not intended to be overridden.
|
|
##
|
|
|
|
import PhraseBuilder
|
|
import types
|
|
|
|
class ScalarPhrases(PhraseBuilder.PhraseBuilder):
|
|
def __init__(self):
|
|
PhraseBuilder.PhraseBuilder.__init__(self)
|
|
|
|
############################################
|
|
### PUBLIC SCALAR WEATHER ELEMENT PHRASES
|
|
### To override, override the associated method in your text product class.
|
|
|
|
### T
|
|
def temp_trend_nlValue(self, tree, node):
|
|
# THRESHOLD FOR REPORTING TEMPERATURE TRENDS
|
|
return 20.0
|
|
### Td
|
|
### MaxT and MinT
|
|
### Sky
|
|
|
|
def pop_sky_lower_threshold(self, tree, node):
|
|
"""Do not include an explicit Sky forecast when PoPs are
|
|
>= 60% for the majority of the forecast period.
|
|
"""
|
|
# Get all the PoP stats for this component
|
|
component = node.getComponent()
|
|
compRange = component.getTimeRange()
|
|
popStats = tree.stats.get('PoP', compRange, node.getAreaLabel(),
|
|
mergeMethod="List")
|
|
|
|
# If the PoP stats are missing
|
|
if popStats is None or popStats == []:
|
|
return 100.0 # keep sky cover as a precaution
|
|
|
|
# Initialize a counter to keep track of the number of subperiods
|
|
# where the PoP >= 55% (rounds to 60%)
|
|
count = 0
|
|
# Look at each PoP value
|
|
for (value, timeRange) in popStats:
|
|
# See if PoP is 'likely' or 'categorical'
|
|
if value >= 55.0:
|
|
count += 1 # count this subphrase period
|
|
|
|
# Determine the percentage of the time PoP is 'likely' or 'categorical'
|
|
percent = 100.0 * float(count)/float(len(popStats))
|
|
# If the majority of the period has 'likely' or 'categorical' PoPs
|
|
if percent > 50.0:
|
|
val = 59.0 # omit sky cover from the forecast
|
|
else:
|
|
val = 100.0 # sky cover required
|
|
return val
|
|
|
|
def clearing_threshold(self, tree, node):
|
|
# Threshold for phrases such as:
|
|
# mostly cloudy in the morning then clearing
|
|
# Used by sky_phrase
|
|
return 31
|
|
|
|
def sky_valueList(self, tree, node):
|
|
# Phrases for sky given values. Tuples consist of:
|
|
# (threshold, dayTime phrase, nightTime phrase)
|
|
# Used by skyRange_phrase
|
|
# NOTE: If you change these words, you MUST also
|
|
# adjust the similarSkyWords_list and preferredSkyWords
|
|
# used for sub-phrase combining and reporting sky trends.
|
|
return [
|
|
(5, "sunny", "clear"),
|
|
(25, "sunny", "mostly clear"),
|
|
(50, "mostly sunny", "partly cloudy"),
|
|
(69, "partly sunny", "mostly cloudy"),
|
|
(87, "mostly cloudy", "mostly cloudy"),
|
|
(100, "cloudy", "cloudy"),
|
|
]
|
|
|
|
def similarSkyWords_list(self, tree, node):
|
|
# The following pairs of sky words will be considered
|
|
# "equal" when comparing for phrase combining
|
|
# and redundancy
|
|
#
|
|
# For trends, (e.g. Sunny in the morning then partly cloudy in the afternoon.)
|
|
# the following transitions are not allowed:
|
|
# Day time:
|
|
# Sunny <--> mostly sunny
|
|
# Mostly sunny <--> partly sunny
|
|
# Partly cloudy <--> mostly cloudy
|
|
# Night time:
|
|
# Clear <--> mostly clear
|
|
# Mostly clear <--> partly cloudy
|
|
# Mostly cloudy <--> cloudy
|
|
#
|
|
# In other words these transitions are allowed:
|
|
# Day time:
|
|
# sunny <--> partly sunny or above
|
|
# mostly sunny <--> mostly cloudy or above
|
|
# partly sunny <--> sunny or cloudy
|
|
# mostly cloudy <--> mostly sunny
|
|
# Night time:
|
|
# clear can go to partly cloudy or above
|
|
# mostly clear <--> mostly cloudy or above
|
|
# partly cloudy <--> mostly cloudy or above
|
|
# mostly cloudy <--> partly cloudy or below
|
|
|
|
dayNight = self.getPeriod(node.getTimeRange(), 1)
|
|
if dayNight == self.DAYTIME():
|
|
return [
|
|
("sunny", "mostly sunny"),
|
|
("mostly sunny", "partly sunny"),
|
|
("partly sunny", "mostly cloudy"),
|
|
("mostly cloudy", "cloudy"),
|
|
]
|
|
else:
|
|
return [
|
|
("clear", "mostly clear"),
|
|
("mostly clear", "partly cloudy"),
|
|
("mostly cloudy", "cloudy"),
|
|
]
|
|
|
|
def similarSkyWords_flag(self, tree, node, words1, words2):
|
|
# Returns 1 if the pair of words is equal or similar
|
|
# according to the "similarSkyWords_list"
|
|
if words1 == words2:
|
|
return 1
|
|
# Check for similarity
|
|
for value1, value2 in self.similarSkyWords_list(tree, node):
|
|
if (words1 == value1 and words2 == value2) or \
|
|
(words2 == value1 and words1 == value2):
|
|
return 1
|
|
return 0
|
|
|
|
def preferredSkyWords(self, tree, node, words1, words2):
|
|
# Returns the preferred words given the pair
|
|
# of words1, words2
|
|
preferredList = ["mostly sunny", "mostly clear", "cloudy"]
|
|
if words1 in preferredList:
|
|
return words1
|
|
if words2 in preferredList:
|
|
return words2
|
|
return words1
|
|
|
|
def reportIncreasingDecreasingSky_flag(self, tree, node):
|
|
# If 1, will use "increasing clouds", "decreasing clouds"
|
|
# wording instead of "mostly cloudy becoming sunny"
|
|
|
|
# You have 3 options:
|
|
# return 0 -- do not use increasing/decreasing wording
|
|
# return 1 -- use increasing/decreasing wording if applicable
|
|
# Use the code shown below to use increasing/decreasing wording
|
|
# but avoid repetitive usage.
|
|
return 0
|
|
#return 1
|
|
|
|
# Use the following code to avoid redundancy e.g.
|
|
# SUNDAY...Increasing clouds.
|
|
# SUNDAY NIGHT...Increasing clouds.
|
|
#
|
|
#If the previous period had increasing or decreasing wording, return 0
|
|
# Otherwise, return 1
|
|
|
|
# Check to see if previous period had increasing or decreasing wording
|
|
component = node.getComponent()
|
|
prevComp = component.getPrev()
|
|
if prevComp is not None:
|
|
# Look at the sky_phrase
|
|
skyWords = self.findWords(
|
|
tree, prevComp, "Sky", node.getAreaLabel(),
|
|
phraseList=[node.getAncestor('name')], phraseLevel=1)
|
|
if skyWords is not None:
|
|
if skyWords.find("increasing") >= 0 or \
|
|
skyWords.find("decreasing") >= 0:
|
|
return 0
|
|
return 1
|
|
return 1
|
|
|
|
def reportClearSkyForExtendedPeriod_flag(self, tree, node):
|
|
# If 1, will report clear/mostly clear wording for periods that
|
|
# exceed 12 hours. Otherwise, will report sunny/mostly sunny.
|
|
return 1
|
|
|
|
def sky_value(self, tree, node, value, dayNight, returnIndex=0):
|
|
# Check for areal coverage term
|
|
# Otherwise, access the sky_valueList and return words corresponding to value
|
|
if value is None:
|
|
return ""
|
|
words = self.areal_sky_value(tree, node, value, dayNight)
|
|
if words is not None:
|
|
# Set to use then connector only
|
|
node.set("connector", " then ")
|
|
# Return areal wording
|
|
if returnIndex:
|
|
return words, 0
|
|
else:
|
|
return words
|
|
sky_valueList = self.sky_valueList(tree, node)
|
|
for i in range(len(sky_valueList)):
|
|
threshold, dayWords, nightWords = sky_valueList[i]
|
|
if value <= threshold:
|
|
flag = self.reportClearSkyForExtendedPeriod_flag(tree, node)
|
|
if flag == 1:
|
|
if dayNight == self.DAYTIME():
|
|
words = dayWords
|
|
else:
|
|
words = nightWords
|
|
else:
|
|
if dayNight == self.NIGHTTIME():
|
|
words = nightWords
|
|
else:
|
|
words = dayWords
|
|
if returnIndex:
|
|
return words, i
|
|
else:
|
|
return words
|
|
|
|
def areal_sky_flag(self, tree, node):
|
|
# Set to 1 if you want to use areal (e.g. patchy clouds, areas of clouds)
|
|
# vs. traditional sky wording when appropriate.
|
|
# BE SURE AND SET THE "arealSkyAnalysis" flag to 1 in the Definition section!
|
|
# You may want to base this decision on the current edit area and/or
|
|
# component e.g. "Period_1"
|
|
return 0
|
|
|
|
def areal_sky_value(self, tree, node, value, dayNight):
|
|
if not self.areal_sky_flag(tree, node):
|
|
return None
|
|
skyBins = tree.stats.get("Sky", node.getTimeRange(),
|
|
node.getAreaLabel(),
|
|
statLabel="binnedPercent",
|
|
mergeMethod="MergeBins")
|
|
#print "skyBins", skyBins, node.getTimeRange()
|
|
if skyBins is None:
|
|
return None
|
|
|
|
# Determine percent in highest bin
|
|
length = len(skyBins)
|
|
highBin = skyBins[length-1]
|
|
low, high, highBinPercent = highBin
|
|
|
|
# Base wording on high bin percent
|
|
words = None
|
|
#print "highBinPercent", highBinPercent
|
|
for skyPercent, skyWords in self.areal_skyPercentages(tree, node):
|
|
#print "skyPercent", skyPercent
|
|
if highBinPercent > skyPercent:
|
|
words = skyWords
|
|
break
|
|
#print "words", words
|
|
if words is None:
|
|
return None # Revert to traditional coverage
|
|
|
|
# Check for sky-related Wx
|
|
wxStats = tree.stats.get("Wx", node.getTimeRange(), node.getAreaLabel(),
|
|
mergeMethod="Average")
|
|
if wxStats is None:
|
|
return None
|
|
# Keep track of skyRelatedWx that we have added to the wording already
|
|
# so we don't end up with "Areas of low clouds and fog and fog."
|
|
foundWx = []
|
|
for wx in self.areal_skyRelatedWx(tree, node):
|
|
# Look for "dense" fog
|
|
dense = ""
|
|
if wx == "F":
|
|
for subkey, rank in wxStats:
|
|
if subkey.wxType() == "F" and subkey.intensity() == "+":
|
|
dense = "dense "
|
|
for subkey, rank in wxStats:
|
|
if subkey.wxType() == wx and wx not in foundWx:
|
|
foundWx.append(wx)
|
|
# Add wording
|
|
words = words + " and " + dense + subkey.wxDef().typeDesc(wx).lower()
|
|
return words
|
|
|
|
def areal_skyPercentages(self, tree, node):
|
|
# Used IF the areal_sky_flag is 1.
|
|
# Each tuple is a (skyValue, words) pair such that if the
|
|
# sky percentage with the highest areal coverage exceeds
|
|
# the given skyValue, the associated words are used.
|
|
return [
|
|
(80, "low clouds"),
|
|
(40, "areas of clouds"),
|
|
(9, "patchy clouds"),
|
|
]
|
|
|
|
def areal_skyRelatedWx(self, tree, node):
|
|
# Used IF the areal_sky_flag is 1.
|
|
# Weather types that are related to sky cover and will be included in the
|
|
# sky phrase if their areal coverage matches the sky areal coverage.
|
|
# For example: areas of low clouds and fog in the morning, then mostly sunny.
|
|
return ["F", "L"]
|
|
|
|
def disableSkyRelatedWx(self, tree, node):
|
|
# Disable the areal_skyRelatedWx subkeys for the given node
|
|
wxStats = tree.stats.get("Wx", node.getTimeRange(), node.getAreaLabel(),
|
|
mergeMethod="Average")
|
|
if wxStats is None:
|
|
return
|
|
disabled = node.getAncestor("disabledSubkeys")
|
|
if disabled is None:
|
|
disabled = []
|
|
#print "wxStats", wxStats
|
|
for wx in self.areal_skyRelatedWx(tree, node):
|
|
for subkey, rank in wxStats:
|
|
if subkey.wxType() == wx:
|
|
disabled.append(subkey)
|
|
node.set("disabledSubkeys", disabled)
|
|
|
|
def sky_phrase(self):
|
|
return {
|
|
"setUpMethod": self.sky_setUp,
|
|
"wordMethod": self.sky_words,
|
|
"phraseMethods": [
|
|
self.checkLocalEffects,
|
|
self.combineSky,
|
|
self.skySpecialCases,
|
|
self.combineWords,
|
|
self.fillNulls,
|
|
self.timeDescriptorModeration,
|
|
self.sky_timeDescriptorModeration,
|
|
self.assembleSubPhrases,
|
|
self.postProcessPhrase,
|
|
]
|
|
}
|
|
def sky_setUp(self, tree, node):
|
|
sky = self.ElementInfo("Sky", "List")
|
|
elementInfoList = [sky]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
return self.DONE()
|
|
|
|
def combineSky(self, tree, node):
|
|
return self.combineChildren(tree, node, self.combine_sky)
|
|
def combine_sky(self, tree, node, subPhrase1, subPhrase2):
|
|
skyValue1 = self.getScalarData(tree, subPhrase1, "Sky", "MinMax")
|
|
skyValue2 = self.getScalarData(tree, subPhrase2, "Sky", "MinMax")
|
|
if skyValue1 is None and skyValue2 is None:
|
|
return 1, None
|
|
if skyValue1 is None or skyValue2 is None:
|
|
return 0, None
|
|
timeRange = node.getTimeRange()
|
|
if timeRange.duration() > 12*3600:
|
|
dayNight = -1
|
|
else:
|
|
dayNight = self.getPeriod(timeRange, 1)
|
|
words1 = self.sky_value(tree, subPhrase1, self.getValue(skyValue1), dayNight)
|
|
words2 = self.sky_value(tree, subPhrase2, self.getValue(skyValue2), dayNight)
|
|
if self.similarSkyWords_flag(tree, subPhrase1, words1, words2):
|
|
min1, max1 = skyValue1
|
|
min2, max2 = skyValue2
|
|
newVal = (min(min1, min2), max(max1, max2))
|
|
elementInfoList = node.get("elementInfoList")
|
|
newSubPhrase = self.combine2SubPhrases(
|
|
tree, node, subPhrase1, subPhrase2, elementInfoList, newVal)
|
|
return 1, newSubPhrase
|
|
else:
|
|
return 0, None
|
|
|
|
def skySpecialCases(self, tree, node):
|
|
# If phrase has exactly 2 subphrases,
|
|
# Look for clearing.
|
|
# If not, then if reportIncreasingDecreasing,
|
|
# report increasing/decreasing wording.
|
|
subPhrases = node.get("childList")
|
|
if len(subPhrases) == 2:
|
|
words = None
|
|
skyValue1 = self.getScalarData(tree, subPhrases[0], "Sky", "Average")
|
|
skyValue2 = self.getScalarData(tree, subPhrases[1], "Sky", "Average")
|
|
# Look for clearing
|
|
clearing_threshold = self.clearing_threshold(tree, node)
|
|
if skyValue1 > skyValue2 and skyValue2 <= clearing_threshold and skyValue1 > clearing_threshold:
|
|
period1Phrase = self.timePeriod_descriptor(tree, node, subPhrases[0].getTimeRange())
|
|
period1Phrase = self.addSpace(period1Phrase, "leading")
|
|
timeRange = node.getTimeRange()
|
|
if timeRange.duration() > 12*3600:
|
|
dayNight = -1
|
|
else:
|
|
dayNight = self.getPeriod(timeRange, 1)
|
|
words1 = self.sky_value(tree, subPhrases[0], skyValue1, dayNight)
|
|
words = words1 + period1Phrase + " then clearing"
|
|
else:
|
|
reportIncreasingDecreasing = self.reportIncreasingDecreasingSky_flag(tree, node)
|
|
if reportIncreasingDecreasing:
|
|
if skyValue2 > skyValue1:
|
|
words = "increasing clouds"
|
|
else:
|
|
words = "decreasing clouds"
|
|
if words is not None:
|
|
# End processing of the phrase; we are done
|
|
node.set("doneList", node.get("methodList"))
|
|
return self.setWords(node, words)
|
|
return self.DONE()
|
|
|
|
def sky_timeDescriptorModeration(self, tree, node):
|
|
# If only two subphrases, turn off second time descriptor
|
|
#
|
|
childList = node.get("childList")
|
|
length = len(childList)
|
|
# Check for words
|
|
if length > 0:
|
|
words = childList[0].get("words")
|
|
if words is None:
|
|
return
|
|
else:
|
|
return self.DONE()
|
|
if length == 2:
|
|
words0 = childList[0].get("words")
|
|
words1 = childList[1].get("words")
|
|
if words0 != "" and words1 != "":
|
|
# Neither is null
|
|
flag0 = 1
|
|
flag1 = 0
|
|
else: # One is null
|
|
flag0 = 1
|
|
flag1 = 1
|
|
if words0 == "": # First sub-phrase is null
|
|
childList[1].set("words", "becoming " + words1)
|
|
childList[0].set("timeDescFlag", flag0)
|
|
childList[1].set("timeDescFlag", flag1)
|
|
return self.DONE()
|
|
|
|
def sky_words(self, tree, node):
|
|
# Create sky phrase.
|
|
statDict = node.getStatDict()
|
|
sky = self.getStats(statDict, "Sky")
|
|
if sky is None:
|
|
return self.setWords(node, "")
|
|
|
|
# Check Pop i.e. don't report sky if we can assume overcast
|
|
threshold = self.pop_sky_lower_threshold(tree, node)
|
|
if self.lowPop_flag(tree, node, threshold) == 0:
|
|
return self.setWords(node, "")
|
|
|
|
sky = self.getValue(sky)
|
|
timeRange = node.getTimeRange()
|
|
if timeRange.duration() > 12*3600:
|
|
words = self.getSkyDiurnalWords(tree, node)
|
|
if words is not None:
|
|
return self.setWords(node, words)
|
|
dayNight = -1
|
|
else:
|
|
dayNight = self.getPeriod(timeRange, 1)
|
|
words = self.sky_value(tree, node, sky, dayNight)
|
|
return self.setWords(node, words)
|
|
|
|
def getSkyDiurnalWords(self, tree, node):
|
|
# Produce words such as
|
|
# xx in the night and morning otherwise yy
|
|
# where xx is the sky value for the night and morning
|
|
# and yy is the sky value otherwise
|
|
#
|
|
# If the night and morning words are the same as the
|
|
# evening and afternoon, (no diurnal pattern),
|
|
# return None
|
|
|
|
# If we have not tested for diurnal sky and wx, return
|
|
if "DiurnalSkyWx" not in self.periodCombining_elementList(tree, node):
|
|
return None
|
|
|
|
wordList = []
|
|
index = 0
|
|
trList = self.divideRange(node.getTimeRange(), 6)
|
|
dayNight = self.getPeriod(trList[0], 1)
|
|
# Need to save timeRange so we can re-set it for determining
|
|
# words for sub-ranges
|
|
saveTR = node.getTimeRange()
|
|
# Only need to use first 12 hours to check for similarity
|
|
for tr in trList[0:2]:
|
|
sky = tree.stats.get("Sky", tr, node.getAreaLabel(),
|
|
mergeMethod="Average")
|
|
sky = self.getValue(sky)
|
|
node.timeRange = tr
|
|
result = self.sky_value(tree, node, sky, dayNight)
|
|
wordList.append(result)
|
|
#print "\nsky, tr", sky, tr
|
|
#print "words", result
|
|
index += 1
|
|
# Re-set timeRange
|
|
node.timeRange = saveTR
|
|
#print "\nwordList", wordList
|
|
if wordList[0] == wordList[1]:
|
|
return None
|
|
if dayNight == self.DAYTIME():
|
|
# First period is the morning
|
|
words1 = wordList[0]
|
|
words2 = wordList[1]
|
|
descriptor = " in the morning and night"
|
|
else:
|
|
# First period is the evening
|
|
words1 = wordList[1]
|
|
words2 = wordList[0]
|
|
descriptor = " in the night and morning"
|
|
words2 = words2.replace("sunny", "clear")
|
|
words = words1 + descriptor + ", otherwise " + words2
|
|
#print "returning", words
|
|
return words
|
|
|
|
def simple_sky_phrase(self):
|
|
return {
|
|
"phraseMethods": [
|
|
self.simple_sky_words, # phrase.words
|
|
],
|
|
}
|
|
def simple_sky_words(self, tree, phrase):
|
|
# Create sky phrase.
|
|
|
|
# If no information, do not report sky condition
|
|
timeRange = phrase.getTimeRange()
|
|
#print "Getting sky"
|
|
skyStats = tree.stats.get("Sky", timeRange, phrase.getAreaLabel(), mergeMethod="List")
|
|
#print "Sky ", skyStats
|
|
statsByRange = self.makeRangeStats(tree, self.SCALAR(), skyStats, timeRange)
|
|
#print "Sky ", statsByRange
|
|
if statsByRange is None:
|
|
return self.setWords(phrase, "")
|
|
|
|
# Check Pop i.e. don't report sky if we can assume overcast
|
|
threshold = self.pop_sky_lower_threshold(tree, phrase)
|
|
if self.lowPop_flag(tree, phrase, threshold) == 0:
|
|
return self.setWords(phrase, "")
|
|
|
|
reportIncreasingDecreasing = self.reportIncreasingDecreasingSky_flag(tree, phrase)
|
|
|
|
# Get values for each part of time range
|
|
if len(statsByRange) == 1:
|
|
skyTime1, period1 = statsByRange[0]
|
|
skyTime2, period2 = statsByRange[0]
|
|
else:
|
|
skyTime1, period1 = statsByRange[0]
|
|
skyTime2, period2 = statsByRange[1]
|
|
|
|
skyTime1 = self.getValue(skyTime1)
|
|
skyTime2 = self.getValue(skyTime2)
|
|
|
|
dayNight1 = self.getPeriod(period1, 1)
|
|
dayNight2 = self.getPeriod(period2, 1)
|
|
|
|
# Determine category and phrase for skyTime1 and skyTime2
|
|
skyPhrase, valueIndex = self.sky_value(tree, phrase, skyTime1, dayNight1, 1)
|
|
words1 = skyPhrase
|
|
index1 = valueIndex
|
|
skyPhrase, valueIndex = self.sky_value(tree, phrase, skyTime2, dayNight2, 1)
|
|
words2 = skyPhrase
|
|
index2 = valueIndex
|
|
|
|
period1Phrase = self.timePeriod_descriptor(tree, phrase, period1)
|
|
period1Phrase = self.addSpace(period1Phrase, "leading")
|
|
|
|
# Look for clearing
|
|
clearing_threshold = self.clearing_threshold(tree, phrase)
|
|
if skyTime1 > skyTime2 and skyTime2 <= clearing_threshold and skyTime1 > clearing_threshold:
|
|
return self.setWords(phrase, words1 + period1Phrase + " then clearing")
|
|
|
|
# See if skyTime1 is different from skyTime2 by more than
|
|
# one category of sky values
|
|
if abs(index1 - index2) > self.sky_index_difference(tree, phrase):
|
|
if reportIncreasingDecreasing == 1:
|
|
if skyTime2 > skyTime1:
|
|
return self.setWords(phrase, "increasing clouds")
|
|
else:
|
|
return self.setWords(phrase, "decreasing clouds")
|
|
else:
|
|
return self.setWords(phrase, words1 + period1Phrase + " then becoming " + words2)
|
|
# Report Average value
|
|
else:
|
|
skyValue = self.average(skyTime1, skyTime2)
|
|
if timeRange.duration() > 12*3600:
|
|
dayNight = -1
|
|
else:
|
|
dayNight = self.getPeriod(timeRange, 1)
|
|
words = self.sky_value(tree, phrase, skyValue, dayNight)
|
|
return self.setWords(phrase, words)
|
|
|
|
# PoP
|
|
def wxQualifiedPoP_flag(self, tree, node):
|
|
# If 1, PoP phrases will be qualified with the weather type
|
|
# E.g. "Chance of rain and snow 20 percent." instead of
|
|
# "Chance of precipitation 20 percent."
|
|
return 1
|
|
|
|
def popMax_phrase(self):
|
|
return {
|
|
"setUpMethod": self.popMax_setUp,
|
|
"wordMethod": self.popMax_words,
|
|
"phraseMethods": self.standard_phraseMethods()
|
|
}
|
|
def popMax_setUp(self, tree, node):
|
|
# NOTE: The method is set to "Average" instead of "List" so
|
|
# that the PoP phrase will always cover the full period.
|
|
# It doesn't matter what method (other than List) we choose
|
|
# since the popMax_words method gets its PoP value directly from
|
|
# the "matchToWx" method.
|
|
elementInfoList = [self.ElementInfo("PoP", "Average")]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
return self.DONE()
|
|
|
|
|
|
def popMax_words(self, tree, node) :
|
|
"Create phrase Probability of Precipitation for maximum value"
|
|
# Wait for weather phrase to complete
|
|
wxWords = ""
|
|
attrDict = {}
|
|
if self.wxQualifiedPoP_flag(tree, node) == 1:
|
|
compArea = node.getComponent().getAreaLabel()
|
|
wxWords, attrDict = self.findWords(tree, node, "Wx", [node.getAreaLabel(), compArea],
|
|
phraseList=["weather_phrase", "skyPopWx_phrase"],
|
|
attributes=['reportedRankList'])
|
|
if wxWords is None:
|
|
return
|
|
#print "wxWords", wxWords
|
|
if wxWords == "":
|
|
#print "setting popMax to Null"
|
|
return self.setWords(node, "null")
|
|
|
|
#print "PopMax", node.getAreaLabel(), wxWords
|
|
pop = self.matchToWx(tree, node, "PoP")
|
|
#print " Pop", pop
|
|
if pop is None:
|
|
return self.setWords(node, "")
|
|
|
|
# Check pop thresholds
|
|
pop = self.getValue(pop, "Max")
|
|
if pop < self.pop_lower_threshold(tree, node) or \
|
|
pop > self.pop_upper_threshold(tree, node):
|
|
return self.setWords(node, "")
|
|
|
|
popType = self.getPopType(tree, node, pop, wxWords, attrDict)
|
|
node.set("popType", popType)
|
|
result = self.checkRepeatingString(tree, node, popType, "popType",0)
|
|
if result == -1:
|
|
# Wait for previous phrase to finish
|
|
return
|
|
popType = self.addSpace(result)
|
|
|
|
unit = self.units_descriptor(tree, node, "unit", "percent")
|
|
popStr = self.getPopStr(tree, node, pop)
|
|
words = popType + popStr + " " + unit
|
|
|
|
# Need to try and set phrase descriptor at this point since
|
|
# weather phrase was not complete during phrase set-up
|
|
phrase = node.parent
|
|
if phrase.get("descriptor") is None:
|
|
descriptor = self.phrase_descriptor(tree, phrase, "PoP", "PoP")
|
|
phrase.set("descriptor", descriptor)
|
|
|
|
return self.setWords(node, words)
|
|
|
|
def getPopStr(self, tree, node, pop):
|
|
pop = int(pop)
|
|
if pop >= 100:
|
|
popWords = "near 100"
|
|
else:
|
|
popWords = repr(pop)
|
|
return popWords
|
|
|
|
def getPopType(self, tree, node, pop, wxWords, attrDict):
|
|
popType = "precipitation"
|
|
if self.wxQualifiedPoP_flag(tree, node) == 1:
|
|
# Examine reported weather type(s) from phrase.
|
|
# If there is more than one descriptor for precipitating weather
|
|
# or if they are general weather types,
|
|
# return "precipitation"
|
|
# Otherwise, describe the weather type
|
|
# e.g. chance of rain, chance of snow
|
|
wxTypes = []
|
|
if "reportedRankList" in attrDict:
|
|
rankList = attrDict["reportedRankList"]
|
|
for subkey, rank in rankList:
|
|
wxTypes.append(subkey.wxType())
|
|
generalTypes = ["IP", "ZL", "ZR", "ZF", "ZY"]
|
|
for general in generalTypes:
|
|
if general in wxTypes:
|
|
return "precipitation"
|
|
descriptors = {
|
|
"R": "rain",
|
|
"RW": "showers",
|
|
"S": "snow",
|
|
"SW": "snow",
|
|
"T": "thunderstorms",
|
|
}
|
|
popTypes = []
|
|
for wxType in wxTypes:
|
|
if wxType in ["R", "S", "RW", "SW", "T"]:
|
|
desc = descriptors[wxType]
|
|
if desc not in popTypes:
|
|
popTypes.append(desc)
|
|
if len(popTypes) > 1:
|
|
popType = "precipitation"
|
|
elif len(popTypes) == 1:
|
|
popType = popTypes[0]
|
|
return popType
|
|
|
|
# This version will report only the weather types that
|
|
# match the reported PoP
|
|
## def getPopType(self, tree, node, pop, wxWords, attrDict):
|
|
## popType = "precipitation"
|
|
## if self.wxQualifiedPoP_flag(tree, node) == 1:
|
|
## ## Need to find weather type(s) from phrase.
|
|
## ## "wxWords" is the concatenation of all weather phrases
|
|
## ## for this component.
|
|
## ## Returns "popType" e.g. chance of rain, chance of rain and snow
|
|
## wxTypes = []
|
|
## if attrDict.has_key("reportedRankList"):
|
|
## rankList = attrDict["reportedRankList"]
|
|
## for subkey, rank in rankList:
|
|
## # Check the coverage against the reported PoP
|
|
## covLow, covHigh = self.coveragePoP_value(subkey.coverage())
|
|
## if covHigh >= pop:
|
|
## wxTypes.append(subkey.wxType())
|
|
## popType = None
|
|
## generalTypes = ["IP", "ZL", "ZR", "ZF", "ZY"]
|
|
## for general in generalTypes:
|
|
## if general in wxTypes:
|
|
## popType = "precipitation"
|
|
## if popType is None:
|
|
## rain = 0
|
|
## snow = 0
|
|
## thunder = 0
|
|
## showers = 0
|
|
## snowShowers = 0
|
|
## rainShowers = 0
|
|
## if "R" in wxTypes:
|
|
## rain = 1
|
|
## if "S" in wxTypes:
|
|
## snow = 1
|
|
## if "RW" in wxTypes:
|
|
## showers = 1
|
|
## if "SW" in wxTypes:
|
|
## snowShowers = 1
|
|
## if "T" in wxTypes:
|
|
## thunder = 1
|
|
## if showers and not snowShowers:
|
|
## rainShowers = 1
|
|
## if (rain or rainShowers or thunder) and snow:
|
|
## popType = "precipitation"
|
|
## else:
|
|
## if snow or snowShowers:
|
|
## if rain or rainShowers:
|
|
## if wxWords.find(" or ") > -1:
|
|
## popType = "rain or snow"
|
|
## else:
|
|
## popType = "rain and snow"
|
|
## else:
|
|
## popType = "snow"
|
|
## elif rain and not rainShowers:
|
|
## popType = "rain"
|
|
## elif showers:
|
|
## popType = "showers"
|
|
## if thunder:
|
|
## popType = "showers and thunderstorms"
|
|
## elif thunder:
|
|
## popType = "thunderstorms"
|
|
## else:
|
|
## popType = "precipitation"
|
|
## if popType is None:
|
|
## popType = "precipitation"
|
|
## return popType
|
|
|
|
def areal_or_chance_pop_descriptor(self, tree, node, key, elementName):
|
|
# Stats: dominantWx
|
|
# Returns descriptor for a pop phrase based on Wx
|
|
# Returns areal coverage of precipitation OR
|
|
# chance of precipitation
|
|
# Get weather. Determine if ANY terms in the period are convective. If so,
|
|
# change the phrase to "areal coverage". This is an Amarillo WFO
|
|
# preference.
|
|
wxPhrase = self.findWords(tree, node, "Wx", node.getAreaLabel(),
|
|
phraseList=["weather_phrase", "skyPopWx_phrase"])
|
|
if wxPhrase is None:
|
|
return None
|
|
if wxPhrase == "":
|
|
return "chance of"
|
|
use_areal = 0
|
|
|
|
if wxPhrase.find("isolated") >= 0:
|
|
use_areal = 1
|
|
if wxPhrase.find("scattered") >= 0:
|
|
use_areal = 1
|
|
if wxPhrase.find("numerous") >= 0:
|
|
use_areal = 1
|
|
if wxPhrase.find("widespread") >= 0:
|
|
use_areal = 1
|
|
|
|
if use_areal == 1:
|
|
return "areal coverage of"
|
|
else:
|
|
return "chance of"
|
|
|
|
def allAreal_or_chance_pop_descriptor(self, tree, node, key, elementName):
|
|
# Stats: rankedWx
|
|
# Returns descriptor for a pop phrase based on Wx
|
|
# Returns areal coverage of precipitation OR
|
|
# chance of precipitation
|
|
# Get weather. Determine if ALL terms in the period are convective. If so,
|
|
# change the phrase to "areal coverage". This is an Amarillo WFO
|
|
# preference.
|
|
statsByRange = tree.stats.get(
|
|
"Wx", node.getTimeRange(), node.getAreaLabel(), mergeMethod="List")
|
|
if statsByRange is None:
|
|
return "chance of"
|
|
use_areal = 1
|
|
|
|
for rankList, subRange in statsByRange:
|
|
subkeys = self.getSubkeys(rankList)
|
|
for subkey in subkeys:
|
|
if self.precip_related_flag(tree, node, subkey):
|
|
cov = subkey.coverage()
|
|
if cov not in ["Iso", "Sct", "Num", "Wide", "<NoCov>"]:
|
|
use_areal = 0
|
|
break
|
|
|
|
if use_areal == 1:
|
|
return "areal coverage of"
|
|
else:
|
|
return "chance of"
|
|
|
|
# Temperature worded phrases:
|
|
# HIGHS IN THE MIDDLE 80S
|
|
# HIGHS IN THE MIDDLE 80S TO LOWER 90S
|
|
# using temp_phrase_threshold
|
|
|
|
def highs_phrase(self):
|
|
return {
|
|
"setUpMethod": self.highs_setUp,
|
|
"wordMethod": self.temp_words,
|
|
"phraseMethods": self.standard_phraseMethods()
|
|
}
|
|
def highs_setUp(self, tree, node):
|
|
elementInfoList = [self.ElementInfo("MaxT", "List")]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
return self.DONE()
|
|
|
|
def lows_phrase(self):
|
|
return {
|
|
"setUpMethod": self.lows_setUp,
|
|
"wordMethod": self.temp_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
def lows_setUp(self, tree, node):
|
|
elementInfoList = [self.ElementInfo("MinT", "List")]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
return self.DONE()
|
|
|
|
def temp_words(self, tree, node):
|
|
stats = self.getTempStats(tree, node)
|
|
if stats is None:
|
|
return self.setWords(node, "")
|
|
elementName = node.getAncestor("elementName")
|
|
words = self.getTempPhrase(tree, node, stats, elementName)
|
|
return self.setWords(node, words)
|
|
|
|
def tempDiff_threshold(self, tree, node):
|
|
# If the difference between the minimum and maximum temperature values
|
|
# exceeds this range, report the actual values e.g. 23 to 29.
|
|
return 4
|
|
|
|
def getTempPhrase(self, tree, node, temp, elementName):
|
|
minVal, maxVal = self.getValue(temp, "MinMax")
|
|
minVal = int(minVal)
|
|
maxVal = int(maxVal)
|
|
|
|
# Chris Gibson's version
|
|
## # Handle teens
|
|
## ave = int((maxVal + minVal)/2)
|
|
## if ave < 20 and ave > 9:
|
|
## if ave > 15:
|
|
## return "15-20"
|
|
## elif ave == 15 or ave == 14:
|
|
## return "near 15"
|
|
## elif ave == 10 or ave == 11:
|
|
## return "near 10"
|
|
## else:
|
|
## return "10-15"
|
|
## if ave < 10 and ave > 0:
|
|
## if ave > 4:
|
|
## return "5 to 10 above"
|
|
## else:
|
|
## return "zero to 5 above"
|
|
|
|
## if minVal <=0 or maxVal <=0:
|
|
## maxVal = int(self.round(maxVal, "Nearest", 5))
|
|
## minVal = int(self.round(minVal, "Nearest", 5))
|
|
|
|
# End Chris Gibson's version
|
|
|
|
# Check for exceptions
|
|
exceptions = self.tempPhrase_exceptions(tree, node)
|
|
for minBoundaries, maxBoundaries, equalityPhrase, phrase in exceptions:
|
|
if minVal >= minBoundaries[0] and minVal <= minBoundaries[1] and \
|
|
maxVal >= maxBoundaries[0] and maxVal <= maxBoundaries[1]:
|
|
if minVal == maxVal:
|
|
resultPhrase = equalityPhrase
|
|
else:
|
|
resultPhrase = phrase
|
|
return self.constructTempException(resultPhrase, minVal, maxVal)
|
|
|
|
# Handle actual range values
|
|
if abs(maxVal-minVal) > self.tempDiff_threshold(tree, node):
|
|
return repr(minVal) + " to " + repr(maxVal)
|
|
|
|
# set up for "lower," "mid," or "upper" wording
|
|
# Modulus (%) gets tricky below zero so have to take
|
|
# modulus of abs(temperature)
|
|
decadeMaxStr = self.getDecadeStr(maxVal)
|
|
decadeMinStr = self.getDecadeStr(minVal)
|
|
digitMax = abs(maxVal) % 10
|
|
digitMin = abs(minVal) % 10
|
|
boundaries = self.tempPhrase_boundary_dict(tree, node)
|
|
digitMinStr = self.getDigitStr(digitMin, boundaries)
|
|
digitMaxStr = self.getDigitStr(digitMax, boundaries)
|
|
lowerMax = boundaries["lower"][1]
|
|
upperMin = boundaries["upper"][0]
|
|
if decadeMinStr == decadeMaxStr:
|
|
# this solves the problem of returning "...IN THE LOWER 60s TO LOWER 60s..."
|
|
if digitMinStr == digitMaxStr:
|
|
return "in the " + digitMinStr + " " + decadeMinStr
|
|
|
|
# shortens a return of "...lower to upper..." to "...in the xxS"
|
|
elif digitMin <= lowerMax and digitMax >= upperMin:
|
|
return "in the " + decadeMaxStr
|
|
|
|
else:
|
|
return "in the " + digitMinStr + " to " + digitMaxStr + " " + decadeMaxStr
|
|
elif digitMinStr == digitMaxStr:
|
|
# return 50s TO LOWER 60s (not LOWER 50s TO LOWER 60s)
|
|
return "in the " + decadeMinStr + " to " + digitMaxStr + " " + decadeMaxStr
|
|
else: # different decade
|
|
if maxVal >= 100 and minVal < 100: # UPPER 80s to 102
|
|
return digitMinStr + " " + decadeMinStr + " to " + str(maxVal)
|
|
# return NEAR 60 (not UPPER 50s TO LOWER 60s)
|
|
elif digitMin >= upperMin and digitMax <= lowerMax and maxVal - minVal <= 10:
|
|
roundedMax = int(self.round(maxVal, "Nearest", 10))
|
|
return self.constructTempException("near %max", minVal, roundedMax)
|
|
# return 50s and 60s (not lower 50s to upper 60s)
|
|
elif digitMin <= lowerMax and digitMax >= upperMin:
|
|
return "in the " + decadeMinStr + " to " + decadeMaxStr
|
|
digitMinPhrase = digitMinStr + " " + decadeMinStr
|
|
digitMaxPhrase = digitMaxStr + " " + decadeMaxStr
|
|
return "in the " + digitMinPhrase + " to " + digitMaxPhrase
|
|
|
|
def constructTempException(self, phrase, minVal, maxVal):
|
|
phrase = phrase.replace("%min", repr(minVal))
|
|
phrase = phrase.replace("%max", repr(maxVal))
|
|
zeroPhraseMin = self.getZeroPhrase(minVal)
|
|
zeroPhraseMax = self.getZeroPhrase(maxVal)
|
|
phrase = phrase.replace("%zeroPhraseMin", zeroPhraseMin)
|
|
phrase = phrase.replace("%zeroPhraseMax", zeroPhraseMax)
|
|
return phrase
|
|
|
|
def getDecade(self, value):
|
|
decade = abs(int(value)) // 10 * 10
|
|
if value < 0:
|
|
decade = -decade
|
|
return decade
|
|
|
|
def getDecadeStr(self, value):
|
|
decade = self.getDecade(value)
|
|
if decade == 0:
|
|
return "single digits"
|
|
elif decade == 10:
|
|
return "teens"
|
|
elif decade == -10:
|
|
return "teens below zero"
|
|
else:
|
|
return repr(decade) + "s"
|
|
|
|
def getDigitStr(self, value, boundaries):
|
|
for (key, pair) in boundaries.items():
|
|
lower, upper = pair
|
|
if value >= lower and value <= upper:
|
|
return key
|
|
|
|
def tempPhrase_exceptions(self, tree, node):
|
|
# These exceptions to the getTempPhrase are processed before trying to
|
|
# generate a phrase such as "in the lower 20's to upper 30's".
|
|
return [
|
|
# Boundaries are inclusive
|
|
# Min boundaries # Max boundaries # phrase if Min == Max # phrase if Min != Max
|
|
# %min will be replaced by the minimum temperature value
|
|
# %max will be replaced by the maximum temperature value
|
|
# %zeroPhraseMin will be replaced with a zero-based phrase for the min e.g.
|
|
# 12 below
|
|
# %zeroPhraseMax will be replaced with a zero-based phrase for the min e.g.
|
|
# 5 above
|
|
|
|
# Both 100 and above
|
|
[(100,200), (100,200), "around %min", "%min to %max"],
|
|
# Min in 90's, Max 100 and above
|
|
[(90, 99), (100,200), "", "%min to %max"],
|
|
|
|
# Handle lower temperatures
|
|
[(1, 19), (1, 29), "around %min", "%min to %max"],
|
|
# Handle zero temperatures
|
|
[(0, 0), (0, 29), "near zero", "zero to %zeroPhraseMax"],
|
|
[(-200, 0), (0, 0), "near zero", "%zeroPhraseMin to zero"],
|
|
|
|
# Min below zero, Max above zero
|
|
[(-200,-1), (1,200), "near zero", "%zeroPhraseMin to %zeroPhraseMax zero"],
|
|
# Both below zero
|
|
#[(-200,-1), (-200,-1), "%zeroPhraseMin","%zeroPhraseMax to %zeroPhraseMin zero"],
|
|
[(-200,-1), (-200,-1), "around %zeroPhraseMin","%zeroPhraseMax to %zeroPhraseMin zero"],
|
|
|
|
## # Chris Gibson's version Comment out the above exception and use this instead:
|
|
## #[(-200,-1), (-200,-1), "near %zeroPhraseMax","%zeroPhraseMax to %zeroPhraseMin zero"]
|
|
|
|
# Around phrases fix from Steve Nelson
|
|
[(20, 20), (20, 20), "around %min", "%min to %max"],
|
|
[(30, 30), (30, 30), "around %min", "%min to %max"],
|
|
[(40, 40), (40, 40), "around %min", "%min to %max"],
|
|
[(50, 50), (50, 50), "around %min", "%min to %max"],
|
|
[(60, 60), (60, 60), "around %min", "%min to %max"],
|
|
[(70, 70), (70, 70), "around %min", "%min to %max"],
|
|
[(80, 80), (80, 80), "around %min", "%min to %max"],
|
|
[(90, 90), (90, 90), "around %min", "%min to %max"],
|
|
|
|
]
|
|
|
|
def tempPhrase_boundary_dict(self, tree, node):
|
|
return {
|
|
"lower": (0,3),
|
|
"mid": (4,6),
|
|
"upper": (7,9),
|
|
}
|
|
|
|
# Temperature worded phrases:
|
|
# HIGHS 45 TO 50
|
|
# using range_nlValue for "MinT", "MaxT"
|
|
|
|
def highs_range_phrase(self):
|
|
return {
|
|
"setUpMethod": self.highs_setUp,
|
|
"wordMethod": self.tempRange_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
|
|
def lows_range_phrase(self):
|
|
return {
|
|
"setUpMethod": self.lows_setUp,
|
|
"wordMethod": self.tempRange_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
|
|
def tempRange_words(self, tree, node) :
|
|
"Create phrase for Min or Max Temperature"
|
|
stats = self.getTempStats(tree, node)
|
|
if stats is None:
|
|
return self.setWords(node, "")
|
|
elementName = node.getAncestor("elementName")
|
|
words = self.getTempRangePhrase(tree, node, stats, elementName)
|
|
return self.setWords(node, words)
|
|
|
|
def getTempRangePhrase(self, tree, node, temp, elementName):
|
|
connector = self.value_connector(tree, node, elementName, elementName)
|
|
min, max = self.getValue(temp, "MinMax")
|
|
|
|
decadeMax = self.getDecade(max)
|
|
digitMax = max % 10
|
|
decadeMin = self.getDecade(min)
|
|
digitMin = min % 10
|
|
diff = abs(max - min)
|
|
|
|
# "Around" phrases
|
|
# e.g. a range of 19-21 --> "highs around 20"
|
|
around = self.addSpace(self.phrase_descriptor(tree, node, "around", elementName))
|
|
if 0 < diff <= 3 and (digitMax == 0 or digitMax == 1):
|
|
if decadeMax <= 10:
|
|
decadeMax = self.getZeroPhrase(decadeMax)
|
|
else:
|
|
decadeMax = repr(decadeMax)
|
|
return around + decadeMax
|
|
|
|
# Report the range
|
|
min = int(min)
|
|
max = int(max)
|
|
if min == max:
|
|
# Adjust descriptor e.g. highs --> high
|
|
descriptor = node.parent.get("descriptor")
|
|
if descriptor is not None and around == "":
|
|
descriptor = descriptor.replace("s", "")
|
|
node.parent.set("descriptor", descriptor)
|
|
if min <= 10:
|
|
min = self.getZeroPhrase(min)
|
|
else:
|
|
min = repr(min)
|
|
return around + min
|
|
elif min > 0 and max > 0:
|
|
return repr(min) + connector + repr(max)
|
|
elif min <= 0 and max > 0:
|
|
minval = self.getZeroPhrase(min)
|
|
maxval = self.getZeroPhrase(max, 1)
|
|
return minval + " to " + maxval
|
|
else:
|
|
if min < 0 and max < 0:
|
|
firstVal = self.getZeroPhrase(max)
|
|
secondVal = self.getZeroPhrase(min, 1)
|
|
else:
|
|
firstVal = self.getZeroPhrase(min)
|
|
secondVal = self.getZeroPhrase(max, 1)
|
|
return firstVal + connector + secondVal
|
|
|
|
def getZeroPhrase(self, val, addZero=0):
|
|
if val == 0:
|
|
return "zero"
|
|
if val < 0:
|
|
phrase = repr(abs(val)) + " below"
|
|
else:
|
|
phrase = repr(val) + " above"
|
|
if addZero == 1:
|
|
phrase = phrase + " zero"
|
|
return phrase
|
|
|
|
# Extended Temperatures
|
|
def extended_temp_range(self, tree, node):
|
|
# Range for extended temperatures e.g.
|
|
# "Highs 45 to 55."
|
|
# This value must be 10 or 5.
|
|
# Other values are not supported for extended ranges.
|
|
return 10
|
|
#return 5
|
|
|
|
def extended_highs_phrase(self):
|
|
return {
|
|
"setUpMethod": self.highs_setUp,
|
|
"wordMethod": self.extended_temp_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
def extended_lows_phrase(self):
|
|
return {
|
|
"setUpMethod": self.lows_setUp,
|
|
"wordMethod": self.extended_temp_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
|
|
def extended_temp_words(self, tree, node) :
|
|
"Create phrase for Min or Max Temperature"
|
|
stats = self.getTempStats(tree, node)
|
|
if stats is None:
|
|
return self.setWords(node, "")
|
|
elementName = node.get("elementName")
|
|
if elementName == "MaxT":
|
|
mergeMethod = "Max"
|
|
else:
|
|
mergeMethod = "Min"
|
|
temp = int(self.getValue(stats, mergeMethod))
|
|
words = self.getExtendedTempPhrase(tree, node, temp)
|
|
return self.setWords(node, words)
|
|
|
|
def getExtendedTempPhrase(self, tree, node, temp):
|
|
# Temperatures above 99
|
|
# Give exact value
|
|
if temp > 99:
|
|
if node.getIndex() == 0:
|
|
parent = node.getParent()
|
|
descriptor = parent.get("descriptor")
|
|
descriptor = descriptor.replace("s ", " ")
|
|
parent.set("descriptor", descriptor)
|
|
return repr(int(temp))
|
|
|
|
# Temperatures below 10
|
|
# Build and return special phrases
|
|
if temp < -27:
|
|
return "25 below to 35 below"
|
|
elif temp < -22:
|
|
return "20 below to 30 below"
|
|
elif temp < -17:
|
|
return "15 below to 25 below"
|
|
elif temp < -12:
|
|
return "10 below to 20 below"
|
|
elif temp < -7:
|
|
return "5 below to 15 below"
|
|
elif temp < -2:
|
|
return "zero to 10 below"
|
|
elif temp < 3:
|
|
return "5 below zero to 5 above"
|
|
elif temp < 8:
|
|
return "zero to 10 above"
|
|
elif temp < 10:
|
|
return "5 to 15"
|
|
|
|
# Determine modifier for temperature: around, lower, mid, upper
|
|
decade = self.getDecade(temp)
|
|
digit = temp % 10
|
|
|
|
range = self.extended_temp_range(tree, node)
|
|
if range == 10:
|
|
if digit >= 0 and digit <= 2:
|
|
phrase = self.getExtTemp(decade-5, decade+5)
|
|
elif digit >= 3 and digit <= 7:
|
|
if decade == 10:
|
|
phrase = "in the " + "teens"
|
|
elif decade <= 0 or decade >= 100:
|
|
phrase = self.getExtTemp(decade, decade+10)
|
|
else:
|
|
phrase = "in the " + repr(decade) + "s"
|
|
elif digit >= 8 and digit <=9:
|
|
phrase = self.getExtTemp(decade+5, decade+15)
|
|
else: # Assume range of 5
|
|
if digit >= 0 and digit <= 2:
|
|
phrase = self.getExtTemp(decade, decade+5)
|
|
elif digit >= 3 and digit <= 7:
|
|
if decade == 10:
|
|
phrase = "in the " + "teens"
|
|
elif decade <= 0 or decade >= 100:
|
|
phrase = self.getExtTemp(decade, decade+5)
|
|
else:
|
|
phrase = "in the " + repr(decade) + "s"
|
|
elif digit >= 8 and digit <=9:
|
|
phrase = self.getExtTemp(decade+5, decade+10)
|
|
|
|
return phrase
|
|
|
|
def getExtTemp(self, val1, val2):
|
|
v1 = repr(val1)
|
|
if val1 < 0:
|
|
v1 = v1 + " below"
|
|
v2 = repr(val2)
|
|
if val2 < 0:
|
|
v2 = v2 + " below"
|
|
return v1 + " to " + v2
|
|
|
|
def getTempStats(self, tree, node):
|
|
"Get correct Temperature stats (MaxT or MinT) to determine temperature phrase"
|
|
elementName = node.getAncestor("elementName")
|
|
timeRange = node.getTimeRange()
|
|
areaLabel = node.getAreaLabel()
|
|
day = self.getPeriod(timeRange, 1)
|
|
# day is 1=DAYTIME or 0=NIGHTTIME or -1=DAYNIGHT (spans both day and night)
|
|
# In the normal case, MaxT is greater than MinT and:
|
|
# for highs, return MaxT
|
|
# for lows, return MinT
|
|
# If, however, MaxT is less than MinT, then MaxT and MinT have to be switched
|
|
|
|
# Don't do highs at night or lows in the day
|
|
if elementName == "MaxT":
|
|
dayValue = self.DAYTIME()
|
|
else:
|
|
dayValue = self.NIGHTTIME()
|
|
if not day == self.DAYNIGHT() and not day == dayValue:
|
|
return None
|
|
|
|
if timeRange.duration() <= 12*3600:
|
|
statDict = node.getStatDict()
|
|
stats = self.getStats(statDict, elementName)
|
|
return stats
|
|
else:
|
|
# If the time period spans day and night,
|
|
# get the conglomerate stats.
|
|
maxT = tree.stats.get("MaxT", timeRange, areaLabel,
|
|
mergeMethod="MinMax")
|
|
minT = tree.stats.get("MinT", timeRange, areaLabel,
|
|
mergeMethod="MinMax")
|
|
if maxT is None and minT is None:
|
|
return None
|
|
if maxT is None:
|
|
if dayValue == self.DAYTIME():
|
|
return None
|
|
else:
|
|
return minT
|
|
if minT is None:
|
|
if dayValue == self.NIGHTTIME():
|
|
return None
|
|
else:
|
|
return maxT
|
|
# Check for case of MaxT < MinT
|
|
max = self.getValue(maxT, "Max")
|
|
min = self.getValue(minT, "Max")
|
|
if max < min:
|
|
temp = maxT
|
|
maxT = minT
|
|
minT = temp
|
|
if dayValue == self.DAYTIME():
|
|
return maxT
|
|
else:
|
|
return minT
|
|
|
|
def temp_trends_addToPhrase_flag(self, tree, node):
|
|
# If set to 0, will report:
|
|
# "Temperatures falling in the afternoon."
|
|
# If set to 1:
|
|
# "Temperatures falling to the 50's in the afternoon."
|
|
# If set to 2:
|
|
# "Temperatures falling to the lower 50's in the afternoon."
|
|
return 2
|
|
|
|
def temp_trends(self):
|
|
return {
|
|
"setUpMethod": self.temp_trends_setUp,
|
|
"wordMethod": self.temp_trends_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
def temp_trends_setUp(self, tree, node):
|
|
duration = node.getTimeRange().duration()
|
|
if duration > 12*3600:
|
|
return self.setWords(node, "")
|
|
timeRange = node.getTimeRange()
|
|
dayNight = self.getPeriod(timeRange, 1)
|
|
if dayNight == self.NIGHTTIME():
|
|
eleInfo = self.ElementInfo("MinT", "Min")
|
|
else:
|
|
eleInfo = self.ElementInfo("MaxT", "Max")
|
|
elementInfoList = [eleInfo]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
node.set("descriptor", "")
|
|
return self.DONE()
|
|
|
|
def temp_trends_words(self, tree, node):
|
|
"Look for sharp temperature increases or decreases"
|
|
# Determine if temps rise or fall in a non-diurnal way.
|
|
# MaxT/MinT temps -- min/max tuple for each
|
|
# Hourly Temp Stats: list of hourly temperature tuples
|
|
# Each tuple has:
|
|
# -- average temperature value
|
|
# -- hour of occurrence
|
|
# For a Daytime period, compare MaxT to T for the last grid
|
|
# of the period and report "temperatures falling in the afternoon"
|
|
# if the difference exceeds the temp_trend_nlValue
|
|
# For a Nighttime period, compare MinT to T for the last grid
|
|
# of the period and report "temperatures rising overnight"
|
|
# if the difference exceeds the temp_trend_threshold.
|
|
|
|
statDict = node.getStatDict()
|
|
timeRange = node.getTimeRange()
|
|
tStats = tree.stats.get("T", timeRange, node.getAreaLabel(),
|
|
mergeMethod="List")
|
|
if tStats is None:
|
|
return self.setWords(node, "")
|
|
tStats, subRange = tStats[0]
|
|
if tStats is None:
|
|
return self.setWords(node, "")
|
|
dayNight = self.getPeriod(timeRange,1)
|
|
trend_nlValue = self.temp_trend_nlValue(tree, node)
|
|
if dayNight == self.DAYTIME():
|
|
maxT = self.getStats(statDict, "MaxT")
|
|
if maxT is None:
|
|
return self.setWords(node, "")
|
|
maxT = self.getValue(maxT)
|
|
threshold = self.nlValue(trend_nlValue, maxT)
|
|
else:
|
|
minT = self.getStats(statDict, "MinT")
|
|
if minT is None:
|
|
return self.setWords(node, "")
|
|
minT = self.getValue(minT)
|
|
threshold = self.nlValue(trend_nlValue, minT)
|
|
halfWay = len(tStats) // 2
|
|
|
|
index = len(tStats)-1
|
|
while index >= halfWay:
|
|
tempValue, curHour = tStats[index]
|
|
if tempValue is None:
|
|
index = index - 1
|
|
continue
|
|
|
|
if dayNight == self.DAYTIME():
|
|
if tempValue <= (maxT - threshold):
|
|
toPhrase = self.getToPhrase(tree, node, tempValue)
|
|
words = "temperatures falling" + toPhrase + " in the afternoon"
|
|
return self.setWords(node, words)
|
|
else:
|
|
if tempValue >= (minT + threshold):
|
|
toPhrase = self.getToPhrase(tree, node, tempValue)
|
|
words = "temperatures rising" + toPhrase + " after midnight"
|
|
return self.setWords(node, words)
|
|
break
|
|
return self.setWords(node, "")
|
|
|
|
def getToPhrase(self, tree, node, tempValue):
|
|
flag = self.temp_trends_addToPhrase_flag(tree, node)
|
|
if flag > 0:
|
|
if flag > 1:
|
|
rangeStr = self.getDigitStr(
|
|
abs(tempValue)%10, self.tempPhrase_boundary_dict(tree, node))
|
|
rangeStr += " "
|
|
else:
|
|
rangeStr = ""
|
|
return " into the " + rangeStr + self.getDecadeStr(tempValue)
|
|
else:
|
|
return ""
|
|
|
|
## def temp_trends_words(self, tree, node):
|
|
## "Look for sharp temperature increases or decreases"
|
|
|
|
## # Here is an alternative temp_trends method provided by Tom Spriggs.
|
|
## # If a 12-hour period, it looks at the 12, 3, and 5 o'clock grids
|
|
## # (both am/pm depending on time of day) and verifies the trend (either
|
|
## # going down or up) and then looks at the difference between the
|
|
## # 5 o'clock grid and the MaxT/MinT grid. It only needs to look at the
|
|
## # 5 o'clock grid since that is the last one in the 12-hour period,
|
|
## # and if it is going to trip the threshold anywhere, it will be on that
|
|
## # hour since if you have an unusual temperature trend, it will peak at
|
|
## # that grid. If less than a 12-hour period, then the 3 times that it
|
|
## # checks will be adjusted accordingly inside the smaller time range.
|
|
## statDict = node.getStatDict()
|
|
## timeRange = node.getTimeRange()
|
|
## tStats = tree.stats.get("T", timeRange, node.getAreaLabel(),
|
|
## mergeMethod="List")
|
|
## if tStats is None:
|
|
## return self.setWords(node, "")
|
|
## tStats, subRange = tStats[0]
|
|
## if tStats is None:
|
|
## return self.setWords(node, "")
|
|
## dayNight = self.getPeriod(timeRange,1)
|
|
## trend_nlValue = self.temp_trend_nlValue(tree, node)
|
|
## if dayNight == self.DAYTIME():
|
|
## maxT = self.getStats(statDict, "MaxT")
|
|
## if maxT is None:
|
|
## return self.setWords(node, "")
|
|
## maxT = self.getValue(maxT)
|
|
## threshold = self.nlValue(trend_nlValue, maxT)
|
|
## else:
|
|
## minT = self.getStats(statDict, "MinT")
|
|
## if minT is None:
|
|
## return self.setWords(node, "")
|
|
## minT = self.getValue(minT)
|
|
## threshold = self.nlValue(trend_nlValue, minT)
|
|
|
|
## if len(tStats) >= 6:
|
|
## halfWay = len(tStats) - 6
|
|
## quarterWay = len(tStats) - 3
|
|
## endPoint = len(tStats) - 1
|
|
## elif len(tStats) >= 4:
|
|
## halfWay = 0
|
|
## quarterWay = len(tStats) - 3
|
|
## endPoint = len(tStats) - 1
|
|
## elif len(tStats) == 1:
|
|
## halfWay = 0
|
|
## quarterWay = 0
|
|
## endPoint = 0
|
|
## else:
|
|
## halfWay = 0
|
|
## quarterWay = 1
|
|
## endPoint = len(tStats) - 1
|
|
|
|
## tempValue_halfWay, curHour1 = tStats[halfWay]
|
|
## tempValue_quarterWay, curHour2 = tStats[quarterWay]
|
|
## tempValue_endPoint, curHour3 = tStats[endPoint]
|
|
|
|
## if tempValue_halfWay is None:
|
|
## return self.setWords(node, "")
|
|
## if tempValue_quarterWay is None:
|
|
## return self.setWords(node, "")
|
|
## if tempValue_endPoint is None:
|
|
## return self.setWords(node, "")
|
|
|
|
## words = ""
|
|
## if dayNight == self.DAYTIME():
|
|
## if tempValue_quarterWay < tempValue_halfWay:
|
|
## if tempValue_endPoint <= tempValue_quarterWay:
|
|
## if tempValue_endPoint <= (maxT - threshold):
|
|
## # large temp fall (i.e. >= threshold)
|
|
## toPhrase = self.getToPhrase(tree, node, tempValue_endPoint)
|
|
## mxPhrase = self.getToPhrase(tree, node, maxT)
|
|
## if (toPhrase == mxPhrase):
|
|
## # avoid saying--"high in the upper 50s. temperature falling
|
|
## # into the 50s in the afternoon."
|
|
## # instead say--"high in the upper 50s. temperature falling
|
|
## # through the 50s in the afternoon."
|
|
## toPhrase = " through" + toPhrase[5:]
|
|
## if len(tStats) <= 6: #assumes already in the afternoon
|
|
## words = "temperature falling" + toPhrase + " by late afternoon"
|
|
## else:
|
|
## words = "temperature falling" + toPhrase + " in the afternoon"
|
|
## elif tempValue_endPoint < maxT:
|
|
## # small temp fall (i.e. < threshold)
|
|
## if len(tStats) <= 6: #assumes already in the afternoon
|
|
## words = "temperature steady or slowly falling through late afternoon"
|
|
## else:
|
|
## words = "temperature steady or slowly falling in the afternoon"
|
|
## else:
|
|
## if tempValue_quarterWay > tempValue_halfWay:
|
|
## if tempValue_endPoint >= tempValue_quarterWay:
|
|
## if tempValue_endPoint >= (minT + threshold):
|
|
## # large temp rise (i.e. >= threshold)
|
|
## toPhrase = self.getToPhrase(tree, node, tempValue_endPoint)
|
|
## mnPhrase = self.getToPhrase(tree, node, minT)
|
|
## if (toPhrase == mnPhrase):
|
|
## # avoid saying--"low in the lower 30s. temperature rising
|
|
## # into the 30s after midnight."
|
|
## # instead say--"low in the lower 30s. temperature rising
|
|
## # through the 30s after midnight."
|
|
## toPhrase = " through" + toPhrase[5:]
|
|
## if len(tStats) <= 6: #assumes already after midnight
|
|
## words = "temperature rising" + toPhrase + " through sunrise"
|
|
## else:
|
|
## words = "temperature rising" + toPhrase + " after midnight"
|
|
## elif tempValue_endPoint > minT:
|
|
## # small temp rise (i.e. < threshold)
|
|
## if len(tStats) <= 6: #assumes already after midnight
|
|
## words = "temperature steady or slowly rising through sunrise"
|
|
## else:
|
|
## words = "temperature steady or slowly rising after midnight"
|
|
|
|
## return self.setWords(node, words)
|
|
|
|
|
|
|
|
def reportTrends(self):
|
|
return {
|
|
"setUpMethod": self.reportTrends_setUp,
|
|
"wordMethod": self.reportTrends_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
def reportTrends_setUp(self, tree, node):
|
|
timeRange = node.getTimeRange()
|
|
dayNight = self.getPeriod(timeRange, 1)
|
|
if dayNight == self.NIGHTTIME():
|
|
eleInfo = self.ElementInfo("MinT", "Min")
|
|
else:
|
|
eleInfo = self.ElementInfo("MaxT", "Max")
|
|
elementName = "MaxT"
|
|
elementInfoList = [eleInfo]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
node.set("descriptor", "")
|
|
return self.DONE()
|
|
|
|
def reportTrends_words(self, tree, node):
|
|
"Compare current analysis to previous analysis for trends"
|
|
elementName = node.get("elementName")
|
|
statDict = node.getStatDict()
|
|
curStats = self.getStats(statDict, elementName)
|
|
if curStats is None:
|
|
return self.setWords(node, "")
|
|
timeRange = node.getTimeRange()
|
|
areaLabel = node.getAreaLabel()
|
|
prevTimeRange = self.adjustTimeRange(timeRange, -24)
|
|
prevStats = tree.stats.get(elementName, prevTimeRange, areaLabel,
|
|
mergeMethod="Average")
|
|
#print "Report trends", timeRange, elementName, curStats, prevStats
|
|
if prevStats is None:
|
|
return self.setWords(node, "")
|
|
|
|
prevStats = self.getValue(prevStats)
|
|
curStats = self.getValue(curStats)
|
|
#print "stats", prevStats, curStats
|
|
diff = curStats - prevStats
|
|
value = self.reportTrends_valueStr(tree, node, diff, curStats)
|
|
#print " returning ", value
|
|
return self.setWords(node, value)
|
|
|
|
def reportTrends_valueStr(self, tree, node, diff, temp):
|
|
# Given a difference between current and 24-hour prior
|
|
# MaxT or MinT grids, report a trend.
|
|
|
|
var = self.colder_warmer_dict(tree, node)
|
|
timeRange = node.getTimeRange()
|
|
dayNight = self.getPeriod(timeRange, 1)
|
|
if dayNight == self.DAYTIME():
|
|
if diff > 10:
|
|
return self.nlValue(var["HighWarmer"], temp)
|
|
elif diff < -20:
|
|
return self.nlValue(var["HighMuchColder"], temp)
|
|
elif diff <= -10 and diff >= -20:
|
|
return self.nlValue(var["HighColder"], temp)
|
|
else:
|
|
return ""
|
|
|
|
else:
|
|
if diff > 10:
|
|
return self.nlValue(var["LowWarmer"], temp)
|
|
elif diff < -20:
|
|
return self.nlValue(var["LowMuchColder"], temp)
|
|
elif diff <= -10 and diff >= -20:
|
|
return self.nlValue(var["LowColder"], temp)
|
|
else:
|
|
return ""
|
|
|
|
return ""
|
|
|
|
# colder_warmer_Dict
|
|
# Dictionary of non-linear dictionaries each with
|
|
# phrases to use instead of colder/warmer
|
|
# based on the temperature
|
|
|
|
def colder_warmer_dict(self, tree, node):
|
|
# This dictionary of non-linear dictionaries controls what phrase is returned
|
|
# for cold/much colder warmer/much warmer. It is based off
|
|
# of the maxT or MinT
|
|
dict = {}
|
|
dict["LowColder"] = {
|
|
(-80,45): "colder",
|
|
(45,70): "cooler",
|
|
(70,150): "not as warm",
|
|
"default": "",
|
|
}
|
|
dict["LowMuchColder"] = {
|
|
(-80,45): "much colder",
|
|
(45,70): "much cooler",
|
|
(70,150): "not as warm",
|
|
"default": "",
|
|
}
|
|
dict["LowWarmer"] = {
|
|
(-80,35): "not as cold",
|
|
(35,50): "not as cool",
|
|
(50,150): "warmer",
|
|
"default": "",
|
|
}
|
|
dict["HighColder"]= {
|
|
(-80,45): "colder",
|
|
(45,75): "cooler",
|
|
(75,90): "not as warm",
|
|
(90,150): "not as hot",
|
|
"default": "",
|
|
}
|
|
dict["HighMuchColder"]= {
|
|
(-80,45): "much colder",
|
|
(45,75): "much cooler",
|
|
(75,90): "not as warm",
|
|
(90,150): "not as hot",
|
|
"default": "",
|
|
}
|
|
dict["HighWarmer"]= {
|
|
(-80,45): "not as cold",
|
|
(45,65): "not as cool",
|
|
(65,150): "warmer",
|
|
"default": "",
|
|
}
|
|
return dict
|
|
|
|
|
|
## def reportTrends_valueStr(self, tree, node, diff):
|
|
## # Given a difference between current and 24-hour prior
|
|
## # MaxT or MinT grids, report a trend.
|
|
## if diff > 15 and diff < 25:
|
|
## return "warmer"
|
|
## elif diff >= 25:
|
|
## return "much warmer"
|
|
## elif diff < -15 and diff > -25:
|
|
## return "cooler"
|
|
## elif diff <= -25:
|
|
## return "much colder"
|
|
## else:
|
|
## return ""
|
|
|
|
def extremeTemps_phrase(self):
|
|
### NEW METHOD written by Tom Spriggs
|
|
### ZFP_Local
|
|
return {
|
|
"setUpMethod": self.extremeTemps_setUp,
|
|
"wordMethod": self.extremeTemps_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
|
|
def extremeTemps_setUp(self, tree, node):
|
|
dayNight = self.getPeriod(node.getTimeRange(), 1)
|
|
if dayNight == self.DAYTIME():
|
|
elementInfoList = [
|
|
self.ElementInfo("MaxT", "Max"),
|
|
self.ElementInfo("MinT", "Min"),
|
|
]
|
|
else:
|
|
elementInfoList = [
|
|
self.ElementInfo("MinT", "Min"),
|
|
self.ElementInfo("MaxT", "Max"),
|
|
]
|
|
elementInfoList.append(self.ElementInfo("HeatIndex", "Max"))
|
|
elementInfoList.append(self.ElementInfo("WindChill", "Min"))
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
node.set("descriptor", "")
|
|
return self.DONE()
|
|
|
|
|
|
def extremeTemps_words(self, tree, node):
|
|
"Compare current analysis to previous analysis for trends"
|
|
|
|
tempPhrases = ["reportTrends"]
|
|
words = self.findWords(tree, node, None, node.getAreaLabel(),
|
|
phraseList=tempPhrases)
|
|
|
|
if words is None:
|
|
# If words have not yet been set, return
|
|
# We need to wait for reportTrends to complete
|
|
# before doing the extremeTemps_phrase
|
|
return
|
|
|
|
statDict = node.getStatDict()
|
|
timeRange = node.getTimeRange()
|
|
dayNight = self.getPeriod(timeRange, 1)
|
|
if dayNight == self.DAYTIME():
|
|
element = "MaxT"
|
|
else:
|
|
element = "MinT"
|
|
tStats = self.getStats(statDict, element)
|
|
if tStats is None:
|
|
return self.setWords(node, "")
|
|
tStats = self.getValue(tStats)
|
|
|
|
chillStats = self.getStats(statDict, "WindChill")
|
|
chillStats = self.getValue(chillStats, "Min")
|
|
heatStats = self.getStats(statDict, "HeatIndex")
|
|
heatStats = self.getValue(heatStats, "Max")
|
|
|
|
words = ""
|
|
|
|
if dayNight == self.DAYTIME():
|
|
if tStats > 99:
|
|
if heatStats is None:
|
|
words = "very hot"
|
|
elif (heatStats - tStats) > 7:
|
|
words = "very hot and humid"
|
|
else:
|
|
words = "very hot"
|
|
elif tStats > 95:
|
|
if heatStats is None:
|
|
words = "hot"
|
|
elif (heatStats - tStats) > 6:
|
|
words = "hot and humid"
|
|
else:
|
|
words = "hot"
|
|
elif tStats < 20:
|
|
if chillStats is None:
|
|
words = "very cold"
|
|
elif chillStats < -9:
|
|
words = "bitterly cold"
|
|
else:
|
|
words = "very cold"
|
|
elif heatStats is None:
|
|
words = ""
|
|
elif heatStats >= self.heatIndex_threshold(tree, node):
|
|
words = "hot and humid"
|
|
elif chillStats is None:
|
|
words = ""
|
|
elif chillStats <= self.windChill_threshold(tree, node):
|
|
words = "bitterly cold"
|
|
else:
|
|
if tStats < 5:
|
|
if chillStats is None:
|
|
words = "very cold"
|
|
elif chillStats <= self.windChill_threshold(tree, node):
|
|
words = "bitterly cold"
|
|
else:
|
|
words = "very cold"
|
|
elif chillStats is None:
|
|
words = ""
|
|
elif chillStats <= self.windChill_threshold(tree, node):
|
|
words = "bitterly cold"
|
|
|
|
if words == "":
|
|
return self.setWords(node, words)
|
|
|
|
# Clear the words in reportTrends to
|
|
# prevent extra temperature phrases
|
|
component = node.getComponent()
|
|
progeny = component.getProgeny()
|
|
for child in progeny:
|
|
phraseName = child.get("name")
|
|
if phraseName in tempPhrases:
|
|
child.set("words", "")
|
|
return self.setWords(node, words)
|
|
|
|
## Submitted by Brian Walawender
|
|
## Reviewed by Tracy Hansen
|
|
def steady_temp_threshold(self, tree, node):
|
|
# Diurnal ranges less than this value will
|
|
# be reported as steady temperatures
|
|
return 4
|
|
|
|
def steady_temp_trends(self):
|
|
return {
|
|
"setUpMethod": self.steady_temp_trends_setUp,
|
|
"wordMethod": self.steady_temp_trends_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
|
|
def steady_temp_trends_setUp(self, tree, node):
|
|
elementInfoList = []
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
node.set("descriptor", "")
|
|
return self.DONE()
|
|
|
|
def steady_temp_trends_words(self, tree, node):
|
|
"Look for small diurnal changes"
|
|
# Check Diurnal range in T. If range is
|
|
# less than steady_temp_threshold report
|
|
# a temperatures steady phrase
|
|
# i.e. "temperature steady in the mid 20s"
|
|
tempPhrases = ["highs_phrase", "lows_phrase",
|
|
"highs_range_phrase", "lows_range_phrase",
|
|
"temp_trends",
|
|
"extended_lows_phrase", "extended_highs_phrase"
|
|
]
|
|
words = self.findWords(tree, node, None, node.getAreaLabel(),
|
|
phraseList=tempPhrases)
|
|
if words is None:
|
|
# If words have not yet been set, return
|
|
# We need to wait for all highs_phrases to complete
|
|
# before doing the steady_temp_phrase
|
|
return
|
|
|
|
timeRange = node.getTimeRange()
|
|
tStats = tree.stats.get("T", timeRange, node.getAreaLabel(),
|
|
mergeMethod="List")
|
|
if tStats is None:
|
|
return self.setWords(node, "")
|
|
# tStats is a list of (hourlyTemp, subRange) tuples
|
|
|
|
max = -999
|
|
min = 999
|
|
words = ""
|
|
sum = 0
|
|
count = 0
|
|
for hourlyTemps, subRange in tStats:
|
|
if hourlyTemps is None:
|
|
return self.setWords(node, "")
|
|
for t, hr in hourlyTemps:
|
|
if t is None:
|
|
return self.setWords(node, "")
|
|
if t < min:
|
|
min = t
|
|
if t > max:
|
|
max = t
|
|
sum = sum + t
|
|
count = count + 1
|
|
|
|
diff = max - min
|
|
|
|
if diff >= self.steady_temp_threshold(tree,node):
|
|
return self.setWords(node, "")
|
|
|
|
dayNight = self.getPeriod(timeRange, 1)
|
|
if dayNight == self.DAYTIME():
|
|
avg = int((sum/count)+0.5)
|
|
else:
|
|
avg = int(sum/count)
|
|
|
|
phrase = self.getTempPhrase(tree, node, avg, "")
|
|
words = "near steady temperature " + phrase
|
|
|
|
# Clear the words in high and lows phrase
|
|
# prevent extra temperature phrases
|
|
component = node.getComponent()
|
|
progeny = component.getProgeny()
|
|
for child in progeny:
|
|
phraseName = child.get("name")
|
|
if phraseName in tempPhrases:
|
|
child.set("words", "")
|
|
|
|
# Begin ER changes
|
|
# Not sure if this is used...but set anyway
|
|
child.set("emptyPhrase", 1)
|
|
|
|
# Now erase subphrase words. This is what seems to fix
|
|
# the problem of high/low phrases still appearing with
|
|
# the steady phrase - PJ
|
|
subphrases=child.get("childList")
|
|
|
|
if subphrases is not None:
|
|
for n in subphrases:
|
|
n.set("words", "")
|
|
|
|
return self.setWords(node, words)
|
|
|
|
### SnowAmt
|
|
def pop_snow_lower_threshold(self, tree, node):
|
|
# Snow accumulation will not be reported
|
|
# if Pop is below this threshold
|
|
return 60
|
|
|
|
def getSnowReportEndDay(self, tree, node):
|
|
# This is the first day we do not try to report total accumulation.
|
|
return self.createTimeRange(96,108)
|
|
|
|
def snow_phrase(self):
|
|
return {
|
|
"setUpMethod": self.snow_setUp,
|
|
"wordMethod": self.snow_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
|
|
def snow_setUp(self, tree, node):
|
|
elementInfoList = [self.ElementInfo("SnowAmt", "List")]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
component = node.getComponent()
|
|
index = component.getIndex()
|
|
|
|
# Calculate past snow
|
|
prodTR = tree.getTimeRange()
|
|
pastSnowMin = 0
|
|
pastSnowMax = 0
|
|
pastSnowTimeRange = self.makeTimeRange(prodTR.startTime() - 12*3600,
|
|
prodTR.startTime())
|
|
stats = tree.stats.get("SnowAmt", pastSnowTimeRange,
|
|
node.getAreaLabel(), mergeMethod="MinMax")
|
|
|
|
if stats is not None:
|
|
# site is using a past SnowAmt grid
|
|
pastSnowMin, pastSnowMax = self.getValue(stats, "MinMax")
|
|
# If first period is less than 12 hours long, thus an "update"
|
|
# report as "new" snow accumulation ONLY IF
|
|
# there was some previous snow accumulation
|
|
timeRange = node.getTimeRange()
|
|
if index == 0 and timeRange.duration() < 12*3600 and \
|
|
pastSnowMax > 0.0:
|
|
node.set("newFlag", 1)
|
|
else:
|
|
# site is NOT using a past SnowAmt grid
|
|
# If first period is less than 12 hours long, thus an "update"
|
|
# report as "new" snow accumulation
|
|
timeRange = node.getTimeRange()
|
|
if index == 0 and timeRange.duration() < 12*3600:
|
|
node.set("newFlag", 1)
|
|
|
|
return self.DONE()
|
|
|
|
def snow_words(self, tree, node):
|
|
# First check if the pop threshold has been met
|
|
# If not, then do not generate phrase
|
|
threshold = self.pop_snow_lower_threshold(tree, node)
|
|
lowPopFlag = self.lowPop_flag(tree, node, threshold)
|
|
if lowPopFlag == 1:
|
|
return self.setWords(node, "")
|
|
|
|
# Second, wait for weather phrase to complete and make sure there
|
|
# is mention of accumulating weather
|
|
wxWords, attrDict = self.findWords(tree, node, "Wx", node.getAreaLabel(),
|
|
phraseList=["weather_phrase", "skyPopWx_phrase"],
|
|
attributes=["reportedRankList"])
|
|
if wxWords is None:
|
|
return
|
|
accumFlag, descriptor = self.checkAccumulatingWx(tree, node, wxWords, attrDict)
|
|
if accumFlag == 0:
|
|
return self.setWords(node, "null")
|
|
|
|
# Third, load in the SnowAmt statistics, check for low amounts, then round to nearest inch
|
|
currentSnow = tree.stats.get("SnowAmt", node.getTimeRange(), node.getAreaLabel(), mergeMethod="MinMax")
|
|
if currentSnow is None:
|
|
return self.setWords(node, "")
|
|
min, max = self.getValue(currentSnow, "MinMax")
|
|
if min == 0 and max == 0:
|
|
node.parent.set("descriptor", "")
|
|
return self.setWords(node, "no " + descriptor)
|
|
elif min < 0.5 and max < 0.5:
|
|
node.parent.set("descriptor", "")
|
|
return self.setWords(node, "little or no " + descriptor)
|
|
min = int(min+0.5)
|
|
max = int(max+0.5)
|
|
|
|
# Finally, generate the snow accumulation phrase
|
|
# Decide on singular or plural units
|
|
if max == 1:
|
|
units = self.units_descriptor(tree, node, "unit", "in")
|
|
else:
|
|
units = self.units_descriptor(tree, node, "units", "in")
|
|
# Create worded phrase based on type of range
|
|
if min == 0:
|
|
upTo = self.addSpace(self.phrase_descriptor(tree, node, "up to", "SnowAmt"))
|
|
snowPhrase = upTo + repr(max)
|
|
elif min == max:
|
|
around = self.addSpace(self.phrase_descriptor(tree, node, "around", "SnowAmt"))
|
|
snowPhrase = around + repr(max)
|
|
else:
|
|
snowPhrase = "of " + repr(min) + " to " + repr(max)
|
|
snowPhrase = snowPhrase + " " + units
|
|
|
|
return self.setWords(node, snowPhrase)
|
|
|
|
def checkAccumulatingWx(self, tree, node, wxWords, attrDict):
|
|
accumulatingWx = [
|
|
('S', 'Snow'),
|
|
('SW', 'Snow'),
|
|
('IP', 'Sleet'),
|
|
('IC', 'IceCrystal'),
|
|
]
|
|
desc = ""
|
|
wxTypes = []
|
|
if "reportedRankList" in attrDict:
|
|
rankList = attrDict["reportedRankList"]
|
|
for subkey, rank in rankList:
|
|
# DR_18506
|
|
if subkey.wxType() in ['SW'] and subkey.intensity() == "--":
|
|
pass
|
|
elif subkey.wxType() in ['IC']:
|
|
pass
|
|
else:
|
|
wxTypes.append(subkey.wxType())
|
|
for wxType, wxVar in accumulatingWx:
|
|
if wxType in wxTypes:
|
|
desc += wxVar
|
|
if desc == "":
|
|
return 0, ""
|
|
# Determine the phrase descriptor
|
|
descriptor = self.phrase_descriptor(tree, node, desc, "SnowAmt")
|
|
if node.getAncestor('newFlag') == 1:
|
|
new = self.addSpace(self.phrase_descriptor(tree, node, "New", "SnowAmt"))
|
|
if new != "":
|
|
descriptor = new + descriptor
|
|
node.parent.set("descriptor", descriptor)
|
|
# The handle the case of embedded local effects, set the parent's parent as well
|
|
node.parent.parent.set("descriptor", descriptor)
|
|
return 1, descriptor
|
|
|
|
## Modifications submitted by Tom Spriggs LSX for accurately reporting total snow
|
|
|
|
## Since the total_snow_phrase uses the SnowAmt element exclusively for tallying
|
|
## up storm totals, you can use a SnowAmt grid that exists in the past
|
|
## (before the current hour) to tell us how much snow has already fallen from THIS STORM
|
|
## (this will not include already existing snow pack from other storms)
|
|
## and thus compensate for the diminishing/shrinking forecast totals.
|
|
## The method simply samples and adds the already fallen snow to what is still forecast
|
|
## to produce an accuate total snow amount for an ongoing event
|
|
## (as well as an event still yet to happen).
|
|
|
|
## This "past" SnowAmt grid will end at the current time and can go as far back as
|
|
## the user wants--but it MUST be a single grid for previously fallen snow,
|
|
## not a bunch of fragmented grids. The actual values in the "past" SnowAmt grid
|
|
## will then need to be filled with values based on already collected snow reports.
|
|
## One method to create this "old" SnowAmt grid would be through use of the CONTOUR tool.
|
|
|
|
## Using this method, it is now possible to kick off a total snow phrase in the
|
|
## first period when the total snow differs from the still yet to fall/forecasted
|
|
## snow in the first period.
|
|
|
|
## If you leave the "past" SnowAmt grid as zero, the formatter will know to treat
|
|
## it as a non-ongoing event.
|
|
|
|
def total_snow_phrase(self):
|
|
return {
|
|
"setUpMethod": self.total_snow_setUp,
|
|
"wordMethod": self.total_snow_words,
|
|
"phraseMethods": self.standard_phraseMethods()
|
|
}
|
|
|
|
def total_snow_setUp(self, tree, node):
|
|
elementInfoList = [self.ElementInfo("SnowAmt", "MinMax"),
|
|
self.ElementInfo("IceAccum", "MinMax", primary=0)]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
descriptor = self.phrase_descriptor(tree, node, "TotalSnow", "SnowAmt")
|
|
node.set("descriptor", descriptor)
|
|
return self.DONE()
|
|
|
|
def total_snow_words(self, tree, node):
|
|
# Return a total accumulation phrase if appropriate
|
|
# Example:
|
|
# TOTAL SNOW ACCUMULATION 7 INCHES.
|
|
|
|
component = node.getComponent()
|
|
index = component.getIndex()
|
|
totalSnow = ""
|
|
|
|
# See if we are prior to the snow report end day
|
|
timeRange = node.getParent().getTimeRange()
|
|
snowReportEndDay = self.getSnowReportEndDay(tree, node)
|
|
shiftedTimeRange = self.shiftedTimeRange(timeRange)
|
|
if shiftedTimeRange.startTime() < snowReportEndDay.startTime():
|
|
### Round up stats--need current period snow, next period snow, and past snow
|
|
# Obtain minimum PoP needed to report accumulations
|
|
threshold = self.pop_snow_lower_threshold(tree, node)
|
|
# Get snow stats for the current period
|
|
currentSnow = tree.stats.get("SnowAmt", node.getTimeRange(), node.getAreaLabel(), mergeMethod="MinMax")
|
|
if currentSnow is None:
|
|
return self.setWords(node, "")
|
|
currentMin, currentMax = self.getValue(currentSnow, "MinMax")
|
|
currentMin = int(currentMin+0.5)
|
|
currentMax = int(currentMax+0.5)
|
|
# Check PoP threshold for the current period--zero out if below threshold PoP
|
|
popStats = self.matchToWx(tree, node, "PoP", node.getTimeRange())
|
|
if popStats is None or popStats < threshold:
|
|
currentMin = 0
|
|
currentMax = 0
|
|
# Get snow stats for the next period--does the event come to an end?
|
|
nextComp = component.getNext()
|
|
if nextComp is None:
|
|
return self.setWords(node, "")
|
|
nextTimeRange = nextComp.getTimeRange()
|
|
nextSnow = tree.stats.get("SnowAmt", nextTimeRange,
|
|
node.getAreaLabel(), mergeMethod="Max")
|
|
if nextSnow is None:
|
|
return self.setWords(node, "")
|
|
nextSnow = int(nextSnow+0.5)
|
|
# Check PoP threshold for the next period--zero out if below threshold PoP
|
|
threshold = self.pop_snow_lower_threshold(tree, node)
|
|
popStats = self.matchToWx(tree, node, "PoP", nextTimeRange)
|
|
if popStats is None or popStats < threshold:
|
|
nextSnow = 0
|
|
# Get snow stats for both already fallen snow AND preceding forecast periods
|
|
minSum, maxSum = self.sumPrevStats(tree, component,
|
|
node.getAreaLabel(), "SnowAmt", "MinMax")
|
|
|
|
### Generate total snow accumulation phrase if conditions met
|
|
# We produce a total accumulation phrase for the current period IF
|
|
# the next period's snow is 0--thus snow will cease by the end of the current period AND
|
|
# there is snow accumulation expected in the current period AND
|
|
# there is snow accumulation in one or more periods immediately preceding
|
|
if nextSnow == 0 and currentMax > 0 and maxSum > 0:
|
|
# Finalize total snow amount
|
|
finalMinSum = int(currentMin + minSum)
|
|
finalMaxSum = int(currentMax + maxSum)
|
|
# Decide on singular or plural units
|
|
if finalMaxSum == 1:
|
|
units = self.units_descriptor(tree, node, "unit", "in")
|
|
else:
|
|
units = self.units_descriptor(tree, node, "units", "in")
|
|
# Create worded phrase based on type of range
|
|
if finalMinSum == 0:
|
|
upTo = self.addSpace(self.phrase_descriptor(tree, node,
|
|
"up to", "SnowAmt"))
|
|
totalSnowPhrase = upTo + repr(finalMaxSum)
|
|
elif finalMinSum == finalMaxSum:
|
|
around = self.addSpace(self.phrase_descriptor(tree, node,
|
|
"around", "SnowAmt"))
|
|
totalSnowPhrase = around + repr(finalMaxSum)
|
|
else:
|
|
totalSnowPhrase = repr(finalMinSum) + " to " + repr(finalMaxSum)
|
|
totalSnow = totalSnowPhrase + " " + units
|
|
else:
|
|
return self.setWords(node, "")
|
|
|
|
return self.setWords(node, totalSnow)
|
|
|
|
def getSnowValue(self, tree, node, areaLabel=None):
|
|
# Return min and max snow values
|
|
threshold = self.pop_snow_lower_threshold(tree, node)
|
|
lowPopFlag = self.lowPop_flag(tree, node, threshold)
|
|
if lowPopFlag == 1:
|
|
return None
|
|
if areaLabel is None:
|
|
areaLabel = node.getAreaLabel()
|
|
stats = tree.stats.get("SnowAmt", node.getTimeRange(),
|
|
areaLabel, mergeMethod="MinMax")
|
|
if stats is None:
|
|
return None
|
|
min, max = self.getValue(stats, "MinMax")
|
|
min = int(min+0.5)
|
|
max = int(max+0.5)
|
|
if min < 1 and max < 1:
|
|
return None
|
|
|
|
return min, max
|
|
|
|
def getTotalSnow(self, tree, node, areaLabel=None, snowValue=None):
|
|
component = node.getComponent()
|
|
# Get sum of previous periods
|
|
if areaLabel is None:
|
|
areaLabel = node.getAreaLabel()
|
|
if snowValue is None:
|
|
snowValue = self.getSnowValue(tree, node, areaLabel)
|
|
if snowValue is None:
|
|
return None
|
|
minSnowValue, maxSnowValue = snowValue
|
|
minSum, maxSum = self.sumPrevStats(tree, component, areaLabel, "SnowAmt", "MinMax")
|
|
# Add this period's value to the sum
|
|
minSum = minSum + minSnowValue
|
|
maxSum = maxSum + maxSnowValue
|
|
minIncrement = self.nlValue(self.increment_nlValue(
|
|
tree, node, "SnowAmt", "SnowAmt"), minSum)
|
|
maxIncrement = self.nlValue(self.increment_nlValue(
|
|
tree, node, "SnowAmt", "SnowAmt"), maxSum)
|
|
minSum = self.round(minSum, "Nearest", minIncrement)
|
|
maxSum = self.round(maxSum, "Nearest", maxIncrement)
|
|
return minSum, maxSum
|
|
|
|
## TOTAL SNOW Phrase submitted by Virgil Mittendorf
|
|
def stormTotalSnow_phrase(self):
|
|
return {
|
|
"setUpMethod": self.stormTotalSnow_setUp,
|
|
"wordMethod": self.stormTotalSnow_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
def stormTotalSnow_setUp(self, tree, node):
|
|
elementInfoList = [self.ElementInfo("StormTotalSnow", "List")]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
return self.DONE()
|
|
|
|
def stormTotalSnow_words(self, tree, node):
|
|
"Create phrase for Storm Total Snow accumulation"
|
|
|
|
# Load in the statistics for storm total snow
|
|
elementName = "StormTotalSnow"
|
|
statDict = node.getStatDict()
|
|
stats = self.getStats(statDict, elementName)
|
|
|
|
#print "storm total snow stats", stats
|
|
|
|
# test...if no stats then don't create phrase (i.e. grid missing)
|
|
if stats is None:
|
|
return self.setWords(node, "")
|
|
|
|
min, max = self.getValue(stats, "MinMax")
|
|
threshold = 1
|
|
incMin = 1
|
|
incMax = 1
|
|
|
|
if min%1 == 0:
|
|
min = int(min)
|
|
minStr = repr(min)
|
|
else:
|
|
minStr = repr(int(min+0.5))
|
|
if max%1 == 0:
|
|
max = int(max)
|
|
maxStr = repr(max)
|
|
else:
|
|
maxStr = repr(int(max+0.5))
|
|
|
|
#print "min, max", min, max, node.getTimeRange(), node.getAreaLabel(), "storm total accumulation"
|
|
|
|
if min == 0 and max == 0:
|
|
return self.setWords(node,"")
|
|
elif min < 0.5 and max < 0.5:
|
|
return self.setWords(node,"")
|
|
|
|
outUnits = self.element_outUnits(tree, node, elementName, elementName)
|
|
unit = self.units_descriptor(tree, node,"unit", outUnits)
|
|
units = self.units_descriptor(tree, node,"units", outUnits)
|
|
|
|
min = int(min+0.5)
|
|
max = int(max+0.5)
|
|
|
|
# Single Value input
|
|
if min == max:
|
|
# Handle case of 1 inch
|
|
if min == 1:
|
|
units = unit
|
|
value = "around " + minStr
|
|
|
|
# Range
|
|
else:
|
|
value = "of " + minStr + " to " + maxStr
|
|
# Handle case when lower value is 0
|
|
if min == 0:
|
|
value = "up to " + maxStr
|
|
if max == 1:
|
|
units = unit
|
|
|
|
snowPhrase = value + " " + units
|
|
return self.setWords(node, snowPhrase)
|
|
|
|
# New def by Scott. According to Directive 10-503, descriptive terms
|
|
# should be used in period 4 and beyond. This function returns
|
|
# a descriptive snow phrase.
|
|
def descriptive_snow_phrase(self):
|
|
return {
|
|
"setUpMethod": self.descriptive_snow_setUp,
|
|
"wordMethod": self.descriptive_snow_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
def descriptive_snow_setUp(self, tree, node):
|
|
elementInfoList = [self.ElementInfo("SnowAmt", "MinMax")]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
# Do not want phrase descriptor
|
|
node.set("descriptor", "")
|
|
return self.DONE()
|
|
|
|
def descriptive_snow_words(self, tree, node):
|
|
"Create phrase for snow accumulation"
|
|
# According to Directive 10-503, descriptive terms
|
|
# should be used in period 4 and beyond. This function returns
|
|
# a descriptive snow phrase.
|
|
#
|
|
# According to Directive 10-503, snow accumulation
|
|
# should not be mentioned if PoP is under 60%.
|
|
threshold = self.pop_snow_lower_threshold(tree, node)
|
|
lowPopFlag = self.lowPop_flag(tree, node, threshold)
|
|
if lowPopFlag == 1:
|
|
return self.setWords(node, "")
|
|
|
|
statDict = node.getStatDict()
|
|
stats = self.getStats(statDict, "SnowAmt")
|
|
if stats is None:
|
|
return self.setWords(node, "")
|
|
|
|
max = int(self.getValue(stats, "Max"))
|
|
|
|
if max < 1:
|
|
words = ""
|
|
elif max >= 1 and max <= 2:
|
|
words = "light snow accumulations"
|
|
elif max > 2 and max <= 5:
|
|
words = "moderate snow accumulations"
|
|
else:
|
|
words = "heavy snow accumulations"
|
|
return self.setWords(node, words)
|
|
|
|
### SnowLevel
|
|
def pop_snowLevel_upper_threshold(self, tree, node):
|
|
# Snow level will be reported if Pop is above this threshold
|
|
return 60
|
|
|
|
def snowLevel_maximum_phrase(self, tree, node):
|
|
# This returns the maximum snow level value to be reported and the
|
|
# the corresponding snow level phrase. It can be set up by
|
|
# edit area as follows:
|
|
# editAreaList = [
|
|
# ("area1", 8000, "above 8000 feet"),
|
|
# ("area2", 6000, "above 6000 feet"),
|
|
# # Don't mention snow level at all in area3:
|
|
# ("area3", 0, ""),
|
|
# ]
|
|
#maxElev = 0
|
|
#phrase = ""
|
|
#for area, elev, elevPhrase in editAreaList:
|
|
# if self.currentAreaContains(tree, [area]):
|
|
# if elev > maxElev:
|
|
# maxElev = elev
|
|
# phrase = elevPhrase
|
|
#return (maxElev, phrase)
|
|
return (8000, "above 8000 feet")
|
|
|
|
def snowLevel_upper_topo_percentage(self, tree, node):
|
|
# If this percentage of the edit area is above the snow level,
|
|
# do not report snow level
|
|
return 80
|
|
|
|
def snowLevel_lower_topo_percentage(self, tree, node):
|
|
# If this percentage of the edit area is below or equal to the snow level,
|
|
# do not report snow level
|
|
return 80
|
|
|
|
def snowLevel_phrase(self):
|
|
return {
|
|
"setUpMethod": self.snowLevel_setUp,
|
|
"wordMethod": self.snowLevel_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
def snowLevel_setUp(self, tree, node):
|
|
elementInfoList = [self.ElementInfo("SnowLevel", "List")]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
return self.DONE()
|
|
|
|
def snowLevel_words(self, tree, node):
|
|
"Create phrase for reporting snow level"
|
|
|
|
# Check for low pop
|
|
threshold = self.pop_snowLevel_upper_threshold(tree, node)
|
|
lowPopFlag = self.lowPop_flag(tree, node, threshold)
|
|
|
|
if lowPopFlag == 1:
|
|
return self.setWords(node, "")
|
|
|
|
statDict = node.getStatDict()
|
|
snowLevel = self.getStats(statDict, "SnowLevel")
|
|
if snowLevel is None:
|
|
return self.setWords(node, "")
|
|
snowLevel = self.getValue(snowLevel)
|
|
element = "SnowLevel"
|
|
roundingMethod = self.rounding_method(tree, node, element, element)
|
|
increment_nlValue = self.increment_nlValue(tree, node, element, element)
|
|
snowLevel = self.roundValue(snowLevel, roundingMethod, "Nearest", increment_nlValue, 0)
|
|
|
|
# Check Wx for R or RW
|
|
stats = tree.stats.get("Wx", node.getTimeRange(), node.getAreaLabel(),
|
|
mergeMethod="List")
|
|
if stats is None:
|
|
return self.setWords(node, "")
|
|
|
|
found = 0
|
|
for rankList, subRange in stats:
|
|
subkeys = self.getSubkeys(rankList)
|
|
for subkey in subkeys:
|
|
if subkey.wxType() == "R" or subkey.wxType() == "RW":
|
|
found = 1
|
|
break
|
|
|
|
if found == 0:
|
|
return self.setWords(node, "")
|
|
|
|
# Check for upper and lower topo percentages
|
|
percentage_above = self.calcTopoPercentage(tree, node, node.getAreaLabel(), snowLevel)
|
|
percentage_below = 100 - percentage_above
|
|
|
|
if percentage_above > self.snowLevel_upper_topo_percentage(tree, node):
|
|
return self.setWords(node, "")
|
|
if percentage_below > self.snowLevel_lower_topo_percentage(tree, node):
|
|
return self.setWords(node, "")
|
|
|
|
# Check for maximum snow level to be reported
|
|
max, words = self.snowLevel_maximum_phrase(tree, node)
|
|
|
|
if snowLevel < max:
|
|
units = self.units_descriptor(tree, node, "units", "ft")
|
|
words = repr(int(snowLevel)) + " " + units
|
|
return self.setWords(node, words)
|
|
|
|
### IceAccum
|
|
def ice_accumulation_threshold(self, tree, node):
|
|
# If maximum IceAccum is greater than this threshold, it will be
|
|
# reported instead of SnowAmt in the snow_phrase
|
|
return .10
|
|
|
|
def iceAccumulation_phrase(self):
|
|
return {
|
|
"setUpMethod": self.iceAccumulation_setUp,
|
|
"wordMethod": self.iceAccumulation_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
def iceAccumulation_setUp(self, tree, node):
|
|
elementInfoList = [self.ElementInfo("IceAccum", "MinMax", primary=0)]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
return self.DONE()
|
|
|
|
def iceAccumulation_words(self, tree, node):
|
|
"Create phrase for ice accumulation"
|
|
|
|
threshold = self.pop_snow_lower_threshold(tree, node)
|
|
lowPopFlag = self.lowPop_flag(tree, node, threshold)
|
|
if lowPopFlag == 1:
|
|
return self.setWords(node, "")
|
|
|
|
# Check for IceAccum. If it is significant, report it.
|
|
statDict = node.getStatDict()
|
|
|
|
stats = self.getStats(statDict, "IceAccum")
|
|
reportIceAccum = 0
|
|
if stats is not None:
|
|
threshold = self.ice_accumulation_threshold(tree, node)
|
|
min, max = self.getValue(stats, "MinMax")
|
|
if max >= threshold:
|
|
reportIceAccum = 1
|
|
if reportIceAccum == 1:
|
|
component = node.getComponent()
|
|
index = component.getIndex()
|
|
timeRange = node.getTimeRange()
|
|
if index == 0 and timeRange.duration() < 12*3600:
|
|
descriptor = self.phrase_descriptor(
|
|
tree, node, "NewIceAccum", "IceAccum")
|
|
else:
|
|
descriptor = self.phrase_descriptor(tree, node, "IceAccum", "IceAccum")
|
|
node.parent.set("descriptor", descriptor)
|
|
elementName = "IceAccum"
|
|
else:
|
|
return self.setWords(node, "")
|
|
|
|
if min < 0.2:
|
|
minStr = "less than one quarter"
|
|
elif min >= 0.2 and min < 0.4:
|
|
minStr = "one quarter"
|
|
elif min >= 0.4 and min < 0.7:
|
|
minStr = "one half"
|
|
elif min >= 0.7 and min < 0.9:
|
|
minStr = "three quarters"
|
|
elif min >= 0.9 and min < 1.3:
|
|
minStr = "one"
|
|
elif min >= 1.3 and min < 1.8:
|
|
minStr = "one and a half"
|
|
elif min >= 1.8:
|
|
minStr = repr(int(min+0.5))
|
|
if max < 0.2:
|
|
maxStr = "less than one quarter"
|
|
elif max >= 0.2 and max < 0.4:
|
|
maxStr = "one quarter"
|
|
elif max >= 0.4 and max < 0.7:
|
|
maxStr = "one half"
|
|
elif max >= 0.7 and max < 0.9:
|
|
maxStr = "three quarters"
|
|
elif max >= 0.9 and max < 1.3:
|
|
maxStr = "one"
|
|
elif max >= 1.3 and max < 1.8:
|
|
maxStr = "one and a half"
|
|
elif max >= 1.8:
|
|
maxStr = repr(int(max+0.5))
|
|
if min >= 0.9 and min < 1.3:
|
|
minStr = repr(int(min+0.5))
|
|
|
|
#print "min, max", min, max, node.getTimeRange(), node.getAreaLabel()
|
|
|
|
outUnits = self.element_outUnits(tree, node, elementName, elementName)
|
|
unit = self.units_descriptor(tree, node,"unit", outUnits)
|
|
units = self.units_descriptor(tree, node,"units", outUnits)
|
|
|
|
# Single Value input
|
|
if minStr == maxStr:
|
|
if min < 0.2:
|
|
icePhrase = "of " + minStr + " of an " + unit
|
|
elif min >= 0.2 and min < 0.9:
|
|
icePhrase = "around " + minStr + " of an " + unit
|
|
elif min >= 0.9 and min < 1.3:
|
|
icePhrase = "around " + minStr + " " + unit
|
|
elif min >= 1.3:
|
|
icePhrase = "around " + minStr + " " + units
|
|
else:
|
|
return self.setWords(node, "")
|
|
|
|
# Range
|
|
else:
|
|
if min < 0.2:
|
|
if max < 0.9:
|
|
icePhrase = "of up to " + maxStr + " of an " + unit
|
|
elif max >= 0.9 and max < 1.3:
|
|
icePhrase = "of up to " + maxStr + " " + unit
|
|
elif max >= 1.3:
|
|
icePhrase = "of up to " + maxStr + " " + units
|
|
else:
|
|
return self.setWords(node, "")
|
|
elif min >= 0.2 and min < 0.9:
|
|
if max < 0.9:
|
|
icePhrase = "of " + minStr + " to " + maxStr + " of an " + unit
|
|
elif max >= 0.9 and max < 1.3:
|
|
icePhrase = "of " + minStr + " of an " + unit + " to " + maxStr + " " + unit
|
|
elif max >= 1.3:
|
|
icePhrase = "of " + minStr + " of an " + unit + " to " + maxStr + " " + units
|
|
else:
|
|
return self.setWords(node, "")
|
|
elif min >= 0.9:
|
|
if max >= 1.3:
|
|
icePhrase = "of " + minStr + " to " + maxStr + " " + units
|
|
else:
|
|
return self.setWords(node, "")
|
|
return self.setWords(node, icePhrase)
|
|
|
|
### FzLevel
|
|
### WindChill
|
|
def windChill_threshold(self, tree, node):
|
|
# THRESHOLD FOR REPORTING WIND CHILL
|
|
return 0.0
|
|
|
|
def windChillTemp_difference(self, tree, node):
|
|
# Difference between wind chill and temperature
|
|
# for reporting wind chill
|
|
return 5
|
|
|
|
def windChill_phrase(self):
|
|
return {
|
|
"setUpMethod": self.windChill_setUp,
|
|
"wordMethod": self.windChill_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
def windChill_setUp(self, tree, node):
|
|
elementInfoList = [self.ElementInfo("WindChill", "List")]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
return self.DONE()
|
|
|
|
def windChill_words(self, tree, node):
|
|
"Create phrase for Wind Chill"
|
|
|
|
statDict = node.getStatDict()
|
|
stats = self.getStats(statDict, "WindChill")
|
|
if stats is None:
|
|
return self.setWords(node, "")
|
|
t = tree.stats.get("T", node.getTimeRange(),
|
|
node.getAreaLabel(), statLabel="minMax",
|
|
mergeMethod="MinMax")
|
|
if t is None:
|
|
return self.setWords(node, "")
|
|
|
|
min, max = self.getValue(stats, "MinMax")
|
|
|
|
timeRange = node.getTimeRange()
|
|
day = self.getPeriod(timeRange, 1)
|
|
if day == self.DAYTIME():
|
|
# Compare to max T
|
|
t = self.getValue(t,"Max")
|
|
else:
|
|
# Compare to min T
|
|
t = self.getValue(t,"Min")
|
|
|
|
diff = self.windChillTemp_difference(tree, node)
|
|
if min <= self.windChill_threshold(tree, node) and min <= t - diff:
|
|
words = self.getTempRangePhrase(tree, node, (min, max), "WindChill")
|
|
else:
|
|
words = ""
|
|
return self.setWords(node, words)
|
|
|
|
# Alternate phrase based on wind speed
|
|
def windChill_wind_threshold(self, tree, node):
|
|
# Minimum wind speed (mph) required for reporting wind chill
|
|
return 10
|
|
|
|
def windBased_windChill_phrase(self):
|
|
return {
|
|
"setUpMethod": self.windChill_setUp,
|
|
"wordMethod": self.windBased_windChill_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
|
|
def windBased_windChill_words(self, tree, node) :
|
|
"Create phrase for Wind Chill"
|
|
|
|
# Wait for wind phrase to complete
|
|
windWords = self.findWords(tree, node, "Wind", node.getAreaLabel())
|
|
if windWords is None:
|
|
return
|
|
|
|
statDict = node.getStatDict()
|
|
stats = self.getStats(statDict, "WindChill")
|
|
if stats is None:
|
|
return self.setWords(node, "")
|
|
|
|
if windWords == "":
|
|
return self.setWords(node, "")
|
|
|
|
min, max = self.getValue(stats, "MinMax")
|
|
|
|
# Check wind speed
|
|
# First try to re-use information from wind_phrase
|
|
maxWind = node.getComponent().get("maxMag")
|
|
if maxWind is None:
|
|
# Have to access it from statistics dictionary
|
|
timeRange = node.getTimeRange()
|
|
wind = tree.stats.get("Wind", timeRange, node.getAreaLabel(), mergeMethod="Max")
|
|
if wind is None:
|
|
return self.setWords(node, "")
|
|
maxWind, dir = wind
|
|
|
|
if maxWind < self.windChill_wind_threshold(tree, node):
|
|
return self.setWords(node, "")
|
|
|
|
# WC must be less or equal to threshold
|
|
if min <= self.windChill_threshold(tree, node):
|
|
words = self.getTempRangePhrase(tree, node, (min, max), "WindChill")
|
|
else:
|
|
words = ""
|
|
return self.setWords(node, words)
|
|
|
|
### HeatIndex
|
|
def heatIndex_threshold(self, tree, node):
|
|
# THRESHOLD FOR REPORTING HEAT INDEX
|
|
return 108.0
|
|
|
|
def heatIndexTemp_difference(self, tree, node):
|
|
# Difference between heat index and temperature
|
|
# for reporting heat index
|
|
return 5
|
|
|
|
def heatIndex_phrase(self):
|
|
return {
|
|
"setUpMethod": self.heatIndex_setUp,
|
|
"wordMethod": self.heatIndex_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
def heatIndex_setUp(self, tree, node):
|
|
elementInfoList = [self.ElementInfo("HeatIndex", "List")]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
return self.DONE()
|
|
|
|
def heatIndex_words(self, tree, node) :
|
|
"Create phrase for Heat Index"
|
|
statDict = node.getStatDict()
|
|
stats = self.getStats(statDict, "HeatIndex")
|
|
if stats is None:
|
|
return self.setWords(node, "")
|
|
t = tree.stats.get("T", node.getTimeRange(),
|
|
node.getAreaLabel(), statLabel="minMax",
|
|
mergeMethod="MinMax")
|
|
if t is None:
|
|
return self.setWords(node, "")
|
|
|
|
min, max = self.getValue(stats, "MinMax")
|
|
|
|
timeRange = node.getTimeRange()
|
|
day = self.getPeriod(timeRange,1)
|
|
if day == self.DAYTIME():
|
|
# Compare to max T
|
|
t = self.getValue(t,"Max")
|
|
else:
|
|
# Compare to min T
|
|
t = self.getValue(t,"Min")
|
|
# HI must be greater or equal to threshold and at least
|
|
# two degrees higher than the maximum T.
|
|
diff = self.heatIndexTemp_difference(tree, node)
|
|
if max >= self.heatIndex_threshold(tree, node) and max >= t + diff:
|
|
words = self.getTempRangePhrase(tree, node, (min, max), "HeatIndex")
|
|
else:
|
|
words = ""
|
|
return self.setWords(node, words)
|
|
|
|
# RH -- Contributed by ER 8/04
|
|
def rh_threshold(self, tree, node):
|
|
# Threshold for reporting RH in extended narrative. If MinRH grid is
|
|
# lower than this threshold, an RH phrase will be formatted.
|
|
# To turn off phrase completely, set to -1.
|
|
if "_rhPhraseThreshold" in self.__dict__:
|
|
# Use Definition setting if defined
|
|
return self._rhPhraseThreshold
|
|
else:
|
|
# Default to no phrase
|
|
return -1
|
|
|
|
def rh_phrase(self):
|
|
return {
|
|
"setUpMethod": self.rh_setUp,
|
|
"wordMethod": self.rh_words,
|
|
"phraseMethods": self.standard_phraseMethods(),
|
|
}
|
|
def rh_setUp(self, tree, node):
|
|
elementInfoList = [self.ElementInfo("MinRH", "List")]
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
return self.DONE()
|
|
|
|
def rh_words(self, tree, node):
|
|
# Creates phrase for MinRH. Phrase will be generated if the MinRH
|
|
# value is <= rh_threshold(tree, node). Also uses only MinRH
|
|
# grids during the day part of the extended period. Requires the
|
|
# sample analysis method to use [0] for the time duration.
|
|
minRH = None
|
|
words = ""
|
|
statDict = node.getStatDict()
|
|
|
|
rhStats = tree.stats.get("MinRH", node.getTimeRange(), node.getAreaLabel(),
|
|
mergeMethod="List")
|
|
|
|
if rhStats is None:
|
|
return self.setWords(node, "")
|
|
for rhValues, tr in rhStats:
|
|
# Use data only from daytime timeranges
|
|
if self.getPeriod(tr, 1):
|
|
rh = self.getValue(rhValues, "Min")
|
|
if minRH is None or rh < minRH:
|
|
minRH = rh
|
|
if minRH is not None and minRH <= self.rh_threshold(tree, node):
|
|
words = "minimum RH " + repr(int(minRH)) + " percent"
|
|
return self.setWords(node, words)
|
|
|
|
# MultipleElementTable calls
|
|
def multipleElementTable_perPeriod_phrase(self):
|
|
return {
|
|
"setUpMethod": self.multipleElementTable_perPeriod_setUp,
|
|
"wordMethod": self.multipleElementTable_perPeriod_words,
|
|
"phraseMethods": [],
|
|
}
|
|
def multipleElementTable_perPeriod_setUp(self, tree, node):
|
|
elementInfoList = []
|
|
self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
|
|
return self.DONE()
|
|
|
|
def multipleElementTable_perPeriod_words(self, tree, node):
|
|
# Make a MultipleElementTable for this period
|
|
words = self.makeMultipleElementTable(
|
|
node.getAreaLabel(), node.getTimeRange(), tree,
|
|
byTimeRange=1)
|
|
return self.setWords(node.parent, words)
|
|
|
|
|