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

4237 lines
173 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.
#
# PhraseBuilder.py
# Methods for building phrases for Narrative products.
#
# Author: hansen
#
# SOFTWARE HISTORY
#
# Date Ticket# Engineer Description
# ------------- -------- --------- --------------------------------------------
# Dec 28, 2012 15596 J.Zeng Added checkWeatherSimilarity for two lists
# based on Virgil's suggestion
# Apr 20, 2015 4027 randerso Changes for mixed case product generation.
# Jan 08, 2016 5129 dgilling Fix signatures to calls in WeatherSubKey.
# Jul 15, 2016 5749 randerso Replace ellipses with commas
# Apr 10, 2018 7255 randerso Fix missing label on first period.
# Apr 11, 2018 7106 dgilling Fix sentence capitalization in
# assembleUntilSubPhrases.
# Feb 25, 2019 7640 randerso Fix TypeError in adjustWords.
# ----------------------------------------------------------------------------
##
# This is a base file that is not intended to be overridden.
##
import types
import functools
import TimeDescriptor
import ConfigVariables
import StringUtils
import UnitConvertor
import TimeRange, WeatherSubKey
class PhraseBuilder(ConfigVariables.ConfigVariables,
TimeDescriptor.TimeDescriptor,
UnitConvertor.UnitConvertor,
StringUtils.StringUtils):
def __init__(self):
ConfigVariables.ConfigVariables.__init__(self)
StringUtils.StringUtils.__init__(self)
TimeDescriptor.TimeDescriptor.__init__(self)
UnitConvertor.UnitConvertor.__init__(self)
def SCALAR(self):
return 0
def MAGNITUDE(self):
return 1
def DIRECTION(self):
return 2
def VECTOR(self):
return 3
def VECTOR_TEXT(self):
return 4
def VECTOR_NUM(self):
return 5
def WEATHER(self):
return 6
def DISCRETE(self):
return 7
def DONE(self):
return 1
def call(self, method, tree, node):
# Try to call with tree, node arguments
try:
return method(tree, node)
except:
return method()
def setWords(self, node, words):
node.set("words", words)
# If the words are empty, we can close down node
if words == "":
#node.remove()
node.doneList = node.methodList
return 1
def useCommas(self, tree, node, words):
# Instead of:
# "Chance of rain and snow and freezing rain..."
# use
# "Chance of rain, snow and freezing rain..."
if words.count(" and ") > 1:
hiIndex = words.rfind(" and ")
words = words[:hiIndex].replace(" and ", ", ") + words[hiIndex:]
return words
def setDone(self, node):
# Set the node doneList to the methodList
# so that the node is defunct
node.doneList = node.methodList
def standard_phraseMethods(self):
return [
self.consolidatePhrase,
self.checkLocalEffects,
self.combinePhraseStats,
self.consolidateTrends,
self.chooseMostImportant,
self.combineWords,
self.fillNulls,
self.timeDescriptorModeration,
self.assembleSubPhrases,
self.postProcessPhrase,
]
def postProcessPhrase(self, tree, node):
words = node.get("words")
rval = None
if words is not None:
words = words.replace("rain showers and thunderstorms", "showers and thunderstorms")
# To handle snow amt local effects
words = words.replace("except of", "except")
# Translate phrase
# This is necessary so that word-wrap works correctly
try:
words = self.translateForecast(words, self._language)
except:
words = self.translateForecast(words, "english")
rval = self.setWords(node, words)
return rval
def roundStatistic(self, tree, node, value, elementName):
roundingMethod = self.rounding_method(tree, node, elementName, elementName)
nlIncrement = self.nlValue(self.increment_nlValue(
tree, node, elementName, elementName), value)
return self.roundValue(value, roundingMethod, "Nearest", nlIncrement)
def findWords(self, tree, node, firstElementName, areaLabel=None, phraseList=None,
ignoreAreaIfLastChance=1, phraseLevel=0, attributes=None):
# Return a text string which is the concatenation of the words for all
# leaves of the tree in the same component as the given node
# and have the given firstElement at their phrase level.
# "Leaves" will be sub-phrases in most case unless a phrase node has
# words set and has empty sub-phrases.
# The method returns None unless all leaves that meet the qualifications
# have words set.
#
# If an areaLabel is given, it is applied to the selection of leaves.
# Otherwise all leaves meeting the firstElementName criteria are accepted.
# "areaLabel" can optionally be a list of areaLabels.
#
# If a phraseList is given, only leaves belonging to a phrase
# in that list are examined.
# If phraseLevel is 1 and words are set at the phrase level, those
# are concatenated to the words as well.
#
# if attributes is not None, return a dictionary:
# {attribute: [list of attribute values for leaves with words]}
#
leaves = self.getLeaves(tree, node)
found = 0
words = ""
attrDict = {}
#print "\nFind Words", firstElementName
if areaLabel is not None:
if type(areaLabel) is not list:
areaLabel = [areaLabel]
for child in leaves:
firstElement = child.getAncestor("firstElement")
if firstElement is None:
continue
if firstElement.name == firstElementName or firstElementName is None:
if phraseList is not None:
if child.getAncestor("name") not in phraseList:
continue
#print "firstElement", firstElement.name
#print "child", child, child.get("childList")
#print "words", child.get('words')
# Check the area
if ignoreAreaIfLastChance:
# If last time thru because no changes were made to tree,
# ignore area
if tree.get("lastChance") == 1:
areaLabel = None
if areaLabel is not None:
if child.getAreaLabel() not in areaLabel:
continue
# This is a phrase for which we want words
childWords = child.get('words')
if childWords is not None:
found = 1
#print "Adding words", child.get('name'), child.getAreaLabel()
#print " ", child.getTimeRange()
#print " ", childWords
words = words + " " + childWords
if attributes is not None:
for attribute in attributes:
attrVal = child.getAncestor(attribute)
if attrVal is not None:
self.addToDictionary(attrDict, attribute, attrVal)
if phraseLevel:
parentWords = child.parent.get('words')
if parentWords is not None:
words = words + " " + parentWords
else:
# wait for words to complete
words = None
break
if not found:
words = None
if attributes is not None:
return words, attrDict
else:
return words
def addToDictionary(self, dictionary, key, value):
# Add the given value to the dictionary where the key
# entry is a list of values
if key in dictionary:
if type(value) is list:
dictionary[key] += value
else:
dictionary[key].append(value)
else:
if type(value) is list:
dictionary[key] = value
else:
dictionary[key] = [value]
def getLeaves(self, tree, node):
# Return a list of "leaves" for the component to which the node belongs.
# "Leaves" will be nodes that have no children (sub-phrases)
# EXCEPT if a node has words set AND it's children do not.
# (Some phrases e.g. waveHeight_phrase, will sometimes by-pass
# the sub-phrases and set the phrase words directly.)
#
component = node.getComponent()
progeny = component.getProgeny()
leaves = []
for child in progeny:
childList = child.get('childList')
#print "child", child.getAncestor('name'), childList
#print " ", child.get('words'), child.getAreaLabel()
if childList == [] or childList is None:
# This is a leaf
# Check to see if it has words
leaf = child
if child.get('words') is None:
# If not AND it's parent has words,
# take the parent as the leaf.
if child.parent.get('words') is not None:
leaf = child.parent
leaves.append(leaf)
return leaves
def dayOrNight_element(self, tree, node, dayElement, nightElement):
dayNight = self.getPeriod(node.getTimeRange(), 1)
if dayNight == self.DAYTIME():
element = dayElement
else:
element = nightElement
return element
def removeComponentPhrases(self, tree, node, phraseName, exceptions=[], areaLabels=[]):
# Remove all phrases with the given phraseName in the current component
# If areaLabel is not None, check for a match there as well and only
# remove the phrase if it is for that area.
component = node.getComponent()
progeny = component.getProgeny()
#print "\nRemoving Phrases", phraseName, areaLabels, exceptions
for child in progeny:
name = child.get("name")
if name == phraseName:
#print "child", child, child.getAreaLabel()
if child not in exceptions:
if areaLabels != []:
if child.getAreaLabel() in areaLabels:
#print "Removing 1", name, areaLabels, child
#import traceback
#traceback.print_stack(limit=3)
child.remove()
else:
child.remove()
#print "Removing 2", name, areaLabels
#import traceback
#traceback.print_stack(limit=3)
class ElementInfo:
def __init__(self, name, mergeMethod="Average", dataType=0,
statLabel="", primary=1, phraseDef=None):
self.name = name
self.mergeMethod = mergeMethod
self.statLabel = statLabel
self.primary=primary
self.dataType = dataType
self.phraseDef = phraseDef
## def name(self):
## return self.name
## def mergeMethod(self):
## return self.mergeMethod
## def statLabel(self):
## return self.statLabel
## def primary(self):
## return self.primary()
## def dataType(self):
## return self.dataType()
## def phraseDef(self):
## return self.phraseDef()
class LocalEffectArea:
def __init__(self, areaLabel, areaWords, conjAreaWords=None, intersectFlag=1):
# AreaLabel can be "__Current__" which will use the
# current edit area.
self.areaLabel = areaLabel
# Words to describe this local effect area.
# This can be a text string or a method
# arguments: (tree, node, LocalEffectArea)
# returns a text string
self.areaWords = areaWords
# This can be a text string or a method
# arguments: (tree, node, LocalEffectArea)
# returns a text string
# If you want different area words for the conjunctive
# phrase versus the embedded phrase, add them here
if conjAreaWords is None:
self.conjAreaWords = areaWords
else:
self.conjAreaWords = conjAreaWords
# If 1, area is to be intersected with the current area to
# look for local effects and the area must be listed as an
# intersectArea in the current product component
self.intersectFlag = intersectFlag
class LocalEffect:
def __init__(self, leAreaList, triggerMethod, exceptionWords):
# List of LocalEffectAreas
# Currently only two areas can be handled by the local effect methods
self.leAreaList = leAreaList
self.triggerMethod = triggerMethod
self.exceptionWords = exceptionWords
def makeRangeStats(self, tree, dataType, stats, timeRange):
if stats is None:
return None
if tree.library.isStatsByRange(dataType, stats, timeRange):
return stats
else:
return[(stats, timeRange)]
def sumPrevStats(self, tree, node, areaLabel, elementName,
mergeMethod="Average", increment=1):
# Return a sum of stats going backward in time
# until there is a zero value (when rounded to the nearest increment)
# and NOT including the current value
# For example:
# Period1 = 2 inches snow amt
# Period2 = 0 inches
# Period3 = 2 inches
# Period4 = 2 inches
# Period5 = 4 inches
# If sumPrevStats is called during evaluation of
# Period5, the sum will be 4 (sum of Period3 and Period4)
# 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,
areaLabel, mergeMethod="MinMax")
if stats is not None:
pastSnowMin, pastSnowMax = self.getValue(stats, "MinMax")
pastSnowMin = int(pastSnowMin+0.5)
pastSnowMax = int(pastSnowMax+0.5)
else:
pastSnowMin = 0
pastSnowMax = 0
minSum = pastSnowMin
maxSum = pastSnowMax
# Calculate snow in forecast periods
childList = node.getParent().get("childList")
timeRange = node.getTimeRange()
for child in childList:
childTimeRange = child.getTimeRange()
if childTimeRange == timeRange:
break
stats = tree.stats.get("SnowAmt", childTimeRange,
areaLabel, mergeMethod="MinMax")
if stats is None:
continue
min, max = self.getValue(stats, "MinMax")
min = int(min+0.5)
max = int(max+0.5)
threshold = self.pop_snow_lower_threshold(tree, node)
popStats = self.matchToWx(tree, node, "PoP", childTimeRange)
if popStats is None or popStats < threshold:
min = 0
max = 0
if max == 0:
# Start over
minSum = 0
maxSum = 0
else:
minSum = minSum + min
maxSum = maxSum + max
return minSum, maxSum
def getScalarRangeStr(self, tree, node, element, min, max):
min1=int(min)
max1=int(max)
if min1 == max1:
return repr(min1)
else:
maxRange = self.maximum_range_nlValue(tree, node, element, element)
maxRange = self.nlValue(maxRange, max1)
if (maxRange == 0):
return repr(max1)
if abs(min1-max1) > maxRange:
min1 = max1 - maxRange
connector = self.value_connector(tree, node, element, element)
return repr(min1) + connector + repr(max1)
def makeSentence(self, tree, node):
"Make a sentence from the words at the node level"
words = node.get("words")
if words is None:
return
if words == "":
words = "MISSING."
else:
words = self.sentence(words)
return self.setWords(node, words)
def chooseElement(self, tree, node, elementNameList):
# Return first elementName for which there are stats for
# this node. If none, return last elementName.
for elementName in elementNameList:
stats = tree.stats.get(elementName, node.getTimeRange(),
node.getAreaLabel(), mergeMethod="Avg")
if stats is not None:
return elementName
return elementName
# Narrative Level
def assembleChildWords(self, tree, node):
fcst = ""
for child in node.get("childList"):
words = child.get("words")
if words is None:
return
fcst = fcst + words
return self.setWords(node, fcst)
# Component Level
def noWords(self, tree, component):
self.setWords(component, "")
return self.DONE()
def assembleSentences(self, tree, node):
for phrase in node.get("childList"):
words = phrase.get("words")
if words is None:
return
fcst = ""
lastQualifier = None
lastPhrase = None
self.orderWxPhrases(tree, node)
self.consolidateLocalEffectPhrases(tree, node)
for child in node.get("childList"):
words = child.get("words")
words, lastQualifier = self.qualifyWords(
child, words, "conjunctiveQualifier", lastQualifier,
lastPhrase)
lastPhrase = child
fcst = fcst + words
return self.setWords(node, fcst)
def assemblePhrases(self, tree, component):
# Assemble component phrases and add Label
# Qualify the phrases with local effect qualifiers
# if present.
# e.g. "near the coast"
phrases = []
for phrase in component.get("childList"):
words = phrase.get("words")
if words is None:
return
if words != "":
phrases.append(phrase)
#print "\nAssemblePhrases"
# Remove empty word phrases
component.childList = phrases
self.orderWxPhrases(tree, component)
self.consolidateLocalEffectPhrases(tree, component)
#print
fcst = ""
lastQualifier = None
lastPhrase = None
phraseList = []
includeOnlyPhrases = self.includeOnlyPhrases_list(tree, component)
for phrase in component.get("childList"):
words = phrase.get("words")
words = self.adjustWords(tree, phrase, words)
#print phrase.get('name'), phrase.getAreaLabel()
#print " ", words
if type(includeOnlyPhrases) is list and len(includeOnlyPhrases) > 0 and \
phrase.get('name') not in includeOnlyPhrases:
# Do not include this phrase
continue
words, lastQualifier = self.qualifyWords(
phrase, words, "conjunctiveQualifier", lastQualifier, lastPhrase)
lastPhrase = phrase
if words not in phraseList:
phraseList.append(words)
fcst = fcst + words
# Add label
curLocalTime, shift = self.determineTimeShift()
issuanceInfo = tree.get("issuanceInfo")
index = component.getIndex()
label = self.createLabel(tree, component, component.get("timeRange"),
issuanceInfo, curLocalTime, shift, index)
fcst = self.combineSentences(fcst)
return self.setWords(component, label + fcst)
def adjustWords(self, tree, phrase, words):
# Make any special adjustments to phrases
# This one is necessary for popMax since we have
# removed repeating popType wording assuming
# an embedded PoP phrase will result.
# If it happens to end up as a conjunctive,
# then we have to put the popType back.
if phrase.get('name') in ["popMax_phrase"]:
if not phrase.get('embedded'):
popType = phrase.getDescendent('popType')
if popType and words.find(popType) == -1:
desc = phrase.get('descriptor')
words = words.replace(desc, desc + " " + popType)
return words
def assembleIndentedPhrases(self, tree, component):
# Assemble and indent component phrases and add Label
# Qualify the phrases with local effect qualifiers
# if present.
# e.g. "near the coast"
for phrase in component.get("childList"):
words = phrase.get("words")
#print phrase, words
if words is None:
return
# DR_18964
self.consolidateLEPerPhraseInstance(tree, component)
#self.consolidatePerPhraseNameGroup(tree, component)
fcst = ""
lastQualifier = None
lastPhrase = None
self.orderWxPhrases(tree, component)
for phrase in component.get("childList"):
words = phrase.get("words")
if words is None:
return
if words == "":
if self.removeEmptyPhrase(tree, phrase):
continue
# Handle multiple element table phrase
# that appears per period
# No need to indent or qualify
name = phrase.get("name")
if name == "multipleElementTable_perPeriod_phrase":
fcst = fcst + words
continue
if phrase.get("compound"):
makeSentence = 0
else:
makeSentence = 1
words, lastQualifier = self.qualifyWords(
phrase, words, "conjunctiveQualifier", lastQualifier, lastPhrase,
makeSentence=makeSentence)
lastPhrase = phrase
indentLabel = phrase.get("indentLabel")
label = self.phrase_descriptor(
tree, phrase, indentLabel, indentLabel)
#print "indentLabel, label", indentLabel, label
if indentLabel is not None and label == "":
label = indentLabel
if words == "":
words = " "
words = self.labelIndent(words, label)
print(phrase, words)
fcst = fcst + words
# Add label
curLocalTime, shift = self.determineTimeShift()
issuanceInfo = tree.get("issuanceInfo")
index = component.getIndex()
label = self.createLabel(tree, component, component.get("timeRange"),
issuanceInfo, curLocalTime, shift, index)
return self.setWords(component, label + "\n" + fcst + "\n")
def consolidateLEPerPhraseInstance(self, tree, component):
# Do the LE consolidation/combination for each (compound) phrase
# create a pseudo component for the LE phrases to hang onto
pseudo = component.copy()
lePhraseNameGroups = []
le_groups = {}
for phrase in component.get("childList"):
if phrase.get("localEffect"):
lePhraseNameGroup, firstName = self.getLePhraseNameGroup(
tree, component, lePhraseNameGroups, phrase)
le_groups.setdefault(lePhraseNameGroup, []).append(phrase)
for name, nodes in le_groups.items():
#print name, nodes
# put the nodes under the pseudo-component and do the
# LE consolidation/combination
pseudo.set("childList", nodes)
self.consolidateLocalEffectPhrases(tree, pseudo)
#self.combineConjunctivePhrases(tree, pseudo)
# add the resultant nodes back under the component,
# replacing the original ones
newChildren = pseudo.get("childList")
#print 'new nodes:', newChildren
if newChildren == nodes:
continue
childList = []
inserted = 0
for child in component.get("childList"):
if child in nodes:
if not inserted:
childList += newChildren
inserted = 1
else:
childList.append(child)
component.set("childList", childList)
# delete the pseudo component
pseudo.remove()
pseudo.set("parent", None)
pseudo.set("childList", [])
def consolidatePerPhraseNameGroup(self, tree, component):
# Do the LE consolidation/combination for each (compound) phrase
# create a pseudo component for the LE phrases to hang onto
pseudo = component.copy()
lePhraseNameGroups = self.lePhraseNameGroups(tree, component)
le_groups = {}
for phrase in component.get("childList"):
if phrase.get("localEffect"):
lePhraseNameGroup, firstName = self.getLePhraseNameGroup(
tree, component, lePhraseNameGroups, phrase)
le_groups.setdefault(lePhraseNameGroup, []).append(phrase)
for name, nodes in le_groups.items():
#print name, nodes
# put the nodes under the pseudo-component and do the
# LE consolidation/combination
pseudo.childList = nodes
self.consolidateLocalEffectPhrases(tree, pseudo)
self.combineConjunctivePhrases(tree, pseudo)
# add the resultant nodes back under the component,
# replacing the original ones
newChildren = pseudo.childList
childList = []
inserted = 0
for child in component.childList:
if child in nodes:
if not inserted:
childList += newChildren
inserted = 1
else:
childList.append(child)
component.childList = childList
# delete the pseudo component
pseudo.remove()
def weatherPhraseNames(self, tree, node):
return ["weather_phrase", "skyPopWx_phrase"]
def orderWxPhrases(self, tree, component):
# Sort the weather phrases (weather_phrase, skyPopWx_phrase)
# according to their time span for non-empty sub-phrases.
# Then replace the weather phrases in the component childList.
# We will assume that all the weather phrases for the
# component are consecutive.
wxPhraseNames = self.weatherPhraseNames(tree, component)
phraseList = component.get("childList")
wxPhrases = []
for phrase in phraseList:
if phrase.get("name") in wxPhraseNames:
wxPhrases.append(phrase)
#print "appending", phrase.get('name'), phrase.get('words')
wxPhrases.sort(key=self.sortPhraseTimeSpans)
#print "sorted list"
#for phrase in wxPhrases:
# print phrase.get('name'), phrase.get('words')
newPhraseList = []
firstTime = 1
for phrase in phraseList:
if phrase.get("name") in wxPhraseNames:
if firstTime:
# Add in the sorted wxPhrases
newPhraseList += wxPhrases
firstTime = 0
continue
# If not a wx phrase, append to the list
newPhraseList.append(phrase)
component.set("childList", newPhraseList)
@property
def sortPhraseTimeSpans(self):
# This method calls orderTimeRanges and expects that to be a comparison
# function, while other code expects orderTimeRanges to be a key function.
# There is no key_to_cmp function in the standard library, and cmp is
# gone in Python 3 so we have to roll our own.
cmp = lambda a, b: (a > b) - (a < b)
def cmpfunc(phrase1, phrase2):
# Determine which phrase should come first according
# to it's time span of non-empty sub-phrases
# First, determine the timeSpan of each phrase:
defaultTR = TimeRange.default()
for phrase in [phrase1, phrase2]:
# If time span already calculated, skip it
timeSpan = phrase.get("timeSpan")
if timeSpan is not None:
continue
startTime = None
endTime = None
for subPhrase in phrase.get("childList"):
if subPhrase.get("words") != "":
tr = subPhrase.getTimeRange()
trStart = tr.startTime()
trEnd = tr.endTime()
if startTime is None:
startTime = trStart
elif startTime > trStart:
startTime = trStart
if endTime is None:
endTime = trEnd
elif endTime < trEnd:
endTime = trEnd
if startTime is not None and endTime is not None:
# Make a time span for this phrase's words
phraseTR = TimeRange.TimeRange(startTime, endTime)
phrase.set("timeSpan", phraseTR)
else:
phrase.set("timeSpan", defaultTR)
# Order the phrases according to their time spans.
timeSpan1 = phrase1.get("timeSpan")
timeSpan2 = phrase2.get("timeSpan")
if timeSpan1 == defaultTR or timeSpan2 == defaultTR:
return 0
ts1Key = self.orderTimeRanges(timeSpan1)
ts2Key = self.orderTimeRanges(timeSpan2)
return cmp(ts1Key, ts2Key)
return functools.cmp_to_key(cmpfunc)
@property
def orderTimeRanges(self):
def cmpfunc(tr1, tr2):
# If tr1 should come before tr2, return -1
# If equal, return 0, else return 1
# print "\nin orderTimeRanges", tr1, tr2
s1 = tr1.startTime()
s2 = tr2.startTime()
if s1 < s2:
# print "return1 -1"
return -1
elif s2 < s1:
# print "return2 1"
return 1
else:
# They start at the same time
e1 = tr1.endTime()
e2 = tr2.endTime()
# Put the one with the shortest
# span first
if e1 < e2:
# print "return3 -1"
return -1
elif e1 > e2:
# print "return4 1"
return 1
else:
# print "return5 0"
return 0
return functools.cmp_to_key(cmpfunc)
def consolidateSubPhrases(self, tree, component):
## Timing: This method runs at the component level
## AFTER all sub-phrase words have been set and
## BEFORE they have been assembled into phrases at the phrase level.
##
## Purpose: Check for duplicate subPhrases and consolidate
# them into one.
##
## For example: (see Case 2 below)
## Chance of thunderstorms in the morning (windward)
## Chance of thunderstorms in the morning (leeward)
## Chance of rain in the afternoon (windward)
## Chance of snow in the afternoon (leeward)
##
## becomes:
## Chance of thunderstorms in the morning (unqualified)
## Chance of rain in the afternoon (windward)
## Chance of snow in the afternoon (leeward)
# Set a flag to make sure we pass by this method the first time
# so that the phrase set-up methods have a chance to run and
# create sub-phrases before we try to consolidate them
if component.get('first') is None:
component.set('first', 1)
return
# Make sure all subPhrases have completed i.e. have words set
subPhraseList = []
leaves = self.getLeaves(tree, component)
leFlag = 0
for child in leaves:
words = child.get("words")
#print "Consolidate SubPhrases", child.getAncestor("name"), words
if words is None:
#print "Returning"
return
le = child.getAncestor('localEffect')
if le is not None:
leFlag = 1
subPhraseList.append(child)
# If no localEffects, skip this method
if not leFlag:
#print "In Consolidate SubPhrases: No local effects"
return self.DONE()
if self.__dict__.get("_leDebug", 0):
print("\nConsolidateSubPhrases", tree.get('passes'))
# Create subPhraseDict =
# {(words, tr, lePhraseNameGroup):
# list of subPhrases with those words, tr, and lePhraseNameGroup}
lePhraseNameGroups = self.lePhraseNameGroups(tree, component)
subPhraseDict = {}
for subPhrase in subPhraseList:
tr = subPhrase.getTimeRange()
words = subPhrase.get("words")
lePhraseNameGroup, firstName = self.getLePhraseNameGroup(
tree, component, lePhraseNameGroups, subPhrase.parent)
if words == "":
continue
if self.__dict__.get("_leDebug", 0):
print(subPhrase.getAncestor("name"))#, subPhrase.parent
print(" ", subPhrase.getAreaLabel(), tr, words)
print(" local effect", subPhrase.getAncestor('localEffect'))
self.addToDictionary(subPhraseDict, (words,tr,lePhraseNameGroup), subPhrase)
if self.__dict__.get('_leDebug', 0):
print("subPhraseDict", subPhraseDict)
# Check for duplicate subPhrases and consolidate them into one.
# Case 1: If the duplicates are all for the same areaLabel,
# set the areaLabel for the consolidated subPhrase to that.
# Case 2: If the duplicates are for a local effect and
# cover all possible local effect areas for their phrase,
# create a new phrase for component.getAreaLabel()
# with this subPhrase wording. Remove the local effect subPhrases.
# Case 3: If the duplicates are for a local effect
# and they cover a subset of the local effect areas,
# leave them alone except for removing any component.getAreaLabel()
# duplicate subPhrases.
compArea = component.getAreaLabel()
if self.__dict__.get('_leDebug',0):
print("\nDetermine Case for each set of duplicate phrases. compArea", compArea)
for (key, subPhrases) in subPhraseDict.items():
words, tr, lePhraseNameGroup = key
if len(subPhrases) <= 1:
continue
# We have duplicate subPhrases to consolidate.
# Gather the areaLabels for these duplicate subphrases
# and the possible localEffect Area labels
areaLabels, leAreas = self.gatherDupAreaLabels(
tree, component, compArea, subPhrases)
if self.__dict__.get('_leDebug',0):
print("\n", words)
print(" ", tr, len(subPhrases))
print("areaLabels, leAreas", areaLabels, leAreas)
# Determine the consolidated areaLabel
if len(areaLabels) == 1:
# Case 1
if self.__dict__.get('_leDebug',0):
print("CASE 1")
# Remove all but the first subPhrase
for subPhrase in subPhrases[1:]:
subPhrase.set('words', "")
else:
parent = subPhrases[0].parent
localEffect = subPhrases[0].getAncestor('localEffect')
if localEffect is None:
continue
# See if all local effect areas are covered
allAreasCovered = self.allLeAreasCovered(
tree, component, compArea, leAreas, areaLabels)
if allAreasCovered:
# Case 2: Consolidate
if self.__dict__.get('_leDebug',0):
print("CASE 2")
parent = subPhrases[0].parent
newNode = tree.copyPhrase(
parent, areaLabel=compArea,
copyAttrs=["doneList", "disabledSubkeys", "disabledElements",
"firstElement", "elementName", "elementInfoList",
"descriptor", "indentLabel"])
component.insertChild(parent, newNode)
newSubPhrase = subPhrases[0].copy()
newNode.set('childList', [newSubPhrase])
for subPhrase in subPhrases:
subPhrase.set('words', "")
else:
# Case 3: Throw out any compArea subPhrase and
# leave local effect ones alone for now
if self.__dict__.get('_leDebug',0):
print("CASE 3")
for subPhrase in subPhrases:
if subPhrase.getAreaLabel() == compArea:
subPhrase.set("words", "")
return self.DONE()
def gatherDupAreaLabels(self, tree, component, compArea, subPhrases):
areaLabels = []
leAreas = []
for subPhrase in subPhrases:
subArea = subPhrase.getAreaLabel()
if subArea not in areaLabels:
areaLabels.append(subArea)
#print "subArea", subArea, subPhrase.getAncestor('name')
if subArea != compArea:
localEffect = subPhrase.getAncestor("localEffect")
if localEffect is not None:
leAreas += self.getLeAreaList(tree, subPhrase, localEffect)
return areaLabels, leAreas
def allLeAreasCovered(self, tree, component, compArea, leAreas, areaLabels):
allAreasCovered = 1
if leAreas != []:
# Determine if the subPhrases cover all possible local effect areas
for leArea in leAreas:
if leArea.intersectFlag:
areaName = self.getIntersectName(
compArea, leArea.areaLabel)
elif leArea.areaLabel == "__Current__":
areaName = compArea
else:
areaName = leArea.areaLabel
#print "le Area Name", areaName
if areaName not in areaLabels:
allAreasCovered = 0
break
return allAreasCovered
def consolidateSubPhrases_trigger(self, tree, node):
# Checking to see if consolidateSubPhrases has been
# completed (if it is on the component methodList)
# This assumes that "node" is a phrase and its parent is
# a component.
parent = node.parent
if self.consolidateSubPhrases in parent.methodList and \
self.consolidateSubPhrases not in parent.doneList:
return 0
return 1
def consolidateLocalEffectPhrases(self, tree, node):
# Organize the local effect and non-local effect phrases.
# "node" can be a component or a compound phrase.
# Convert to embedded local effect phrases if appropriate.
# Apply the Local Effect thresholds:
# repeatingEmbedded_localEffect_threshold
# repeatingPhrase_localEffect_threshold
hasLE = 0
for phrase in node.get('childList'):
le = phrase.get('localEffect')
if le is not None:
hasLE = 1
break
if not hasLE:
# No local effect phrases so no work to to be done
return
if not self.incorporateNonLocalEffectPhrases(tree, node):
self.convertToEmbedded(tree, node)
self.orderLocalEffectPhrases(tree, node)
# Add later as an enhancement
def incorporateNonLocalEffectPhrases(self, tree, node):
# Try to incorporate non-qualified phrases
# If there is exactly one leArea group in the set of phrases
# AND this group is composed of intersect areas
# AND there is more than one local effect phrase
# AND the number of non-local effect phrases
# < repeatingPhrase_localEffect_threshold:
# Convert them to conjunctive local effect phrases
# (one for each intersect local effect area)
# return 1
# Else:
# return 0
# EXAMPLE:
# Instead of:
# Chance of thunderstorms in the morning.
# Windward...Cloudy...Rain likely...Chance of precipitation 70 percent.
# Leeward...Partly cloudy...Scattered showers...Chance of precipitation 30
# percent. Highs in the 40s. Winds 20 mph.
#
# We will produce:
# Windward...Cloudy....Rain likely...Chance of thunderstorms in the morning...
# Chance of precipitation 70 percent.
# Leeward...Partly cloudy...Scattered showers...Chance of thunderstorms in
# the morning...Chance of precipitation 30 percent. Highs in the 40s.
# Winds 20 mph.
return 0
# Replaces combineConjunctiveLocalEffects
def convertToEmbedded(self, tree, component):
#
# Converts conjunctive local effects to embedded if possible.
# For each leGroup:
# If number of possible embedded phrases
# < repeatingEmbedded_localEffect_threshold
# AND there are NO mandatory conjunctives:
# Replace conjunctive phrases with an embedded phrase.
#
if self.__dict__.get('_leDebug',0):
print("\nConvert to embedded")
lePhraseNameGroups = self.lePhraseNameGroups(tree, component)
lePhraseDict = self.createLePhraseDict(tree, component, lePhraseNameGroups)
if self.__dict__.get('_leDebug',0):
print("\nlePhraseDict", lePhraseDict)
repeatThreshold = self.repeatingEmbedded_localEffect_threshold(
tree, component)
qualifiersDict = self.createQualifiersDict(
tree, component, lePhraseDict, repeatThreshold)
if self.__dict__.get('_leDebug',0):
print("\nqualifiersDict", qualifiersDict)
self.createEmbeddedPhrases(
tree, component, lePhraseDict, qualifiersDict, repeatThreshold)
if self.__dict__.get('_leDebug',0):
print("\nlePhraseDict", lePhraseDict)
self.insertEmbeddedPhrases(
tree, component, lePhraseDict, lePhraseNameGroups)
def createLePhraseDict(self, tree, component, lePhraseNameGroups):
# Organize phrases in the component by lePhraseNameGroups. lePhraseDict:
# lePhraseNameGroup: {
# qualifiers: e.g. ["leeward", "windward"]
# phrases: [phrases]
# firstElementName: firstElement.name
# }
lePhraseDict = {}
for phrase in component.get("childList"):
if self.__dict__.get('_leDebug',0):
print("phrase", phrase.get('name'), phrase.get('words'))
print(" ", phrase.getAreaLabel())
print(" ", phrase.get('conjunctiveQualifier'))
print(" ", phrase.get('embeddedQualifier'))
localEffect = phrase.get('localEffect')
if localEffect is None:
continue
lePhraseNameGroup, firstName = self.getLePhraseNameGroup(
tree, component, lePhraseNameGroups, phrase)
qualifier = phrase.get('embeddedQualifier')
# Add the entry to the dictionary
if lePhraseNameGroup in lePhraseDict:
entry = lePhraseDict[lePhraseNameGroup]
if qualifier not in entry["qualifiers"]:
entry["qualifiers"].append(qualifier)
entry["phrases"].append(phrase)
else:
lePhraseDict[lePhraseNameGroup] = {
"qualifiers": [qualifier],
"phrases": [phrase],
"firstElementName": firstName,
}
return lePhraseDict
def createQualifiersDict(self, tree, component, lePhraseDict, repeatThreshold):
# Find out how many potential embedded phrases there are for each
# unique set of qualifiers.
# Create a qualifiersDict: qualifiers: count
# for lePhraseNameGroup in lePhraseDict:
# Can it be embedded i.e. are there NO mandatory conjunctives?
# If so, flag it as such and increase count for it's qualifier set.
#
qualifiersDict = {}
for (lePhraseNameGroup, nameDict) in lePhraseDict.items():
embedded = 1
phrases = nameDict["phrases"]
# For the phraseNameGroup to be embedded,
# all phrases in the phrase name group must qualify
# to be embedded i.e.the phrase must have just one subphrase
# which is non-empty and covers the phrase time range.
#
# Also, create areaCountDict to keep track of number of
# potentially embedded phrases per areaLabel:
# areaLabel:count
# Unless the phraseNameGroup has multiple phrases (e.g. sky, pop, wx),
# the count for each areaLabel will be 1.
# If the number of embedded phrases for any area exceeds the
# repeatThreshold, do not do an embedded phrase.
#
areaCountDict = {}
for phrase in phrases:
subPhrases = phrase.get('childList')
if len(subPhrases) != 1:
embedded = 0
break
subPhrase = subPhrases[0]
if subPhrase.get("words") == "" or \
subPhrase.getTimeRange() != phrase.getTimeRange():
embedded = 0
break
# Keep track of count for each area
areaLabel = phrase.getAreaLabel()
if areaLabel in areaCountDict:
areaCountDict[areaLabel] += 1
else:
areaCountDict[areaLabel] = 1
if areaCountDict[areaLabel] > repeatThreshold:
if self.__dict__.get('_leDebug',0):
print("areaCount exceeded", areaLabel, areaCountDict[areaLabel])
embedded = 0
break
if embedded:
qualifiers = nameDict["qualifiers"]
# Sort, removeDups and re-store qualifiers as a tuple
# We convert the qualifiers from a list to a tuple
# so that the qualifiers can be dictionary keys in qualifiersDict
qualifiers.sort()
qualifiers = self.removeDups(qualifiers)
tQualifiers = tuple(qualifiers)
nameDict["qualifiers"] = tQualifiers
if tQualifiers in qualifiersDict:
qualifiersDict[tQualifiers] += 1
else:
qualifiersDict[tQualifiers] = 1
nameDict["embedded"] = embedded
return qualifiersDict
def createEmbeddedPhrases(self, tree, component, lePhraseDict, qualifiersDict,
repeatThreshold):
# Convert to embedded if repeatingEmbedded_localEffect_threshold is not exceeded.
# for lePhraseNameGroup in lePhraseDict:
# If it's leGroup count < repeatingEmbedded_localEffect_threshold:
# convert to embedded and add to lePhraseNameGroup entry
#
for lePhraseNameGroup in lePhraseDict:
nameDict = lePhraseDict[lePhraseNameGroup]
if not nameDict["embedded"]:
continue
qualifiers = nameDict["qualifiers"]
count = qualifiersDict[qualifiers]
if count > repeatThreshold:
nameDict["embedded"] = 0
continue
# Create an embedded phrase
phrases = nameDict["phrases"]
nameDict["embeddedPhrase"] = self.makeEmbeddedFromConjunctiveLE(
tree, component, phrases)
def insertEmbeddedPhrases(self, tree, component, lePhraseDict, lePhraseNameGroups):
# Insert the embedded phrases at the proper places in the component
# phraseList and remove the associated conjunctive phrases.
# Embedded phrases are inserted at the site of the first
# associated conjunctive phrase.
#
newPhraseList = []
# doneList keeps track of those lePhraseNameGroups for which we've already
# inserted the embedded phrase
doneList = []
if self.__dict__.get('_leDebug',0):
print("\nStep")
for phrase in component.get("childList"):
if self.__dict__.get('_leDebug',0):
print("phrase", phrase.get('words'))
localEffect = phrase.get('localEffect')
if localEffect is None:
newPhraseList.append(phrase)
continue
# Determine lePhraseNameGroup for this phrase
lePhraseNameGroup, firstName = self.getLePhraseNameGroup(
tree, component, lePhraseNameGroups, phrase)
if self.__dict__.get('_leDebug',0):
print(" lePhraseNameGroup", lePhraseNameGroup)
nameDict = lePhraseDict[lePhraseNameGroup]
if not nameDict["embedded"]:
newPhraseList.append(phrase)
continue
if lePhraseNameGroup not in doneList:
# insert the embedded phrase
newPhraseList.append(nameDict["embeddedPhrase"])
doneList.append(lePhraseNameGroup)
component.set("childList", newPhraseList)
def getLePhraseNameGroup(self, tree, node, lePhraseNameGroups, phrase):
# Unless the group is an explicitly defined by lePhraseNameGroups,
# the name returned will be the <phraseName_firstElementName>.
# Make sure all the phrases for each lePhraseNameGroup have the
# same firstElement UNLESS the lePhraseNameGroup is listed explicitly
# in self.lePhraseNameGroups.
# If not, make a separate dictionary entry name for each firstElement.
# For example: In the FWF, the"dayOrNight_phrase" may have MaxT for some phrases
# and MinRH for others. We want to keep them separate when converting
# to embedded local effect phrases.
explicitGroup = 0
phraseName = phrase.get('name')
for group in lePhraseNameGroups:
if phraseName in group:
lePhraseNameGroup = group
explicitGroup = 1
# Check the firstElement
firstElement = phrase.get('firstElement')
if firstElement is None:
firstName = "None"
else:
firstName = firstElement.name
if not explicitGroup:
lePhraseNameGroup = phraseName + "_" + firstName
return lePhraseNameGroup, firstName
def orderLocalEffectPhrases(self, tree, node):
#
# Group all conjunctive local effect phrases
# for each local effect area together
# (at the location of the first occurrence).
#
# EXAMPLE:
# LEEWARD...SUNNY IN THE MORNING THEN BECOMING PARTLY SUNNY...SCATTERED SHOWERS.
# WINDWARD...MOSTLY CLOUDY WITH SCATTERED SHOWERS
#
# instead of:
# LEEWARD...SUNNY IN THE MORNING THEN BECOMING PARTLY SUNNY.
# WINDWARD...MOSTLY CLOUDY WITH SCATTERED SHOWERS.
# LEEWARD...SCATTERED SHOWERS.
#
phraseList = node.get("childList")
newList = []
doneAreas = []
#print "\nOrder LE phrases"
for phrase in phraseList:
localEffect = phrase.get("localEffect")
areaLabel = phrase.getAreaLabel()
embedded = phrase.get("embedded")
#print "PHRASE", phrase.get('words')
if localEffect is None or embedded:
newList.append(phrase)
else:
#print " phrase", phrase.get("name")
#print " conjqualifier", phrase.get("conjunctiveQualifier")
#print " area", areaLabel
if areaLabel in doneAreas:
# We already added this phrase to the newList
# as a Local Effect area
continue
newList.append(phrase)
# Gather the other phrases for this local effect area.
index = phraseList.index(phrase)
for p in phraseList[index+1:]:
p_localEffect = p.get("localEffect")
p_area = p.getAreaLabel()
if p_localEffect is not None and p_area == areaLabel:
newList.append(p)
doneAreas.append(areaLabel)
node.set("childList", newList)
def combineConjunctivePhrases(self, tree, component):
# Check for Conjunctive Local Effects and make sure
# we do not repeat the indented label.
#
# For example:
# LAL.................IN THE VALLEYS ...1.
# LAL.................IN THE MOUNTAINS...3 UNTIL 2400, THEN 1.
#
# Should be:
# LAL.................IN THE VALLEYS ...1.
# IN THE MOUNTAINS...3 UNTIL 2400, THEN 1.
#
for phrase in component.get("childList"):
words = phrase.get("words")
if words is None:
return
newChildList = []
lastName = ""
lastElement = ""
lastPhrase = None
for phrase in component.get("childList"):
words = phrase.get("words")
if words is None:
return
curName = phrase.get("name")
curElement = phrase.get("elementName")
if lastPhrase is None:
lastPhrase = phrase
lastName = curName
else:
# Look for a local effect phrase to be combined with
# lastPhrase
localEffect = phrase.get("localEffect")
embedded = phrase.get('embedded')
#print "phrase", curName, lastName, localEffect
if localEffect is not None and curName == lastName and \
embedded != 1 and curElement == lastElement:
# Combine this phrase words into last one
# Add conjunctive qualifier
#print "combining"
phraseWords = phrase.get("words")
qualifier = phrase.get("conjunctiveQualifier")
if qualifier is not None and qualifier != "":
phraseWords = qualifier + " "+ phraseWords
newWords = lastPhrase.get("words") + "." + \
phraseWords
lastPhrase.set("words", newWords)
else:
# Add phrase to new list
#print "switching"
newChildList.append(lastPhrase)
lastPhrase = phrase
lastName = curName
lastElement = curElement
# Clean up lastPhrase
if lastPhrase is not None:
newChildList.append(lastPhrase)
component.set("childList", newChildList)
def makeEmbeddedFromConjunctiveLE(self, tree, component, conjList):
# Make an embedded phrase from the list of conjunctive phrases
#
conjWords = ""
embeddedPhrase = tree.copyPhrase(
conjList[0], areaLabel = component.getAreaLabel(),
copyAttrs=["doneList", "descriptor", "indentLabel",
"embeddedDescriptor", "localEffect"])
descriptor = self.addSpace(embeddedPhrase.get("descriptor"))
if descriptor == "":
descriptor = self.addSpace(embeddedPhrase.get("embeddedDescriptor"))
#print "\nIn makeEmbeddedFromConjunctiveLE", descriptor
index = 0
localEffect = embeddedPhrase.get("localEffect")
fcst = ""
# Gather words
for phrase in conjList:
words = phrase.get("words")
if words == "":
continue
if not index == 0:
# Get connector
connector = localEffect.exceptionWords
fcst = fcst + connector
# Get rid of duplicate descriptor
if descriptor != "":
words = words.replace(descriptor, "")
# Local Effect Descriptor
areaWords = phrase.getAncestor("embeddedQualifier")
if areaWords is None:
areaWords = ""
areaWords = self.addSpace(areaWords, "leading")
fcst = fcst + words + areaWords
index = index + 1
embeddedPhrase.set("words", fcst)
embeddedPhrase.set('embedded', 1)
self.postProcessPhrase(tree, embeddedPhrase)
#print "embedded words", embeddedPhrase.get('words')
return embeddedPhrase
def qualifyWords(self, node, words, qualifierName, lastQualifier,
lastPhrase, makeSentence=1):
# Qualifies words with local effect qualifiers
# Also, if makeSentence==1, makes the words into a sentence
# when appropriate.
# Returns the modified words and the qualifier (if any)
#
# Logic:
# If empty words, skip.
# If no qualifier:
# if makeSentence:
# makeSentence and return words and lastQualifier
# If there is a qualifier:
# Handle a new qualifier.
# If qualifier is new and non-empty:
# Add the qualifier and ellipses to beginning of words
# Handle a continuation: If the next phrase will be qualified
# with the same qualifier,
# Add ellipses to the end of the words. In this case,
# we will not add a period to the end of the words
# when making a sentence.
# if makeSentence, make the words into a sentence with or without
# a period at the end.
# return words and qualifier
#
qualifier = node.get(qualifierName)
#print "\nQualify words: qualifier, lastQualifier, words", qualifier, lastQualifier, words
if words == "":
return words, lastQualifier
addPeriod = 1
if qualifier is not None:
if qualifier != lastQualifier and qualifier != "":
words = qualifier + ", " + words
next = self.getNext_nonEmpty(node, "words")
if next is not None:
nextQualifier = next.get(qualifierName)
#print "nextQualifier, qualifier", nextQualifier, "X", qualifier, "X", words
if nextQualifier == qualifier:
addPeriod = 0
words = words + ", "
if makeSentence:
words = self.sentence(words, addPeriod)
#print "returning", words
return words, qualifier
def getNext_nonEmpty(self, node, attrName):
next = node.getNext()
while 1:
if next is None:
break
val = next.get(attrName)
if val is not None and val != "":
break
next = next.getNext()
return next
def namesEqual(self, name1, name2):
weatherPhrases = ["skyPopWx_phrase", "weather_phrase"]
if name1 == name2 or \
(name1 in weatherPhrases and name2 in weatherPhrases):
namesEqual = 1
else:
namesEqual = 0
return namesEqual
def wordWrap(self, tree, component):
# Wrap the component.words()
compWords = component.get("words")
if compWords is None:
return
compWords = self.endline(compWords, tree.get("lineLength"))
return self.setWords(component, compWords)
def createLabel(self, tree, node, timeRange, issuanceInfo, currentLocalTime, shift, index=0):
# Make a label given the timeRange in GMT and the shift to
# convert it to local time. currentLocalTime can be used to
# compare to current day.
# NOTE: If you make changes to this method, change the SAF_Overrides
# file as it is overridden there.
if timeRange.duration() < 3600:
return ""
if index == 0:
try:
label = issuanceInfo.period1Label()
if label != "":
return label
except:
pass
try:
today = issuanceInfo.todayFlag()
except:
today = 1
try:
useHolidays = self._useHolidays
except:
useHolidays = 1
nextDay24HourLabel = self.nextDay24HourLabel_flag(tree, node)
splitDay24HourLabel = self.splitDay24HourLabel_flag(tree, node)
label = self.getWeekday(timeRange, holidays=useHolidays, shiftToLocal=1,
labelType="CapitalWithPeriod", today=today,
tomorrow=0, nextDay24HourLabel=nextDay24HourLabel,
splitDay24HourLabel=splitDay24HourLabel)
return label
# Ordering phrases
def orderPhrases(self, tree, component):
# Reorder highs and lows based on start period
reorderList = []
timeRange=component.getTimeRange()
areaLabel= component.getAreaLabel()
if timeRange.duration() >= 24 * 3600:
startTR = TimeRange.TimeRange(timeRange.startTime(),
timeRange.startTime() + (12 * 3600))
dayNight = self.getPeriod(startTR, 1)
if dayNight == self.NIGHTTIME():
reorderList.append(("lows_phrase", "highs_phrase"))
reorderList.append(("lows_range_phrase", "highs_range_phrase"))
for phrase1, phrase2 in reorderList:
self.moveAbove(tree, component, phrase1, phrase2)
return self.DONE()
def moveAbove(self, tree, component, phrase1, phrase2):
# Move the phrase phrase1 above phrase2 in the
# component list
# Find Phrase to move
savedPhrase = ""
for phrase in component.childList:
name = phrase.get("name")
if name == phrase1:
savedPhrase = phrase
break
# Create new phrase list inserting savedPhrase in
# the new location
newPhraseList = []
for phrase in component.childList:
name = phrase.get("name")
if name == phrase1:
continue
if name == phrase2 and savedPhrase != "":
newPhraseList.append(savedPhrase)
newPhraseList.append(phrase)
component.set("childList", newPhraseList)
return self.DONE()
# Phrase Level
def subPhraseSetUp(self, tree, phrase, elementInfoList, connectorMethod, resolution=None):
# Set up subPhrase nodes and "statDict" statistics for multiple elements.
# The temporal resolution of the first element determines the number of subPhrases created.
# If the elementInfoList is empty, one empty subPhrase is created with an empty "statDict".
# If there is noData for the first element, one empty subPhrase is created.
#
# Sets up the following attributes:
# Phrase Level
# descriptor -- based on phrase_descriptor for the first element
# connectorMethod -- based on setUp arguments
# elementInfoList -- adds "outUnits" to each elementInfo in list
# firstElement -- elementInfo for first in elementInfoList
# elementName -- elementName for first in elementInfoList
#
# SubPhrase Level
# elementName -- elementName for first in elementInfoList
# timeRange
# statDict -- entries for all elements in elementInfoList
#
timeRange = phrase.getTimeRange()
areaLabel = phrase.getAreaLabel()
statDictList = []
if len(elementInfoList) < 1:
# Make phrase with one empty subphrase
self.makeEmptySubPhrase(tree, phrase, None)
return self.DONE()
# Make sub ranges based on first element
first = elementInfoList[0]
elementName = first.name
first.outUnits = self.element_outUnits(tree, phrase, first.name, first.name)
# Check to see if the timeRange is great enough to collapse sub-phrases
# automatically
hours = self.collapseSubPhrase_hours(tree, phrase, elementName, elementName)
if timeRange.duration() > hours * 3600:
first.mergeMethod = self.mergeMethod(tree, phrase, elementName, elementName)
#print "Getting first", first.name, timeRange, areaLabel
# Check to see if we are requesting a particular time resolution
if resolution is not None:
# Create sub-ranges with this resolution and provide list
# of stats for each time range
subRanges = self.divideRange(timeRange, resolution)
stats = []
for subRange in subRanges:
subStats = tree.stats.get(first.name, subRange, areaLabel,
first.statLabel, first.mergeMethod)
stats.append((subStats, subRange))
else:
stats = tree.stats.get(first.name, timeRange, areaLabel,
first.statLabel, first.mergeMethod)
#print "stats", stats
statsByRange = self.makeRangeStats(tree, first.dataType, stats, timeRange)
#print "statsByRange", first.name, statsByRange
# Case of no data for first element
if statsByRange is None:
self.makeEmptySubPhrase(tree, phrase, first)
return self.setWords(phrase, "")
phrase.set("emptyPhrase", 0)
# Set up descriptor and connector
if phrase.get("descriptor") is None:
descriptor = self.phrase_descriptor(tree, phrase, first.name, first.name)
phrase.set("descriptor", descriptor)
phrase.set("connectorMethod", connectorMethod)
# Create sub phrases based on first element
# Create subPhrase List of (statDict, subRange) pairs
# This list will be added to by each element
subPhraseList = []
for stats, subRange in statsByRange:
#print "stats going into statDict", stats
subPhraseList.append(({first.name:stats}, subRange))
for subPhrase in subPhraseList:
# Add each additional element to the sub range statDict
statDict, subRange = subPhrase
for elementInfo in elementInfoList[1:]:
name = elementInfo.name
elementInfo.outUnits = self.element_outUnits(tree, subPhrase, name, name)
#print "Getting sub stats", elementInfo
stats = tree.stats.get( elementInfo.name, subRange, areaLabel,
elementInfo.statLabel, elementInfo.mergeMethod)
# Add to subPhrase statDict for each subPhrase
statDict[name] = stats
# Make SubPhrase children
subPhraseMethods = phrase.get("subPhraseMethods")
childList = []
#print "subPhraseList", subPhraseList
for statDict, subRange in subPhraseList:
subPhrase = tree.makeNode([], subPhraseMethods, phrase)
#print "statDict", statDict
subPhrase.set("statDict", statDict)
subPhrase.set("timeRange", subRange)
subPhrase.set("changeFlag", 0)
subPhrase.set("elementName", elementName)
childList.append(subPhrase)
#print "Setting childList"
phrase.set("firstElement", first)
phrase.set("elementInfoList", elementInfoList)
phrase.set("elementName", elementName)
phrase.set("childList", childList)
if childList == []:
self.setWords(phrase, "")
#print "AFTER SET-UP"
#if elementName == "Wx":
# print phrase.printNode(phrase)
return self.DONE()
def makeEmptySubPhrase(self, tree, phrase, firstElement):
phrase.set("emptyPhrase", 1)
phrase.set("firstElement", firstElement)
phrase.set("connectorMethod", None)
subPhraseMethods = phrase.get("subPhraseMethods")
subPhrase = tree.makeNode([], subPhraseMethods, phrase)
if firstElement is None:
phrase.set("elementInfoList", [])
phrase.set("elementName", None)
subPhrase.set("elementName", None)
subPhrase.set("statDict", {})
else:
phrase.set("elementInfoList", [firstElement])
phrase.set("elementName", firstElement.name)
subPhrase.set("elementName", firstElement.name)
subPhrase.set("statDict", {firstElement.name:None})
subPhrase.set("timeRange", phrase.getTimeRange())
subPhrase.set("changeFlag", 0)
phrase.set("childList", [subPhrase])
### Checking for differences between sub-phrases
def checkForDifferences(self, tree, node, elementInfo, magOnly=0, dirOnly=0):
# Return 1 if there are differences among the subPhrase values
# for the given element.
# If VECTOR and magOnly==1, only the magnitude is checked.
# If VECTOR and dirOnly==1, only the direction is checked.
# If no data, return 1 as well
elementName = elementInfo.name
dataType = elementInfo.dataType
statList = self.getSubStats(node, elementName)
if len(statList) > 1:
# Check each subphrase against the first
# Return when a difference is found
if dataType == self.SCALAR():
if statList[0] is None:
return 1
value = self.getValue(statList[0], "MinMax")
min1, max1 = value
for statVal in statList[1:]:
if statVal is None:
return 1
statVal = self.getValue(statVal, "MinMax")
min2, max2 = statVal
differenceFlag = self.checkScalarDifference(
tree, node, elementName, min1, max1, min2, max2)
if differenceFlag:
return 1
return 0
if dataType == self.VECTOR():
if statList[0] is None:
return 1
mag, dir1 = self.getValue(statList[0], "MinMax", self.VECTOR())
min1, max1 = mag
for stats in statList[1:]:
if stats is None:
return 1
statMag, dir2 = self.getValue(stats, "MinMax", self.VECTOR())
min2, max2 = statMag
differenceFlag = self.checkVectorDifference(
tree, node, elementName, min1, max1, dir1, min2, max2, dir2, magOnly, dirOnly)
if differenceFlag:
return 1
return 0
if dataType == self.WEATHER() or dataType == self.DISCRETE():
wx = statList[0]
for wxVal in statList[1:]:
if wxVal is None or wx is None:
return 1
if wxVal != wx:
return 1
return 0
def checkScalarDifference(self, tree, node, elementName, min1, max1, min2, max2):
# Return 1 if the min/max pairs show a difference
# First see if both are below null threshold
threshold = self.null_nlValue(tree, node, elementName, elementName)
threshold1 = self.nlValue(threshold, max1)
threshold2 = self.nlValue(threshold, max2)
if max1 < threshold1 and max2 < threshold2:
return 0
# See if only one is below null threshold
if self.null_alwaysDistinct_flag(tree, node, elementName, elementName):
if max1 < threshold1 or max2 < threshold2:
return 1
# If one set of min/max has only one value,
# and that value matches the min or
# max of the other set, show no difference.
if min1 == max1 and (min1==min2 or max1==max2):
return 0
if min2 == max2 and (min1==min2 or max1==max2):
return 0
# Compare mins and compare maxs
diff_nlValue = self.scalar_difference_nlValue(tree, node, elementName, elementName)
diff_min = self.nlValue(diff_nlValue, min(min1, min2))
diff_max = self.nlValue(diff_nlValue, max(max1, max2))
if abs(min1-min2) < diff_min and abs(max1-max2) < diff_max:
return 0
return 1
def checkVectorDifference(self, tree, node, elementName,
min1, max1, dir1, min2, max2, dir2, magOnly=0, dirOnly=0):
# Return 1 if the min/max/dir pairs show a difference
#print "Checking", elementName, min1, max2, dir1, min2, max2, dir2, magOnly
if magOnly == 0 or dirOnly == 1:
# DR_18632
# if self.direction_difference(dir1, dir2) >= self.vector_dir_difference(
# tree, node, elementName, elementName):
# return 1
diff = self.direction_difference(dir1, dir2)
nlValue_dict = self.vector_dir_difference_nlValue(
tree, node, elementName, elementName)
threshold_min = self.nlValue(nlValue_dict, min(min1, min2))
threshold_max = self.nlValue(nlValue_dict, max(max1, max2))
if diff >= min(threshold_min, threshold_max):
return 1
if dirOnly == 1:
return 0
# Check magnitude
# Compare mins and maxs
# Add special check for marine wording:
# This will prevent:
# NORTHWEST GALES TO 35 KNOTS RISING TO GALES TO 35 KNOTS AFTER MIDNIGHT.
# And will facilitate:
# "N WINDS 30 KT IN THE MORNING INCREASING TO
# GALES TO 35 KT EARLY IN THE AFTERNOON, THEN
# EASING TO 30 KT LATE IN THE AFTERNOON."
if elementName == "Wind":
if self.marine_wind_combining_flag(tree, node):
if max1 > 30 or max2 > 30:
# Check for both within the same warning thresholds
warnThreshold1 = self.getWarnThreshold(max1)
warnThreshold2 = self.getWarnThreshold(max2)
if warnThreshold1 == warnThreshold2:
return 0
else:
return 1
# First see if both are below null threshold
threshold = self.null_nlValue(tree, node, elementName, elementName)
threshold1 = self.nlValue(threshold, max1)
threshold2 = self.nlValue(threshold, max2)
if max1 < threshold1 and max2 < threshold2:
return 0
# See if only one is below null threshold
if self.null_alwaysDistinct_flag(tree, node, elementName, elementName):
if max1 < threshold1 or max2 < threshold2:
return 1
# If one set of min/max has only one value,
# and that value matches the min or
# max of the other set, show no difference.
if min1 == max1 and (min1==min2 or max1==max2):
return 0
if min2 == max2 and (min1==min2 or max1==max2):
return 0
# Check for magnitude differences
mag_nlValue = self.vector_mag_difference_nlValue(
tree, node, elementName, elementName)
mag_diff_min = self.nlValue(mag_nlValue, min(min1, min2))
mag_diff_max = self.nlValue(mag_nlValue, max(max1, max2))
if abs(min1-min2) >= mag_diff_min or abs(max1-max2) >= mag_diff_max:
return 1
return 0
def getWarnThreshold(self, max):
if max >= 65:
return 3
elif max > 45:
return 2
elif max > 30:
return 1
else:
return 0
def maskSubkeys(self, subkeyList, intensity=None):
# Make a new weather key masking the given intensity with the given value
if intensity is not None:
newkeyList = []
for subkey in subkeyList:
newSubkey = WeatherSubKey.weatherSubKey(self._argDict['site'], subkey.coverage(), subkey.wxType(), intensity,
subkey.visibility(), subkey.attributes())
newkeyList.append(newSubkey)
subkeyList = newkeyList
return subkeyList
def checkWeatherSimilarity(self, tree, node, rankList1, rankList2,
node1=None, node2=None, tr1=None, tr2=None,
al1=None, al2=None):
### FIXES BUG BY FORCING ATTRIBUTES CHECK WHEN CHECKING FOR SIMILAR WX
# Return 0 if the two sets of subkeys in the rankLists are significantly
# different
# If the keys can be considered similar:
# Return 1 if the first set of keys presides
# Return 2 if the second set of keys presides
# Return a new aggregated rankList if there are multiple subkeys in the
# rankLists AND they are similar.
#
# Optional nodes and time ranges may be supplied. These are used for
# accessing PoP stats. All are necessary since "similar_diurnal" does
# comparisons for various time ranges and local effects does comparisons
# for various areas.
#print "\nCheckWxSimilarity"
# The ranks are available, but not currently used
stats1 = self.getSubkeys(rankList1)
stats2 = self.getSubkeys(rankList2)
# Sort for comparison
stats1.sort(key=self.rankedSortOrder)
stats2.sort(key=self.rankedSortOrder)
diff = []
for element in stats1:
test = 1
for el in stats2:
if str(element) == str(el):
test = 0
if test and str(element) not in diff:
diff.append(str(element))
for element in stats2:
test = 1
for el in stats1:
if str(element) == str(el):
test = 0
if test and str(element) not in diff:
diff.append(str(element))
if len(diff) == 0:
return 1
if stats1 == stats2:
#print 'checkWx return 1'
return 1
# Check for equal length of statistics
if len(stats1) == len(stats2):
# If there is only one subkey to worry about
if len(stats1) == 1:
# If the types, intensities, and coverages are similar
if self.similarWxTypes(tree, node, stats1[0], stats2[0]):
if self.similarIntensities(tree, node, stats1[0], stats2[0]):
if self.similarAttributes(tree, node, stats1[0], stats2[0]):
flag = self.similarCoverages(tree, node, stats1[0], stats2[0])
if flag > 0:
#print "returning flag", flag
return flag
else:
# Different wxTypes are not similar
#print "returning diff wxTypes 0"
return 0
# Node can turn off this check.
# Some phrases (severeWeather_phrase, heavyPrecip_phrase, heavyRain_phrase)
# are checking intensities, so we don't want to loose them
if node.getAncestor("noIntensityCombining") != 1:
# Make new subkeys that all have the same intensity
stats1 = self.maskSubkeys(stats1, intensity="-")
stats2 = self.maskSubkeys(stats2, intensity="-")
if stats1 == stats2:
#print 'checkWx return 1'
return 1
# Handle case of len(stats) > 1
if len(stats1) > 1:
return self.checkSubkeysSimilarity(
tree, node, rankList1, rankList2, node1, node2, tr1, tr2, al1, al2)
# Check the PoP.
# If low for both time periods and areas
# AND there is no non-precip Wx
# then we can assume the Wx is the same
if node1 is None:
node1 = node
if node2 is None:
node2 = node
if tr1 is None:
tr1 = node1.getTimeRange()
if tr2 is None:
tr2 = node2.getTimeRange()
if al1 is None:
al1 = node1.getAreaLabel()
if al2 is None:
al2 = node2.getAreaLabel()
popstats1 = self.matchToWx(tree, node1, "PoP", tr1, al1)
popstats2 = self.matchToWx(tree, node2, "PoP", tr2, al2)
#print "popstats", popstats1, popstats2
if ((popstats1 is None or popstats1 < self.pop_wx_lower_threshold(tree, node1)) and
(popstats2 is None or popstats2 < self.pop_wx_lower_threshold(tree, node2))):
for subkey in stats1:
if not self.pop_related_flag(tree, node1, subkey):
return 0
for subkey in stats2:
if not self.pop_related_flag(tree, node2, subkey):
return 0
return 1
#print 'checkWx return 0'
return 0
def checkSubkeysSimilarity(self, tree, node, rankList1, rankList2,
node1, node2, tr1, tr2, al1, al2):
# Return 0 if the two sets of subkeys in the rankLists are significantly
# different
# Otherwise, return a new rankList of the combined subkeys and ranks
# sorted in rank order
#
# We combine if:
# The set of wxTypes in rankList1 is equal to the set of wxTypes in rankList2 AND
# Each wxType individually can be combined i.e. they have similar coverages
#print "\nCheckSubkeysSimilarity"
# Sort ranklists by wxType
list1 = self.removeNoWx(rankList1)
list2 = self.removeNoWx(rankList2)
list1.sort(key=self.rankedWxTypeOrder)
list2.sort(key=self.rankedWxTypeOrder)
#print "rankList1, rankList2", rankList1, rankList2
newRankList = []
for i in range(len(list1)):
subkey1, rank1 = list1[i]
wxType1 = subkey1.wxType()
subkey2, rank2 = list2[i]
wxType2 = subkey2.wxType()
if not wxType1 == wxType2:
# We cannot combine
return 0
# See of the wxTypes have similar coverages
similarFlag = self.checkWeatherSimilarity(
tree, node, [list1[i]],[list2[i]], node1, node2, tr1, tr2, al1, al2)
if similarFlag == 0:
return 0
newRank = int((rank1 + rank2)/2.0)
newSubkey = self.makeAggregateSubkey(subkey1, rank1, subkey2, rank2)
newRankList.append((newSubkey, newRank))
## if similarFlag == 1:
## newRankList.append((subkey1, newRank))
## else:
## newRankList.append((subkey2, newRank))
# Sort newRankList
newRankList.sort(key=self.rankedSortOrder)
#print "returning", newRankList
return newRankList
def removeNoWx(self, rankList):
newList = []
for subkey, rank in rankList:
if subkey.wxType() == "<NoWx>":
continue
newList.append((subkey, rank))
return newList
def similarWxTypes(self, tree, node, subkey1, subkey2):
# If wxTypes should be similar, return 1
# else return 0
wxType1 = subkey1.wxType()
wxType2 = subkey2.wxType()
inten1 = subkey1.intensity()
inten2 = subkey2.intensity()
# Take care of sprinkles and flurries
if wxType1 == wxType2 and wxType1 in ["RW", "SW"]:
if inten1 != inten2 and (inten1 == "--" or inten2 == "--"):
return 0
if wxType1 == wxType2:
return 1
return 0
def similarIntensities(self, tree, node, subkey1, subkey2):
intenList = ['<NoInten>', '-', 'm']
# If intensities are close enough
inten1 = subkey1.intensity()
inten2 = subkey2.intensity()
if (inten1==inten2 or
(inten1 in intenList and inten2 in intenList)):
return 1
return 0
def similarCoverages(self, tree, node, subkey1, subkey2):
# Return 0 if coverages of subkey1 and subkey2 are significantly
# different
# Return 1 if coverages are similar and the coverage of subkey1
# is dominant.
# Return 2 if coverages are similar and the coverage of subkey2
# is dominant.
cov1 = subkey1.coverage()
cov2 = subkey2.coverage()
for coverageList in self.similarCoverageLists(tree, node, subkey1, subkey2):
if (cov1 in coverageList and cov2 in coverageList):
index1 = coverageList.index(cov1)
index2 = coverageList.index(cov2)
if index1 >= index2:
#print 'checkWx return 1 - use subkey1'
return 1
else:
#print 'checkWx return 2 - use subkey2'
return 2
return 0
def similarAttributeLists(self):
# Lists weather attributes that can be combined or considered equal.
# These lists are examined when producing rankLists,
# combining sub-phrases, and
# determining if there is a local effect to report.
# Used by
# PhraseBuilder:checkWeatherSimilarity
# SampleAnalysis: getDominantValues
return [
["DmgW", "GW"],
["LgA", "SmA"],
]
def similarAttributes(self, tree, node, subkey1, subkey2):
# If weather attributes are similar, return 1; otherwise return 0
attrs1 = subkey1.attributes()
attrs2 = subkey2.attributes()
attrs1 = self.removeSpecialAttributes(attrs1)
attrs2 = self.removeSpecialAttributes(attrs2)
attrs1.sort()
attrs2.sort()
# If the lists are equal, they are similar.
if attrs1 == attrs2:
return True
# Otherwise, check that each attribute for subkey1 matches
# an attribute for subkey2 and vice versa
if self.matchAttrs(attrs1, attrs2) and self.matchAttrs(attrs2, attrs1):
return True
else:
return False
def removeSpecialAttributes(self, attrs):
rv = []
for attr in attrs:
if attr not in ["MX", "OR", "Mention", "Primary"]:
rv.append(attr)
return rv
def matchAttrs(self, attrs1, attrs2):
for attr1 in attrs1:
if not self.checkAttrs(attr1, attrs2):
return False
return True
def checkAttrs(self, attr1, attrs2):
# Check to see if there is a match for attr1 in attrs2
# A "match" is equality OR there exists an attr2 in attrs2
# such that both attr1 and attr2 are in one of the similarAttrsLists
# E.g., "GW" is attr1, "DmgW" is attr2 and attrList is ["GW","DmgW"]
if attr1 in attrs2:
return True
for attrList in self.similarAttributeLists():
if attr1 in attrList:
for attr2 in attrs2:
if attr2 in attrList:
return True
return False
# Consolidation
def consolidatePhrase(self, tree, phrase):
# See if ready to process
if not self.phrase_trigger(tree, phrase, setUpOnly=1):
return
# Separate out primary elements that are constant throughout phrase
elementInfoList = phrase.get("elementInfoList")
if elementInfoList is None or len(elementInfoList) <= 1:
return self.DONE()
subPhrases = phrase.get("childList")
if len(subPhrases) <= 1:
return self.DONE()
first = 1
constants = []
nonConstants = []
constantFirst = 0
disabled = phrase.get("disabledElements", [])
if disabled is None:
disabled = []
for elementInfo in elementInfoList:
if elementInfo.name in disabled:
continue
if elementInfo.primary:
# Primary elements
diffFlag = self.checkForDifferences(tree, phrase, elementInfo)
#print "element", elementInfo.name, diffFlag
if diffFlag == 0:
constants.append(elementInfo)
# If first element is in constant list,
# make that the first phrase when split
# else, make it the second phrase
if first:
constantFirst = 1
else:
nonConstants.append(elementInfo)
else:
# Secondary elements remain with first element
if constantFirst:
constants.append(elementInfo)
else:
nonConstants.append(elementInfo)
first = 0
# Split off elements that are not in the same list as the first element
#print "Constants", constantFirst
#for eleInfo in constants:
# print eleInfo.name
#print "NonConstants"
#for eleInfo in nonConstants:
# print eleInfo.name
if constantFirst:
splitElements = nonConstants
curElements = constants
else:
splitElements = constants
curElements = nonConstants
length = len(splitElements)
if length > 0 and length < len(elementInfoList):
self.splitPhrase(tree, phrase, curElements, splitElements)
return self.DONE()
def splitPhrase(self, tree, phrase, curElements, splitElements):
# For each element in splitElements e.g. Swell2 or WindGust:
# set the current phrase disabledElements
# (to turn them off for the original phrase)
# add a new phrase for the split element
# For each element left in the current phrase e.g. Swell or Wind,
# set the new phrase disabled elements
# (to turn them off in the new phrase)
#
disabledElements = []
newDis = []
for elementInfo in curElements:
newDis.append(elementInfo.name)
for elementInfo in splitElements:
disabledElements.append(elementInfo.name)
newPhrase = tree.addPhraseDef(phrase, elementInfo.phraseDef)
newPhrase.set("disabledElements", newDis)
currentNone = phrase.getAncestor("disabledElements")
if currentNone is not None:
phrase.set("disabledElements", disabledElements + currentNone)
else:
phrase.set("disabledElements", disabledElements)
for key in ["spawnedWxPhrases", "conjunctiveQualifier",
"embeddedQualifier", "localEffect", "localEffectsList",
"firstElement", "elementName", "elementInfoList"]:
#"descriptor", "indentLabel"]:
newPhrase.set(key, phrase.get(key))
#print "\nSplitPhrase: New phrase", newPhrase, newDis
#print "Current phrase disabled", phrase, phrase.get("disabledElements")
def consolidateDirection(self, tree, phrase):
# See if ready to process
if not self.phrase_trigger(tree, phrase, setUpOnly=1):
return
# If vector direction is progressive and mags are similar,
# use only first and last subPhrases
elementInfoList = phrase.get("elementInfoList")
if elementInfoList is None or len(elementInfoList) < 1:
return self.DONE()
subPhrases = phrase.get("childList")
if len(subPhrases) <= 1:
return self.DONE()
firstElement = phrase.get("firstElement")
diffFlag = self.checkForDifferences(tree, phrase, firstElement, magOnly=1)
if diffFlag == 0:
vectorStats = self.getSubStats(phrase, firstElement.name)
dirList = []
for mag, dir in vectorStats:
dirList.append(dir)
progression = self.checkProgression(dirList)
if progression:
childList = phrase.get("childList")
new = []
new.append(childList[0])
new.append(childList[len(childList)-1])
phrase.set("childList", new)
return self.DONE()
def consolidateTrends(self, tree, phrase):
# See if we need to ignore this method
if self.ignoreTrends(tree, phrase):
return self.DONE()
# See if ready to process
if not self.phrase_trigger(tree, phrase, setUpOnly=1):
return
# If there is a progression of magnitudes,
# use only first and last subPhrases with no time descriptor
elementInfoList = phrase.get("elementInfoList")
if elementInfoList is None or len(elementInfoList) < 1:
return self.DONE()
subPhrases = phrase.get("childList")
if len(subPhrases) <= 2:
return self.DONE()
firstElement = phrase.get("firstElement")
# If Vector, make sure directions are the same
diffFlag = 0
dataType = firstElement.dataType
if dataType == self.VECTOR():
diffFlag = self.checkForDifferences(tree, phrase, firstElement, dirOnly=1)
# Check for an increasing or decreasing magnitude progression
if diffFlag == 0:
statList = self.getSubStats(phrase, firstElement.name)
trend = self.checkTrend(statList, dataType)
# If trend, take first and last children only
if trend:
childList = phrase.get("childList")
new = []
new.append(childList[0])
new.append(childList[len(childList)-1])
phrase.set("childList", new)
# Turn off time descriptors
phrase.set("noTimeDescriptors", 1)
return self.DONE()
def ignoreTrends(self, tree, node):
if node.get('name') in ["windChill_phrase", "windBased_windChill_phrase",
"heatIndex_phrase", "apparentT_phrase"]:
return 1
else:
return 0
def checkProgression(self, dirList):
# make a list of differences
diffList = []
for i in range(1, len(dirList)):
diff = dirList[i] - dirList[i-1] # calc difference
# normalize the difference to remove the 359 -> 0 effect
if diff > 180:
diff = diff - 360
elif diff < -180:
diff = diff + 360
diffList.append(diff)
minVal = min(diffList)
maxVal = max(diffList)
# any diffs >= 90 not allowed
if maxVal >= 90 or minVal <= -90:
return 0
# see if all the diff are of the same sign, if not return 0
if minVal * maxVal < 0:
return 0
else:
return 1
def checkTrend(self, statList, dataType):
# check to see if increasing/decreasing values
lastMax = None
trend = None
for stats in statList:
if dataType == self.VECTOR():
stats, dir = stats
min, max = self.getValue(stats, "MinMax")
if lastMax is None:
lastMax = max
elif trend is None:
trend = lastMax > max
lastMax = max
else:
# Test for an decreasing trend
if trend == 1 and lastMax > max:
lastMax = max
continue
# Test for an increasing trend
if trend == 0 and lastMax <= max:
lastMax = max
continue
return 0
return 1
def chooseMostImportant(self, tree, phrase):
# If there is more than 1 sub-phrase AND mostImportant_dict
# is set, report only the "Min" or "Max" sub-phrase using
# the "mostImportant_descriptor"
if not self.phrase_trigger(tree, phrase, setUpOnly=1):
return
elementInfoList = phrase.get("elementInfoList")
if elementInfoList is None or len(elementInfoList) < 1:
return self.DONE()
subPhrases = phrase.get("childList")
if len(subPhrases) <= 1:
return self.DONE()
elementName = phrase.get("elementName")
mostImportant = self.mostImportant(tree, phrase, elementName, elementName)
if mostImportant is None:
return self.DONE()
# Find the index of the sub-phrase with the Min or Max value
firstElement = phrase.get("firstElement")
statList = self.getSubStats(phrase, elementName)
dataType = firstElement.dataType
for i in range(len(statList)):
if dataType == self.VECTOR():
stats, dir = statList[i]
else:
stats = statList[i]
min, max = self.getValue(stats, "MinMax")
if i == 0:
if mostImportant == "Min":
importantVal = min
else:
importantVal = max
importantIndex = 0
else:
if mostImportant == "Min":
if min < importantVal:
importantVal = min
importantIndex = i
else:
if max > importantVal:
importantVal = max
importantIndex = i
# Null out the other sub-phrases
for i in range(len(subPhrases)):
if i != importantIndex:
statDict = subPhrases[i].getStatDict()
statDict[elementName] = None
#print "subPhrase", subPhrases[i].getTimeRange()
# Set up the mostImportant_descriptor
descriptor = self.mostImportant_descriptor(
tree, phrase, elementName, elementName)
if descriptor is not None:
phrase.set("descriptor", descriptor)
return self.DONE()
def getSubStats(self, phrase, elementName):
# Return a list of stats for the subPhrases
statList = []
for subPhrase in phrase.childList:
statDict = subPhrase.getStatDict()
if statDict is None:
continue
statList.append(statDict[elementName])
return statList
def splitWxPhrase(self, tree, node, disabledSubkeys1, disabledSubkeys2, doneList,
newPhraseDef=None):
# Set disableSubkeys1 for original node
# Create a new node with disabledSubkeys2
# Set new phrase doneList using "doneList"
# Add to new phrase according to newPhraseDef if provided,
# otherwise, duplicate current node
# Make sure to propagate "disabledSubkeys" and "spawnedWxPhrases"
# from the original node to the new node
#print "\nSplit Wx Phrase: original node", node.get("name"), node.getAreaLabel()
#print " ", node
#import traceback
#traceback.print_stack(limit=3)
#print " localEffect", node.get('localEffect')
#print " parent", node.parent
#tree.printNode(node)
disabled = node.getAncestor("disabledSubkeys")
if disabled is None:
disabled = []
disabledSubkeys1 = disabledSubkeys1 + disabled
node.set("disabledSubkeys", disabledSubkeys1)
if newPhraseDef is None:
newPhrase = tree.addPhrase(node)
disabledSubkeys2 = disabledSubkeys2 + disabled
else:
newPhrase = tree.addPhraseDef(node, newPhraseDef)
newPhrase.set("disabledSubkeys", disabledSubkeys2)
newPhrase.set("doneList", doneList)
#print " disabled", node.get("disabledSubkeys")
#print "new node", newPhrase, newPhrase.get("name")
#print " parent", newPhrase.parent
for key in ["spawnedWxPhrases", "conjunctiveQualifier",
"embeddedQualifier", "localEffect", "localEffectsList",
"firstElement", "elementName", "elementInfoList"]:
#"descriptor", "indentLabel"]:
#print " setting ", key, node.get(key)
newPhrase.set(key, node.get(key))
#print " ", newPhrase.getAreaLabel()
#print " disabled", newPhrase.get("disabledSubkeys")
#tree.printNode(newPhrase)
return newPhrase
## Combining
## If you want to alter the combining criteria,
## override starred (**) methods
##
## combinePhraseStats (phrase level)
## combineWords (phrase level)
## combineComponentStats (tree level)
## combineWords (any level)
## combineChildren -- loops through child nodes
## (tree, node, combineMethod)
## **combineScalars (subPhrase1, subPhrase2)
## **combineVectors (subPhrase1, subPhrase2)
## **combineWx (subPhrase1, subPhrase2)
## **combineComponents (component1, component2)
## combine2SubPhrases (tree, phrase, subPhrase1, subPhrase2)
## combine2Components (tree, tree, component1, component2)
## combineChildWords (any node with children that has words)
## combine2Children (tree, node, child1, child2)
def combinePhraseStats(self, tree, phrase):
# See if ready to process
if not self.phrase_trigger(tree, phrase, setUpOnly=1):
return
return self.combineChildren(tree, phrase, self.combineStats)
#print "before combine ", phrase.get("elementName"), len(phrase.get("childList"))
#result = self.combineChildren(tree, phrase, self.combineStats)
#print "after combine ", phrase.get("elementName"), len(phrase.get("childList"))
#return result
def recallCombinePhraseStats(self, tree, phrase):
# This is needed because we want to call combinePhraseStats twice in some phrases.
# If we simply put in two calls to a method, it gets put on the "doneList" with
# the first call and is not called again.
# See if ready to process
if not self.phrase_trigger(tree, phrase, setUpOnly=1):
return
return self.combinePhraseStats(tree, phrase)
def combineComponentStats(self, tree, node):
return self.combineChildren(tree, node, self.combineComponents)
def combineWords(self, tree, node):
# Check for data
if not self.phrase_trigger(tree, node):
return
children = node.get("childList")
if len(children) <= 1:
return self.DONE()
return self.combineChildren(tree, node, self.combineChildWords)
def combineChildren(self, tree, node, combineMethod):
# Combine similar nodes if possible
length = len(node.childList)
if length <= 1:
return self.DONE()
index = 1
while index < len(node.childList):
# Try to combine with previous subPhrase
combineFlag, combinedChild = combineMethod(
tree, node, node.childList[index-1], node.childList[index])
if combineFlag:
# Reset childList for phrase
node.childList[index-1] = combinedChild
del node.childList[index]
else:
index = index + 1
return self.DONE()
def combineComponents(self, tree, node, component1, component2):
# Criteria to set combine_flag
#print "\nTrying to combine"
# Don't combine components with different names since their
# analysis lists could be different.
comp1Name = component1.get("name")
comp2Name = component2.get("name")
if comp1Name != comp2Name:
#print "Different component names", comp1Name, comp2Name
return 0, None
# Make sure we don't combine periods any earlier than we should
noCombineUntil = self.periodCombining_startHour(tree, node)
if self.hoursPastProductStart(tree, component1) <= noCombineUntil:
return 0, None
## call the "similar" methods to see if each element is roughly
## the same. Any element that is not similar will cause the
## combine flag to evaluate to 0 or false.
elements = self.periodCombining_elementList(tree, component1)
combine_flag = 1
for element in elements:
#print "trying to combine", element
method = getattr(self, f"similar{element}")
combine_flag = combine_flag and method(tree, component1, component2)
#print "result", combine_flag
if combine_flag:
#print "combining"
newComp = self.combine2Components(tree, tree, component1, component2)
return 1, newComp
return 0, None
def similarWind(self, tree, comp1, comp2):
# Returns true if the wind stats are similar
# Also, return true (combine) if past the first 5 period since
# wind is not reported in these periods
# these numbers determine if components are close enough to combine
magThreshold = 10
dirThreshold = 45
al1 = comp1.getAreaLabel()
al2 = comp2.getAreaLabel()
tr1 = comp1.getTimeRange()
tr2 = comp2.getTimeRange()
stats1 = tree.stats.get("Wind", tr1, al1, mergeMethod = "Average")
stats2 = tree.stats.get("Wind", tr2, al2, mergeMethod = "Average")
# If past the first 5 periods, return 1 (combine)
hours = self.hoursPastProductStart(tree, comp1)
if hours >= 5*12:
return 1
# check for none
if stats1 is None or stats2 is None:
return 0
mag1 = stats1[0]
mag2 = stats2[0]
dir1 = stats1[1]
dir2 = stats2[1]
# calculate the differences, mag and dir
magDiff = abs(mag1 - mag2)
dirDiff = abs(dir1 - dir2)
# account for the 360 to 0 problem
if dirDiff > 180:
dirDiff = abs(dirDiff - 360.0)
if magDiff <= magThreshold and dirDiff <= dirThreshold:
return 1
return 0
def hoursPastProductStart(self, tree, node):
# Compute the hours past the product start time (prodTR)
# that the current time range (curTR) starts.
# If the prodTR is not a multiple of 12, then it is either
# --an update and the first period is less than 12 hours, or
# --a pre-first period issuance.
# In these case, we return the hours past the product start
# as if the first period was a full 12-hour period.
# For example,
# A morning update issuance starting at 10 am would
# have an hoursPastProductStart for the first period
# of 4 hours.
# A pre-first period issuance starting at 4 am would
# have an hoursPastProductStart for the first period
# of -2 hours.
prodTR = tree.getTimeRange()
curTR = node.getTimeRange()
prodHours = prodTR.duration() // 3600
prodMod = prodHours%12
if prodMod > 0:
try:
# check for 'pre-first period issuances'
period1Hours = self._issuanceInfo.period1TimeRange().duration() // 3600
if period1Hours > 12:
adjustHours = prodMod
else:
adjustHours = -(12-prodMod)
except:
adjustHours = 0
else:
adjustHours = 0
prodStart = prodTR.startTime() + adjustHours * 3600
return (curTR.startTime() - prodStart) // 3600
def similarSky(self, tree, comp1, comp2):
# Returns true if sky stats are similar
# Necessary because of the override to sky_valueList above
al1 = comp1.getAreaLabel()
al2 = comp2.getAreaLabel()
tr1 = comp1.getTimeRange()
tr2 = comp2.getTimeRange()
return self.similarSkyLogic(tree, comp1, comp2, tr1, al1, tr2, al2)
def similarWx(self, tree, comp1, comp2):
# Returns true if wx stats are similar
al1 = comp1.getAreaLabel()
al2 = comp2.getAreaLabel()
tr1 = comp1.getTimeRange()
tr2 = comp2.getTimeRange()
return self.similarWxLogic(tree, comp1, comp2, tr1, al1, tr2, al2)
def similarPoP(self, tree, comp1, comp2):
# returns true if PoP stats are similar
stats1 = self.matchToWx(tree, comp1, "PoP")
stats2 = self.matchToWx(tree, comp2, "PoP")
if stats1 is None and stats2 is None:
return 1
# check for none
#if stats1 is None or stats2 is None:
# return 0
if stats1 == stats2:
return 1
if ((stats1 is None or stats1 < self.pop_lower_threshold(tree, comp1)) and
(stats2 is None or stats2 < self.pop_lower_threshold(tree, comp2))):
return 1
if stats1 > self.pop_upper_threshold(tree, comp1) and \
stats2 > self.pop_upper_threshold(tree, comp2):
return 1
return 0
## Submitted by Brian Walawender 3/05
## The problem with combining long time periods, is that the
## combined period is growing 12 hours at a time. If you get rid of the bleed
## over grids for MinT and MaxT (SampleAnalysis temporalCoverage_hours_dict),
## then you start returning None for either MaxT or MinT during these 12 hour periods.
## To combat this, I check the duration of tr1 and tr2.
## If it is 12 or less then I check to see if it is day or night.
## For MaxT, it will return a combine if the period is 12 hours or less and it is a
## nighttime period.
## For MinT, it will return a combine if the period is 12 hours
## or less and it is a daytime period. This allowed long periods to be grouped
## together without bleed over.
def similarMaxT(self, tree, comp1, comp2):
# returns true if temp stats are similar
# this number determines if components are close enough to combine
tempThreshold = 5 # degrees
al1 = comp1.getAreaLabel()
al2 = comp2.getAreaLabel()
tr1 = comp1.getTimeRange()
tr2 = comp2.getTimeRange()
hours = (tr2.endTime() - tr1.startTime()) // 3600
if hours <= 24:
return 1
if (tr1.duration() // 3600) <= 12:
dayNight = self.getPeriod(tr1, 1)
if dayNight == self.NIGHTTIME():
return 1
if (tr2.duration() // 3600) <= 12:
dayNight = self.getPeriod(tr2, 1)
if dayNight == self.NIGHTTIME():
return 1
stats1 = tree.stats.get("MaxT", tr1, al1, mergeMethod = "Average")
stats2 = tree.stats.get("MaxT", tr2, al2, mergeMethod = "Average")
# check for none
if stats1 is None or stats2 is None:
return 0
if abs(stats1 - stats2) < tempThreshold:
return 1
return 0
def similarMinT(self, tree, comp1, comp2):
# returns true if temp stats are similar
# this number determines if components are close enough to combine
tempThreshold = 5 # degrees
al1 = comp1.getAreaLabel()
al2 = comp2.getAreaLabel()
tr1 = comp1.getTimeRange()
tr2 = comp2.getTimeRange()
hours = (tr2.endTime() - tr1.startTime()) // 3600
if hours <= 24:
return 1
if (tr1.duration() // 3600) <= 12:
dayNight = self.getPeriod(tr1, 1)
if dayNight == self.DAYTIME():
return 1
if (tr2.duration() // 3600) <= 12:
dayNight = self.getPeriod(tr2, 1)
if dayNight == self.DAYTIME():
return 1
# check for none
stats1 = tree.stats.get("MinT", tr1, al1, mergeMethod = "Average")
stats2 = tree.stats.get("MinT", tr2, al2, mergeMethod = "Average")
if stats1 is None or stats2 is None:
return 0
if abs(stats1 - stats2) < tempThreshold:
return 1
return 0
def similarWaveHeight(self, tree, comp1, comp2):
# returns true if seas stats are similar
# this number dtermines if components are close enough to combine
seaThreshold = 4 # feet
al1 = comp1.getAreaLabel()
al2 = comp2.getAreaLabel()
tr1 = comp1.getTimeRange()
tr2 = comp2.getTimeRange()
stats1 = tree.stats.get("WaveHeight", tr1, al1, mergeMethod ="Average")
stats2 = tree.stats.get("WaveHeight", tr2, al2, mergeMethod ="Average")
# check for none
if stats1 is None or stats2 is None:
return 0
if abs(stats1 - stats2) < seaThreshold:
return 1
return 0
def similarDiurnalSkyWx(self, tree, comp1, comp2):
return self.similar_diurnal(tree, comp1, comp2, ["Sky", "Wx"])
def similar_diurnal(self, tree, comp1, comp2, elementList):
# Returns true if stats for the given elements are similar
# in the night and morning AND the afternoon and evening.
# NOTE: the night and morning MAY be similar to the afternoon
# and evening, so word methods need to test for this case.
#
# Meant to handle the case of clouds and fog in the
# night and morning clearing in the afternoon and
# evening.
# Assumes comp2 is a 12-hour period.
#print "similar_diurnal"
al1 = comp1.getAreaLabel()
al2 = comp2.getAreaLabel()
comp1TR = comp1.getTimeRange()
comp2TR = comp2.getTimeRange()
# comp2 morning, comp2 afternoon OR
# comp2 evening, comp2 night
c2tr1, c2tr2 = self.divideRange(comp2TR,6)
comparisons = []
if comp1TR.duration() == 12*3600:
# Compare comp1 night to comp2 morning
# and comp1 evening to comp2 afternoon
# OR comp1 afternoon to comp2 evening
# and comp1 morning to comp2 night
c1tr1, c1tr2 = self.divideRange(comp1TR,6)
comparisons.append((c1tr1, c2tr2))
comparisons.append((c1tr2, c2tr1))
else:
# We have already combined at least once so
# comp1 is at least 24 hours. Use the most
# recent 24 hours for comparison.
# if comp2 is daytime:
# compare comp1 morning to comp2 morning
# and comp1 afternoon to comp2 afternoon
# else
# compare comp1 evening to comp2 evening
# and comp1 night to comp2 night
subRanges = self.divideRange(comp1TR, 6)
length = len(subRanges)-1
c1tr1 = subRanges[length-3]
c1tr2 = subRanges[length-2]
comparisons.append((c1tr1, c2tr1))
comparisons.append((c1tr2, c2tr2))
# Do comparisons
wordDict = {}
for element in elementList:
wordDict[element] = []
#print "\nComparisons"
for tr1, tr2 in comparisons:
for element in elementList:
#print "comparing", tr1, tr2
#print " ", element
method = getattr(self, f"similar{element}Logic")
flag = method(tree, comp1, comp2, tr1, al1, tr2, al2)
#print "flag", flag
if not flag:
#print "returning 0"
return 0
#print "returning 1"
return 1
def similarSkyLogic(self, tree, comp1, comp2, tr1, al1, tr2, al2):
stats1 = tree.stats.get("Sky", tr1, al1, mergeMethod ="Average")
stats2 = tree.stats.get("Sky", tr2, al2, mergeMethod ="Average")
# check for none
#print "stats1", stats1
#print "stats2", stats2
if stats1 is None or stats2 is None:
return 0
saveTR1 = comp1.timeRange
saveTR2 = comp2.timeRange
comp1.timeRange = tr1
comp2.timeRange = tr2
words1 = self.sky_value(tree, comp1, self.getValue(stats1), -1)
words2 = self.sky_value(tree, comp2, self.getValue(stats2), -1)
comp1.timeRange = saveTR1
comp2.timeRange = saveTR2
#print "words1, words2", words1, words2
if words1 == words2:
return 1
#if words1.find("partly") > -1 and words2.find("partly")> -1:
# return 1
return 0
def similarWxLogic(self, tree, comp1, comp2, tr1, al1, tr2, al2):
# Returns true if wx stats are similar
stats1 = tree.stats.get("Wx", tr1, al1, mergeMethod = "Average")
stats2 = tree.stats.get("Wx", tr2, al2, mergeMethod = "Average")
# check for none
#print "stats1, stats2", stats1, stats2
if stats1 is None or stats2 is None:
return 0
stats1 = self.cleanOutNoWx(stats1)
stats2 = self.cleanOutNoWx(stats2)
similarWx = self.checkWeatherSimilarity(
tree, comp1, stats1, stats2, comp1, comp2, tr1, tr2, al1, al2)
#print "similarWx", similarWx
if similarWx == 0:
return 0
else:
return 1
def cleanOutNoWx(self, stats):
# Cleans out NoWx from stats list
if stats is None:
return None
newList = []
for stat in stats:
if type(stat) is tuple:
subkey, rank = stat
else:
subkey = stat
if subkey.wxType() == "<NoWx>":
continue
newList.append(stat)
return newList
def combineStats(self, tree, phrase, subPhrase1, subPhrase2):
firstElement = phrase.get("firstElement")
elementName = firstElement.name
dataType = firstElement.dataType
if dataType == self.SCALAR():
combineFlag, newVal = self.combineScalars(
tree, phrase, subPhrase1, subPhrase2, elementName)
elif dataType == self.VECTOR():
combineFlag, newVal = self.combineVectors(
tree, phrase, subPhrase1, subPhrase2, elementName)
elif dataType == self.WEATHER():
combineFlag, newVal = self.combineWeather(
tree, phrase, subPhrase1, subPhrase2, elementName)
elif dataType == self.DISCRETE():
combineFlag, newVal = self.combineDiscrete(
tree, phrase, subPhrase1, subPhrase2, elementName)
if combineFlag:
elementInfoList = phrase.get("elementInfoList")
newSubPhrase = self.combine2SubPhrases(
tree, phrase, subPhrase1, subPhrase2, elementInfoList, newVal)
return 1, newSubPhrase
else:
return 0, None
def combineScalars(self, tree, node, subPhrase1, subPhrase2, elementName):
min1, max1 = self.getScalarData(tree, subPhrase1, elementName, "MinMax")
min2, max2 = self.getScalarData(tree, subPhrase2, elementName, "MinMax")
#print "combining", min1, max1, min2, max2
if min1 is None and max1 is None and min2 is None and max2 is None:
return 1, None
if min1 is None or max1 is None or min2 is None or max2 is None:
return 0, None
differenceFlag = self.checkScalarDifference(
tree, subPhrase1, elementName, min1, max1, min2, max2)
if differenceFlag == 0:
combine_singleValues = self.combine_singleValues_flag(
tree, subPhrase1, elementName, elementName)
if combine_singleValues == 1:
newValue = self.average(min(min1, min2), max(max1, max2))
newValue = self.roundStatistic(tree, subPhrase1, newValue, elementName)
else:
# Combine using mins and maxs to catch slow trends
min1 = self.roundStatistic(tree, subPhrase1, min(min1, min2), elementName)
max1 = self.roundStatistic(tree, subPhrase1, max(max1, max2), elementName)
min1, max1 = self.applyRanges(tree, node, min1, max1, elementName)
newValue = (min1, max1)
#print "combined"
return 1, newValue
#print "not combined"
return 0, None
def combineVectors(self, tree, phrase, subPhrase1, subPhrase2, elementName):
mag1, dir1, dirStr1 = self.getVectorData(tree, subPhrase1, elementName, "MinMax")
mag2, dir2, dirStr2 = self.getVectorData(tree, subPhrase2, elementName, "MinMax")
if mag1 is None and mag2 is None:
return 1, (None, dir1)
if mag1 is None or mag2 is None:
return 0, (None, dir1)
min1, max1 = mag1
min2, max2 = mag2
differenceFlag = self.checkVectorDifference(
tree, subPhrase1, elementName, min1, max1, dir1, min2, max2, dir2)
if differenceFlag == 0:
combine_singleValues = self.combine_singleValues_flag(
tree, subPhrase1, elementName, elementName)
if combine_singleValues == 1:
newMag, newDir = self.vectorAverage((min(min1, min2), dir1), (max(max1, max2), dir2))
newMag = self.roundStatistic(tree, subPhrase1, newMag, elementName)
newValue = (newMag, newDir)
else:
# Combine using mins and maxs to catch slow trends
newMin = min(min1, min2)
newMax = max(max1, max2)
newMin, newMax = self.applyRanges(tree, phrase, newMin, newMax, elementName)
magAvg, newDir = self.vectorAverage((newMin, dir1), (newMax, dir2))
newValue = ((newMin, newMax), newDir)
return 1, newValue
return 0, None
def combineWeather(self, tree, phrase, subPhrase1, subPhrase2, elementName):
# This method now only used for skyPopWx and visibility phrases
statDict1 = subPhrase1.getStatDict()
stats1 = statDict1[elementName]
statDict2 = subPhrase2.getStatDict()
stats2 = statDict2[elementName]
if stats1 is None and stats2 is None:
return 1, None
if stats1 is None or stats2 is None:
return 0, None
subkeys1 = self.getSubkeys(stats1)
subkeys2 = self.getSubkeys(stats2)
# Special case of combining based only on Visibility
combineVisibility = phrase.get("combineVisibility")
if combineVisibility == 1:
# Combine if low visibility is the same for each subPhrase
lowVis1 = self.getVis(subkeys1)
lowVis2 = self.getVis(subkeys2)
if lowVis1 == lowVis2:
return 1, stats1
else:
return 0, None
# Check weather key differences
similarResult = self.checkWeatherSimilarity(
tree, phrase, stats1, stats2, subPhrase1, subPhrase2)
if type(similarResult) is list:
return 1, similarResult
elif similarResult == 1:
return 1, stats1
elif similarResult == 2:
return 1, stats2
else:
return 0, None
def combineDiscrete(self, tree, phrase, subPhrase1, subPhrase2, elementName):
statDict1 = subPhrase1.getStatDict()
stats1 = statDict1[elementName]
statDict2 = subPhrase2.getStatDict()
stats2 = statDict2[elementName]
if stats1 is None and stats2 is None:
return 1, None
if stats1 is None or stats2 is None:
return 0, None
if stats1 == stats2:
return 1, stats1
return 0, None
def combineChildWords(self, tree, node, child1, child2):
words1 = child1.get("words")
if words1 is None:
return 0, None
words2 = child2.get("words")
if words2 is None:
return 0, None
if words1 == words2:
newChild = self.combine2Children(tree, node, child1, child2)
return 1, newChild
return 0, None
def combine2SubPhrases(self, tree, node, subPhrase1, subPhrase2, elementInfoList, newVal):
# Combine time ranges
subRange1 = subPhrase1.get("timeRange")
subRange2 = subPhrase2.get("timeRange")
newTimeRange = TimeRange.TimeRange(subRange1.startTime(), subRange2.endTime())
# Make new Node so methods will be re-run
# Preserve other elements in statDict
newSubPhrase = tree.makeNode([], subPhrase1.methodList)
# Make new statDict based on new time range
first = elementInfoList[0]
statDict = {}
statDict[first.name] = newVal
areaLabel = node.getAreaLabel()
for elementInfo in elementInfoList[1:]:
stats = tree.stats.get(
elementInfo.name, newTimeRange, areaLabel, elementInfo.statLabel,
elementInfo.mergeMethod)
statDict[elementInfo.name] = stats
newSubPhrase.set("statDict", statDict)
newSubPhrase.set("timeRange", newTimeRange)
newSubPhrase.parent = node
return newSubPhrase
def combine2Components(self, tree, node, comp1, comp2):
# Combine time ranges
timeRange1 = comp1.getTimeRange()
timeRange2 = comp2.getTimeRange()
newTimeRange = TimeRange.TimeRange(timeRange1.startTime(), timeRange2.endTime())
# Get fresh component definition so methods will be re-run
newComp = tree.makeComponent(comp1.get("name"), newTimeRange, comp1.get("definition"))
newComp.parent = tree
return newComp
def combine2Children(self, tree, node, child1, child2):
# Combine time ranges and take same values so methods will not be re-run
# Used for combining words
timeRange1 = child1.get("timeRange")
timeRange2 = child2.get("timeRange")
newTimeRange = TimeRange.TimeRange(timeRange1.startTime(), timeRange2.endTime())
child1.set("timeRange", newTimeRange)
return child1
####################################
def fillNulls(self, tree, node):
# Data Needed: subPhrase "words"
# Fill in the subPhrases designated as "null" with configurable
# null phrases (first_null_phrase, null_phrase)
# See if ready to process
if not self.phrase_trigger(tree, node):
return
index = 0
#print "fillNulls node", node.get("firstElement"), node.get("elementName")
try:
elementName = node.get("firstElement").name
except:
return self.DONE()
firstNullPhrase = self.first_null_phrase(tree, node, elementName, elementName)
nullPhrase = self.null_phrase(tree, node, elementName, elementName)
for subPhrase in node.get("childList"):
words = subPhrase.get("words")
if words is None:
return
if words == "null":
if index == 0:
subPhrase.set("words", firstNullPhrase)
else:
subPhrase.set("words", nullPhrase)
subPhrase.set("null",1)
index = index + 1
# Collapse empty word sub-phrases
self.collapsePhraseWords(tree, node)
return self.DONE()
def collapsePhraseWords(self, tree, phrase):
# Collapse empty word sub-phrases
childList = phrase.childList
if len(childList) <= 1:
return
newList = []
lastWords = None
index = 0
emptyIndex = None
for subPhrase in phrase.childList:
subWords = subPhrase.get("words")
if subWords == "":
if lastWords == "":
# Add to empty phrase
subRange = subPhrase.getTimeRange()
emptyRange = childList[emptyIndex].getTimeRange()
newRange = TimeRange.TimeRange(emptyRange.startTime(), subRange.endTime())
childList[emptyIndex].set("timeRange", newRange)
else:
# Start an empty phrase
emptyIndex = index
else:
if lastWords == "":
newList.append(childList[emptyIndex])
emptyIndex = None
newList.append(subPhrase)
lastWords = subWords
index = index + 1
if emptyIndex is not None:
newList.append(childList[emptyIndex])
phrase.childList = newList
def timeDescriptorModeration(self, tree, phrase):
# Moderates the time descriptor
# Needs subPhrase "words"
# Looks at subPhrase "null" or empty
# Sets subPhrase "timeDescFlag" indicating whether or not
# to generate a timeDescriptor for this subPhrase
#
# Algorithm:
# if last subPhrase is null, (make sure to flag last non-null)
# If odd number of subPhrases,
# flag even else flag odd
# elif the first even subPhrase is null, flag odd subPhrases
# else, flag even subPhrases
#
# See if ready to process
if not self.phrase_trigger(tree, phrase):
return
childList = phrase.get("childList")
length = len(childList)
if length == 0:
return self.DONE()
# Set all subPhrases if time descriptors are always
# to be on OR off
flag = None
if phrase.get("noTimeDescriptors") == 1:
flag = 0
elif phrase.get("allTimeDescriptors") == 1:
flag = 1
if flag is not None:
for subPhrase in childList:
subPhrase.set("timeDescFlag", flag)
return self.DONE()
# If one subPhrase, we need time descriptor IF
# the subPhrase time range differs from the
# phrase time range
if length == 1:
subPhrase = childList[0]
if subPhrase.getTimeRange() == phrase.getTimeRange():
flag = 0
else:
flag = 1
subPhrase.set("timeDescFlag",flag)
return self.DONE()
odd = length%2
lastNull = self.isNull(childList[length-1])
if lastNull:
if odd:
flagOdd = 0
else:
flagOdd = 1
else:
firstEven = self.isNull(childList[1])
if firstEven:
flagOdd = 1
else:
flagOdd = 0
index = 0
for subPhrase in childList:
#print "words", subPhrase.get("words")
#print "null", subPhrase.get("null")
flag = 0
if index%2 == 0: # odd subPhrase
if flagOdd == 1:
flag = 1
else: # even subPhrase
if flagOdd == 0:
flag = 1
# Uncomment the following line if you want ALL sub-phrases
# to have a time descriptor.
#
#flag = 1
#
# Alternatively, you could test per weather element:
#
#if phrase.get("elementName") == "Wx":
# flag = 1
subPhrase.set("timeDescFlag", flag)
index = index + 1
return self.DONE()
def isNull(self, subPhrase):
if subPhrase.get("null") == 1:
return 1
if subPhrase.get("words") == "":
return 1
return 0
def checkPhrasesDone(self, tree, node, areaLabel=None, exceptions=[]):
# Check that all phrases (except those with names listed in exceptions)
# are done for the component associated with node
# If areaLabel is not None, check only those phrases that have
# the given areaLabel.
# Return the list of phrases that are done.
# We need to look at all progeny -- not just children
# since phrases can have child phrases
leaves = self.getLeaves(tree, node)
phraseList = []
for child in leaves:
childWords = child.get("words")
childName = child.getAncestor('name')
#print " child", childName, childWords
if childName is None or childName in exceptions:
continue
if areaLabel is not None:
if child.getAreaLabel() != areaLabel:
continue
if childWords is not None:
phraseList.append(child)
else:
# If no words yet, return
#print "returning to wait"
return None
if phraseList is []:
return None
else:
return phraseList
def phrase_trigger(self, tree, phrase, setUpOnly=0):
# Return 1 if trigger is met, else 0
# If setUpOnly == 1, trigger will be met if setUp method
# has been completed
# Make sure set-up method was completed
#if len(phrase.get("childList")) == 0 and phrase.get("words") is None:
# return 0
if not phrase.setUpMethod in phrase.doneList:
return 0
if setUpOnly:
return 1
# Make sure sub-phrases have words
for subPhrase in phrase.get("childList"):
# Check to make sure we have words
words = subPhrase.get("words")
if words is None:
return 0
return 1
def assembleSubPhrases(self, tree, phrase):
# Assembles sub-phrases adding the time descriptor
# Check for data
# See if ready to process
if not self.phrase_trigger(tree, phrase):
return
if not self.consolidateSubPhrases_trigger(tree, phrase):
return
#print "NODE", phrase.get("name"), phrase.getTimeRange()
if self.useUntilPhrasing(tree, phrase):
return self.assembleUntilSubPhrases(tree, phrase)
fcst = ""
index = 0
#print "\nAssemble Subphrases", phrase.get('name'), phrase
for subPhrase in phrase.get("childList"):
# Check to make sure we have words
words = subPhrase.get("words")
if words is None:
return
#print " words", words
#print " ", subPhrase.getTimeRange(), subPhrase
#print " ", subPhrase.getAncestor("conjunctiveQualifier")
#print " ", subPhrase.getAreaLabel()
if words == "":
continue
if index == 0:
#if not subPhrase.get("null"):
if not self.isNull(subPhrase):
# Get descriptor
descriptor = phrase.get("descriptor")
if descriptor is not None and descriptor != "":
fcst = fcst + descriptor + " "
else:
# Get connector
connectorMethod = phrase.get("connectorMethod")
connector = connectorMethod(tree, subPhrase)
if index == 2:
# Add conjunctive "THEN" to make 3+ subPhrase phrases
# flow better. e.g.
# "N WIND 10 TO 20 KT RISING TO 30 KT EARLY IN THE
# AFTERNOON, THEN RISING TO GALES TO 40 KT LATE
# IN THE AFTERNOON."
elementName = phrase.getAncestor("elementName")
useThenConnector = self.useThenConnector(
tree, phrase, elementName, elementName)
if useThenConnector:
thenConnector = self.thenConnector(
tree, phrase, elementName, elementName)
if thenConnector != "":
# Add another time descriptor
subPhrase.set("timeDescFlag", 1)
connector = thenConnector + connector
fcst = fcst + connector
# Time Descriptor
timeDescriptor = self.format(
self.subPhrase_timeDescriptor(tree, phrase, subPhrase))
# Get words again in case they were changed by connector method
fcst = fcst + subPhrase.get("words") + timeDescriptor
index = index + 1
#print " words", fcst
phrase.set("words", fcst)
return self.DONE()
def assembleUntilSubPhrases(self, tree, phrase):
# Create a phrase that reports a list of (value, timeRange)
# tuples. Optionally, an associated range may be added to the
# phrase values.
elementName = phrase.getAncestor("elementName")
untilFormat = self.untilPhrasing_format(tree, phrase, elementName, elementName)
timeRange = phrase.getTimeRange()
# Make lists of consecutive subphrases
phraseLists = []
curList = []
lastTR = None
for subPhrase in phrase.get("childList"):
tr = subPhrase.getTimeRange()
# First time thru -- start curList
if lastTR is None:
curList.append(subPhrase)
lastTR = tr
continue
# Check for consecutive sub ranges
if tr.startTime() == lastTR.endTime():
curList.append(subPhrase)
# If not consecutive, clear out curList
# and append current subPhrase
else:
if curList != []:
phraseLists.append(curList)
curList = []
curList.append(subPhrase)
lastTR = tr
if curList != []:
phraseLists.append(curList)
#print "\nUNTIL NODE", phrase.getTimeRange()
phrases = []
for phraseList in phraseLists:
words = ""
index = 0
subWords = ""
firstWords = 1
for subPhrase in phraseList:
# Check to make sure we have words
lastWords = subWords
subWords = subPhrase.get("words")
if subWords is None:
return
#print " words", subWords, subPhrase.getTimeRange()
if index == 0:
#if not subPhrase.get("null"):
if not self.isNull(subPhrase):
# Get descriptor
descriptor = phrase.get("descriptor")
if descriptor is not None and descriptor != "":
words = words + descriptor + " "
index += 1
if subWords == "":
continue
subRange = subPhrase.getTimeRange()
# Add connector , then if words came before this
if not firstWords:
words = words + ", then "
# Use after if lastWords were empty and subRange
# starts after timeRange
words = words + subWords
if lastWords == "":
if subRange.startTime() != timeRange.startTime():
afterTime = self.getTimeStr(untilFormat, subRange, "begin")
if afterTime == "0000":
afterTime = "2400"
words = words + " after " + afterTime
# Use until if the subRange ends before the time range
if subRange.endTime() < timeRange.endTime():
untilTime = self.getTimeStr(untilFormat, subRange, "end")
if untilTime == "0000":
untilTime = "2400"
words = words + " until " + untilTime
firstWords = 0
phrases.append(words)
# String together phrases and insert periods
words = ""
for i, p in enumerate(phrases):
if p:
words += p[0].upper() + p[1:]
if i < len(phrases) - 1 and phrases[i+1] and words:
words += ". "
return self.setWords(phrase, words)
def getTimeStr(self, format, timeRange, endBegin):
if endBegin == "end":
if format == "military":
return self.timeDisplay(timeRange, "LT","","","%H")+"00"
else:
str = self.timeDisplay(timeRange, "LT", "", "", "%I %p")
if str[0] == "0":
str = str[1:]
return str
else:
if format == "military":
return self.timeDisplay(timeRange, "LT","","%H","")+"00"
else:
str = self.timeDisplay(timeRange, "LT", "", "%I %p", "")
if str[0] == "0":
str = str[1:]
return str
def useUntilPhrasing(self, tree, phrase):
# Check to see if the subPhrases warrant "until" phrasing
# Can be set for the phrase
elementName = phrase.getAncestor("elementName")
if self.untilPhrasing_flag(tree, phrase, elementName, elementName):
return 1
elif self.onTheFly_untilPhrasing_flag(
tree, phrase, elementName, elementName) != 1:
return 0
# Examine sub-phrase time ranges
tr = phrase.getTimeRange()
timeStart = tr.startTime()
timeEnd = tr.endTime()
for subPhrase in phrase.get("childList"):
#print "subTimeRange", subPhrase.getTimeRange()
subTr = subPhrase.getTimeRange()
subStart = subTr.startTime()
subEnd = subTr.endTime()
# See if subRange end time or start time is
# not a multiple of 3 hours back from timeRange end time.
# If start time is the same as phrase start time,
# do not count as until phrasing.
if timeEnd != subEnd:
timeDiff = timeEnd - subEnd
#print timeDiff, timeDiff % (3*3600)
if timeDiff % (3*3600) != 0:
return 1
if timeStart != subStart:
timeDiff = timeEnd - subEnd
if timeDiff % (3*3600) != 0:
return 1
return 0
def format(self, str):
if str is None:
str = ""
str = self.addSpace(str, "leading")
return str
def subPhrase_timeDescriptor(self, tree, phrase, subPhrase):
if subPhrase.get("timeDescFlag"):
subRange = subPhrase.getTimeRange()
phraseRange = phrase.getTimeRange()
if phrase.get("name") in self.weatherPhraseNames(tree, phrase) and \
len(phrase.get("childList")) > 1 and subRange == phraseRange:
dayNight = self.getPeriod(phraseRange, 1)
elementName = phrase.get("elementName")
if dayNight == self.DAYTIME():
return self.phrase_descriptor(
tree, phrase, "through the day", elementName)
elif dayNight == self.NIGHTTIME():
return self.phrase_descriptor(
tree, phrase, "through the night", elementName)
else:
return self.timePeriod_descriptor(tree, phrase, subRange)
else:
return ""
# Connectors
def scalarConnector(self, tree, subPhrase):
# return connector phrase to connect subPhrase and previous one
elementName = subPhrase.getAncestor("firstElement").name
then = self.phrase_connector(tree, subPhrase, "then", elementName)
#if subPhrase.get("null") or subPhrase.getPrev().get("null"):
prev = subPhrase.getPrev()
if self.isNull(subPhrase) or self.isNull(prev):
return then
# Check for either subPhrase specifying only special connector
connector = subPhrase.get("connector")
if connector is not None:
return connector
connector = prev.get("connector")
if connector is not None:
return connector
# Check for increasing/decreasing values
subPhrase1 = subPhrase.getPrev()
val1 = self.getScalarData(tree, subPhrase1, elementName, "Average")
val2 = self.getScalarData(tree, subPhrase, elementName, "Average")
if val1 is None and val2 is None:
connector = then
elif val2 is None or (val1 is not None and val1 > val2):
connector = self.phrase_connector(
tree, subPhrase, "decreasing to", elementName)
elif val1 is None or (val2 is not None and val1 < val2):
connector = self.phrase_connector(
tree, subPhrase, "increasing to", elementName)
else:
connector = then
return connector
def wxConnector(self, tree, subPhrase):
# Return connector string to connect subPhrase and previous one.
# If subPhrases cover neighboring time ranges, connect them with "then"
# Otherwise, connect them with ". "
# Make sure that we do not connect more than two subPhrases in a row
# with a "then" connector by setting and re-setting the "useThenConnector"
# flag at the phrase level.
thenConnector = self.phrase_connector(tree, subPhrase, "then", "Wx")
connector = '. '
prev = subPhrase.getPrev()
if prev is None:
return ""
phrase = subPhrase.getParent()
index = subPhrase.getIndex()
if index == 1:
# Initialize so that we are ready to use the thenConnector
# if appropriate
phrase.set("useThenConnector", 1)
useThenConnector = phrase.get("useThenConnector")
prevEnd = prev.getTimeRange().endTime()
# If the start time of this subPhrase is the same
# as the end time of the previous subphrase
if useThenConnector and prevEnd == subPhrase.getTimeRange().startTime():
# use the then connector
connector = ', then '
# Re-set useThenConnector so we don't get
# a long string of ", then" connected sub-phrases
phrase.set("useThenConnector", 0)
else:
# Can re-set connector so we are ready to use the
# then connector on the next subPhrase
phrase.set("useThenConnector", 1)
newSubPhrase = subPhrase.get("words")[0].upper() + subPhrase.get("words")[1:]
subPhrase.set("words", newSubPhrase)
return connector
def visConnector(self, tree, subPhrase):
# return connector phrase to connect subPhrase and previous one
elementName = subPhrase.getAncestor("firstElement").name
then = self.phrase_connector(tree, subPhrase, "then", elementName)
#if subPhrase.get("null") or subPhrase.getPrev().get("null"):
prev = subPhrase.getPrev()
if self.isNull(subPhrase) or self.isNull(prev):
return then
# Check for either subPhrase specifying only special connector
connector = subPhrase.get("connector")
if connector is not None:
return connector
connector = prev.get("connector")
if connector is not None:
return connector
# Check for increasing/decreasing values
subPhrase1 = subPhrase.getPrev()
# Get vis for previous sub-phrase
statDict = subPhrase1.getStatDict()
rankList = self.getStats(statDict, "Wx")
if rankList is None or len(rankList) == 0:
return self.setWords(node, "")
subkeyList = self.getSubkeys(rankList)
val1 = self.getVis(subkeyList)
if val1 is None:
return then
# Get vis for current sub-phrase
statDict = subPhrase.getStatDict()
rankList = self.getStats(statDict, "Wx")
if rankList is None or len(rankList) == 0:
return self.setWords(node, "")
subkeyList = self.getSubkeys(rankList)
val2 = self.getVis(subkeyList)
if val2 is None:
return then
if val1 > val2:
connector = self.phrase_connector(
tree, subPhrase, "decreasing to", elementName)
elif val1 < val2:
connector = self.phrase_connector(
tree, subPhrase, "increasing to", elementName)
else:
connector = then
return connector
def vectorConnector(self, tree, subPhrase):
# return connector phrase to connect subPhrase and previous one
elementName = subPhrase.getAncestor("firstElement").name
becoming = self.phrase_connector(tree, subPhrase, "becoming", elementName)
#if subPhrase.get("null") or subPhrase.getPrev().get("null"):
if self.isNull(subPhrase) or self.isNull(subPhrase.getPrev()):
return becoming
subPhrase1 = subPhrase.getPrev()
mag1, dir1, dirStr1 = self.getVectorData(
tree, subPhrase1, elementName, "Average")
mag2, dir2, dirStr2 = self.getVectorData(
tree, subPhrase, elementName, "Average")
increasingTo = self.phrase_connector(
tree, subPhrase, "increasing to", elementName)
decreasingTo = self.phrase_connector(
tree, subPhrase, "decreasing to", elementName)
# Directions same
if dirStr1 == dirStr2:
increment = self.nlValue(self.increment_nlValue(
tree, subPhrase, elementName, elementName), mag1)
# Magnitudes same
if abs(mag1-mag2) < increment:
connector = becoming
# Magnitudes different
elif mag1 < mag2:
connector = increasingTo
else:
connector = decreasingTo
# Directions different
else:
magDiff = self.nlValue(self.vector_mag_difference_nlValue(
tree, subPhrase, elementName, elementName), mag1)
# Magnitudes same
if abs(mag1 - mag2) < magDiff:
connector = self.phrase_connector(
tree, subPhrase, "shifting to the", elementName)
# Magnitudes different
else:
# If high wind conditions report both "becoming" and
# "increasing/decreasing"
# Southeast winds around 70 mph becoming south
# and increasing to around 105 mph
increasing = mag1 < mag2
if max(mag1, mag2) > self.highValue_threshold(
tree, subPhrase, elementName, elementName):
dirStr = subPhrase.get("dirStr")
words = subPhrase.get("words")
words = words.replace(dirStr+" ", "")
subPhrase.set("words", words)
direction = becoming + dirStr + " and"
if increasing:
connector = direction + increasingTo
else:
connector = direction + decreasingTo
# Otherwise, report both "increasing" or "becoming"
# SOUTHEAST WINDS AROUND 20 MPH BECOMING SOUTH
# AROUND 15 MPH
else:
if increasing:
connector = increasingTo
else:
connector = becoming
return connector
def marine_vectorConnector(self, tree, subPhrase):
# return connector phrase to connect subPhrase and previous one
elementName = subPhrase.parent.get("firstElement").name
if self.isNull(subPhrase) or self.isNull(subPhrase.getPrev()):
return self.phrase_connector(tree, subPhrase, "becoming", elementName)
subPhrase1 = subPhrase.getPrev()
mag1, dir1, dirStr1 = self.getVectorData(
tree, subPhrase1, elementName, "Average")
mag2, dir2, dirStr2 = self.getVectorData(
tree, subPhrase, elementName, "Average")
if dirStr1 == dirStr2:
increment = self.nlValue(self.increment_nlValue(
tree, subPhrase, elementName, elementName), mag1)
if abs(mag2-mag1) < increment:
connector = self.phrase_connector(tree, subPhrase, "becoming", elementName)
elif mag1 < mag2:
connector = self.phrase_connector(tree, subPhrase, "rising to", elementName)
else:
connector = self.phrase_connector(tree, subPhrase, "easing to", elementName)
else:
magDiff = self.nlValue(self.vector_mag_difference_nlValue(
tree, subPhrase, elementName, elementName), mag1)
if abs(mag2 - mag1) < magDiff:
# Put in test for sea breeze i.e. becoming onshore
if self.seaBreeze_flag(tree, subPhrase, elementName) == 1:
connector = self.phrase_connector(tree, subPhrase, "becoming onshore", elementName)
# Remove subPhrase words
subPhrase.set("words", "")
else:
movement = self.direction_movement(dir1, dir2)
if movement > 0: # clockwise
connector = self.phrase_connector(tree, subPhrase, "veering", elementName)
else:
connector = self.phrase_connector(tree, subPhrase, "backing", elementName)
else:
connector = self.phrase_connector(tree, subPhrase, "becoming", elementName)
return connector
def removeDirection(self, tree, subPhrase):
# Remove the direction from the subPhrase words
dirStr = subPhrase.get("dirStr")
if dirStr is not None:
words = subPhrase.get("words")
words = words.replace(dirStr, "")
subPhrase.set("words",words)
def getVectorData(self, tree, subPhrase, elementName, accessMethod):
# Get vector data for subPhrase for the given elementName
statDict = subPhrase.getStatDict()
stats = statDict[elementName]
if stats is None:
return None, None, None
mag, dir = stats
mag = self.getValue(mag, accessMethod)
dirStr= self.dirToText(dir)
return mag, dir, dirStr
def getScalarData(self, tree, subPhrase, elementName, accessMethod):
# Get scalar data for subPhrase for the given elementName
matchingInfo = self.matchToWxInfo(tree, subPhrase, elementName, elementName)
if matchingInfo != "":
val = self.matchToWx(tree, subPhrase, elementName)
if accessMethod == "MinMax":
val = (val, val)
else:
firstElement = subPhrase.getAncestor("firstElement")
dataType = firstElement.dataType
statDict = subPhrase.getStatDict()
val = statDict[elementName]
val = self.getValue(val, accessMethod, dataType)
return val
def seaBreeze_flag(self, tree, subPhrase, elementName):
# Return 1 if an onshore breeze is detected from the prior range
# Get local effects areas directions for Offshore previous subPhrase
# and Onshore for subPhrase
# offshoreDir, onshoreDir
offShoreArea, onShoreArea = self.seaBreeze_areaLabels(tree, subPhrase)
if offShoreArea is None:
return 0
subPhrase1 = subPhrase.getPrev()
if subPhrase1 is None:
return 0
timeRange1 = subPhrase1.getTimeRange()
timeRange2 = subPhrase.getTimeRange()
areaLabel1 = subPhrase1.getAreaLabel()
areaLabel2 = subPhrase.getAreaLabel()
offshore = tree.stats.get(
"Wind", timeRange1, offShoreArea, mergeMethod="Max",
intersectWith=areaLabel1)
onshore = tree.stats.get(
"Wind", timeRange2, onShoreArea, mergeMethod="Max",
intersectWith=areaLabel2)
if offshore is None or onshore is None:
return 0
mag, offshoreDir = offshore
mag, onshoreDir = onshore
# Get thresholds
offshore1, offshore2, onshore1, onshore2 = self.seaBreeze_thresholds(tree, subPhrase)
if self.direction_between(offshoreDir, offshore1, offshore2) and \
self.direction_between(onshoreDir, onshore1, onshore2):
return 1
return 0
# Subphrase Level
def checkRepeatingString(self, tree, node, str, strName, matchAreaLabels=1):
# Given a text string, str, and a descriptive name for that string,
# see if it repeats in the previous phrase, sub-phrase or embedded phrase.
# If we find a repeating string, return an empty string
# Otherwise return the original string.
# If matchAreaLabels, the areaLabel of previous node must match
# that of the current if we are to return an empty string.
# This prevents phrases such as:
# Chance of rain and snow 20 percent windward rain and snow 40 percent leeward.
#
# Check sub-phrases
#print "Check Repeating", node.getAncestor('name'), str
#print " matchAreaLabels", matchAreaLabels
prevNode = node.getPrev()
if prevNode is not None:
if matchAreaLabels and \
prevNode.getAreaLabel() != node.getAreaLabel():
return str
prevStr = prevNode.get(strName)
if prevStr is not None and str == prevStr:
# Do not repeat previous str
#print "return 1"
return ""
# Check degenerate conjunctive local effect
# We are looking for these conditions:
# --This phrase has only one sub-phrase
# --The previous phrase has only one sub-phrase AND
# has the same name as the current phrase (e.g. popMax_phrase
# --The str for the sub-phrases are the same
phrase = node.getParent()
#tree.printNode(phrase.parent)
if len(phrase.childList) == 1:
prevPhrase = phrase.getPrev()
if prevPhrase is not None:
if matchAreaLabels and \
prevPhrase.getAreaLabel() != node.getAreaLabel():
return str
if prevPhrase.get("name") == phrase.get("name"):
if len(prevPhrase.childList) == 1:
prevSubPhrase = prevPhrase.childList[0]
prevStr = prevSubPhrase.get(strName)
if prevSubPhrase.get('words') is None:
# Must wait for previous words to finish
return -1
if prevStr is not None and str == prevStr:
# Do not repeat previous str
#print "return 2"
return ""
return str
# Local Effects
def checkLocalEffects(self, tree, node):
localEffectsList = self.getLocalEffectsList(tree, node)
#print " le list", localEffectsList
if localEffectsList is None or len(localEffectsList) == 0:
return self.DONE()
childList = node.get("childList")
if childList is None or len(childList) < 1:
return self.DONE()
if self.__dict__.get('_leDebug',0):
print("\nChecking local effects for", node.get('name'), node.getAreaLabel())
print(" node", node)
print(" parent", node.parent)
print(" disabled", node.get('disabledSubkeys'), node.getAncestor('disabledSubkeys'))
print("\ncomp phrases before:")
self.printCompPhrases(tree, node)
for localEffect in localEffectsList:
# If ANY subPhrase has a local effect, create conjunctive local effect.
# If ALL subPhrases have the same local effect "groups", use that grouping.
# Otherwise, create a conjunctive phrase for each local effect area.
flag = 0
firstTime = 1
sameGroups = 1
for checkNode in childList:
nodeFlag, nodeGroups = self.checkLocalEffect(tree, checkNode, localEffect)
if nodeFlag:
flag = 1
if firstTime:
groups = nodeGroups
firstTime = 0
elif groups != nodeGroups:
# flag must be 1
sameGroups = 0
break
if flag:
# Create conjunctive local effect
#print "Creating conjunctive local effect"
if sameGroups == 0:
groups = []
leAreaList = self.getLeAreaList(tree, node, localEffect)
for leArea in leAreaList:
groups.append([leArea])
nodeList = self.makeLocalEffectNodes(tree, node, localEffect, groups)
# Applies only to the skyPopWx_phrase
# Set up includeSky for new local effect nodes
includeSky = self.getIncludeSky(tree, node)
for newNode in nodeList:
newNode.set("includeSky", includeSky)
if self.__dict__.get('_leDebug',0):
print("newNode", newNode.get("name"), newNode.get("areaLabel"))
print(" includeSky", includeSky, newNode)
node.replace(nodeList)
if flag: # There is a local effect
self.localEffect_hook(tree, node)
if self.__dict__.get('_leDebug',0):
print("\ncomp phrases after:", self.printCompPhrases(tree, node))
return self.DONE()
def checkLocalEffect(self, tree, node, localEffect):
# Check each local effect area against all others for the given node.
# Determine "groups" i.e. group the local effect areas according to
# similar statistics.
# Return
# -- a flag to indicate if any local effect areas showed differing
# statistics.
# -- the "groups"
triggerMethod = localEffect.triggerMethod
leAreaList = self.getLeAreaList(tree, node, localEffect)
if len(leAreaList) == 0:
return 0, []
# Begin with one group consisting of first local effect edit area
groups = [[leAreaList[0]]]
# This loop checks each subsequent local effect edit area against
# the existing groups and appends it to the first group which
# has similar statistics.
# If no existing group has similar statistics, a new group is
# created.
for leArea1 in leAreaList[1:]:
addedToExisting = 0
for group in groups:
leArea2 = group[0]
difference = self.checkThreshold(
tree, node, triggerMethod, leArea1, leArea2, localEffect)
if difference == 0:
# Similar statistics, so
# append it to the current group
group.append(leArea1)
addedToExisting = 1
break
if addedToExisting == 0:
# Did not find similar group, so create a new group
groups.append([leArea1])
if len(groups) == 1:
flag = 0
else:
flag = 1
return flag, groups
def getLocalEffectsList(self, tree, node):
leList = node.get("localEffectsList")
if type(leList) is types.MethodType:
return leList(tree, node)
else:
return leList
def getLeAreaList(self, tree, node, localEffect):
leAreaList = localEffect.leAreaList
if type(leAreaList) is types.MethodType:
return leAreaList(tree, node)
else:
return leAreaList
def getLeAreaLabel(self, tree, node, leArea):
if leArea.areaLabel == "__Current__":
return node.getAreaLabel()
elif leArea.intersectFlag:
return self.getIntersectName(node.getAreaLabel(), leArea.areaLabel)
#return self.getIntersectName(tree.getAreaLabel(), leArea.areaLabel)
else:
return leArea.areaLabel
def getLeQualifiers(self, tree, node, group):
# Return the qualifiers for this group of leAreas
# There is a qualifer for embedded local effect phrases
# and one for conjunctive local effect phrases.
embeddedQualifier = ""
conjQualifier = ""
length = len(group)
index = 0
for leArea in group:
areaWords = leArea.areaWords
if type(areaWords) is types.MethodType:
areaWords = areaWords(tree, node, leArea)
embeddedQualifier = embeddedQualifier + areaWords
conjWords = leArea.conjAreaWords
if type(conjWords) is types.MethodType:
conjWords = conjWords(tree, node, leArea)
conjQualifier = conjQualifier + conjWords
# if last one, do not add conjunction
if index == length - 1:
break
embeddedQualifier = embeddedQualifier + " and "
conjQualifier = conjQualifier + " and "
index = index + 1
return embeddedQualifier, conjQualifier
def checkThreshold(self, tree, node, triggerMethod, leArea1, leArea2, localEffect):
# Return 1 if the difference between leArea1 and leArea2 stats is
# greater than the threshold
# Handles stats that are a min/max or a singleValue
leArea1Label = self.getLeAreaLabel(tree, node, leArea1)
leArea2Label = self.getLeAreaLabel(tree, node, leArea2)
if type(triggerMethod) is types.MethodType:
flag = triggerMethod(tree, node, localEffect, leArea1Label, leArea2Label)
else:
first = node.getAncestor("firstElement")
element = first.name
dataType = first.dataType
if dataType == self.WEATHER():
mergeMethod = "Average"
else:
mergeMethod = "MinMax"
timeRange = node.getTimeRange()
area1Stats = tree.stats.get(element, timeRange, leArea1Label,
mergeMethod=mergeMethod)
area2Stats = tree.stats.get(element, timeRange, leArea2Label,
mergeMethod=mergeMethod)
area1Stats = self.applyDisabled(tree, node, area1Stats)
area2Stats = self.applyDisabled(tree, node, area2Stats)
if self.__dict__.get("_leDebug", 0):
print("\nCheckThreshold", element, timeRange)
print(leArea1Label, area1Stats)
print(leArea2Label, area2Stats)
if area1Stats is None or area2Stats is None:
return 0
flag = self.checkLocalEffectDifference(
tree, node, dataType, triggerMethod, area1Stats, area2Stats,
leArea1Label, leArea2Label)
if self.__dict__.get("_leDebug", 0):
print("returning", flag)
return flag
def applyDisabled(self, tree, node, stats):
if stats is None:
return stats
disabledSubkeys = node.getAncestor('disabledSubkeys')
#print "/n applyDisabled: disabled", disabledSubkeys
#print "stats", stats
if disabledSubkeys is not None:
newStats = []
for subkey, rank in stats:
if subkey not in disabledSubkeys:
newStats.append((subkey, rank))
stats = newStats
if stats == []:
emptyKey = WeatherSubKey.weatherSubKey(self._argDict['site'],
"<NoCov>", "<NoWx>", "<NoInten>", "<NoVis>", [])
stats = [(emptyKey, 100)]
return stats
def checkLocalEffectDifference(self, tree, node, dataType, threshold,
area1Stats, area2Stats, al1, al2):
if dataType == self.DISCRETE():
if area1Stats != area2Stats:
return 1
else:
return 0
if dataType == self.WEATHER():
flag = self.checkWeatherSimilarity(
tree, node, area1Stats, area2Stats, al1=al1, al2=al2)
# checkWeatherSimilarity returns 0 if there IS a difference and, thus,
# should be a local effect
if flag == 0:
return 1
else:
return 0
if dataType == self.VECTOR():
area1Stats, dir = area1Stats
area2Stats, dir = area2Stats
if type(area1Stats) is tuple:
min1, max1 = area1Stats
min2, max2 = area2Stats
diff1 = self.absDiff(min1, min2)
diff2 = self.absDiff(max1, max2)
# Check to see if one range is included within the other
if self.rangeIncluded(min1, max1, min2, max2) == 1:
return 0
if self.rangeIncluded(min2, max2, min1, max1) == 1:
return 0
# Check to see if either min or max is greater than threshold
if diff1 > threshold or diff2 > threshold:
return 1
else:
return 0
else:
absDiff = self.absDiff(area1Stats, area2Stats)
if absDiff > threshold:
return 1
else:
return 0
def checkSkyWxDifference(self, tree, node, localEffect, leArea1Label, leArea2Label):
timeRange = node.getTimeRange()
wxStats1 = tree.stats.get("Wx", timeRange, leArea1Label,
mergeMethod="Average")
wxStats2 = tree.stats.get("Wx", timeRange, leArea2Label,
mergeMethod="Average")
wxStats1 = self.applyDisabled(tree, node, wxStats1)
wxStats2 = self.applyDisabled(tree, node, wxStats2)
#print "wxStats1", wxStats1
#print "wxStats2", wxStats2
wxSame = self.checkWeatherSimilarity(
tree, node, wxStats1, wxStats2, al1=leArea1Label, al2=leArea2Label)
#print "wxSame", wxSame
if wxSame == 0:
wxDiff = 1
else:
wxDiff = 0
skyDiff = self.checkSkyDifference(tree, node, localEffect,
leArea1Label, leArea2Label)
# Determine if ANY of the sub-phrases have a sky local effect
# and store this information at the parent level for later
# use by the "checkLocalEffects" method.
skyLE = node.parent.get("skyLE")
if skyLE is None:
node.parent.set("skyLE", 0)
if skyDiff:
node.parent.set("skyLE", 1)
#return wxDiff
return skyDiff or wxDiff
def getIncludeSky(self, tree, node):
# If this is called, then we know we have a LE
# i.e. there was a wx local effect.
skyLE = node.get("skyLE")
if skyLE:
return None # Want to make sure we check at the LE level
else:
return 0
def checkSkyDifference(self, tree, node, localEffect,
leArea1Label, leArea2Label):
timeRange = node.getTimeRange()
skyValue1 = tree.stats.get("Sky", timeRange, leArea1Label,
mergeMethod="Average")
skyValue2 = tree.stats.get("Sky", timeRange, leArea2Label,
mergeMethod="Average")
if timeRange.duration() > 12*3600:
dayNight = -1
else:
dayNight = self.getPeriod(timeRange, 1)
words1 = self.sky_value(tree, node, self.getValue(skyValue1), dayNight)
words2 = self.sky_value(tree, node, self.getValue(skyValue2), dayNight)
return not self.similarSkyWords_flag(tree, node, words1, words2)
def localEffect_hook(self, tree, node):
return
def rangeIncluded(self, min1, max1, min2, max2):
# Return 1 if min1, max1 are included in min2, max2
if min1 >= min2 and max1 <= max2:
return 1
return 0
def absDiff(self, val1, val2):
# Return the absolute difference between the values
# Note: this handles negative values
if (val1 > 0 and val2 > 0) or (val1 < 0 and val2 < 0):
return abs(val1 - val2)
else:
return abs(val1) + abs(val2)
def makeLocalEffectNodes(self, tree, node, localEffect, groups):
# Make a node phrase for each group of local effect areas in groups
nodeList = []
for group in groups:
leAreaLabel = self.getLeAreaLabel(tree, node, group[0])
newNode = tree.copyPhrase(
node, node.getTimeRange(), leAreaLabel,
copyAttrs=["disabledSubkeys", "disabledElements",
"firstElement", "elementName", "elementInfoList",
"descriptor", "indentLabel"])
embeddedQualifier, conjQualifier = self.getLeQualifiers(tree, node, group)
newNode.set("embeddedQualifier", embeddedQualifier)
newNode.set("conjunctiveQualifier", conjQualifier)
newNode.set("localEffect", localEffect)
newNode.set("leGroup", group)
nodeList.append(newNode)
return nodeList
def printCompPhrases(self, tree, node):
comp = node.getComponent()
print("Component phrases for", node)
for phrase in comp.get('childList'):
print(phrase.get('name'), phrase.getAreaLabel(), phrase)
print(" ", phrase.get('words'))