awips2/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/CombinedPhrases.py
2022-05-05 12:34:50 -05:00

708 lines
30 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.
#
# CombinedPhrases.py
# Methods for producing text forecast from SampleAnalysis statistics.
#
# Author: hansen
# ----------------------------------------------------------------------------
##
# This is a base file that is not intended to be overridden.
##
import ScalarPhrases
import VectorRelatedPhrases
import WxPhrases
import DiscretePhrases
class CombinedPhrases(ScalarPhrases.ScalarPhrases, VectorRelatedPhrases.VectorRelatedPhrases,
WxPhrases.WxPhrases, DiscretePhrases.DiscretePhrases):
def __init__(self):
ScalarPhrases.ScalarPhrases.__init__(self)
VectorRelatedPhrases.VectorRelatedPhrases.__init__(self)
WxPhrases.WxPhrases.__init__(self)
DiscretePhrases.DiscretePhrases.__init__(self)
############################################
### COMBINED ELEMENT PHRASES
# Weather OR Sky
def weather_orSky_phrase(self):
return {
"phraseList": [
self.weather_phrase,
self.sky_phrase,
],
"phraseMethods": [
self.orSkyTest,
],
}
def orSkyTest(self, tree, node):
" Develop phrase for weather Stats"
# If there is not weather, report Sky
# Check for empty words
for child in node.get("childList"):
words = child.get("words")
if words is None:
return
# Order the weather phrases
self.orderWxPhrases(tree, node)
# Gather the words for the child phrases
wordList = []
for child in node.get("childList"):
wordList.append((child.get("name"), child.get("words")))
wxWords = ""
skyWords = ""
# If weather, use that. Else use sky.
for name, words in wordList:
if name == "weather_phrase":
if wxWords != "":
wxWords = wxWords + ". "
wxWords = wxWords + words
if name == "sky_phrase":
if skyWords != "":
skyWords = skyWords + ". "
skyWords = skyWords + words
if wxWords != "":
return self.setWords(node, wxWords)
else:
return self.setWords(node, skyWords)
### Sky, PoP, Wx
## The skyPopWx_phrase will produce a combined Sky, Pop, Wx phrase IF appropriate.
## For example:
## "Partly cloudy with a 20 percent chance of showers and thunderstorms."
## "Sunny then partly cloudy with a 20 percent chance of
## showers and thunderstorms in the afternoon."
##
## NOTE: IF you are using this phrase, you must also include the
## sky_phrase, weather_phrase and popMax_phrase in your phraseList to be
## used if the combined phrase cannot be generated.
##
## Based on algorithms by Chris Gibson and Brian Walawender
##
## This phrase operates much like the weather_phrase according to these rules :
## --Sub-phrases are split out initially based on the Wx parameter. In other words,
## "Wx" is the primary element.
## Sub-phrases are consolidated and combined based on "Wx".
## --Only precip-related weather types will be included in the phrase.
## Non-precip-related weather types will be reported in a separate weather
## phrase.
## --After combining, if there are more than 2 sub-phrases, this phrase
## is removed and independent Sky, PoP, and Wx phrases are generated.
## Otherwise, independent sky_phrase, weather_phrase, popMax_phrases
## are removed IF the information is to be included in the skyPopWx_phrase:
## --If there are ANY areal coverage terms in the weather phrase, the PoP will
## be reported in a separate phrase. However, the Sky and Wx can still be reported
## in the combined phrase.
## --Only one PoP value can be reported in a combined phrase, so if there are
## multiple sub-phrases with precip, an independent PoP phrase will be generated.
## E.g.: Partly cloudy with a chance of rain in the morning, then a
## chance of thunderstorms in the afternoon. Probability of precipitation 50 percent.
## --If the value for Sky is the same throughout the period it will not be repeated.
## Instead of: Partly cloudy with a chance of rain showers in the morning
## then partly cloudy with a slight chance of thunderstorms in the afternoon.
## Produce: Partly cloudy. A chance of rain showers in the morning then a
## slight chance of thunderstorms in the afternoon.
##
## IMPLEMENTATION: This phrase takes into consideration Sky PoP and Wx,
## checking for required criteria ("checkSkyPopWx"), combining on a sub-phrase by
## sub-phrase basis, special handling of non-precip
## ("separateNonPrecip") and the word method ("skyPopWx_words").
def useCombinedSkyPopWx(self, tree, node):
# If set to 1, the combined skyPopWx_phrase will be used when appropriate
# as long as it is included in the product component definition
# If this is set to zero, the combined skyPopWx_phrase will not be used
return 1
def useSkyPopWx_consolidation(self, tree, node):
# If set to 1, the skyPopWx phrase will consolidate weather keys that
# span all time ranges to produce:
# Partly cloudy with a chance of rain.
# Snow in the morning, then sleet in the afternoon.
#
# instead of:
# Partly cloudy. Chance of rain and snow in the morning,
# then a chance of rain and sleet in the afternoon.
return 0
def skyPopWx_excludePoP_flag(self, tree, node):
# If set to 1, PoP will not be included in the skyPopWx_phrase
return 0
def skyPopWx_phrase(self):
return {
"setUpMethod": self.skyPopWx_setUp,
"wordMethod": self.skyPopWx_words,
"phraseMethods": [
self.skyPopWx_separateNonPrecip,
self.skyPopWx_consolidateWx,
self.checkLocalEffects,
self.combinePhraseStats,
self.checkSkyPopWx,
self.combineWords,
self.fillNulls,
self.timeDescriptorModeration,
self.assembleSubPhrases,
self.postProcessPhrase,
]
}
def skyPopWx_setUp(self, tree, node):
if self.useCombinedSkyPopWx(tree, node) == 0:
return self.setWords(node, "")
resolution = node.get("resolution")
if resolution is not None:
mergeMethod = "Average"
else:
mergeMethod = "List"
elementInfoList = [self.ElementInfo("Wx", mergeMethod, self.WEATHER())]
self.subPhraseSetUp(tree, node, elementInfoList, self.wxConnector,
resolution)
if self.areal_sky_flag(tree, node):
self.disableSkyRelatedWx(tree, node)
spawnedWxPhrases = node.get("spawnedWxPhrases")
if spawnedWxPhrases is None:
node.set("spawnedWxPhrases", [])
node.set("allTimeDescriptors", 1)
return self.DONE()
def skyPopWx_separateNonPrecip(self, tree, node):
# If > designated subPhrases, separate into precip/non-precip
statList = self.getSubStats(node, "Wx")
length = len(statList)
if self.__dict__.get("_leDebug", 0):
print("\n\nSPW separateNonPrecip", node.get('name'), node.getAreaLabel())
print(" node", node)
print(" disabled", node.getAncestor("disabledSubkeys"))
print(" timerange", node.getTimeRange())
print(" statList", statList)
#print " doneList", node.doneList
#print " disabled", node.get('disabledSubkeys')
if length > 0:
precip = []
nonPrecip = []
for rankList in statList:
subkeys = self.getSubkeys(rankList)
for subkey in subkeys:
if subkey.wxType() == "<NoWx>":
continue
if self.precip_related_flag(tree, node, subkey):
precip.append(subkey)
else:
nonPrecip.append(subkey)
if self.__dict__.get("_leDebug", 0): print("precip, nonPrecip", precip, nonPrecip)
if len(precip) >= 0 and len(nonPrecip) >= 1:
# Save this information so we can remove this new phrase later if
# we do not end up doing a combined sky, pop, weather phrase.
#print "\nCalling splitWxPhrase for SPW"
newPhrase = self.splitWxPhrase(
tree, node, nonPrecip, precip,
[self.separateNonPrecip, self.skyPopWx_separateNonPrecip,
self.consolidateWx],
newPhraseDef=self.weather_phrase)
spawnedWxPhrases = node.get("spawnedWxPhrases")
spawnedWxPhrases.append(newPhrase)
node.set("spawnedWxPhrases", spawnedWxPhrases)
return self.DONE()
def skyPopWx_consolidateWx(self, tree, node):
# If any wxTypes span all subPhrases, separate into their own phrase
if self.useSkyPopWx_consolidation(tree, node) == 0:
return self.DONE()
statList = self.getSubStats(node, "Wx")
length = len(statList)
subkeyDict = {}
if self.__dict__.get("_leDebug", 0):
print("\nSPW Consolidating ", node.get('name'), node.getAreaLabel())
print(" node", node)
print(" disabled", node.getAncestor("disabledSubkeys"))
print(" timerange", node.getTimeRange())
print(" statList", statList)
#print " doneList", node.doneList
if length > 1:
# Count occurrences of each weather key
for rankList in statList:
subkeys = self.getSubkeys(rankList)
for subkey in subkeys:
if subkey not in subkeyDict:
subkeyDict[subkey] = 1
else:
subkeyDict[subkey] += 1
if self.__dict__.get("_leDebug", 0):
print("subkeyDict", subkeyDict)
# Find subkeys to disable in first phrase and second phrase,
# respectively
list1 = []
list2 = []
for (subkey, value) in subkeyDict.items():
count = value
if count >= length:
list2.append(subkey)
else:
list1.append(subkey)
if self.__dict__.get("_leDebug", 0):
print("list1", list1)
print("list2", list2)
if len(list1) > 0 and len(list2) > 0:
newPhrase = self.splitWxPhrase(
tree, node, list1, list2,
[self.consolidateWx, self.separateNonPrecip,
self.skyPopWx_consolidateWx])
newPhrase.set("includeSky", 0)
newPhrase.set("includePoP", 0)
node.set("includePoP", 0)
return self.DONE()
# This method takes care of removing independent sky, pop and weather phrases if we are
# going to report combined skyPopWx.
#
# For PoP:
# If PoP is included in the combined phrase (includePoP == 1),
# we remove all independent PoP phrases from the component.
# This means that if this is a local effect phrase,
# we remove the local effect Pop phrase in addition to the
# Pop phrase for the component area.
#
# For Sky:
# If Sky is to be included in the combined phrase (includeSky == 1),
# we remove the independent Sky phrase associated with this
# node area.
#
# For Wx:
# We cannot simply remove all independent weather phrases because
# --They may have been spawned from consolidateWx or separateNonPrecip
# and need to remain.
# --If this is a local effect node, weather phrases may have been
# spawned by the component level area and need to remain.
# Thus, we remove non-spawned weather phrases for the node area.
# If this is a local effect area,
# we also remove weather phrases for the component area IFF
# they would be reporting the same subkeys as this skyPopWx phrase.
#
def checkSkyPopWx(self, tree, node):
# Check criteria to see if we can produce a combined phrase
# Enhanced by Dave Zaff
if self.__dict__.get("_leDebug", 0): print("\nCheckSPW", node.getTimeRange(), node.getAreaLabel())
# Determine non-empty weather subPhrases
wxSubPhrases = self.getNonEmptyWxSubPhrases(tree, node)
length = len(wxSubPhrases)
#print "length in skyPopWx", length
if length > 2:
return self.skyPopWx_cleanUp(tree, node)
# PoP
includePoP = self.checkIncludePoP(tree, node)
if self.__dict__.get("_leDebug", 0):
print("\nBefore removing independent phrases:")
self.printCompPhrases(tree, node)
print()
print("includePoP", includePoP)
print("If -1, clean-up. If 1, remove area and compArea popMax")
if includePoP == -1:
#print "cleaning up"
return self.skyPopWx_cleanUp(tree, node)
#return self.setWords(node, "")
if includePoP:
# Remove the independent PoP phrases
# Note that we remove both the local effect phrase
# and the component level phrase.
self.removeComponentPhrases(
tree, node, "popMax_phrase",
areaLabels=[
node.getAreaLabel(),
node.getComponent().getAreaLabel()
])
node.set("includePoP", includePoP)
# Sky
includeSky = self.checkIncludeSky(tree, node, wxSubPhrases)
if self.__dict__.get("_leDebug", 0):
print("includeSky", includeSky)
print("If 1, remove sky_phrase for area")
if includeSky:
self.removeComponentPhrases(tree, node, "sky_phrase",
areaLabels=[node.getAreaLabel()])
node.set("includeSky", includeSky)
# Wx
# Don't remove the spawned phrases
spawnedWxPhrases = node.get("spawnedWxPhrases")
wxExceptionPhrases = self.getWxExceptions(tree, node, "weather_phrase")
self.removeComponentPhrases(
tree, node, "weather_phrase",
spawnedWxPhrases + wxExceptionPhrases,
areaLabels=[
node.getAreaLabel(),
node.getComponent().getAreaLabel()
])
if self.__dict__.get("_leDebug", 0):
print("Removed weather phrases", node.getAreaLabel())
print("\nAfter removing independent phrases:")
self.printCompPhrases(tree, node)
return self.DONE()
def getWxExceptions(self, tree, node, phraseName):
# Return a list of the phrases with the given phraseName that
# --do not have the same areaLabel AND
# --do not have the same Wx stats as the given node.
nodeStats = self.getWxStats(tree, node)
nodeKeys = []
for subkey, rank in nodeStats:
nodeKeys.append(subkey)
component = node.getComponent()
progeny = component.getProgeny()
wxExceptions = []
if self.__dict__.get("_leDebug", 0):
print("\nGetting exceptions for", nodeStats)
for child in progeny:
if child.getAreaLabel() == node.getAreaLabel():
continue
name = child.get("name")
if name == phraseName:
# Check the stats
wxStats = self.getWxStats(tree, child)
if self.__dict__.get("_leDebug", 0):
print("\nChecking", wxStats)
for subkey, rank in wxStats:
if subkey in nodeKeys or subkey.wxType() == "<NoWx>":
continue
if self.__dict__.get("_leDebug", 0):
print("Appending")
wxExceptions.append(child)
return wxExceptions
def getWxStats(self, tree, node):
wxStats = tree.stats.get("Wx", node.getTimeRange(), node.getAreaLabel(),
mergeMethod="Average")
wxStats = self.applyDisabled(tree, node, wxStats)
#print "\ngetWxStats", node.getAreaLabel(), node.getTimeRange()
#print " wxStats", wxStats
return wxStats
def getNonEmptyWxSubPhrases(self, tree, node):
wxSubPhrases = []
for child in node.get("childList"):
statDict = child.getStatDict()
rankList = self.getStats(statDict, "Wx")
if rankList is None or len(rankList) == 0:
continue
# See if all subkeys are NoWx
for subkey, rank in rankList:
if subkey.wxType() != "<NoWx>":
wxSubPhrases.append(child)
break
return wxSubPhrases
def checkIncludePoP(self, tree, node):
# Determine number of sub-phrases with precip-related Wx
# Also, determine if there are any areal coverage terms
# for a precip-related weather type
# Return 1 if we are to include PoP
# 0 if we are not to include PoP
# -1 if there is no precip weather and we are to abort the skyPopWx_phrase
includePoP = 1
withPrecip = 0
arealCov = 0
arealCovs = self.arealCoverages()
pop = self.matchToWx(tree, node, "PoP", algorithm="Max")
if pop is None:
return -1
covList=[]
noWx = 1
for subPhrase in node.childList:
precipFound = 0
statDict = subPhrase.getStatDict()
if statDict is None:
continue
rankList = statDict["Wx"]
subkeys = self.getSubkeys(rankList)
#print "sub-phrase keys", subkeys
for subkey in subkeys:
if pop < self.pop_wx_lower_threshold(tree, node) and \
not self.pop_related_flag(tree, node, subkey):
noWx = 0
elif pop >= self.pop_wx_lower_threshold(tree, node) and \
subkey.wxType() != "<NoWx>":
noWx = 0
if noWx == 0 and self.precip_related_flag(tree, node, subkey):
precipFound = 1
cov = subkey.coverage()
if cov in arealCovs:
arealCov = 1
# Condition added by Dave Zaff:
# If any coverages are different,
# we will report PoP separately
else:
for getCov in covList:
if cov!=getCov:
includePoP=0
covList.append(cov)
if precipFound:
withPrecip = withPrecip + 1
if withPrecip == 0 and noWx:
# Do not use combined skyPopWx_phrase
self.skyPopWx_cleanUp(tree, node)
return -1 # Signal that we are Done
# Check for excluding PoP
if self.skyPopWx_excludePoP_flag(tree, node):
return 0
# If there is more than one sub-phrase with precip,
# report PoP independently.
#print "withPrecip", withPrecip
if withPrecip > 1:
includePoP = 0
# If there are ANY areal coverage terms for a precip-related
# weather type, report PoP independently.
if arealCov == 1:
includePoP = 0
# Check to see if includePoP has been set previously
prevIncludePoP = node.get("includePoP")
if prevIncludePoP == 0:
includePoP = 0
# If PoP >= 60, then report PoP independently
if pop >= 60:
includePoP = 0
return includePoP
def checkIncludeSky(self, tree, node, wxSubPhrases):
# Inclusion of Sky in the skyPopWx_phrase.
# The "includeSky" flag is checked in "checkSkyPopWx" and if set,
# the independent sky_phrase is removed.
# The general rules for including Sky are outlined below.
# In addition:
# The "includeSky" flag is set to zero when a new skyPopWx_phrase
# is spawned by "skyPopWx_consolideWx" since the sky condition
# will be addressed by the original skyPopWx_phrase.
# The "includeSky" flag may be set in "checkLocalEffects" (PhraseBuilder):
# If we find a local effect for the skyPopWx phrase (using the
# "checkSkyWxDifference" method in PhraseBuilder), then
# we will not includeSky in the new local effect phrases
# UNLESS there was also a sky local effect, in which case we
# will "checkIncludeSky" for the local effect nodes.
#
# Check to see if already set by "skyPopWx_consolidateWx" or by "checkLocalEffects"
includeSky = node.get("includeSky")
if includeSky is None:
# We need to check for the following situations to set "includeSky":
# Number of sub-phrases (length) Wx Sky includeSky
#
# 1 similar similar 1
# Mostly sunny with a 50 percent chance of rain.
#
# 2 different similar 0
# Mostly sunny. A chance of rain then a slight chance of snow
# in the afternoon.
#
# 1 similar different 0
# Mostly sunny in the morning then becoming partly cloudy. A
# 50 percent chance of rain.
#
# 2 different different 1
# Mostly sunny with a chance of rain then partly cloudy with
# a slight chance of snow in the afternoon. A 50 percent chance
# of rain and snow.
#
# Compare sky for similarity in the 1st and 2nd half of the period.
# Note: We can't count on Sky having a temporal resolution of [6],
# but, since the skyPopWx_phrase bails after 2 sub-phrases,
# looking at the 2 halves of the timeRange is sufficient.
timeRange = node.getTimeRange()
areaLabel = node.getAreaLabel()
timeRange1, timeRange2 = self.splitRange(timeRange)
skyStats1 = tree.stats.get("Sky", timeRange1, areaLabel,
mergeMethod="Average")
skyStats2 = tree.stats.get("Sky", timeRange2, areaLabel,
mergeMethod="Average")
sky1 = self.getSkyValue(tree, node, skyStats1, timeRange1)
sky2 = self.getSkyValue(tree, node, skyStats2, timeRange2)
#print "similarSky", self.similarSkyWords_flag(tree, node, sky1, sky2)
# Determine if Wx is similar
similarWx = 1
wxSubPhraseLen = len(wxSubPhrases)
if wxSubPhraseLen > 1:
similarWx = 0
# There is one subphrase and we have to see if it covers the
# entire phrase timeRange. If not, we have empty and non-empty
# weather subPhrases and similarWx is 0
elif wxSubPhraseLen == 1 and wxSubPhrases[0].getTimeRange() != node.getTimeRange():
similarWx = 0
if self.similarSkyWords_flag(tree, node, sky1, sky2):
if similarWx:
includeSky = 1
else:
includeSky = 0
else:
if similarWx:
includeSky = 0
else:
includeSky = 1
return includeSky
def skyPopWx_cleanUp(self, tree, node):
# Clean up any non-precip node that we spawned
spawnedWxPhrases = node.get("spawnedWxPhrases")
for phrase in spawnedWxPhrases:
phrase.remove()
node.set('childList', [])
return self.setWords(node, "")
def skyPopWx_words(self, tree, node):
# Create a combined sky, pop, weather sub-phrase
timeRange = node.getTimeRange()
areaLabel = node.getAreaLabel()
# Sky words
includeSky = node.getAncestor("includeSky")
if includeSky is None:
includeSky = 1
# Add sky to the statDict for this node
if includeSky:
skyWords = self.getSkyWords(tree, node)
# If this is the second node, see if the sky words are similar to the first
# If so, do not repeat
index = node.getIndex()
if index == 1:
prevSkyWords = node.getPrev().get("words")
if self.similarSkyWords_flag(tree, node, skyWords, prevSkyWords):
prevSkyWords = self.preferredSkyWords(tree, node, skyWords, prevSkyWords)
node.getPrev().set("words", prevSkyWords)
skyWords = ""
else:
skyWords = ""
# Pop words
includePoP = node.getAncestor("includePoP")
if includePoP:
pop = self.matchToWx(tree, node, "PoP")
if pop is None or pop < self.pop_lower_threshold(tree, node) or \
pop > self.pop_upper_threshold(tree, node):
popWords = ""
else:
popStr = self.getPopStr(tree, node, pop)
popWords = "a " + popStr + " percent chance of"
else:
popWords = ""
# Weather words
self.weather_words(tree, node)
weatherWords = node.get("words")
if weatherWords == "null":
weatherWords = ""
if weatherWords == "":
popWords = ""
## print "\n\n", areaLabel, timeRange
## print "includeSky", includeSky
## print "includePoP", includePoP
## print "skyWords", skyWords
## print "popWords", popWords
## print "weatherWords", weatherWords
if skyWords == "" and weatherWords == "":
return self.setWords(node, "")
words = ""
if includePoP:
if popWords != "":
weatherWords = weatherWords.replace("a slight chance of", "")
weatherWords = weatherWords.replace("a chance of", "")
weatherWords = weatherWords.replace("slight chance of", "")
weatherWords = weatherWords.replace("chance of", "")
weatherWords = weatherWords.replace("likely", "")
weatherWords = weatherWords.replace("occasional", "")
weatherWords = weatherWords.strip()
# Do not repeat weather words from previous sub-phrase
node.set("weatherWords", weatherWords)
if node.getIndex() > 0:
weatherWords = self.checkRepeatingString(
tree, node, weatherWords, "weatherWords")
if weatherWords == "":
popWords = popWords.replace(" of", "")
if popWords == "" and weatherWords == "":
words = skyWords
elif popWords == "" and skyWords == "":
words = weatherWords
elif skyWords == "" and weatherWords == "":
words = popWords
elif popWords == "":
# There must be sky and weather
words = skyWords + " with " + weatherWords
elif skyWords == "":
words = popWords + " " + weatherWords
elif weatherWords == "":
words = skyWords + " with " + popWords
else:
words = skyWords + " with " + popWords + " " + weatherWords
else:
weatherWords = weatherWords.lstrip()
if skyWords != "" and weatherWords != "":
words = skyWords + " with " + weatherWords
elif skyWords == "" and weatherWords != "":
words = weatherWords
else:
words = skyWords
return self.setWords(node, words)
def getSkyWords(self, tree, node):
timeRange = node.getTimeRange()
areaLabel = node.getAreaLabel()
sky = tree.stats.get("Sky", timeRange, areaLabel, mergeMethod="Average")
statDict = node.getStatDict()
statDict["Sky"] = sky
node.set("statDict", statDict)
self.sky_words(tree, node)
skyWords = node.get("words")
return skyWords
def getSkyValue(self, tree, node, skyStats, timeRange):
if timeRange.duration() > 12*3600:
dayNight = -1
else:
dayNight = self.getPeriod(timeRange, 1)
return self.sky_value(tree, node, skyStats, dayNight)