diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/CreateTCVAreaDictionary.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/CreateTCVAreaDictionary.py index 5c51fcac66..7f647b823d 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/CreateTCVAreaDictionary.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/CreateTCVAreaDictionary.py @@ -67,34 +67,6 @@ class Procedure (SmartScript.SmartScript): # "Downtown Miami", # ], # -# # Threat statements can be overriden here; anything not overriden here -# # will use the generic threat statements -# 'threatStatements': { -# # Section name: "Wind", "Storm Surge", "Flooding Rain" or "Tornado" -# "Wind": { -# # Threat level: "None", "Low", "Mod", "High" or "Extreme" -# "Extreme": { -# # tr: "nothing to see here", "recovery", "hunker down", -# # "complete preparations" or "check plans" -# "hunker down": { -# # "Make plans: " will be prepended to this -# "planning": "For chance that wind could locally reach major " +\\ -# "hurricane force; enact your emergency action plan " +\\ -# "accordingly", -# # "Take action: " will be prepended to this -# "action": "For extremely dangerous and life threatening wind " +\\ -# "to possibly occur; failure to act may result in " +\\ -# "injury or loss of life", -# # "Prepare: " will be prepended to this -# "preparation": "Aggressively for chance of devastating to " +\\ -# "catastrophic wind impacts based on threat " +\\ -# "assessment that considers plausible worst " +\\ -# "case scenario for safety", -# }, -# }, -# }, -# }, -# # # Potential impacts statements can be overriden here; anything not # # overriden here will use the generic potential impacts statements # 'potentialImpactsStatements': { @@ -151,7 +123,6 @@ TCV_AreaDictionary = { """ self._zoneSkeletonContents = { 'locationsAffected' : [], - 'threatStatements' : {}, 'potentialImpactsStatements' : {}, 'infoSection' : [], } diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLSTCV_Common.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textProducts/HLSTCV_Common.py similarity index 62% rename from edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLSTCV_Common.py rename to cave/com.raytheon.viz.gfe/localization/gfe/userPython/textProducts/HLSTCV_Common.py index 42b68fa1b3..96ae55fa47 100644 --- a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLSTCV_Common.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textProducts/HLSTCV_Common.py @@ -282,7 +282,7 @@ class TextProduct(GenericHazards.TextProduct): # Set up the time range for 0-120 hours # Create a time range from the issuanceHour out 120 hours - startTime = self._calculateStartTime(time.localtime(self._issueTime_secs)) + startTime = self._calculateStartTime(time.gmtime(self._issueTime_secs)) self._timeRange = self.makeTimeRange(startTime, startTime+120*3600) # Determine the time range list, making sure they are on hour boundaries @@ -300,21 +300,142 @@ class TextProduct(GenericHazards.TextProduct): day = localCreationTime[2] hour = localCreationTime[3] + # If we are more than halfway though a 6 hr period + if hour % 6 > 3: + adjust = 6 # move on to the next 6 hr block + else: + adjust = 0 +# print "MATT: _calculateStartTime %d adjust = %d" % (hour % 6, adjust) + # Now "truncate" to a 6-hourly boundary and compute startTime in local Time. - hour = int (int(hour/6) * 6) + hour = int( (hour/6) * 6) + adjust + if hour > 23: + hour -= 24 + elif hour < 0: + hour += 24 + startTime = absTimeYMD(year, month, day, hour) - # Finally, convert back to GMT - localTime, shift = self.determineTimeShift() - startTime = startTime - shift return startTime def _resolution(self): return 3 + + def _formatPeriod(self, period, wholePeriod=False, shiftToLocal=True, useEndTime=False, + resolution=3): + # Format period (a timeRange) resulting in + # DAY + MORNING / AFTERNOON / EVENING / OVERNIGHT. + # If wholePeriod, format FROM ... TO... + + print "\nMATT Format period wholePeriod = %s, period = %s, useEndTime =%s" % (str(wholePeriod), str(period), str(useEndTime)) + if period is None: return "" + if useEndTime: + startTime = period.endTime() + else: + startTime = period.startTime() + result = self._getTimeDesc(startTime, resolution, shiftToLocal) + print "MATT result = '%s'" % (result) + if wholePeriod: + endResult = self._getTimeDesc(period.endTime(), resolution, shiftToLocal) + print "MATT endResult = '%s'" % (endResult) + if result != endResult: + result=result + " TO "+ endResult + return result + + def _getTimeDesc(self, startTime, resolution=3, shiftToLocal=True): + # Create phrase such as Tuesday morning + # Handle today/tonight and "this" morning/afternoon/etc.. + # + print "\n\n**************Formatting Period for GMT starttime ", startTime + labels = self.Labels()["SimpleWorded"] + currentTime = self._timeRange.startTime() + print " currentTime = %s" % (repr(currentTime)) + if shiftToLocal: + currentLocalTime, shift = self.determineTimeShift() + startTime = startTime + shift + currentTime = currentTime + shift + print " shift, shifted start, current", shift/3600, startTime, currentTime + hour = startTime.hour + prevDay = False + prevDay, partOfDay = self._getPartOfDay(hour, resolution) +# if prevDay: +# startTime = startTime - 24*3600 + todayFlag = currentTime.day == startTime.day + if todayFlag: + if partOfDay.upper().find("MIDNIGHT")>0: todayWord = "tonight" + else: todayWord = "THIS" + weekday = todayWord + else: + weekday = labels["Weekday"][startTime.weekday()] + if partOfDay.find("") >= 0: + result = partOfDay.replace('', weekday) + else: + result = weekday + " " + partOfDay + print "Result = '%s'" % (result) + return result + + def _getPartOfDay(self, hour, resolution): + prevDay = False + if resolution == 3: + if hour < 3: + prevDay = True + partOfDay = "early morning" +# partOfDay = "AFTER MIDNIGHT" + elif hour < 6: + partOfDay = "early morning" + elif hour < 9: + partOfDay = "morning" + elif hour < 12: + partOfDay = "late morning" + elif hour < 15: + partOfDay = "early afternoon" + elif hour < 18: + partOfDay = "late afternoon" + elif hour < 21: + partOfDay = "early evening" + else: + partOfDay = "late evening" + else: + if hour < 6: + prevDay = True +# partOfDay = "AFTER MIDNIGHT" + partOfDay = "early morning" + elif hour < 12: partOfDay = "morning" + elif hour < 18: partOfDay = "afternoon" + else: partOfDay = "evening" + return prevDay, partOfDay ############################################################### ### Sampling and Statistics related methods + def moderated_dict(self, parmHisto, timeRange, componentName): + """ + Specifies the lower percentages and upper percentages of + data to be thrown out for moderated stats. + """ + # COMMENT: This dictionary defines the low and high limit at which + # outliers will be removed when calculating moderated stats. + # By convention the first value listed is the percentage + # allowed for low values and second the percentage allowed + # for high values. + + # Get Baseline thresholds + dict = SampleAnalysis.SampleAnalysis.moderated_dict( + self, parmHisto, timeRange, componentName) + + # Change thresholds + dict["Wind"] = (0, 15) + dict["WindGust"] = (0, 15) + dict["pws34int"] = (0, 5) + dict["pws64int"] = (0, 5) + dict["pwsD34"] = (0, 5) + dict["pwsN34"] = (0, 5) + dict["pwsD64"] = (0, 5) + dict["pwsN64"] = (0, 5) + dict["InundationMax"] = (0, 5) + dict["InundationTiming"] = (0, 5) + return dict + def _getStatValue(self, statDict, element, method=None, dataType=None): stats = statDict.get(element, None) if stats is None: return None @@ -600,25 +721,44 @@ FORECASTER STEWART""" ############################################################### ### Advisory related methods - - def _archiveCurrentAdvisory(self): - ### Determine if all actions are canceled - allCAN = True - for vtecRecord in self._getAllVTECRecords(): - action = vtecRecord['act'] - #print "vtecRecord", vtecRecord - if action != "CAN": - allCAN = False - break - - self._currentAdvisory["AllCAN"] = allCAN - self._currentAdvisory["CreationTime"] = self._issueTime_secs - self._currentAdvisory["Transmitted"] = False - self._currentAdvisory["StormName"] = self._getStormNameFromTCP() - self._saveAdvisory("pending", self._currentAdvisory) + def _synchronizeAdvisories(self): + pathManager = PathManagerFactory.getPathManager() + context = pathManager.getContextForSite(LocalizationType.CAVE_STATIC, self._site) + + # Retrieving a directory causes synching to occur + file = pathManager.getLocalizationFile(context, self._getAdvisoryPath()).getFile() + + return file + + def _getLocalAdvisoryDirectoryPath(self): + file = self._synchronizeAdvisories() + + path = file.getPath() + return path + + def _loadLastTwoAdvisories(self): + advisoryDirectoryPath = self._getLocalAdvisoryDirectoryPath() + filenames = os.listdir(advisoryDirectoryPath) + allAdvisories = filter(lambda filename: filename[-5:] == ".json", filenames) + + stormAdvisories = filter(lambda filename: self._getStormNameFromTCP() in filename, + allAdvisories) + stormAdvisories = map(lambda filename: filename[:-5], stormAdvisories) + lastTwoAdvisories = sorted(stormAdvisories)[:2] + + self._previousAdvisory = None + if len(lastTwoAdvisories) >= 1: + self._previousAdvisory = self._loadAdvisory(lastTwoAdvisories[0]) + + self._previousPreviousAdvisory = None + if len(lastTwoAdvisories) >= 2: + self._previousPreviousAdvisory = self._loadAdvisory(lastTwoAdvisories[1]) + def _loadAdvisory(self, advisoryName): import json + + self._synchronizeAdvisories() try: jsonDict = self._getFileContents(LocalizationType.CAVE_STATIC, @@ -630,29 +770,24 @@ FORECASTER STEWART""" print pythonDict # Only use transmitted advisories - if pythonDict["Transmitted"] == False: + if pythonDict["Transmitted"] == False and advisoryName != "pending": return None else: return pythonDict except Exception, e: - print "SARAH Load Exception for", self._getAdvisoryFilename(advisoryName), ":", e + print "SARAH Load Exception for %s : %s" % (self._getAdvisoryFilename(advisoryName), e) return None - def _saveAdvisory(self, advisoryName, advisoryDict): - import json - - try: - self._writeFileContents(LocalizationType.CAVE_STATIC, - self._site, - self._getAdvisoryPath() + advisoryName + ".json", - json.dumps(advisoryDict)) - print "SARAH: Wrote file contents for", (self._getAdvisoryPath() + advisoryName + ".json") - except Exception, e: - print "SARAH Save Exception for", (self._getAdvisoryPath() + advisoryName + ".json"), ":", e - def _getAdvisoryPath(self): return "gfe/tcvAdvisories/" + def _getLocalizationFile(self, loctype, siteID, filename): + pathManager = PathManagerFactory.getPathManager() + context = pathManager.getContextForSite(loctype, siteID) + localizationFile = pathManager.getLocalizationFile(context, filename) + + return localizationFile + def _getFileContents(self, loctype, siteID, filename): pathManager = PathManagerFactory.getPathManager() context = pathManager.getContextForSite(loctype, siteID) @@ -662,20 +797,8 @@ FORECASTER STEWART""" return fileContents - def _writeFileContents(self, loctype, siteID, filename, contents): - pathManager = PathManagerFactory.getPathManager() - context = pathManager.getContextForSite(loctype, siteID) - localizationFile = pathManager.getLocalizationFile(context, filename) - with File(localizationFile.getFile(), filename, 'w') as pythonFile: - pythonFile.write(contents) - - localizationFile.save() - def _getAdvisoryFilename(self, advisoryName): - year = time.gmtime(self._issueTime_secs).tm_year advisoryFilename = self._getAdvisoryPath() + \ - self._getStormNameFromTCP().upper() + \ - str(year) + \ advisoryName + \ ".json" return advisoryFilename @@ -711,7 +834,7 @@ FORECASTER STEWART""" # Order and inclusion of GUI1 buttons # Each entry is (name of button in GUI code, desired label on GUI) "buttonList":[ - ("Next","Next"), + ("Run","Run"), ("Cancel","Cancel"), ], } @@ -725,68 +848,6 @@ FORECASTER STEWART""" ############################################################### ### TCV Statistics - def _analysisList_TCV(self): - # Sample over 120 hours beginning at current time - analysisList = [ - # Wind Section - ("Wind", self.vectorModeratedMax, [6]), - ("WindGust", self.moderatedMax, [6]), - ("WindThreat", self.mostSignificantDiscreteValue), - ("pws34int", self.moderatedMax, [6]), - ("pws64int", self.moderatedMax, [6]), - ("pwsD34", self.moderatedMax), - ("pwsN34", self.moderatedMax), - ("pwsD64", self.moderatedMax), - ("pwsN64", self.moderatedMax), - - # Flooding Rain Section - ("QPF", self.accumSum, [72]), - ("FloodingRainThreat", self.mostSignificantDiscreteValue), - - # Tornado Section - ("TornadoThreat", self.mostSignificantDiscreteValue), - ] - - return analysisList - - def _intersectAnalysisList_TCV(self): - # The grids for the Surge Section will be intersected with a special edit area - analysisList = [ - ("InundationMax", self.moderatedMax, [6]), - ("InundationTiming", self.moderatedMax, [6]), - ("StormSurgeThreat", self.mostSignificantDiscreteValue), - ] - - return analysisList - - def moderated_dict(self, parmHisto, timeRange, componentName): - """ - Specifies the lower percentages and upper percentages of - data to be thrown out for moderated stats. - """ - # COMMENT: This dictionary defines the low and high limit at which - # outliers will be removed when calculating moderated stats. - # By convention the first value listed is the percentage - # allowed for low values and second the percentage allowed - # for high values. - - # Get Baseline thresholds - dict = SampleAnalysis.SampleAnalysis.moderated_dict( - self, parmHisto, timeRange, componentName) - - # Change thresholds - dict["Wind"] = (0, 15) - dict["WindGust"] = (0, 15) - dict["pws34int"] = (0, 5) - dict["pws64int"] = (0, 5) - dict["pwsD34"] = (0, 5) - dict["pwsN34"] = (0, 5) - dict["pwsD64"] = (0, 5) - dict["pwsN64"] = (0, 5) - dict["InundationMax"] = (0, 5) - dict["InundationTiming"] = (0, 5) - return dict - def threatKeyOrder(self): return [None, "None", "Elevated", "Mod", "High", "Extreme"] @@ -812,50 +873,7 @@ FORECASTER STEWART""" def _initializeAdvisories(self): self._currentAdvisory = dict() self._currentAdvisory['ZoneData'] = dict() - self._previousAdvisory = self._loadAdvisory("previous") - print "SARAH: loaded previous advisory =", self._previousAdvisory - self._previousPreviousAdvisory = self._loadAdvisory("previousPrevious") - print "SARAH: loaded previousPrevious advisory =", self._previousPreviousAdvisory - - def _sampleTCVData(self, argDict): - # Sample the data - editAreas = self._makeSegmentEditAreas(argDict) - cwa = self._cwa() - editAreas.append((cwa, cwa)) - - self._sampler = self.getSampler(argDict, - (self._analysisList_TCV(), self._timeRangeList, editAreas)) - - intersectAreas = self._computeIntersectAreas(editAreas, argDict) - - self._intersectSampler = self.getSampler(argDict, - (self._intersectAnalysisList_TCV(), self._timeRangeList, intersectAreas)) - - def _getTCVStats(self, argDict, segment, editAreaDict, timeRangeList): - # Get statistics for this segment - print "SARAH: issue time seconds =", self._issueTime_secs - print "SARAH: GMT issue time =", time.gmtime(self._issueTime_secs) - - editArea = editAreaDict[segment] - statList = self.getStatList(self._sampler, - self._analysisList_TCV(), - timeRangeList, - editArea) - - windStats = WindSectionStats(self, segment, statList, timeRangeList) - floodingRainStats = FloodingRainSectionStats(self, segment, statList, timeRangeList) - tornadoStats = TornadoSectionStats(self, segment, statList, timeRangeList) - - # The surge section needs sampling done with an intersected edit area - intersectEditArea = "intersect_"+editArea - intersectStatList = self.getStatList(self._intersectSampler, - self._intersectAnalysisList_TCV(), - timeRangeList, - intersectEditArea) - - stormSurgeStats = StormSurgeSectionStats(self, segment, intersectStatList, timeRangeList) - - return (windStats, stormSurgeStats, floodingRainStats, tornadoStats) + self._loadLastTwoAdvisories() def _initializeSegmentZoneData(self, segment): # The current advisory will be populated when setting a section's stats @@ -894,10 +912,10 @@ FORECASTER STEWART""" if combos is None: LogStream.logVerbose("COMBINATION FILE NOT FOUND: " + self._defaultEditAreas) return [], None - print "\nSegments from Zone Combiner", combos + #print "\nSegments from Zone Combiner", combos # "Overlay" the forecaster-entered combinations onto the segments segmentList = self._refineSegments(hazSegments, combos) - print "\nNew segments", segmentList + #print "\nNew segments", segmentList # Instead of a segment being a group of zones, it will be just a single zone. # So collapse this list of lists down to a list of zones (aka. segments) @@ -925,6 +943,7 @@ FORECASTER STEWART""" # (We need to define self._segmentList for the mapping function # to use) self._segmentList = hazSegments + #print "self._segmentList = %s" % (repr(self._segmentList)) segmentMapping = map(self._findSegment, combo) #print " segmentMapping", segmentMapping @@ -966,568 +985,6 @@ FORECASTER STEWART""" return segment return [] - -############################################################### -### TCV Statistics Classes - -class SectionCommonStats(): - def __init__(self, textProduct, segment): - self._textProduct = textProduct - self._segment = segment - - self._initializeAdvisories() - - self._maxThreat = None - - def _initializeAdvisories(self): - self._currentAdvisory = self._textProduct._currentAdvisory['ZoneData'][self._segment] - - self._previousAdvisory = None -# print "MATT textProduct._previousAdvisory = '%s'" % (textProduct._previousAdvisory) - if self._textProduct._previousAdvisory is not None: - self._previousAdvisory = self._textProduct._previousAdvisory['ZoneData'][self._segment] - -# print "MATT textProduct._previousPreviousAdvisory = '%s'" % \ -# (textProduct._previousPreviousAdvisory) - self._previousPreviousAdvisory = None - if self._textProduct._previousPreviousAdvisory is not None: - self._previousPreviousAdvisory = self._textProduct._previousPreviousAdvisory['ZoneData'][self._segment] - - def _updateThreatStats(self, tr, statDict, threatGridName): - print "SARAH: updateThreatStats for", threatGridName - threatLevel = self._textProduct.getStats(statDict, threatGridName) - if threatLevel is not None: - threatLevels = self._textProduct.threatKeyOrder() - print "SARAH: threatLevel =", threatLevel - print "SARAH: maxThreat =", self._maxThreat - if self._maxThreat is None or \ - threatLevels.index(threatLevel) > threatLevels.index(self._maxThreat): - print "SARAH: updating max threat to =", threatLevel - self._maxThreat = threatLevel - - def _calculateHourOffset(self, targetTime): - seconds = targetTime.unixTime() - self._textProduct._issueTime_secs - hour = int(round(seconds/60/60)) - if hour < 0: - hour = 0 - - return hour - - -class WindSectionStats(SectionCommonStats): - def __init__(self, textProduct, segment, statList, timeRangeList): - SectionCommonStats.__init__(self, textProduct, segment) - self._maxWind = None - self._maxGust = None - self._onset34Hour = None - self._end34Hour = None - self._onset64Hour = None - self._end64Hour = None - self._windowTS = None - self._windowHU = None - - self._setStats(statList, timeRangeList) - - class PwsXXintStats(): - max = None - onsetHour = None - - class PwsTXXStats(): - onsetHour = None - endHour = None - - class TimeInfo(): - onsetHour = None - endHour = None - - class EventsOccurring(): - pwsTXXEvent = False - windXXEvent = False - - def _setStats(self, statList, timeRangeList): - pws34intStats = self.PwsXXintStats() - pws64intStats = self.PwsXXintStats() - pwsT34Stats = self.PwsTXXStats() - pwsT64Stats = self.PwsTXXStats() - wind34timeInfo = self.TimeInfo() - wind64timeInfo = self.TimeInfo() - - events34 = self.EventsOccurring() - events64 = self.EventsOccurring() - - for period in range(len(statList)): - tr, _ = timeRangeList[period] - statDict = statList[period] - - self._updateStatsForPwsXXint(tr, statDict, "pws34int", pws34intStats) - self._updateStatsForPwsXXint(tr, statDict, "pws64int", pws64intStats) - - self._updateStatsForPwsTXX(tr, statDict, "pwsD34", "pwsN34", pwsT34Stats, events34, period) - self._updateStatsForPwsTXX(tr, statDict, "pwsD64", "pwsN64", pwsT64Stats, events64, period) - - wind = self._textProduct._getStatValue(statDict, "Wind", "Max", self._textProduct.VECTOR()) - if wind is not None: - if wind >= 34: - events34.windXXEvent = True - if wind >= 64: - events64.windXXEvent = True - else: - events64.windXXEvent = False - else: - events34.windXXEvent = False - events64.windXXEvent = False - - if self._maxWind is None or wind >= self._maxWind: - self._maxWind = wind - - self._updateWindTimeInfo(tr, wind34timeInfo, speed=34) - self._updateWindTimeInfo(tr, wind64timeInfo, speed=64) - - windGust = self._textProduct._getStatValue(statDict, "WindGust", "Max") - if windGust is not None: - if self._maxGust is None or windGust > self._maxGust: - self._maxGust = windGust - - self._updateThreatStats(tr, statDict, "WindThreat") - - #Tropical Storm - onsetEndInfo = self._computeWindOnsetAndEnd(wind34timeInfo, pws34intStats, pwsT34Stats) - self._onset34Hour = onsetEndInfo.onsetHour - self._end34Hour = onsetEndInfo.endHour - - nonEnding34Event = False - if events34.pwsTXXEvent and (wind34timeInfo.endHour is None or events34.windXXEvent): - nonEnding34Event = True - - print "SARAH: Tropical Storm Window:" - self._windowTS = self._createWindow("Tropical Storm", - self._onset34Hour, - self._end34Hour, - nonEnding34Event) - - #Hurricane - onsetEndInfo = self._computeWindOnsetAndEnd(wind64timeInfo, pws64intStats, pwsT64Stats) - self._onset64Hour = onsetEndInfo.onsetHour - self._end64Hour = onsetEndInfo.endHour - - nonEnding64Event = False - if events64.pwsTXXEvent and (wind64timeInfo.endHour is None or events64.windXXEvent): - nonEnding64Event = True - - print "SARAH: Hurricane Window:" - self._windowHU = self._createWindow("Hurricane", - self._onset64Hour, - self._end64Hour, - nonEnding64Event) - - self._currentAdvisory["WindThreat"] = self._maxThreat - self._currentAdvisory["WindForecast"] = self._maxWind - - def _updateStatsForPwsXXint(self, tr, statDict, gridName, pwsXXintStats): - pwsXXint = self._textProduct._getStatValue(statDict, gridName, "Max") - - if pwsXXint is not None: - if pwsXXintStats.max is None or pwsXXint > pwsXXintStats.max: - pwsXXintStats.max = pwsXXint - pwsXXintStats.onsetHour = self._calculateHourOffset(tr.startTime()) - - print "SARAH: Window Debug: pwsXXintStats gridName =", gridName - print "SARAH: Window Debug: pwsXXintStats pwsXXint =", pwsXXint - print "SARAH: Window Debug: pwsXXintStats tr =", tr - print "SARAH: Window Debug: pwsXXintStats onsetHour =", pwsXXintStats.onsetHour - - def _updateStatsForPwsTXX(self, tr, statDict, dayGridName, nightGridName, pwsTXXStats, events, period): - - # Convert this time to locatime - trStartLocalHour = time.localtime(tr.startTime().unixTime()).tm_hour - dayStartHour = self._textProduct.DAY() - nightStartHour = self._textProduct.NIGHT() - print "MATT _updateStatsForPwsTXX = %s localStartHr = %d" % (repr(tr), - trStartLocalHour) - print "MATT dayStart = %s nightStart = %s" % (repr(dayStartHour), - repr(nightStartHour)) - - pwsDXX = self._textProduct._getStatValue(statDict, dayGridName, "Max") - pwsNXX = self._textProduct._getStatValue(statDict, nightGridName, "Max") - maxPws = None - print "MATT pwsDXX = %s pwsNXX = %s " % (repr(pwsDXX), repr(pwsNXX)) - -# if pwsDXX is not None: -# print "SARAH: Window Debug: pwsTXXStats DAY" -# maxPws = pwsDXX -# elif pwsNXX is not None: -# print "SARAH: Window Debug: pwsTXXStats NIGHT" -# maxPws = pwsNXX - - # SARAH - if we are close to the end of a day/night period, the first - # period we would really want to consider would be the next period. - # This is hard-coded to 3 hours to prove the concept. - if (nightStartHour >= trStartLocalHour and \ - (nightStartHour - trStartLocalHour) <= 3) or pwsDXX is None: - print "MATT: Window Debug: pwsTXXStats NIGHT" - maxPws = pwsNXX - elif (dayStartHour >= trStartLocalHour and \ - (dayStartHour - trStartLocalHour) <= 3) or pwsNXX is None: - print "MATT: Window Debug: pwsTXXStats DAY" - maxPws = pwsDXX - - threshold34index = 0 - threshold64index = 1 - if maxPws is not None: - if "64" in dayGridName: - index = threshold64index - else: #if "34" - index = threshold34index - - threshold = None - thresholds = self.windSpdProb_thresholds() - if period == 0: - (thresholdLow, thresholdHigh) = thresholds[period][index] - threshold = thresholdLow - else: - if period >= 10: # SARAH: TODO - remove??? - period = 9 - threshold = thresholds[period][index] - - if maxPws > threshold: - events.pwsTXXEvent = True - - configuredEndTime = self._getCorrespondingConfiguredTime(tr.endTime(), isOnset = False) - pwsTXXStats.endHour = self._calculateHourOffset(configuredEndTime) - - print "SARAH: Window Debug: pwsTXXStats dayGridName =", dayGridName - print "SARAH: Window Debug: pwsTXXStats nightGridName =", nightGridName - print "SARAH: Window Debug: pwsTXXStats original tr =", tr - print "SARAH: Window Debug: pwsTXXStats maxPws =", maxPws - print "SARAH: Window Debug: pwsTXXStats endHour =", pwsTXXStats.endHour - - if pwsTXXStats.onsetHour is None: - configuredStartTime = self._getCorrespondingConfiguredTime(tr.startTime(), isOnset = True) - pwsTXXStats.onsetHour = self._calculateHourOffset(configuredStartTime) - - print "SARAH: Window Debug: pwsTXXStats dayGridName =", dayGridName - print "SARAH: Window Debug: pwsTXXStats nightGridName =", nightGridName - print "SARAH: Window Debug: pwsTXXStats original tr =", tr - print "SARAH: Window Debug: pwsTXXStats maxPws =", maxPws - print "SARAH: Window Debug: pwsTXXStats onsetHour =", pwsTXXStats.onsetHour - else: - events.pwsTXXEvent = False - - def _getCorrespondingConfiguredTime(self, gmtTime, isOnset): - dayStartHour = self._textProduct.DAY() - nightStartHour = self._textProduct.NIGHT() - - print "SARAH: gmtTime =", gmtTime - - gmtSeconds = gmtTime.unixTime() - localTime = time.localtime(gmtSeconds) - print "SARAH: localTime =", localTime - - localHour = localTime.tm_hour - print "SARAH: localHour =", localHour - - if isOnset: - print "SARAH: Window Debug: Adjusting start time" - else: - print "SARAH: Window Debug: Adjusting end time" - - newHour = None - if localHour < dayStartHour: - if isOnset: - # Subtract 24 hours to get to the previous day - newGmtTime = gmtTime - 24*60*60 - gmtSeconds = newGmtTime.unixTime() - localTime = time.localtime(gmtSeconds) - print "SARAH: new localTime =", localTime - - newHour = nightStartHour - else: - newHour = dayStartHour - elif dayStartHour <= localHour and localHour < nightStartHour: - if isOnset: - newHour = dayStartHour - else: - newHour = nightStartHour - else: - if isOnset: - newHour = nightStartHour - else: - # Add 24 hours to get to the next day - newGmtTime = gmtTime + 24*60*60 - gmtSeconds = newGmtTime.unixTime() - localTime = time.localtime(gmtSeconds) - print "SARAH: new localTime =", localTime - - newHour = dayStartHour - - print "SARAH: new localHour =", localHour - - newTimeTuple = localTime[:3] + (newHour,) + localTime[4:] - import calendar - seconds = calendar.timegm(newTimeTuple) - adjustedGmtTime = AbsTime(seconds) - print "SARAH: new local time =", adjustedGmtTime - - seconds = time.mktime(newTimeTuple) - adjustedGmtTime = AbsTime(seconds) - print "SARAH: new GMT time =", adjustedGmtTime - return adjustedGmtTime - - # SARAH - we don't want this here. Use the inherited version from the - # VectorRelatedPhrases module instead. This way, changes only need to be - # made in one place. - def windSpdProb_thresholds(self): - return [ - ((45.0, 80.0), (25.0, 60.0)), # Per 1 - (35.0, 20.0), # Per 2 - (30.0, 15.0), # Per 3 - (25.0, 12.5), # Per 4 - (22.5, 10.0), # Per 5 - (20.0, 8.0), # Per 6 - (17.5, 7.0), # Per 7 - (15.0, 6.0), # Per 8 - (12.5, 5.0), # Per 9 - (10.0, 4.0), # Per 10 - ] - - def _updateWindTimeInfo(self, tr, timeInfo, speed): - if self._maxWind is not None and self._maxWind >= speed: - timeInfo.endHour = self._calculateHourOffset(tr.endTime()) - - print "SARAH: Window Debug: timeInfo speed =", speed - print "SARAH: Window Debug: timeInfo maxWind =", self._maxWind - print "SARAH: Window Debug: timeInfo tr =", tr - print "SARAH: Window Debug: timeInfo endHour =", timeInfo.endHour - - if timeInfo.onsetHour is None: - timeInfo.onsetHour = self._calculateHourOffset(tr.startTime()) - - print "SARAH: Window Debug: timeInfo speed =", speed - print "SARAH: Window Debug: timeInfo maxWind =", self._maxWind - print "SARAH: Window Debug: timeInfo tr =", tr - print "SARAH: Window Debug: timeInfo onsetHour =", timeInfo.onsetHour - - def _computeWindOnsetAndEnd(self, windTimeInfo, pwsXXintStats, pwsTXXStats): - onsetEndInfo = self.TimeInfo() - - print "SARAH: Window Debug: windTimeInfo.onsetHour =", windTimeInfo.onsetHour - print "SARAH: Window Debug: pwsTXXStats.onsetHour =", pwsTXXStats.onsetHour - print "SARAH: Window Debug: pwsXXintStats.onsetHour =", pwsXXintStats.onsetHour - print "SARAH: Window Debug: windTimeInfo.endHour =", windTimeInfo.endHour - print "SARAH: Window Debug: pwsTXXStats.endHour =", pwsTXXStats.endHour - - if windTimeInfo.onsetHour is None: -# print "SARAH: Window Debug: windTimeInfo.onsetHour was None; using pwsTXXStats" -# windTimeInfo.onsetHour = pwsTXXStats.onsetHour -# print "SARAH: Window Debug: pwsTXXStats.onsetHour =", pwsTXXStats.onsetHour - - # Short-circuit this logic as a temporary measure. Basically, do - # not include a window if the deterministic winds do not support - # a particular threshold - onsetEndInfo.endHour = None - - if windTimeInfo.onsetHour is not None and pwsXXintStats.onsetHour is not None: - print "SARAH: Window Debug: windTimeInfo.onsetHour & pwsXXintStats.onsetHour not None; taking min" - onsetEndInfo.onsetHour = min(windTimeInfo.onsetHour, pwsXXintStats.onsetHour) - print "SARAH: Window Debug: min onsetHour =", onsetEndInfo.onsetHour - - if onsetEndInfo.onsetHour is not None: - if windTimeInfo.endHour is None: - print "SARAH: Window Debug: windTimeInfo.endHour was None; using pwsTXXStats" - onsetEndInfo.endHour = pwsTXXStats.endHour - print "SARAH: Window Debug: pwsTXXStats.endHour =", pwsTXXStats.endHour - elif pwsTXXStats.endHour is not None: - print "SARAH: windendHour =", windTimeInfo.endHour - print "SARAH: probendHour =", pwsTXXStats.endHour - onsetEndInfo.endHour = int(round(self._textProduct.average(windTimeInfo.endHour, pwsTXXStats.endHour))) - print "SARAH: endHour =", onsetEndInfo.endHour - return onsetEndInfo - - def _createWindow(self, windowName, onsetHour, endHour, nonEndingEvent): - window = "Window for " + windowName + " force winds: " - print "SARAH: window stats:" - print "SARAH: onsetHour =", onsetHour - print "SARAH: endHour =", endHour - print "SARAH: window nonEndingEvent =", nonEndingEvent - - if onsetHour is None: - - # SARAH - we do not want a statement of a non-existent window -# window += "None" - window = None - else: - startTime = AbsTime(self._textProduct._issueTime_secs + onsetHour*60*60) - if endHour is not None: - endTime = AbsTime(self._textProduct._issueTime_secs + endHour*60*60) - windowPeriod = self._textProduct.makeTimeRange(startTime, endTime) - else: - windowPeriod = self._textProduct.makeTimeRange(startTime, startTime + 1) - print "SARAH: window period =", windowPeriod - - startTimeDescriptor = "" - if onsetHour >= 18: - startTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, resolution = 6) - elif 6 <= onsetHour and onsetHour < 18: - startTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, resolution = 3) - - if endHour is None or nonEndingEvent: - if len(startTimeDescriptor) != 0: - window += "Begins " + startTimeDescriptor - else: - window += "None" - else: - connector = "through " - endTimeDescriptor = "the next few hours" - - if endHour >= 18: - endTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, - useEndTime = True, - resolution = 6) - elif 6 <= endHour and endHour < 18: - endTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, - useEndTime = True, - resolution = 3) - - if len(startTimeDescriptor) != 0: - connector = " " + connector - window += startTimeDescriptor + connector + endTimeDescriptor - - return window - - -class StormSurgeSectionStats(SectionCommonStats): - def __init__(self, textProduct, segment, intersectStatList, timeRangeList): - SectionCommonStats.__init__(self, textProduct, segment) - self._inundationMax = None - self._onsetSurgeHour = None - self._endSurgeHour = None - self._windowSurge = None - - self._setStats(intersectStatList, timeRangeList) - - def _setStats(self, statList, timeRangeList): - phishStartTime = None - phishEndTime = None - possibleStop = 0 - -# print "*"*100 -# print "MATT phishStartTime = %s phishEndTime = %s possibleStop = %d" % (str(phishStartTime), str(phishEndTime), possibleStop) - - for period in range(len(statList)): - tr, _ = timeRangeList[period] - statDict = statList[period] - - phishPeak = self._textProduct._getStatValue(statDict, "InundationMax", "Max") - if phishPeak is not None: - if self._inundationMax is None or phishPeak > self._inundationMax: - self._inundationMax = phishPeak - - curPhish = self._textProduct._getStatValue(statDict, "InundationTiming", "Max") -# print "MATT tr = %s" % (repr(tr)) -# print "MATT curPhish = '%s' possibleStop = %d" % (str(curPhish), possibleStop) -# print "MATT phishStartTime = %s phishEndTime = %s" % (str(phishStartTime), str(phishEndTime)) - - if curPhish is not None and possibleStop != 2: - if curPhish > 0: - if phishStartTime is None: - phishStartTime = tr.startTime() - possibleStop = 0 - phishEndTime = None - elif phishStartTime is not None: - possibleStop += 1 - - if phishEndTime is None: - phishEndTime = tr.startTime() - - self._updateThreatStats(tr, statDict, "StormSurgeThreat") - - self._windowSurge = "Window for Storm Surge Inundation: " - - if phishStartTime is None: - self._windowSurge += "None" - else: - self._onsetSurgeHour = self._calculateHourOffset(phishStartTime) - startTime = AbsTime(self._textProduct._issueTime_secs + self._onsetSurgeHour*60*60) - -# print "MATT surge startTime = %s self._onsetSurgeHour = %s " % (repr(startTime), self._onsetSurgeHour) - if phishEndTime is not None: - self._endSurgeHour = self._calculateHourOffset(phishEndTime) - endTime = AbsTime(self._textProduct._issueTime_secs + self._endSurgeHour*60*60) - windowPeriod = self._textProduct.makeTimeRange(startTime, endTime) - else: - windowPeriod = self._textProduct.makeTimeRange(startTime, startTime + 1) - print "SARAH: window period =", windowPeriod - - startTimeDescriptor = self._textProduct._formatPeriod(windowPeriod) - - if phishEndTime is None: - self._windowSurge += "Begins " + startTimeDescriptor - elif phishStartTime == phishEndTime: - self._windowSurge += startTimeDescriptor - else: - endTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, useEndTime = True) - - if self._onsetSurgeHour > 12: - self._windowSurge += startTimeDescriptor +\ - " through " +\ - endTimeDescriptor - else: - self._windowSurge += "through " + endTimeDescriptor - - self._currentAdvisory["StormSurgeThreat"] = self._maxThreat - if self._inundationMax is not None: - # Round so we don't store values like 1.600000023841858 - self._currentAdvisory["StormSurgeForecast"] = \ - int(self._inundationMax * 10.0) / 10.0 - - -class FloodingRainSectionStats(SectionCommonStats): - def __init__(self, textProduct, segment, statList, timeRangeList): - SectionCommonStats.__init__(self, textProduct, segment) - self._sumAccum = None - - self._setStats(statList, timeRangeList) - - def _setStats(self, statList, timeRangeList): - for period in range(len(statList)): - tr, _ = timeRangeList[period] - statDict = statList[period] - - stats = self._textProduct.getStats(statDict, "QPF") - if stats is not None: - for (value, tr) in stats: - - if value is not None: - if self._sumAccum is None: - self._sumAccum = value - else: - self._sumAccum += value - - self._updateThreatStats(tr, statDict, "FloodingRainThreat") - - self._currentAdvisory["FloodingRainThreat"] = self._maxThreat - if self._sumAccum is not None: - # Round so that we don't end up with stats like 4.03143835067749 - self._currentAdvisory["FloodingRainForecast"] = \ - self._textProduct.round(self._sumAccum, "Nearest", 0.5) - - -class TornadoSectionStats(SectionCommonStats): - def __init__(self, textProduct, segment, statList, timeRangeList): - SectionCommonStats.__init__(self, textProduct, segment) - - self._setStats(statList, timeRangeList) - - def _setStats(self, statList, timeRangeList): - for period in range(len(statList)): - tr, _ = timeRangeList[period] - statDict = statList[period] - - self._updateThreatStats(tr, statDict, "TornadoThreat") - - self._currentAdvisory["TornadoThreat"] = self._maxThreat - - import Tkinter class Common_Dialog(Dialog): def __init__(self, parent, title, infoDict=None): diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/TextUtils.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/TextUtils.py index 91a92f5166..e4016803ff 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/TextUtils.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/TextUtils.py @@ -781,8 +781,8 @@ class TextUtils: self._debugDict[name] = count # Print the traceback message - print "\n\tDEBUG:",name, "in", file, "at line",\ - lineno,"Class=", self.__class__, count + print "DEBUG: %s in %s at line %d" % (name, file, lineno) + print "DEBUG: Class = %s %d\n\n" % (self.__class__, count) #print "Super classes:",self.__class__.__bases__ # If there is a message, print that too diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/preferences/configureTextProducts.py b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/preferences/configureTextProducts.py index fa01a14036..3234dfc855 100644 --- a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/preferences/configureTextProducts.py +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/preferences/configureTextProducts.py @@ -1,19 +1,19 @@ ## # 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 +# 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 -# +# 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. ## @@ -51,8 +51,9 @@ DirectFileToProductMapping = { 'PublicMarine_EA_Site_MultiPil_Definition': 'AFD', 'PublicMarineFireWx_EA_Site_MultiPil_Definition': 'AFD', 'FireWxZones_EA_Site_MultiPil_Definition': 'RFW', - 'MarineZones_EA_Site_MultiPil_Definition': ['CFW', 'MWW'], + 'MarineZones_EA_Site_MultiPil_Definition': 'MWW', 'PublicZones_EA_Site_MultiPil_Definition': ['NPW','WSW','CFW','FFA','AQA'], + 'Hazard_TCV': 'TCV', 'Hazard_HLS': 'HLS', } @@ -62,20 +63,19 @@ NWSProducts = ['ADR', 'AFD', 'AFM', 'AQA', 'AVA', 'AVW', 'CAE', 'CCF', 'CDW', 'C 'CFW', 'CWF', 'EQR', 'EQW', 'ESF', 'EVI', 'FFA', 'FRW', 'FWF', 'FWM', 'FWS', 'GLF', 'HLS', 'HMW', 'HWO', 'LAE', 'LEW', 'MWS', 'MVF', 'NOW', 'NPW', 'NSH', 'NUW', 'OFF', 'PFM', 'PNS', 'RFD', - 'RFW', 'RHW', 'SAF', 'SFT', 'SPS', 'SPW', 'SRF', 'TOE', 'VOW', - 'WCN', 'WSW', 'ZFP', 'MWW'] + 'RFW', 'RHW', 'SAF', 'SFT', 'SPS', 'SPW', 'SRF', 'TCV', 'TOE', + 'VOW', 'WCN', 'WSW', 'ZFP', 'MWW'] #Templated files. Named with "Product" in them, will be replaced with the #actual product name. Dictionary contains the template filename, list #contains the products to be generated (e.g., AFM). These products #follow the Baseline, Region, Site technique. -templateProds= ['AFM', 'ZFP', 'CCF', 'CWF', 'CWF_Pacific', 'FWF', - 'FWFTable', 'FWM', 'GLF', 'MVF', 'NSH', 'PFM', 'SFT', 'SRF', 'OFF', 'AFD', - 'Hazard_HLS'] -templateProdsWsaf= ['AFM', 'ZFP', 'CCF', 'CWF', 'CWF_Pacific', 'FWF', +templateProds= ['AFM', 'ZFP', 'CCF', 'CWF', 'CWF_Pacific', 'FWF', 'HLS', + 'FWFTable', 'FWM', 'GLF', 'MVF', 'NSH', 'PFM', 'SFT', 'SRF', 'OFF', 'AFD'] +templateProdsWsaf= ['AFM', 'ZFP', 'CCF', 'CWF', 'CWF_Pacific', 'FWF', 'HLS', 'FWFTable', 'FWM', 'GLF', 'MVF', 'NSH', 'PFM', 'SFT', 'SRF', 'OFF', 'AFD', 'SAF', - 'FWS', 'Hazard_HLS'] + 'FWS', 'Hazard_TCV', 'Hazard_HLS'] templateProds_minus_HLS = ['AFM', 'ZFP', 'CCF', 'CWF', 'CWF_Pacific', 'FWF', 'FWFTable', 'FWM', 'GLF', 'MVF', 'NSH', 'PFM', 'SFT', 'SRF', 'OFF', 'AFD'] TemplatedProducts = { @@ -85,3 +85,4 @@ TemplatedProducts = { 'Product_Region_Overrides': templateProdsWsaf, 'Product_Site_Overrides': templateProdsWsaf, } + diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py index e655279863..a4033b42b7 100644 --- a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py @@ -20,20 +20,20 @@ class TextProduct(HLSTCV_Common.TextProduct): Definition["outputFile"] = "{prddir}/TEXT/HLS.txt" Definition["database"] = "Official" # Source database Definition["debug"] = 1 - Definition["mapNameForCombinations"] = "Zones_" - Definition["defaultEditAreas"] = "EditAreas_PublicMarine_" - Definition["showZoneCombiner"] = 1 # 1 to cause zone combiner to display + Definition["mapNameForCombinations"] = "Zones_MFL" + Definition["defaultEditAreas"] = "Combinations_HLS_MFL" + Definition["showZoneCombiner"] = 0 # 1 to cause zone combiner to display Definition["productName"] = "LOCAL STATEMENT" - Definition["fullStationID" ] = "" - Definition["wmoID" ] = "" - Definition["wfoCityState" ] = "" - Definition["pil" ] = "" - Definition["textdbPil" ] = "" - Definition["awipsWANPil" ] = "" - Definition["site"] = "" - Definition["wfoCity"] = "" + Definition["fullStationID" ] = "KMFL" + Definition["wmoID" ] = "WTUS82" + Definition["wfoCityState" ] = "MIAMI FL" + Definition["pil" ] = "HLSMIA" + Definition["textdbPil" ] = "MIAHLSMIA" + Definition["awipsWANPil" ] = "KMFLHLSMIA" + Definition["site"] = "MFL" + Definition["wfoCity"] = "MIAMI" Definition["areaName"] = "" #optional area name for product Definition["areaDictionary"] = "AreaDictionary" @@ -173,13 +173,16 @@ class TextProduct(HLSTCV_Common.TextProduct): analysisList = [ # Wind Section ("WindThreat", self.rankedDiscreteValue), + ("WindThreat", self.mostSignificantDiscreteValue), # Flooding Rain Section ("QPFtoFFGRatio", self.moderatedMax, [6]), ("FloodingRainThreat", self.rankedDiscreteValue), + ("FloodingRainThreat", self.mostSignificantDiscreteValue), # Tornado Section ("TornadoThreat", self.rankedDiscreteValue), + ("TornadoThreat", self.mostSignificantDiscreteValue), ] return analysisList @@ -198,6 +201,7 @@ class TextProduct(HLSTCV_Common.TextProduct): analysisList = [ ("InundationMax", self.moderatedMax, [6]), ("StormSurgeThreat", self.rankedDiscreteValue), + ("StormSurgeThreat", self.mostSignificantDiscreteValue), ] return analysisList @@ -223,7 +227,7 @@ class TextProduct(HLSTCV_Common.TextProduct): if self._ImpactsAnticipated: includedImpacts = sorted(self._IncludedImpacts, key=self._impactsKeyFunction) for ((_, sectionName), _) in includedImpacts: - print "SARAH: adding section", sectionName + print "SARAH: adding section = '%s'" % (sectionName) partsList.append(sectionName) partsList.append('preparednessSection') @@ -249,7 +253,7 @@ class TextProduct(HLSTCV_Common.TextProduct): return int(indexStr) def _ugcHeader(self, productDict, productSegmentGroup, productSegment): - self._ugcs = self._allAreas() + self._ugcs = self._allAreas() productDict['ugcCodes'] = self._formatUGC_entries() self._ugcHeader_value = self._tpc.formatUGCs(self._ugcs, self._expireTime) productDict['ugcHeader'] = self._ugcHeader_value @@ -334,7 +338,7 @@ class TextProduct(HLSTCV_Common.TextProduct): # If there is only one impact across the entire CWA, and it is the max if impactMax != "none" and impactMin == impactMax and inputThreatDominant != "None": - sectionDict['impactRange'] = "Prepare for " + impactMax + " damage across " + self._cwa() + "." + sectionDict['impactRange'] = "Prepare for " + impactMax + " damage across " + self._cwa_descriptor() + "." # Handle the case where the impacts are not the same across the entire CWA else: sectionDict['variedImpacts'] = True @@ -344,14 +348,27 @@ class TextProduct(HLSTCV_Common.TextProduct): # If there are additional areas if impactRange != impactMax: - sectionDict['additionalImpactRange'].append("Prepare for " + - impactRange + - " damage across " + - self._frame("ENTER AREA DESCRIPTION") + ".") + + curPhrase = "Prepare for %s damage across %s." % \ + (impactRange, self._frame("ENTER AREA DESCRIPTION")) + + # If this phrase is not already part of the additional impacts + if curPhrase not in sectionDict['additionalImpactRange']: + + # Add it now + sectionDict['additionalImpactRange'].append(curPhrase) # If there is no impact across more than one half the area, include a statement for that as well if inputThreatDominant == "None": - sectionDict['additionalImpactRange'].append("Elsewhere across " + self._cwa() + ", little to no impact is anticipated.") + + curPhrase = "Elsewhere across " + self._cwa_descriptor() + \ + ", little to no impact is anticipated." + + # If this phrase is not already part of the additional impacts + if curPhrase not in sectionDict['additionalImpactRange']: + + # Add it now + sectionDict['additionalImpactRange'].append(curPhrase) productDict['windSection'] = sectionDict @@ -384,7 +401,7 @@ class TextProduct(HLSTCV_Common.TextProduct): sectionDict['impactRange'] = "Prepare for " + \ lifeThreatening + impactMax + \ - " damage in surge prone areas of " + self._cwa() + ", with the greatest impacts " + \ + " damage in surge prone areas of " + self._cwa_descriptor() + ", with the greatest impacts " + \ self._frame("ENTER AREA DESCRIPTION") + "." sectionDict['impactLib'] = self._getPotentialImpactsStatements("Storm Surge", self._impactCategoryToThreatLevel(impactMax)) @@ -414,20 +431,40 @@ class TextProduct(HLSTCV_Common.TextProduct): # If there are additional life-threatening surge areas if impactRange != impactMax and impactRange != impactMin: - sectionDict['additionalImpactRange'].append("Brace for " + - lifeThreatening + impactRange + - " damage " + self._frame("ENTER AREA DESCRIPTION")) + + curPhrase = "Brace for %s%s damage across %s." % \ + (lifeThreatening, impactRange, self._frame("ENTER AREA DESCRIPTION")) + + # If this phrase is not already part of the additional impacts + if curPhrase not in sectionDict['additionalImpactRange']: + + # Add it now + sectionDict['additionalImpactRange'].append(curPhrase) # If there are additional areas if impactRangeRest != impactMax: - sectionDict['additionalImpactRange'].append("Prepare for " + - impactRangeRest + - " damage from storm surge " + self._frame("ENTER AREA DESCRIPTION")) + + curPhrase = "Prepare for %s damage from storm surge across %s." % \ + (impactRangeRest, self._frame("ENTER AREA DESCRIPTION")) + + # If this phrase is not already part of the additional impacts + if curPhrase not in sectionDict['additionalImpactRange']: + + # Add it now + sectionDict['additionalImpactRange'].append(curPhrase) # If there is no impact across more than one half the area, include a statement for that as well if inputThreatDominant == "None": - sectionDict['additionalImpactRange'].append("Elsewhere across " + self._cwa() + ", little to no impact is anticipated.") - + + curPhrase = "Elsewhere across " + self._cwa_descriptor() + \ + ", little to no impact is anticipated." + + # If this phrase is not already part of the additional impacts + if curPhrase not in sectionDict['additionalImpactRange']: + + # Add it now + sectionDict['additionalImpactRange'].append(curPhrase) + productDict['surgeSection'] = sectionDict def _floodingRainSection(self, productDict, productSegmentGroup, productSegment): @@ -443,6 +480,9 @@ class TextProduct(HLSTCV_Common.TextProduct): impactRange = self._samplingDict['FloodingRainThreat']['impactRange'] inputThreatDominant = self._samplingDict['FloodingRainThreat']['inputThreatDominant'] + self.debug_print("In _floodingRainSection", 1) + self.debug_print("_samplingDict = %s" % (repr(self._samplingDict['FloodingRainThreat'])), 1) + # Test the simplest case first if impactMin == "none" and impactMax == "none": sectionDict['impactRange'] = impactRange @@ -452,7 +492,7 @@ class TextProduct(HLSTCV_Common.TextProduct): # If there is only one impact across the entire CWA, and it is the max if impactMax != "none" and impactMin == impactMax and inputThreatDominant != "None": - sectionDict['impactRange'] = "Prepare for " + impactMax + " flooding across " + self._cwa() + "." + sectionDict['impactRange'] = "Prepare for " + impactMax + " flooding across " + self._cwa_descriptor() + "." # Handle the case where the impacts are not the same across the entire CWA else: sectionDict['variedImpacts'] = True @@ -462,14 +502,27 @@ class TextProduct(HLSTCV_Common.TextProduct): # If there are additional areas if impactRange != impactMax: - sectionDict['additionalImpactRange'].append("Prepare for " + - impactRange + - " flooding impacts " + - self._frame("ENTER AREA DESCRIPTION") + ".") + + curPhrase = "Prepare for %s flooding impacts across %s." % \ + (impactRange, self._frame("ENTER AREA DESCRIPTION")) + + # If this phrase is not already part of the additional impacts + if curPhrase not in sectionDict['additionalImpactRange']: + + # Add it now + sectionDict['additionalImpactRange'].append(curPhrase) # If there is no impact across more than one half the area, include a statement for that as well if inputThreatDominant == "None": - sectionDict['additionalImpactRange'].append("Elsewhere across " + self._cwa() + ", little to no impact is anticipated.") + + curPhrase = "Elsewhere across " + self._cwa_descriptor() + \ + ", little to no impact is anticipated." + + # If this phrase is not already part of the additional impacts + if curPhrase not in sectionDict['additionalImpactRange']: + + # Add it now + sectionDict['additionalImpactRange'].append(curPhrase) productDict['floodingRainSection'] = sectionDict @@ -517,7 +570,7 @@ class TextProduct(HLSTCV_Common.TextProduct): # If there is only one impact across the entire CWA, and it is the max if impactMax != "none" and impactMin == impactMax and inputThreatDominant != "None": - sectionDict['impactRange'] = "Prepare for " + impactMax + " damage across " + self._cwa() + "." + sectionDict['impactRange'] = "Prepare for " + impactMax + " damage across " + self._cwa_descriptor() + "." # Handle the case where the impacts are not the same across the entire CWA else: sectionDict['variedImpacts'] = True @@ -527,14 +580,27 @@ class TextProduct(HLSTCV_Common.TextProduct): # If there are additional areas if impactRange != impactMax: - sectionDict['additionalImpactRange'].append("Prepare for " + - impactRange + - " damage " + - self._frame("ENTER AREA DESCRIPTION") + ".") + + curPhrase = "Prepare for %s damage across %s." % \ + (impactRange, self._frame("ENTER AREA DESCRIPTION")) + + # If this phrase is not already part of the additional impacts + if curPhrase not in sectionDict['additionalImpactRange']: + + # Add it now + sectionDict['additionalImpactRange'].append(curPhrase) # If there is no impact across more than one half the area, include a statement for that as well if inputThreatDominant == "None": - sectionDict['additionalImpactRange'].append("Elsewhere across " + self._cwa() + ", little to no impact is anticipated.") + + curPhrase = "Elsewhere across " + self._cwa_descriptor() + \ + ", little to no impact is anticipated." + + # If this phrase is not already part of the additional impacts + if curPhrase not in sectionDict['additionalImpactRange']: + + # Add it now + sectionDict['additionalImpactRange'].append(curPhrase) productDict['tornadoSection'] = sectionDict @@ -565,7 +631,7 @@ class TextProduct(HLSTCV_Common.TextProduct): actionsDict['title'] = "Other Preparedness Information" import TCVDictionary - actionsDict['actions'] = TCVDictionary.OtherPreparednessActions + actionsDict['actions'] = TCVDictionary.OtherPreparednessActions[self._GeneralOnsetTime] productDict['otherPreparednessActions'] = actionsDict @@ -581,15 +647,15 @@ class TextProduct(HLSTCV_Common.TextProduct): def _nextUpdate(self, productDict, productSegmentGroup, productSegment): if self._NextUpdate == "LastIssuance" or not self._ImpactsAnticipated: productDict['nextUpdate'] = "As it pertains to this event...this will be the last local statement issued by the National Weather Service in " + \ - self._cwa() + \ + self._wfoCityState + \ " regarding the effects of tropical cyclone hazards upon the area." elif self._NextUpdate == "Conditions": productDict['nextUpdate'] = "The next local statement will be issued by the National Weather Service in " + \ - self._cwa() + \ + self._wfoCityState + \ " as conditions warrant." elif self._NextUpdate == "Enter": productDict['nextUpdate'] = "The next local statement will be issued by the National Weather Service in " + \ - self._cwa() + \ + self._wfoCityState + \ " around " + self._NextUpdate_entry.strip() + ", or sooner if conditions warrant." def _getPotentialImpactsStatements(self, elementName, maxThreat): @@ -621,6 +687,10 @@ class TextProduct(HLSTCV_Common.TextProduct): if error is not None: return error + # Determine time ranges + self._initializeTimeVariables(argDict) + self._determineTimeRanges(argDict) + error = self._initializeStormInformation() if error is not None: return error @@ -629,41 +699,25 @@ class TextProduct(HLSTCV_Common.TextProduct): if self._stormName is None or self._stormName.strip() == "": return "Could not determine the storm name" + self._loadLastTwoAdvisories() + if self._previousAdvisory is None: + return "A TCV must be transmitted before an HLS can be run" + self._initializeHeadlines() self._initializeHazardsTable(argDict) self._determineHazardStates() - # Determine time ranges - self._initializeTimeVariables(argDict) - self._determineTimeRanges(argDict) - - # Sample the data - self._initializeSamplingDict() - - previousAdvisory = self._loadAdvisory("previous") - if previousAdvisory is not None: - self._sampleTCVAdvisory(previousAdvisory) - else: - self._segmentList = self._determineSegments() - print "Segment Information: ", self._segmentList, "\n\n" - if len(self._segmentList) == 0: - return "NO HAZARDS TO REPORT" + if self._ImpactsAnticipated: + # Sample the data + self._initializeSamplingDict() - self._initializeAdvisories() - self._sampleTCVData(argDict) - for segment in self._segmentList: - self._initializeSegmentZoneData(segment) - self._getTCVStats(argDict, segment, self._editAreaDict, self._timeRangeList) - - self._sampleTCVAdvisory(self._currentAdvisory) - - self._sampleHLSData(argDict) - - for threatName in ['WindThreat', 'StormSurgeThreat', 'FloodingRainThreat', 'TornadoThreat']: - self._setHazardImpactCategories(threatName) - + self._sampleHLSData(argDict) + + for threatName in ['WindThreat', 'StormSurgeThreat', 'FloodingRainThreat', 'TornadoThreat']: + self._setHazardImpactCategories(threatName) + # Create the product dictionary and format it to create the output productDict = self._createProductDictionary() productOutput = self._formatProductDictionary(productDict) @@ -672,16 +726,14 @@ class TextProduct(HLSTCV_Common.TextProduct): def _determineHazardStates(self): - hazardTable = self._argDict["hazards"] - hazardsList = hazardTable.getHazardList(self._allAreas()) self._currentHazardsList = [] self._changesHazardsList = [] - for hazard in hazardsList: - if hazard['act'] == 'CON': - self._currentHazardsList.append(hazard) - else: + + for hazard in self._previousAdvisory["HazardsForHLS"]: + print "SARAH DEBUG Hazard: %s" % (repr(hazard)) + if hazard['act'] != 'CON': self._changesHazardsList.append(hazard) - + self._currentHazardsList.append(hazard) ############################################################### ### Sampling and Statistics related methods @@ -698,18 +750,24 @@ class TextProduct(HLSTCV_Common.TextProduct): self._cwa()) for period in range(len(statList)): + + self.debug_print("=" * 100, 1) + self.debug_print("In _sampleHLSData for period %s (%s)" % \ + (period, repr(self._timeRangeList[period][0])), 1) + statDict = statList[period] for threatName in ['WindThreat', 'FloodingRainThreat', 'TornadoThreat']: - self._sampleThreatGrid(threatName, statDict) + self._sampleRankedDiscreteValue(threatName, statDict) + self._sampleMostSignificantDiscreteValue(threatName, statDict) qpfToFfgRatio = self._getStatValue(statDict, "QPFtoFFGRatio", "Max") decidingField = self._samplingDict['FloodingRainThreat']['decidingField'] if decidingField is None or qpfToFfgRatio > decidingField: self._samplingDict['FloodingRainThreat']['decidingField'] = qpfToFfgRatio - print "SARAH: WindThreat =", self._samplingDict['WindThreat']['inputThreatDominant'] - print "SARAH: FloodingRainThreat =", self._samplingDict['FloodingRainThreat']['inputThreatDominant'] - print "SARAH: TornadoThreat =", self._samplingDict['TornadoThreat']['inputThreatDominant'] + print "SARAH: WindThreat = %s" % (self._samplingDict['WindThreat']['inputThreatDominant']) + print "SARAH: FloodingRainThreat = %s" % (self._samplingDict['FloodingRainThreat']['inputThreatDominant']) + print "SARAH: TornadoThreat = %s" % (self._samplingDict['TornadoThreat']['inputThreatDominant']) @@ -744,14 +802,15 @@ class TextProduct(HLSTCV_Common.TextProduct): for period in range(len(statList)): statDict = statList[period] - self._sampleThreatGrid('StormSurgeThreat', statDict) + self._sampleRankedDiscreteValue('StormSurgeThreat', statDict) + self._sampleMostSignificantDiscreteValue('StormSurgeThreat', statDict) inundationMax = self._getStatValue(statDict, "InundationMax", "Max") decidingField = self._samplingDict['StormSurgeThreat']['decidingField'] if decidingField is None or inundationMax > decidingField: self._samplingDict['StormSurgeThreat']['decidingField'] = inundationMax - print "SARAH: StormSurgeThreat =", self._samplingDict['StormSurgeThreat']['inputThreatDominant'] + print "SARAH: StormSurgeThreat = %s" % (self._samplingDict['StormSurgeThreat']['inputThreatDominant']) def _createWholeDomainEditArea(self, argDict): editAreaUtils = EditAreaUtils.EditAreaUtils() @@ -765,16 +824,44 @@ class TextProduct(HLSTCV_Common.TextProduct): refData = ReferenceData(gridLoc, refID, grid2Dbit) editAreaUtils.saveEditAreas([refData]) - def _sampleThreatGrid(self, threatName, statDict): - rankedThreatLevels = self.getStats(statDict, threatName) - print "SARAH: sampling", threatName - print "SARAH: sampleData: rankedThreatLevels =", rankedThreatLevels - dominantThreatLevel = self._getDominantThreatLevel(threatName, rankedThreatLevels) - - currentDominantThreatLevel = self._samplingDict[threatName]['inputThreatDominant'] - self._samplingDict[threatName]['inputThreatDominant'] = self._getHighestThreat(threatName, - dominantThreatLevel, - currentDominantThreatLevel) + def _sampleMostSignificantDiscreteValue(self, threatName, statDict): + print "SARAH: _sampleMostSignificantDiscreteValue for %s" % (threatName) + threatLevel = self.getStats(statDict, threatName + "__mostSignificantDiscreteValue") + print "SARAH: threatLevel =", threatLevel + if threatLevel is not None: + inputThreatLow = self._samplingDict[threatName]['inputThreatLow'] + print "SARAH: current inputThreatLow =", inputThreatLow + if inputThreatLow is None: + self._samplingDict[threatName]['inputThreatLow'] = threatLevel + else: + self._samplingDict[threatName]['inputThreatLow'] = self._getLowestThreat(threatName, + threatLevel, + inputThreatLow) + print "SARAH: new inputThreatLow =", self._samplingDict[threatName]['inputThreatLow'] + + inputThreatHigh = self._samplingDict[threatName]['inputThreatHigh'] + print "SARAH: current inputThreatHigh =", inputThreatHigh + self._samplingDict[threatName]['inputThreatHigh'] = self._getHighestThreat(threatName, + threatLevel, + inputThreatHigh) + print "SARAH: new inputThreatHigh =", self._samplingDict[threatName]['inputThreatHigh'] + + def _sampleRankedDiscreteValue(self, threatName, statDict): + print "-" * 60 + print "_sampleRankedDiscreteValue statDict = %s" % (repr(statDict)) + rankedThreatLevels = self.getStats(statDict, threatName + "__rankedDiscreteValue") + print "SARAH: sampling %s" % (threatName) + print "SARAH: sampleData: rankedThreatLevels = %s" % (repr(rankedThreatLevels)) + if rankedThreatLevels is not None: + dominantThreatLevel = self._getDominantThreatLevel(threatName, rankedThreatLevels) + print "SARAH: dominantThreatLevel =", dominantThreatLevel + + currentDominantThreatLevel = self._samplingDict[threatName]['inputThreatDominant'] + print "SARAH: currentDominantThreatLevel =", currentDominantThreatLevel + self._samplingDict[threatName]['inputThreatDominant'] = self._getHighestThreat(threatName, + dominantThreatLevel, + currentDominantThreatLevel) + print "SARAH: new dominant =", self._samplingDict[threatName]['inputThreatDominant'] def _getDominantThreatLevel(self, threatName, rankedThreatLevels): dominantLevelWithHighestRank = None @@ -805,6 +892,20 @@ class TextProduct(HLSTCV_Common.TextProduct): else: return threatLevel1 + def _getLowestThreat(self, threatName, threatLevel1, threatLevel2): + keyOrderDict = self.mostSignificantDiscrete_keyOrder_dict(None, None, None) + keyOrder = keyOrderDict[threatName] + + level1Index = keyOrder.index(threatLevel1) + level2Index = keyOrder.index(threatLevel2) + + if level1Index < level2Index: + return threatLevel1 + elif level1Index == level2Index: + return threatLevel1 + else: + return threatLevel2 + def _initializeVariables(self, argDict): # Get variables error = self._getVariables(argDict) @@ -860,15 +961,16 @@ class TextProduct(HLSTCV_Common.TextProduct): def _sampleTCVAdvisory(self, advisory): print "SARAH: sampling TCV advisory!" for zone in advisory["ZoneData"]: - print "Looking at zone", zone + print "-" * 60 + print "Looking at zone %s" % (zone) for key in advisory["ZoneData"][zone]: if "Threat" not in key: continue - print "Looking at key", key + print "Looking at key '%s'" % (key) threatLevel = advisory["ZoneData"][zone][key] - print "Threat level =", threatLevel + print " Threat level = %s" % (threatLevel) if self._samplingDict[key]['inputThreatLow'] is None: self._samplingDict[key]['inputThreatLow'] = threatLevel if self._samplingDict[key]['inputThreatHigh'] is None: @@ -883,13 +985,13 @@ class TextProduct(HLSTCV_Common.TextProduct): if threatOrder.index(threatLevel) > threatOrder.index(highThreat): highThreat = threatLevel - print "low threat =", lowThreat - print "high threat =", highThreat + print " low threat = %s" % (lowThreat) + print " high threat = %s" % (highThreat) self._samplingDict[key]['inputThreatLow'] = lowThreat self._samplingDict[key]['inputThreatHigh'] = highThreat - print "Sampling dict =", self._samplingDict + print "Sampling dict = %s" % (repr(self._samplingDict)) def _setHazardImpactCategories(self, threatName): inputThreatLow = self._samplingDict[threatName]['inputThreatLow'] @@ -897,7 +999,10 @@ class TextProduct(HLSTCV_Common.TextProduct): inputThreatDominant = self._samplingDict[threatName]['inputThreatDominant'] decidingField = self._samplingDict[threatName]['decidingField'] catastrophicThreshold = self._samplingDict[threatName]['catastrophicThreshold'] - + + print "-" * 60 + print "MATT DEBUG: _setHazardImpactCategories for %s" % (threatName) + impactMin = None impactMax = None impactRange = None @@ -926,22 +1031,25 @@ class TextProduct(HLSTCV_Common.TextProduct): else: impactMax = "devastating" impactRangeMax = "extensive" - elif inputThreatLow == "High": + elif inputThreatHigh == "High": impactMax = "extensive" impactRangeMax = "significant" - elif inputThreatLow == "Mod": + elif inputThreatHigh == "Mod": impactMax = "significant" impactRangeMax = "limited" - elif inputThreatLow == "Elevated": + elif inputThreatHigh == "Elevated": impactMax = "limited" impactRangeMax = "none" else: impactMax = "none" impactRangeMax = "none" + + print "MATT DEBUG: impactMin = '%s' impactMax = '%s' impactRangeMax = '%s'" % \ + (impactMin, impactMax, impactRangeMax) # Determine dominant impact category for rest of CWA - No impact if impactMin == "none" and impactMax == "none": - impactRange = "No impacts are anticipated at this time across " + self._cwa() + "." + impactRange = "No impacts are anticipated at this time across " + self._cwa_descriptor() + "." # Otherwise, at least some impact will be experienced across the CWA else: # Do not permit the lowest category to be "None", if the highest category is also not "None" @@ -967,18 +1075,29 @@ class TextProduct(HLSTCV_Common.TextProduct): # Fixed pattern to grab headline (MHB 04/08/2009) # See if there is a headline in this text headlineSearch = re.findall("(?ism)^(\.{3}.+?\.{3}) *\n", text) + + # If we could not find original headlines, try to use 'new' HLS style + if headlineSearch is None: + headlineSearch = re.findall("(?ism)^\*\*.+?\*\* *\n", text) self.debug_print("headlineSearch = %s" % (headlineSearch)) # If we found a headline if len(headlineSearch) > 0: + # Remove the first and last ellipses - if they exist + headlineSearch[0] = re.sub("^\.\.\.", "", headlineSearch[0]) + headlineSearch[0] = re.sub("\.\.\.$", "", headlineSearch[0]) + +# # Remove the first and last '**' - if they exist +# headlineSearch[0] = headlineSearch[0].sub("**", "").strip() + # Return the first cleaned-up headline string we found return self._cleanText(headlineSearch[0]) # Otherwise, return an indicator there is no headline in this text else: - return '' # Changed to an null string instead of None + return "" # Changed to an null string instead of None # (MHB 04/08/2009) def _determineHazards(self, segments): @@ -1071,18 +1190,18 @@ class TextProduct(HLSTCV_Common.TextProduct): self._stormIntensityTrend = "Storm Intensity " + stormDict.get("StormIntensity","") print "SARAH: BEGIN STORM INFORMATION" - print "storm dict =", stormDict - print "storm name =", self._stormName - print "type =", self._stormType - print "type name =", self._stormTypeName - print "time =", self._stormTime - print "lat =", self._stormLat - print "lon =", self._stormLon - print "location =", self._stormLocation - print "reference =", self._stormReference - print "references =", self._stormLocalReferences - print "movement trend =", self._stormMovementTrend - print "intensity trend =", self._stormIntensityTrend + print "storm dict = %s" % (stormDict) + print "storm name = %s" % (self._stormName) + print "type = %s" % (self._stormType) + print "type name = %s" % (self._stormTypeName) + print "time = %s" % (self._stormTime) + print "lat = %s" % (self._stormLat) + print "lon = %s" % (self._stormLon) + print "location = %s" % (str(self._stormLocation)) + print "reference = %s" % (self._stormReference) + print "references = %s" % (self._stormLocalReferences) + print "movement trend = %s" % (self._stormMovementTrend) + print "intensity trend = %s" % (self._stormIntensityTrend) print "SARAH: END STORM INFORMATION" def _grabStormInfo(self, tcp): @@ -1758,7 +1877,7 @@ class TextProduct(HLSTCV_Common.TextProduct): removedParts.append(part) for part in removedParts: - print "SARAH: Removing part =", part + print "SARAH: Removing part = %s" % (part) partsList.remove(part) def _noOpParts(self): @@ -1805,7 +1924,7 @@ class TextProduct(HLSTCV_Common.TextProduct): upgPhenSig = record['phen'] + "." + record['sig'] newRecord = self._findNEWAssociatedWithUPG(upgPhenSig, vtecRecords) record['new_record'] = newRecord - print "SARAH: vtecRecord =", record + print "SARAH: vtecRecord = %s" % (repr(record)) segment_vtecRecords_tuples.append((segment, vtecRecords)) productSegmentGroup = { @@ -1888,7 +2007,7 @@ class TextProduct(HLSTCV_Common.TextProduct): def _initializeHazardsTable(self, argDict): import VTECMessageType productID = self._pil[0:3] - vtecMode = VTECMessageType.getVTECMessageType(productID) + vtecMode = VTECMessageType.getVTECMessageType(productID.upper()) argDict["vtecMode"] = vtecMode self._setVTECActiveTable(argDict) @@ -1902,6 +2021,11 @@ class TextProduct(HLSTCV_Common.TextProduct): def _setVTECActiveTable(self, argDict): dataMgr = argDict["dataMgr"] gfeMode = dataMgr.getOpMode().name() + + print "*" *100 + print "gfeMode = '%s'" % (gfeMode) + print "*" *100 + if gfeMode == "PRACTICE": argDict["vtecActiveTable"] = "PRACTICE" @@ -1960,7 +2084,7 @@ class TextProduct(HLSTCV_Common.TextProduct): ("Tornadoes", 'tornadoSection'), ("Other Coastal Hazards", 'coastalHazardsSection') ], - "default": ["Wind", "Surge", "Flooding Rain", "Tornadoes", "Other Coastal Hazards"], + "default": ["Wind", "Surge", "Flooding Rain", "Tornadoes"], }, { "name":"LocalReferencePoints", @@ -1971,14 +2095,14 @@ class TextProduct(HLSTCV_Common.TextProduct): "default": self._localReferencePoints_defaults(), }, { - "name": "MainHeadline", - "label": "Step 5. Input Main Headline (required)", + "name":"GeneralOnsetTime", + "label": "Step 5. General Time to Onset", "options": [ - ("Enter Unique Headline (below)", "Enter"), - ("Use Previous HLS Headline", "UsePrev"), - ("Use Latest TCP Headline", "UseTCP"), - ], - "entryField": " ", + ("Watch", 'check plans'), + ("Warning", 'complete preparations'), + ("Conditions/Ongoing", 'hunker down'), + ("Recovery", 'recovery'), + ], }, { "name": "NextUpdate", @@ -1991,6 +2115,16 @@ class TextProduct(HLSTCV_Common.TextProduct): "default": "Shortly", "entryField": " e.g. 6 AM EDT", }, + { + "name": "MainHeadline", + "label": "Step 7. Input Main Headline (required)", + "options": [ + ("Enter Unique Headline (below)", "Enter"), + ("Use Previous HLS Headline", "UsePrev"), + ("Use Latest TCP Headline", "UseTCP"), + ], + "entryField": " ", + }, ] def _displayGUI(self, infoDict=None): @@ -2043,10 +2177,12 @@ class Overview_Dialog(HLSTCV_Common.Common_Dialog): boxNum = 0 buttonSide=Tkinter.TOP frameSide = Tkinter.LEFT - elif index in [3,4]: + elif index in [3,4,5]: boxNum = 1 - buttonSide=Tkinter.LEFT - frameSide=Tkinter.TOP +# buttonSide=Tkinter.LEFT +# frameSide=Tkinter.TOP + buttonSide=Tkinter.TOP + frameSide=Tkinter.LEFT else: boxNum = 2 buttonSide=Tkinter.TOP @@ -2126,7 +2262,7 @@ class Overview_Dialog(HLSTCV_Common.Common_Dialog): frame = Tkinter.Frame(master) buttonList = self._parent._GUI1_configDict().get("buttonList", []) for button, label in buttonList: - if button == "Next": + if button == "Run": command = self.okCB else: # Cancel command = self.cancelCB @@ -2157,7 +2293,7 @@ class Overview_Dialog(HLSTCV_Common.Common_Dialog): checkList.append((options[i], svar.get())) else: if ivarList[i].get(): - print "SARAH: adding option =", options[i] + print "SARAH: adding option = %s" % (repr(options[i])) checkList.append(options[i]) value = checkList self._setVarDict(name, value) @@ -2195,7 +2331,7 @@ class LegacyFormatter(): @return text -- product string ''' text = '' - print "SARAH: productParts =", productParts + print "SARAH: productParts = %s" % (productParts) for part in productParts: valtype = type(part) if valtype is str: @@ -2203,11 +2339,11 @@ class LegacyFormatter(): elif valtype is tuple: name = part[0] infoDicts = part[1] - print "SARAH: name =", str(name) - print "SARAH: infoDicts =", infoDicts + print "SARAH: name = %s" % (str(name)) + print "SARAH: infoDicts = %s" % (repr(infoDicts)) newtext = self.processSubParts(productDict.get(name), infoDicts) - print "SARAH: newtext type =", type(newtext) - print "SARAH: newtext =", repr(newtext) + print "SARAH: newtext type = %s" % (type(newtext)) + print "SARAH: newtext = %s" % (repr(newtext)) text += newtext continue elif valtype is list: @@ -2291,8 +2427,8 @@ class LegacyFormatter(): text += '&&\n' elif name not in self._noOpParts(): textStr = productDict.get(name) - print "SARAH: name =", name - print "SARAH: textStr =", textStr + print "SARAH: name = %s" % (name) + print "SARAH: textStr = '%s'" % (textStr) if textStr: text += textStr + '\n' return text @@ -2355,7 +2491,7 @@ class LegacyFormatter(): text += self._textProduct.indentText("**" + headline + "**\n", maxWidth=self._textProduct._lineLength) - text = self._textProduct._frame(text) + "\n" + text = self._textProduct._frame(text) + "\n\n" return text @@ -2456,13 +2592,32 @@ class LegacyFormatter(): text += "\n" additionalImpactRangeText = "" + curAdditionalImpactText = "" + count = 1 for additionalImpact in sectionDict['additionalImpactRange']: - additionalImpactRangeText += additionalImpact + " " + + curAdditionalImpactText += \ + self._textProduct.indentText(additionalImpact, + maxWidth=self._textProduct._lineLength) + + if count != len(sectionDict['additionalImpactRange']) and \ + len(curAdditionalImpactText) > 0: + curAdditionalImpactText += "\n" + + # If this additional impact is not already included in the output + if additionalImpactRangeText.find(curAdditionalImpactText) == -1: + + # Add this additional impact text + print "Adding current impact." + additionalImpactRangeText += curAdditionalImpactText + count += 1 # Remove the trailing space - additionalImpactRangeText = additionalImpactRangeText[:-1] +# additionalImpactRangeText = additionalImpactRangeText[:-1] - text += self._textProduct.indentText(additionalImpactRangeText, maxWidth=self._textProduct._lineLength) +# text += self._textProduct.indentText(additionalImpactRangeText, maxWidth=self._textProduct._lineLength) + + text += additionalImpactRangeText text += "\n" return text @@ -2476,10 +2631,10 @@ class LegacyFormatter(): """ text = '' for i in range(len(subParts)): - print "SARAH: subpart subParts[i] =", subParts[i] - print "SARAH: subpart infoDicts[i] =", infoDicts[i] + print "SARAH: subpart subParts[i] = %s" % (subParts[i]) + print "SARAH: subpart infoDicts[i] = %s" % (infoDicts[i]) newtext = self._processProductParts(subParts[i], infoDicts[i].get('partsList')) - print "SARAH: subpart newtext type =", type(newtext) - print "SARAH: subpart newtext =", repr(newtext) + print "SARAH: subpart newtext type = %s" % (type(newtext)) + print "SARAH: subpart newtext = '%s'" % (repr(newtext)) text += newtext return text diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_TCV.py b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_TCV.py index c39aa22e1d..896c45ba46 100644 --- a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_TCV.py +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_TCV.py @@ -7,6 +7,8 @@ import math from AbsTime import * from StartupDialog import IFPDialog as Dialog from LockingFile import File +from com.raytheon.uf.common.localization import PathManagerFactory +from com.raytheon.uf.common.localization import LocalizationContext_LocalizationType as LocalizationType AWIPS_ENVIRON = "AWIPS2" import HLSTCV_Common @@ -18,20 +20,20 @@ class TextProduct(HLSTCV_Common.TextProduct): Definition["outputFile"] = "{prddir}/TEXT/TCV.txt" Definition["database"] = "Official" # Source database Definition["debug"] = 1 - Definition["mapNameForCombinations"] = "Zones_" - Definition["defaultEditAreas"] = "EditAreas_PublicMarine_" + Definition["mapNameForCombinations"] = "Zones_MFL" + Definition["defaultEditAreas"] = "EditAreas_PublicZones_MFL" Definition["showZoneCombiner"] = 1 # 1 to cause zone combiner to display Definition["productName"] = "LOCAL WATCH/WARNING STATEMENT" - Definition["fullStationID" ] = "" - Definition["wmoID" ] = "" - Definition["wfoCityState" ] = "" - Definition["pil" ] = "" - Definition["textdbPil" ] = "" - Definition["awipsWANPil" ] = "" - Definition["site"] = "" - Definition["wfoCity"] = "" + Definition["fullStationID" ] = "KMFL" + Definition["wmoID" ] = "WTUS82" + Definition["wfoCityState" ] = "MIAMI FL" + Definition["pil" ] = "TCVMFL" + Definition["textdbPil" ] = "MIATCVMFL" + Definition["awipsWANPil" ] = "KMFLTCVMFL" + Definition["site"] = "MFL" + Definition["wfoCity"] = "MIAMI" Definition["areaName"] = "" #optional area name for product Definition["areaDictionary"] = "AreaDictionary" @@ -135,6 +137,47 @@ class TextProduct(HLSTCV_Common.TextProduct): def _cwa(self): return "" #"MFL" + ############################################################### + ### Analysis Lists, SampleAnalysis Overrides and other + ### analysis related methods + + def _analysisList(self): + # Sample over 120 hours beginning at current time + analysisList = [ + # Wind Section + ("Wind", self.vectorModeratedMax, [6]), + ("WindGust", self.moderatedMax, [6]), + ("WindThreat", self.mostSignificantDiscreteValue), + ("pws34int", self.moderatedMax, [6]), + ("pws64int", self.moderatedMax, [6]), + ("pwsD34", self.moderatedMax), + ("pwsN34", self.moderatedMax), + ("pwsD64", self.moderatedMax), + ("pwsN64", self.moderatedMax), +# ("pwsD34", self.moderatedMax, [0]), +# ("pwsN34", self.moderatedMax, [0]), +# ("pwsD64", self.moderatedMax, [0]), +# ("pwsN64", self.moderatedMax, [0]), + + # Flooding Rain Section + ("QPF", self.accumSum, [72]), + ("FloodingRainThreat", self.mostSignificantDiscreteValue), + + # Tornado Section + ("TornadoThreat", self.mostSignificantDiscreteValue), + ] + + return analysisList + + def _intersectAnalysisList(self): + # The grids for the Surge Section will be intersected with a special edit area + analysisList = [ + ("InundationMax", self.moderatedMax, [6]), + ("InundationTiming", self.moderatedMax, [6]), + ("StormSurgeThreat", self.mostSignificantDiscreteValue), + ] + + return analysisList ############################################################### ### TCV Product and Segment Parts Definition @@ -149,7 +192,6 @@ class TextProduct(HLSTCV_Common.TextProduct): 'easMessage', 'productHeader', ('segments', segmentParts), - 'endProduct', ] } @@ -174,6 +216,7 @@ class TextProduct(HLSTCV_Common.TextProduct): (floodingRainSection, self._floodingRainSection[segment].sectionParts(segment_vtecRecords_tuple)), (tornadoSection, self._tornadoSection[segment].sectionParts(segment_vtecRecords_tuple)), 'infoSection', + 'endSection' ] # The storm surge section should never be inserted into @@ -196,6 +239,9 @@ class TextProduct(HLSTCV_Common.TextProduct): error = self._initializeVariables(argDict) if error is not None: return error + +# self._archiveTransmittedAdvisory() +# return "Archived" self._segmentList = self._determineSegments() print "Segment Information: ", self._segmentList, "\n\n" @@ -206,7 +252,7 @@ class TextProduct(HLSTCV_Common.TextProduct): self._determineTimeRanges(argDict) # Sample the data - self._sampleTCVData(argDict) + self._sampleData(argDict) # Create the product dictionary and format it to create the output productDict = self._createProductDictionary() @@ -321,7 +367,7 @@ class TextProduct(HLSTCV_Common.TextProduct): segment, vtecRecords = productSegment records = [] for vtecRecord in vtecRecords: - print "SARAH: vtecRecord dict:", vtecRecord + print "SARAH: vtecRecord dict:\n%s" % (vtecRecord) vstr = None vstr = vtecRecord["vtecstr"] @@ -360,6 +406,9 @@ class TextProduct(HLSTCV_Common.TextProduct): segmentDict['infoSection'] = infoSection + def _endSection(self, segmentDict, productSegmentGroup, productSegment): + segmentDict['endSection'] = "\n$$" + def _issuanceTimeDate(self, segmentDict, productSegmentGroup, productSegment): segmentDict['issuanceTimeDate'] = self._timeLabel @@ -443,7 +492,7 @@ class TextProduct(HLSTCV_Common.TextProduct): removedParts.append(part) for part in removedParts: - print "SARAH: Removing part =", part + print "SARAH: Removing part = %s" % (part) partsList.remove(part) def _noOpParts(self): @@ -523,7 +572,7 @@ class TextProduct(HLSTCV_Common.TextProduct): self._initializeSegmentZoneData(segment) windStats, stormSurgeStats, floodingRainStats, tornadoStats = \ - self._getTCVStats(self._argDict, segment, self._editAreaDict, self._timeRangeList) + self._getStats(self._argDict, segment, self._editAreaDict, self._timeRangeList) self._windSection[segment] = WindSection(self, segment, windStats) self._stormSurgeSection[segment] = StormSurgeSection(self, segment, stormSurgeStats) @@ -595,6 +644,49 @@ class TextProduct(HLSTCV_Common.TextProduct): else: return "" + ############################################################### + ### Sampling and Statistics related methods + + def _sampleData(self, argDict): + # Sample the data + editAreas = self._makeSegmentEditAreas(argDict) + cwa = self._cwa() + editAreas.append((cwa, cwa)) + + self._sampler = self.getSampler(argDict, + (self._analysisList(), self._timeRangeList, editAreas)) + + intersectAreas = self._computeIntersectAreas(editAreas, argDict) + + self._intersectSampler = self.getSampler(argDict, + (self._intersectAnalysisList(), self._timeRangeList, intersectAreas)) + + def _getStats(self, argDict, segment, editAreaDict, timeRangeList): + # Get statistics for this segment + print "SARAH: issue time seconds = %s" % (self._issueTime_secs) + print "SARAH: GMT issue time = %s" % (repr(time.gmtime(self._issueTime_secs))) + + editArea = editAreaDict[segment] + statList = self.getStatList(self._sampler, + self._analysisList(), + timeRangeList, + editArea) + + windStats = WindSectionStats(self, segment, statList, timeRangeList) + floodingRainStats = FloodingRainSectionStats(self, segment, statList, timeRangeList) + tornadoStats = TornadoSectionStats(self, segment, statList, timeRangeList) + + # The surge section needs sampling done with an intersected edit area + intersectEditArea = "intersect_"+editArea + intersectStatList = self.getStatList(self._intersectSampler, + self._intersectAnalysisList(), + timeRangeList, + intersectEditArea) + + stormSurgeStats = StormSurgeSectionStats(self, segment, intersectStatList, timeRangeList) + + return (windStats, stormSurgeStats, floodingRainStats, tornadoStats) + ############################################################### ### Time related methods @@ -728,6 +820,115 @@ class TextProduct(HLSTCV_Common.TextProduct): return None else: return dialog.getVarDict() + + ############################################################### + ### Advisory related methods + + def _archiveCurrentAdvisory(self): + ### Determine if all actions are canceled + allCAN = True + for vtecRecord in self._getAllVTECRecords(): + action = vtecRecord['act'] + #print "vtecRecord", vtecRecord + if action != "CAN": + allCAN = False + break + + self._currentAdvisory["AllCAN"] = allCAN + self._currentAdvisory["CreationTime"] = self._issueTime_secs + self._currentAdvisory["Transmitted"] = False + self._currentAdvisory["StormName"] = self._getStormNameFromTCP() + self._currentAdvisory["AdvisoryNumber"] = self._getAdvisoryNumberStringFromTCP() + self._currentAdvisory["HazardsForHLS"] = self._getHazardsForHLS() + + + self._saveAdvisory("pending", self._currentAdvisory) + + def _archiveTransmittedAdvisory(self): + import unicodedata + + pendingDict = self._loadAdvisory("pending") + if pendingDict is None: + return + + stormName = pendingDict["StormName"] + advisoryNumber = pendingDict["AdvisoryNumber"] + + pendingDict["Transmitted"] = True + + transmittedName = stormName + advisoryNumber + transmittedName = unicodedata.normalize('NFKD', transmittedName).encode('ascii','ignore') + + self._saveAdvisory(transmittedName, pendingDict) + + if pendingDict["AllCAN"]: + advisoryDirectoryPath = self._getLocalAdvisoryDirectoryPath() + filenames = os.listdir(advisoryDirectoryPath) + print "all filenames =", filenames + stormAdvisories = filter(lambda filename: (stormName in filename) or \ + ("pending" in filename), + filenames) + print "storm advisories =", stormAdvisories + + for advisory in stormAdvisories: + self._deleteAdvisory(self._getAdvisoryPath() + advisory) + print "deleted advisory", advisory + + def _deleteAdvisory(self, fullAdvisoryName): + localizationFile = self._getLocalizationFile(LocalizationType.CAVE_STATIC, + self._site, + fullAdvisoryName) + localizationFile.delete() + + def _saveAdvisory(self, advisoryName, advisoryDict): + import json + + self._synchronizeAdvisories() + + try: + self._writeFileContents(LocalizationType.CAVE_STATIC, + self._site, + self._getAdvisoryFilename(advisoryName), + json.dumps(advisoryDict)) + print "SARAH: Wrote file contents for", self._getAdvisoryFilename(advisoryName) + + self._synchronizeAdvisories() + except Exception, e: + print "SARAH Save Exception for", self._getAdvisoryFilename(advisoryName), ":", e + + def _writeFileContents(self, loctype, siteID, filename, contents): + pathManager = PathManagerFactory.getPathManager() + context = pathManager.getContextForSite(loctype, siteID) + localizationFile = pathManager.getLocalizationFile(context, filename) + with File(localizationFile.getFile(), filename, 'w') as pythonFile: + pythonFile.write(contents) + + localizationFile.save() + + def _getHazardsForHLS(self): + hazardTable = self._argDict["hazards"] + + + hazSegments = self.organizeHazards(hazardTable.rawAnalyzedTable()) + print "\nSegments from HazardsTable organizeHazards", hazSegments + + combos = [([self._allAreas()], "AllAreas")] + + print "\nSegments from Zone Combiner", combos + # "Overlay" the forecaster-entered combinations onto the segments + segmentList = self._refineSegments(hazSegments, combos) + print "\nSegmentList from refineSegments =", segmentList + + allHazards = [] + for segment in segmentList: + hazardsList = hazardTable.getHazardList(segment) + for hazard in hazardsList: + if hazard['act'] == 'COR': + return self._previousAdvisory["HazardsForHLS"] + else: + allHazards.append(hazard) + + return allHazards import Tkinter class Overview_Dialog(HLSTCV_Common.Common_Dialog): @@ -790,7 +991,7 @@ class Overview_Dialog(HLSTCV_Common.Common_Dialog): frame = Tkinter.Frame(master) buttonList = self._parent._GUI1_configDict().get("buttonList", []) for button, label in buttonList: - if button == "Next": + if button == "Run": command = self.okCB else: # Cancel command = self.cancelCB @@ -882,8 +1083,8 @@ class SectionCommon(): threatKey = elementName + "Threat" forecastKey = elementName + "Forecast" - print "SARAH: getThreatTrendValue _currentAdvisory =", self._stats._currentAdvisory - print "SARAH: getThreatTrendValue _previousAdvisory =", self._stats._previousAdvisory + print "SARAH: getThreatTrendValue _currentAdvisory =\n%s" % (repr(self._stats._currentAdvisory)) + print "SARAH: getThreatTrendValue _previousAdvisory =\n%s" % (repr(self._stats._previousAdvisory)) if (self._stats._currentAdvisory is None) or (self._stats._previousAdvisory is None): # Only compute a threat trend if we have 2 or more advisories @@ -893,7 +1094,7 @@ class SectionCommon(): previousThreat = self._stats._previousAdvisory[threatKey] shorterTermTrendDifference = self._threatDifference(currentThreat, previousThreat) - print "SARAH: shorterTermTrendDifference =", shorterTermTrendDifference + print "SARAH: shorterTermTrendDifference = %s" % (shorterTermTrendDifference) previousPreviousThreat = None longerTermTrendDifference = None @@ -907,7 +1108,7 @@ class SectionCommon(): elif self._isThreatIncreasing(shorterTermTrendDifference, longerTermTrendDifference): threatTrendValue = "INCREASING" elif currentThreat == "Extreme" and \ - self._advisoriesHaveKey(forecastKey) and \ + self._advisoriesHaveValidKey(forecastKey) and \ self._isMagnitudeIncreasing(self._stats._currentAdvisory[forecastKey], self._stats._previousAdvisory[forecastKey], self._stats._previousPreviousAdvisory[forecastKey], @@ -944,13 +1145,15 @@ class SectionCommon(): else: return False - def _advisoriesHaveKey(self, key): - return (self._stats._currentAdvisory.has_key(key) and - self._stats._currentAdvisory[key] is not None) and \ - (self._stats._previousAdvisory is not None and - self._stats._previousAdvisory[key] is not None) and \ - (self._stats._previousPreviousAdvisory is not None and - self._stats._previousPreviousAdvisory[key] is not None) + def _advisoriesHaveValidKey(self, key): + return self._advisoryHasValidKey(self._stats._currentAdvisory, key) and \ + self._advisoryHasValidKey(self._stats._previousAdvisory, key) and \ + self._advisoryHasValidKey(self._stats._previousPreviousAdvisory, key) + + def _advisoryHasValidKey(self, advisory, key): + return (advisory is not None) and \ + (advisory.has_key(key)) and \ + (advisory[key] is not None) def _isMagnitudeIncreasing(self, currentValue, previousValue, previousPreviousValue, threshold): if (currentValue > previousValue and currentValue > previousPreviousValue) or \ @@ -962,9 +1165,9 @@ class SectionCommon(): def _calculateThreatStatementTr(self, onsetHour, endHour, threatTrendValue): tr = None - print "SARAH: onset hour =", onsetHour - print "SARAH: end hour =", endHour - print "SARAH: threatTrendValue =", threatTrendValue + print "SARAH: onset hour = %s" % (onsetHour) + print "SARAH: end hour = %s" % (endHour) + print "SARAH: threatTrendValue = %s" % (threatTrendValue) if (onsetHour is not None) and \ (endHour is not None): @@ -1002,22 +1205,6 @@ class SectionCommon(): planning = statements["planning"] preparation = statements["preparation"] action = statements["action"] - - # Check for any overrides - try: - planning = threatStatements[sectionName][maxThreat][tr]["planning"] - except KeyError: - pass - - try: - preparation = threatStatements[sectionName][maxThreat][tr]["preparation"] - except KeyError: - pass - - try: - action = threatStatements[sectionName][maxThreat][tr]["action"] - except KeyError: - pass return (planning, preparation, action) @@ -1134,7 +1321,8 @@ class WindSection(SectionCommon): for i in range(numRecords): vtecRecord = vtecRecords[i] - if vtecRecord["phensig"] in ["HU.A", "HU.W", "TR.A", "TR.W"] or \ + if (vtecRecord["phensig"] in ["HU.A", "HU.W", "TR.A", "TR.W"] and \ + vtecRecord["act"] not in self._textProduct._ignoreActions()) or \ self._stats._windowTS is not None: forecastText += "Tropical storm force winds remain possible" possibleHazardsFound = True @@ -1303,7 +1491,9 @@ class StormSurgeSection(SectionCommon): if not self._textProduct._PopulateSurge: self._setProductPartValue(segmentDict, 'latestForecastSummary', "Latest local forecast: Not available at this time. To be updated shortly.") - elif self._stats._inundationMax is None or self._stats._inundationMax < 1: + elif "None" not in self._stats._windowSurge or \ + self._stats._inundationMax is None or \ + self._stats._inundationMax < 1: self._setProductPartValue(segmentDict, 'latestForecastSummary', "No storm surge inundation forecast") else: @@ -1449,6 +1639,8 @@ class FloodingRainSection(SectionCommon): if self._segment in areaList: summary = headline + " is in effect" + + self._setProductPartValue(segmentDict, 'latestForecastSummary', "Latest Local Forecast: " + summary) @@ -1486,7 +1678,7 @@ class FloodingRainSection(SectionCommon): else: return "More than two feet" - return "%d-%d inches...with locally higher amounts" % (minAccum, maxAccum) + return "%d-%d inches, with locally higher amounts" % (minAccum, maxAccum) def _threatSubsection(self, segmentDict, productSegmentGroup, productSegment): subsectionDict = collections.OrderedDict() @@ -1594,6 +1786,621 @@ class TornadoSection(SectionCommon): self._setProductPartValue(segmentDict, 'impactsSubsection', subsectionDict) +############################################################### +### TCV Statistics Classes + +class SectionCommonStats(): + def __init__(self, textProduct, segment): + self._textProduct = textProduct + self._segment = segment + + self._initializeAdvisories() + + self._maxThreat = None + + def _initializeAdvisories(self): + self._currentAdvisory = self._textProduct._currentAdvisory['ZoneData'][self._segment] + + self._previousAdvisory = None +# print "MATT textProduct._previousAdvisory = '%s'" % (textProduct._previousAdvisory) + if self._textProduct._previousAdvisory is not None: + self._previousAdvisory = self._textProduct._previousAdvisory['ZoneData'][self._segment] + +# print "MATT textProduct._previousPreviousAdvisory = '%s'" % \ +# (textProduct._previousPreviousAdvisory) + self._previousPreviousAdvisory = None + if self._textProduct._previousPreviousAdvisory is not None: + self._previousPreviousAdvisory = self._textProduct._previousPreviousAdvisory['ZoneData'][self._segment] + + def _updateThreatStats(self, tr, statDict, threatGridName): + print "SARAH: updateThreatStats for %s" % (threatGridName) + threatLevel = self._textProduct.getStats(statDict, threatGridName) + if threatLevel is not None: + threatLevels = self._textProduct.threatKeyOrder() + print "SARAH: threatLevel = %s" % (threatLevel) + print "SARAH: maxThreat = %s" % (self._maxThreat) + if self._maxThreat is None or \ + threatLevels.index(threatLevel) > threatLevels.index(self._maxThreat): + print "SARAH: updating max threat to = %s" % (threatLevel) + self._maxThreat = threatLevel + + def _calculateHourOffset(self, targetTime): + seconds = targetTime.unixTime() - self._textProduct._issueTime_secs + hour = int(round(seconds/60/60)) + if hour < 0: + hour = 0 + + return hour + + +class WindSectionStats(SectionCommonStats): + def __init__(self, textProduct, segment, statList, timeRangeList): + SectionCommonStats.__init__(self, textProduct, segment) + self._maxWind = None + self._maxGust = None + self._onset34Hour = None + self._end34Hour = None + self._onset64Hour = None + self._end64Hour = None + self._windowTS = None + self._windowHU = None + + self._setStats(statList, timeRangeList) + + class PwsXXintStats(): + max = None + onsetHour = None + + class PwsTXXStats(): + onsetHour = None + endHour = None + + class TimeInfo(): + onsetHour = None + endHour = None + + class EventsOccurring(): + pwsTXXEvent = False + windXXEvent = False + + def _setStats(self, statList, timeRangeList): + pws34intStats = self.PwsXXintStats() + pws64intStats = self.PwsXXintStats() + pwsT34Stats = self.PwsTXXStats() + pwsT64Stats = self.PwsTXXStats() + wind34timeInfo = self.TimeInfo() + wind64timeInfo = self.TimeInfo() + + events34 = self.EventsOccurring() + events64 = self.EventsOccurring() + + for period in range(len(statList)): + tr, _ = timeRangeList[period] + statDict = statList[period] + + self._updateStatsForPwsXXint(tr, statDict, "pws34int", pws34intStats) + self._updateStatsForPwsXXint(tr, statDict, "pws64int", pws64intStats) + + self._updateStatsForPwsTXX(tr, statDict, "pwsD34", "pwsN34", pwsT34Stats, events34, period) + self._updateStatsForPwsTXX(tr, statDict, "pwsD64", "pwsN64", pwsT64Stats, events64, period) + + wind = self._textProduct._getStatValue(statDict, "Wind", "Max", self._textProduct.VECTOR()) + if wind is not None: + if wind >= 34: + events34.windXXEvent = True + if wind >= 64: + events64.windXXEvent = True + else: + events64.windXXEvent = False + else: + events34.windXXEvent = False + events64.windXXEvent = False + + if self._maxWind is None or wind >= self._maxWind: + self._maxWind = wind + + self._updateWindTimeInfo(tr, wind34timeInfo, speed=34) + self._updateWindTimeInfo(tr, wind64timeInfo, speed=64) + + windGust = self._textProduct._getStatValue(statDict, "WindGust", "Max") + if windGust is not None: + if self._maxGust is None or windGust > self._maxGust: + self._maxGust = windGust + + self._updateThreatStats(tr, statDict, "WindThreat") + + #Tropical Storm + onsetEndInfo = self._computeWindOnsetAndEnd(wind34timeInfo, pws34intStats, pwsT34Stats) + self._onset34Hour = onsetEndInfo.onsetHour + self._end34Hour = onsetEndInfo.endHour + + nonEnding34Event = False + if events34.pwsTXXEvent and (wind34timeInfo.endHour is None or events34.windXXEvent): + nonEnding34Event = True + + print "SARAH: Tropical Storm Window:" + self._windowTS = self._createWindow("Tropical Storm", + self._onset34Hour, + self._end34Hour, + nonEnding34Event) + + #Hurricane + onsetEndInfo = self._computeWindOnsetAndEnd(wind64timeInfo, pws64intStats, pwsT64Stats) + self._onset64Hour = onsetEndInfo.onsetHour + self._end64Hour = onsetEndInfo.endHour + + nonEnding64Event = False + if events64.pwsTXXEvent and (wind64timeInfo.endHour is None or events64.windXXEvent): + nonEnding64Event = True + + print "SARAH: Hurricane Window:" + self._windowHU = self._createWindow("Hurricane", + self._onset64Hour, + self._end64Hour, + nonEnding64Event) + + self._currentAdvisory["WindThreat"] = self._maxThreat + self._currentAdvisory["WindForecast"] = self._maxWind + + def _updateStatsForPwsXXint(self, tr, statDict, gridName, pwsXXintStats): + pwsXXint = self._textProduct._getStatValue(statDict, gridName, "Max") + + if pwsXXint is not None: + if pwsXXintStats.max is None or pwsXXint > pwsXXintStats.max: + pwsXXintStats.max = pwsXXint + pwsXXintStats.onsetHour = self._calculateHourOffset(tr.startTime()) + + print "SARAH: Window Debug: In _updateStatsForPwsXXint" + print "SARAH: Window Debug: pwsXXintStats gridName = %s" % (gridName) + print "SARAH: Window Debug: pwsXXintStats pwsXXint = %s" % (pwsXXint) + print "SARAH: Window Debug: pwsXXintStats tr = %s" % (repr(tr)) + print "SARAH: Window Debug: pwsXXintStats onsetHour = %s" % (pwsXXintStats.onsetHour) + + def _updateStatsForPwsTXX(self, tr, statDict, dayGridName, nightGridName, pwsTXXStats, events, period): + + # Convert this time to localtime + trStartLocalHour = time.localtime(tr.startTime().unixTime()).tm_hour + dayStartHour = self._textProduct.DAY() + nightStartHour = self._textProduct.NIGHT() + print "*" * 100 + print "MATT _updateStatsForPwsTXX = %s localStartHr = %d" % (repr(tr), + trStartLocalHour) + print "MATT dayStart = %s nightStart = %s" % (dayStartHour, + nightStartHour) + print "MATT _updateStatsForPwsTXX statDict\n%s" % (repr(statDict)) + + pwsDXX = self._textProduct._getStatValue(statDict, dayGridName, "Max") + pwsNXX = self._textProduct._getStatValue(statDict, nightGridName, "Max") + maxPws = None + print "MATT pwsDXX = %s pwsNXX = %s " % (pwsDXX, pwsNXX) + +# if pwsDXX is not None: +# print "SARAH: Window Debug: pwsTXXStats DAY" +# maxPws = pwsDXX +# elif pwsNXX is not None: +# print "SARAH: Window Debug: pwsTXXStats NIGHT" +# maxPws = pwsNXX + + # SARAH - if we are close to the end of a day/night period, the first + # period we would really want to consider would be the next period. + # This is hard-coded to 3 hours to prove the concept. + + # If we are close to starting a DAY period + if ((dayStartHour >= trStartLocalHour and + (dayStartHour - trStartLocalHour) <= 3) or + (trStartLocalHour >= dayStartHour and + trStartLocalHour < nightStartHour and + (nightStartHour - trStartLocalHour) > 3)): + + print "MATT: Window Debug: pwsTXXStats DAY" + if pwsDXX is not None: + maxPws = pwsDXX + else: + maxPws = max(pwsDXX, pwsNXX) + print "MATT:\t\tTaking max pwsDXX/pwsNXX value (%s)" % (maxPws) + + +# # If we are in the middle of a DAY period, and not close to a NIGHT +# elif (trStartLocalHour >= dayStartHour and +# trStartLocalHour < nightStartHour and +# (nightStartHour - trStartLocalHour) > 3): +# +# print "MATT: Window Debug: pwsTXXStats DAY - middle" +# maxPws = pwsDXX + + # If we are close to starting a NIGHT period + elif ((nightStartHour >= trStartLocalHour and + (nightStartHour - trStartLocalHour) <= 3) or + ((trStartLocalHour >= nightStartHour or + trStartLocalHour < dayStartHour) and + abs(dayStartHour - trStartLocalHour) > 3)): + + print "MATT: Window Debug: pwsTXXStats NIGHT" + if pwsDXX is not None: + maxPws = pwsNXX + else: + print "Taking max pwsDXX/pwsNXX value" + maxPws = max(pwsDXX, pwsNXX) + +# # If we are in the middle of a NIGHT period, and not close to a DAY +# elif ((trStartLocalHour >= nightStartHour or +# trStartLocalHour < dayStartHour) and +# abs(dayStartHour - trStartLocalHour) > 3): +# # trStartLocalHour < dayStartHour) or pwsDXX is None): +# print "MATT: Window Debug: pwsTXXStats NIGHT - middle" +# maxPws = pwsNXX + + threshold34index = 0 + threshold64index = 1 + if maxPws is not None: + if "64" in dayGridName: + index = threshold64index + else: #if "34" + index = threshold34index + + threshold = None + thresholds = self.windSpdProb_thresholds() + if period == 0: + (thresholdLow, thresholdHigh) = thresholds[period][index] + threshold = thresholdLow + else: + if period >= 10: # SARAH: TODO - remove??? + period = 9 + threshold = thresholds[period][index] + + if maxPws > threshold: + events.pwsTXXEvent = True + + configuredEndTime = self._getCorrespondingConfiguredTime(tr.endTime(), isOnset = False) + pwsTXXStats.endHour = self._calculateHourOffset(configuredEndTime) + + print "SARAH: Window Debug: pwsTXXStats dayGridName = %s" % (dayGridName) + print "SARAH: Window Debug: pwsTXXStats nightGridName = %s" % (nightGridName) + print "SARAH: Window Debug: pwsTXXStats original tr = %s" % (repr(tr)) + print "SARAH: Window Debug: pwsTXXStats maxPws = %s" %(repr(maxPws)) + print "SARAH: Window Debug: pwsTXXStats endHour = %s" % (repr(pwsTXXStats.endHour)) + + if pwsTXXStats.onsetHour is None: + configuredStartTime = self._getCorrespondingConfiguredTime(tr.startTime(), isOnset = True) + pwsTXXStats.onsetHour = self._calculateHourOffset(configuredStartTime) + + print "SARAH: Window Debug: pwsTXXStats dayGridName = %s" % (dayGridName) + print "SARAH: Window Debug: pwsTXXStats nightGridName = %s" % (nightGridName) + print "SARAH: Window Debug: pwsTXXStats original tr = %s" % (repr(tr)) + print "SARAH: Window Debug: pwsTXXStats maxPws = %s" %(repr(maxPws)) + print "SARAH: Window Debug: pwsTXXStats onsetHour = %s" % (repr(pwsTXXStats.onsetHour)) + else: + events.pwsTXXEvent = False + + def _getCorrespondingConfiguredTime(self, gmtTime, isOnset): + dayStartHour = self._textProduct.DAY() + nightStartHour = self._textProduct.NIGHT() + + print "SARAH: In _getCorrespondingConfiguredTime" + print "SARAH: gmtTime = %s" % (repr(gmtTime)) + + gmtSeconds = gmtTime.unixTime() + localTime = time.localtime(gmtSeconds) + print "SARAH: localTime = %s" % (repr(localTime)) + + localHour = localTime.tm_hour + print "SARAH: localHour = %s" % (localHour) + + if isOnset: + print "SARAH: Window Debug: Adjusting start time" + else: + print "SARAH: Window Debug: Adjusting end time" + + newHour = None + if localHour < dayStartHour: + if isOnset: + # Subtract 24 hours to get to the previous day + newGmtTime = gmtTime - 24*60*60 + gmtSeconds = newGmtTime.unixTime() + localTime = time.localtime(gmtSeconds) + print "SARAH: new localTime = %s" % (repr(localTime)) + + newHour = nightStartHour + else: + newHour = dayStartHour + elif dayStartHour <= localHour and localHour < nightStartHour: + if isOnset: + newHour = dayStartHour + else: + newHour = nightStartHour + else: + if isOnset: + newHour = nightStartHour + else: + # Add 24 hours to get to the next day + newGmtTime = gmtTime + 24*60*60 + gmtSeconds = newGmtTime.unixTime() + localTime = time.localtime(gmtSeconds) + print "SARAH: new localTime = %s" % (repr(localTime)) + + newHour = dayStartHour + + print "SARAH: new localHour = %s" % (localHour) + + newTimeTuple = localTime[:3] + (newHour,) + localTime[4:] + import calendar + seconds = calendar.timegm(newTimeTuple) + adjustedGmtTime = AbsTime(seconds) + + # SARAH; Is this the correct variable? + print "SARAH: new local time = %s" % (repr(adjustedGmtTime)) + + seconds = time.mktime(newTimeTuple) + adjustedGmtTime = AbsTime(seconds) + print "SARAH: new GMT time = %s" % (repr(adjustedGmtTime)) + return adjustedGmtTime + + # SARAH - we don't want this here. Use the inherited version from the + # VectorRelatedPhrases module instead. This way, changes only need to be + # made in one place. + def windSpdProb_thresholds(self): + return [ + ((45.0, 80.0), (25.0, 60.0)), # Per 1 + (35.0, 20.0), # Per 2 + (30.0, 15.0), # Per 3 + (25.0, 12.5), # Per 4 + (22.5, 10.0), # Per 5 + (20.0, 8.0), # Per 6 + (17.5, 7.0), # Per 7 + (15.0, 6.0), # Per 8 + (12.5, 5.0), # Per 9 + (10.0, 4.0), # Per 10 + ] + + def _updateWindTimeInfo(self, tr, timeInfo, speed): + if self._maxWind is not None and self._maxWind >= speed: + timeInfo.endHour = self._calculateHourOffset(tr.endTime()) + + print "SARAH: Window Debug: In _updateWindTimeInfo" + print "SARAH: Window Debug: timeInfo speed = %s" % (speed) + print "SARAH: Window Debug: timeInfo maxWind = %s" % (self._maxWind) + print "SARAH: Window Debug: timeInfo tr = %s" % (repr(tr)) + print "SARAH: Window Debug: timeInfo endHour = %s" % (timeInfo.endHour) + + if timeInfo.onsetHour is None: + timeInfo.onsetHour = self._calculateHourOffset(tr.startTime()) + + print "SARAH: Window Debug: onsetHour was None" + print "SARAH: Window Debug: timeInfo speed = %s" % (speed) + print "SARAH: Window Debug: timeInfo maxWind = %s" % (self._maxWind) + print "SARAH: Window Debug: timeInfo tr = %s" % (repr(tr)) + print "SARAH: Window Debug: timeInfo onsetHour = %s" % (timeInfo.onsetHour) + + def _computeWindOnsetAndEnd(self, windTimeInfo, pwsXXintStats, pwsTXXStats): + onsetEndInfo = self.TimeInfo() + + print "SARAH: Window Debug: In _computeWindOnsetAndEnd" + print "SARAH: Window Debug: windTimeInfo.onsetHour = %s" % (windTimeInfo.onsetHour) + print "SARAH: Window Debug: pwsTXXStats.onsetHour = %s" % (pwsTXXStats.onsetHour) + print "SARAH: Window Debug: pwsXXintStats.onsetHour = %s" % (pwsXXintStats.onsetHour) + print "SARAH: Window Debug: windTimeInfo.endHour = %s" % (windTimeInfo.endHour) + print "SARAH: Window Debug: pwsTXXStats.endHour = %s" % (pwsTXXStats.endHour) + + if windTimeInfo.onsetHour is None: +# print "SARAH: Window Debug: windTimeInfo.onsetHour was None; using pwsTXXStats" +# windTimeInfo.onsetHour = pwsTXXStats.onsetHour +# print "SARAH: Window Debug: pwsTXXStats.onsetHour =", pwsTXXStats.onsetHour + + # Short-circuit this logic as a temporary measure. Basically, do + # not include a window if the deterministic winds do not support + # a particular threshold + onsetEndInfo.endHour = None + + if windTimeInfo.onsetHour is not None and pwsXXintStats.onsetHour is not None: + print "SARAH: Window Debug: windTimeInfo.onsetHour & pwsXXintStats.onsetHour not None; taking min" + onsetEndInfo.onsetHour = min(windTimeInfo.onsetHour, pwsXXintStats.onsetHour) + print "SARAH: Window Debug: min onsetHour = %s" % (onsetEndInfo.onsetHour) + + if onsetEndInfo.onsetHour is not None: + if windTimeInfo.endHour is None: + print "SARAH: Window Debug: windTimeInfo.endHour was None; using pwsTXXStats" + onsetEndInfo.endHour = pwsTXXStats.endHour + print "SARAH: Window Debug: pwsTXXStats.endHour = %s" % (pwsTXXStats.endHour) + elif pwsTXXStats.endHour is not None: + print "SARAH: windendHour = %s" % (windTimeInfo.endHour) + print "SARAH: probendHour = %s" % (pwsTXXStats.endHour) + onsetEndInfo.endHour = int(round(self._textProduct.average(windTimeInfo.endHour, pwsTXXStats.endHour))) + print "SARAH: endHour = %s" % (onsetEndInfo.endHour) + return onsetEndInfo + + def _createWindow(self, windowName, onsetHour, endHour, nonEndingEvent): + window = "Window for " + windowName + " force winds: " + print "SARAH: In _createWindow" + print "SARAH: window stats:" + print "SARAH: onsetHour = %s" % (onsetHour) + print "SARAH: endHour = %s" % (endHour) + print "SARAH: window nonEndingEvent = %s" % (nonEndingEvent) + + if onsetHour is None: + + # SARAH - we do not want a statement of a non-existent window +# window += "None" + window = None + else: + startTime = AbsTime(self._textProduct._issueTime_secs + onsetHour*60*60) + if endHour is not None: + endTime = AbsTime(self._textProduct._issueTime_secs + endHour*60*60) + windowPeriod = self._textProduct.makeTimeRange(startTime, endTime) + else: + windowPeriod = self._textProduct.makeTimeRange(startTime, startTime + 1) + print "SARAH: window period = %s" % (windowPeriod) + + startTimeDescriptor = "" + if onsetHour >= 18: + startTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, resolution = 6) + elif 6 <= onsetHour and onsetHour < 18: + startTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, resolution = 3) + + if endHour is None or nonEndingEvent: + if len(startTimeDescriptor) != 0: + window += "Begins " + startTimeDescriptor + else: + window += "None" + else: + connector = "through " + endTimeDescriptor = "the next few hours" + + if endHour >= 18: + endTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, + useEndTime = True, + resolution = 6) + elif 6 <= endHour and endHour < 18: + endTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, + useEndTime = True, + resolution = 3) + # If we are not talking about the next few hours + if endTimeDescriptor != "the next few hours": + connector = "until " + + if len(startTimeDescriptor) != 0: + connector = " " + connector + window += startTimeDescriptor + connector + endTimeDescriptor + + return window + + +class StormSurgeSectionStats(SectionCommonStats): + def __init__(self, textProduct, segment, intersectStatList, timeRangeList): + SectionCommonStats.__init__(self, textProduct, segment) + self._inundationMax = None + self._onsetSurgeHour = None + self._endSurgeHour = None + self._windowSurge = None + + self._setStats(intersectStatList, timeRangeList) + + def _setStats(self, statList, timeRangeList): + phishStartTime = None + phishEndTime = None + possibleStop = 0 + +# print "*"*100 +# print "MATT phishStartTime = %s phishEndTime = %s possibleStop = %d" % (str(phishStartTime), str(phishEndTime), possibleStop) + + for period in range(len(statList)): + tr, _ = timeRangeList[period] + statDict = statList[period] + + phishPeak = self._textProduct._getStatValue(statDict, "InundationMax", "Max") + if phishPeak is not None: + if self._inundationMax is None or phishPeak > self._inundationMax: + self._inundationMax = phishPeak + + curPhish = self._textProduct._getStatValue(statDict, "InundationTiming", "Max") +# print "MATT tr = %s" % (repr(tr)) +# print "MATT curPhish = '%s' possibleStop = %d" % (str(curPhish), possibleStop) +# print "MATT phishStartTime = %s phishEndTime = %s" % (str(phishStartTime), str(phishEndTime)) + + if curPhish is not None and possibleStop != 2: + if curPhish > 0: + if phishStartTime is None: + phishStartTime = tr.startTime() + possibleStop = 0 + phishEndTime = None + elif phishStartTime is not None: + possibleStop += 1 + + if phishEndTime is None: + phishEndTime = tr.startTime() + + self._updateThreatStats(tr, statDict, "StormSurgeThreat") + + self._windowSurge = "Window for Storm Surge Inundation: " + + if phishStartTime is None or self._inundationMax is None or self._inundationMax < 1: + self._windowSurge += "None" + else: + self._onsetSurgeHour = self._calculateHourOffset(phishStartTime) + startTime = AbsTime(self._textProduct._issueTime_secs + self._onsetSurgeHour*60*60) + +# print "MATT surge startTime = %s self._onsetSurgeHour = %s " % (repr(startTime), self._onsetSurgeHour) + if phishEndTime is not None: + self._endSurgeHour = self._calculateHourOffset(phishEndTime) + endTime = AbsTime(self._textProduct._issueTime_secs + self._endSurgeHour*60*60) + windowPeriod = self._textProduct.makeTimeRange(startTime, endTime) + else: + windowPeriod = self._textProduct.makeTimeRange(startTime, startTime + 1) + print "SARAH: window period =", windowPeriod + + startTimeDescriptor = self._textProduct._formatPeriod(windowPeriod) + + if phishEndTime is None: + self._windowSurge += "Begins " + startTimeDescriptor + elif phishStartTime == phishEndTime: + self._windowSurge += startTimeDescriptor + else: + endTimeDescriptor = self._textProduct._formatPeriod(windowPeriod, useEndTime = True) + + if self._onsetSurgeHour > 12: +# self._windowSurge += startTimeDescriptor +\ +# " through " +\ +# endTimeDescriptor + self._windowSurge += startTimeDescriptor +\ + " until " +\ + endTimeDescriptor + else: + self._windowSurge += "through " + endTimeDescriptor + + self._currentAdvisory["StormSurgeThreat"] = self._maxThreat + if self._inundationMax is not None: + # Round so we don't store values like 1.600000023841858 + self._currentAdvisory["StormSurgeForecast"] = \ + int(self._inundationMax * 10.0) / 10.0 + + +class FloodingRainSectionStats(SectionCommonStats): + def __init__(self, textProduct, segment, statList, timeRangeList): + SectionCommonStats.__init__(self, textProduct, segment) + self._sumAccum = None + + self._setStats(statList, timeRangeList) + + def _setStats(self, statList, timeRangeList): + for period in range(len(statList)): + tr, _ = timeRangeList[period] + statDict = statList[period] + + stats = self._textProduct.getStats(statDict, "QPF") + if stats is not None: + for (value, tr) in stats: + + if value is not None: + if self._sumAccum is None: + self._sumAccum = value + else: + self._sumAccum += value + + self._updateThreatStats(tr, statDict, "FloodingRainThreat") + + self._currentAdvisory["FloodingRainThreat"] = self._maxThreat + if self._sumAccum is not None: + # Round so that we don't end up with stats like 4.03143835067749 + self._currentAdvisory["FloodingRainForecast"] = \ + self._textProduct.round(self._sumAccum, "Nearest", 0.5) + + +class TornadoSectionStats(SectionCommonStats): + def __init__(self, textProduct, segment, statList, timeRangeList): + SectionCommonStats.__init__(self, textProduct, segment) + + self._setStats(statList, timeRangeList) + + def _setStats(self, statList, timeRangeList): + for period in range(len(statList)): + tr, _ = timeRangeList[period] + statDict = statList[period] + + self._updateThreatStats(tr, statDict, "TornadoThreat") + + self._currentAdvisory["TornadoThreat"] = self._maxThreat + + + from xml.etree.ElementTree import Element, SubElement, tostring, dump import xml.dom.minidom as minidom import re @@ -1709,7 +2516,7 @@ class XMLFormatter(): if "._" in sectionKey: sectionKey = re.sub(".*\._", "", sectionKey) - print "SARAH: sectionKey =", sectionKey + print "SARAH: sectionKey = %s" % (sectionKey) return sectionKey def dictionary(self, xml, productDict): @@ -1729,7 +2536,7 @@ class XMLFormatter(): if key not in self.xmlKeys(): sectionKey = self.getSectionKey(key) if sectionKey not in self.sectionKeys(): - print "SARAH: skipping", key, "in XML" + print "SARAH: skipping '%s' in XML" % (key) continue else: key = sectionKey @@ -1771,8 +2578,8 @@ class XMLFormatter(): if data is not None: if 'info' in key and 'Section' in key: subElement = SubElement(xml, key) - print "SARAH: info key =", key - print "SARAH: value =", data + print "SARAH: info key = '%s'" % (key) + print "SARAH: value = %s" % (data) if isinstance(data, list): subkey = 'info' + 'Sub' + key[4:] for value in data: @@ -1821,7 +2628,7 @@ class LegacyFormatter(): @return text -- product string ''' text = '' - print "SARAH: productParts =", productParts + print "SARAH: productParts = %s" % (repr(productParts)) for part in productParts: valtype = type(part) if valtype is str: @@ -1829,11 +2636,11 @@ class LegacyFormatter(): elif valtype is tuple: name = part[0] infoDicts = part[1] - print "SARAH: name =", str(name) - print "SARAH: infoDicts =", infoDicts + print "SARAH: name = %s" % (str(name)) + print "SARAH: infoDicts = %s" % (repr(infoDicts)) newtext = self.processSubParts(productDict.get(name), infoDicts) - print "SARAH: newtext type =", type(newtext) - print "SARAH: newtext =", repr(newtext) + print "SARAH: newtext type = %s" % (type(newtext)) + print "SARAH: newtext = '%s'" % (repr(newtext)) text += newtext continue elif valtype is list: @@ -1869,7 +2676,7 @@ class LegacyFormatter(): text += self.processSubsection(productDict[name]) elif name == 'infoSection': text += self.processInfoSection(productDict['infoSection']) - elif name == 'endProduct': + elif name in ['endProduct', 'endSection']: text += '$$\n' elif name == 'CR': text += '\n' @@ -1877,10 +2684,16 @@ class LegacyFormatter(): text += '&&\n' elif name not in self._noOpParts(): textStr = productDict.get(name) - print "SARAH: name =", name - print "SARAH: textStr =", textStr + print "SARAH: name = %s" % (name) + print "SARAH: textStr = %s" % (textStr) if textStr: text += textStr + '\n' + + # Cleanup the case of the last segment which will wind up with two sets + # of '$$' + text = re.sub("\$\$\n+\$\$", "$$\n", text) + + # Return completed text return text def _noOpParts(self): @@ -1997,7 +2810,7 @@ class LegacyFormatter(): text = "* For more information:\n" text += self._buildInfoSection(infoSection, tabLevel=1) - return text + "\n" + return text + "\n$$\n\n" def _buildInfoSection(self, infoSection, tabLevel): text = "" @@ -2038,11 +2851,11 @@ class LegacyFormatter(): """ text = '' for i in range(len(subParts)): - print "SARAH: subpart subParts[i] =", subParts[i] - print "SARAH: subpart infoDicts[i] =", infoDicts[i] + print "SARAH: subpart subParts[i] = %s" % (subParts[i]) + print "SARAH: subpart infoDicts[i] = %s" % (infoDicts[i]) newtext = self._processProductParts(subParts[i], infoDicts[i].get('partsList')) - print "SARAH: subpart newtext type =", type(newtext) - print "SARAH: subpart newtext =", repr(newtext) + print "SARAH: subpart newtext type = %s" % (type(newtext)) + print "SARAH: subpart newtext = '%s'" % (repr(newtext)) text += newtext return text