Answers to Exercises


Answer to Simple Tabular Product Exercise 1

########################################################################
# Table Definition
#
########################################################################
## EXAMPLE OUTPUT

##    Exercise1 for Apr 02 02 13:00:00 GMT - Apr 03 02 01:00:00 GMT.
##
##    Edit Area    QPF    Precip (%)  Wind (mph)
##
##    Area 1             0             6                 NW  0
##    Area 2             0            11                NE  5
##    Area 3             0             4                 W 10

Definition = {
     ## General Set-Up
     "type": "table",
     "displayName": "TextEx1", # for Product Generation Menu
     "outputFile": "./Exercise1.txt", # default output file

     ## Table Layout
     "constantVariable": "TimePeriod",
     "rowVariable": "EditArea",
     "columnVariable": "WeatherElement",
     "beginningText": "Exercise1 for %TimePeriod. \n\n",
     "endingText": " ",

     ## Edit Areas
     "defaultEditAreas": [
                    ("area1", "Area 1"),
                    ("area2", "Area 2"),
                    ("area3", "Area 3"),
                 ],
     "runTimeEditAreas" : "no", # if yes, ask user at run time
     "areaType" : "Edit Area", # E.g. City, County, Basin, etc.

      ## Time Ranges
     "defaultRanges": ["Today"],
     "runTimeRanges" : "no", # if yes, ask user at run time
     ## Weather Elements
     # Name , Label , Analysis Method , ReportAs Method ,
     # DataType , Rounding , Conversion

     "elementList": [
             ("QPF", "QPF", "avg", "singleValue",
             "Scalar", .01, None),

             ("PoP", "Precip (%)", "avg", "singleValue",
             "Scalar", 1, None),

             ("Wind","Wind (mph)", "vectorRange", "avgValue",
             "Vector", 5,  None),
             ],
}
 

Answer to Simple Tabular Products Exercise 2

########################################################################
# Table Definition
#
########################################################################
## EXAMPLE OUTPUT

##    Experimental Surface Temperature Guidance Product
##
##    Edit Area   13Z/2   16Z/2   19Z/2   22Z/2
##
##    Area 1            28          28          28          25
##    Area 2            30          31          31          27
##    Area 3            32          33          34          27

Definition = {
     ## General Set-up
    "type": "table",
     "displayName": "TextEx2", # for Product Generation Menu
     "outputFile": "./SurfaceTemp.txt", # default output file

     ## Table Layout
     "constantVariable": "WeatherElement",
     "rowVariable": "EditArea",
     "columnVariable": "TimePeriod",
     "beginningText": "Experimental Surface Temperature Guidance Product \n\n",
     "endingText": "",

     ## Edit Areas
     "defaultEditAreas" : [
            ("area1", "Area 1"),
            ("area2", "Area 2"),
            ("area3", "Area 3"),
        ],
     "runTimeEditAreas" : "yes", # if yes, ask user at run time
     "areaType" : "Edit Area", # E.g. City, County, Basin, etc.

       ## Time Ranges
     "defaultRanges": ["Today"],
     "runTimeRanges" : "no", # if yes, ask user at run time

     ## Weather Elements
             "elementList": [
            ("T","Temp", "avg", "singleValue",
             "Scalar", 1,  None),
             ],

     ## Time Period
     "timePeriod": 3,
     "runTimePeriod": "yes", # If yes, ask user at run time for period
     }


Answer to Smart Tabular Exercise 2

########################################################################
# SmartElementTable_Local
#
#   Type: smart
#   Local product:
#     SmartElementTable_Local(type: smart)
#   To customize this product for your site:
#      Set up SmartElementTable_Local (see template below)
#      to override variables, definitions, thresholds, and methods
##
##########################################################################
import SmartElementTable
import string, time, re, os, types, copy
import TimeRange
class TextProduct(SmartElementTable.TextProduct):
    Definition = copy.deepcopy(SmartElementTable.TextProduct.Definition)

    Definition["displayName"] = "SmartElementTable"
    #Definition["outputFile"] = "{prddir}/TEXT/SmartElementTable.txt"
    #Definition["regionList"] = [
    #        ("area1","AREA 1"),
    #        ("area2","AREA 2"),
    #        ("area3","AREA 3"),
    #        ],
    #Definition["regionList"] = [
    #        ("/33",["AREA 1","AREA 2"]),
    #        ("/19",["AREA 3"])
    #        ],

    Definition["elementList"] = ["Temp", "PoP", "Wind"] 
    #Definition["elementList"] = ["Temp", "PoP"] # Default
    #Definition["elementList"] = ["Temp", "Humidity"]
    #Definition["elementList"] = ["Temp", "Humidity", "PoP"]
    #Definition["elementList"] = ["Temp", "PoP", "Humidity"]
    #Definition["elementList"] = ["PoP", "Humidity", "Temp"]
    #Definition["introLetters"] = ".<"

    def __init__(self):
        SmartElementTable.TextProduct.__init__(self)

    def _getAnalysisList(self):
      return [
          ("MinT", self.avg),
          ("MaxT", self.avg),
          ("MinRH", self.avg),
          ("MaxRH", self.avg),
          ("PoP", self.stdDevMaxAvg),
          ("Wind", self.vectorAvg),
          ]

    def _titleDict(self):
        return {
            "Temp":    "TEMPERATURE",
            "PoP":     "PRECIPITATION",
            "Humidity":"HUMIDITY",
            "Wind":    "WIND",
            }

    def _getWindValues(self, statList, argDict):
       # Return a string of PoP values in the statList
       wind = []
       windStr = " "
       index = 0
       for stats in statList:
           val = self._getWindValue(stats)
           if index < len(statList)-1:
               windStr = windStr  + val + " "
           else:
               windStr = windStr + val
           index += 1
       windStr = windStr + " "
       return windStr

    def _getWindValue(self, stats):
       wind = self.getStats(stats,"Wind")
       if wind is None:
           val = "    "
       else:
           mag, dir = wind
           mag = self.round(mag, "Nearest", 5)
           val = self.getVectorVal((mag,dir))
       return val

Answer to Smart Tabular Exercise 3

# ---------------------------------------------------------------------
# This software is in the public domain, furnished "as is", without
# technical  support, and with no warranty, express or implied, as to
# its usefulness for any purpose.
#
# FWFTable_BOU_Overrides.TextUtility
#
#  This file is used for WFO specific overrides of the FWFTable
#  formatter. 
#
#
# Methods:
#   Overrides:
#
#   Additions:
#
# ---------------------------------------------------------------------

import string, time, re, os, types, copy
import TimeRange
import TextRules

#**********************************************************************
# MAKE NO CHANGES HERE
# The minimum contents of this file are the following class definition
# and the __init__ method with only "pass" line in it.

class FWFTable_BOU_Overrides:
    def __init__(self):
        pass

# End MAKE NO CHANGES HERE
#**********************************************************************
    # Make sure to indent methods inside the class statement.
    #----- WFO BOU FWFTable Overrides -----

    # It is helpful to put a debug statement at the beginning of each
    # method to help with trouble-shooting.
    #def _method(self):
        #self.debug_print("Debug: _method in FWFTable_CR_Overrides")

    # Example of Overriding a dictionary from TextRules
    #def phrase_descriptor_dict(self, tree, node):
        #dict = TextRules.TextRules.phrase_descriptor_dict(self, tree, node)
        #dict["PoP"] = "chance of"
        #return dict

    def _rowList(self):
        # The rowList is controls what parameters go into the table.
        # The list is a set of (label:method) pairs.
        # You may change the label if you like.
        # The order of the list determines the order of the rows in the table
        # so you may re-arrange the order if you like.
        return [
            # Directive requirements

            # MODIFICATION: Changing order of Precip and Cloud cover
            #("CLOUD COVER", self._cloudCover_row),
            #("PRECIP TYPE", self._precipType_row),
            ("PRECIP TYPE", self._precipType_row),
            ("CLOUD COVER", self._cloudCover_row),
           
            ("CHANCE PRECIP (%)", self._chancePrecip_row),
            ("TEMP (24H TREND)", self._tempWithTrend_row),
            ("RH % (24H TREND)",self._rhWithTrend_row),
            # Use these if you do not want trends
            #("TEMP", self._temp_row),
            #("RH %", self._rh_row),
            ("20FTWND-VAL/AM(MPH)", self._windValleyMph_row),
            ("20FTWND-RDG/PM(MPH)", self._windRidgeMph_row),
            # Directive optional products
##            ("PRECIP AMOUNT", self._precipAmount_row),
##            ("PRECIP DURATION", self._precipDuration_row),
##            ("PRECIP BEGIN", self._precipBegin_row),
##            ("PRECIP END", self._precipEnd_row),
##            ("MIXING HGT(M-AGL/MSL)", self._mixHgtM_row),
##            ("MIXING HGT(FT-AGL/MSL)", self._mixHgtFt_row),
##            ("TRANSPORT WND (KTS)", self._transWindKts_row),
##            ("TRANSPORT WND (M/S)", self._transWindMS_row),
##            ("TRANSPORT WND (MPH)", self._transWindMph_row),
##            ("VENT RATE (KT-FT)", self._ventRateKtFt_row),
##            ("VENT RATE (M/S-M)", self._ventRate_row),
##            ("DISPERSION", self._dispersion_row),
##            ("DSI", self._dsi_row),
##            ("SUNSHINE HOURS", self._sunHours_row),
##            # If you need Ceiling, uncomment the Ceiling line in _getAnalysisList
##            ("CEILING", self._ceiling_row),
##            ("CWR", self._cwr_row),
##            ("LAL", self._lal_row),
##            ("HAINES INDEX", self._haines_row),
##            ("RH RECOVERY", self._rhRecovery_row),
##            # If you need 500m Mix Hgt Temp, uncomment the MixHgt500
##            # line in _getAnalysisList
##            ("MIX HGT 500", self._mixHgt500_row),
##            ("STABILITY CLASS", self._stability_row),
            ]
 
    def _getVariables(self, argDict):
        # Make argDict accessible
        self.__argDict = argDict

        # Get variables from VariableList
        self._definition = argDict["forecastDef"]
        for key in self._definition.keys():
            exec "self._" + key + "= self._definition[key]"

        varDict = argDict["varDict"]
        self._issuanceType = varDict["Issuance Type"]
        self._productType = varDict["Forecast Product"]
       
        # Determine issue time
        self._issueTime = self.IFP().AbsTime.current()
       
        # Determine expiration time
        self._expirationTimeOffset = 12
        self._expireTime = self._issueTime + self._expirationTimeOffset*3600
        #self._expireTime = time.strftime("%d%H%M",time.gmtime(expireTime))
       
        # Set up product-specific variables
        self._colWidth = 13
        if self._columnJustification == "l":
            self._rowLabelWidth = 22
        else:
            self._rowLabelWidth = 24
        self._fixedValueWidth = 13
        self._analysisList = self._getAnalysisList()

        # Calculate current times

        self._ddhhmmTime = self.getCurrentTime(
            argDict, "%d%H%M", shiftToLocal=0, stripLeading=0)
       
        # MODIFICATION: Changing format of current time
        #self._timeLabel = self.getCurrentTime(
        #    argDict, "%l%M %p %Z %a %b %e %Y", stripLeading=1)
        self._timeLabel = self.getCurrentTime(
            argDict, "%b %e %Y %l%M %p %Z %a", stripLeading=1)
       
        return None
 
    def _sky(self, statDict, timeRange, argList):
        # Return a sky value
        sky = self.getStats(statDict, "Sky")
        print "USING OVERRIDDEN SKY THRESHOLDS"
        # MODIFICATION -- Changing sky thresholds
##        if sky is None:
##            value = ""
##        elif  sky < 10:
##            value = "CLEAR"
##        elif sky < 30:
##            value = "MCLEAR"
##        elif sky <= 60:
##            value = "PCLDY"
##        elif sky <= 80:
##            value = "MCLDY"
##        else:
##            value = "CLOUDY"
        if sky is None:
            value = ""
        elif  sky < 15:
            value = "CLEAR"
        elif sky < 40:
            value = "MCLEAR"
        elif sky <= 70:
            value = "PCLDY"
        elif sky <= 85:
            value = "MCLDY"
        else:
            value = "CLOUDY"
        return value
 

Answer to Smart Tabular Exercise 4

# ---------------------------------------------------------------------
# This software is in the public domain, furnished "as is", without
# technical  support, and with no warranty, express or implied, as to
# its usefulness for any purpose.
#
# FWFTable_BOU_Definition.TextUtility
#
#  This file sets up all the Product Definition overrides for the
#  FWFTable formatter for a site.
#
# ---------------------------------------------------------------------

#**********************************************************************
# MAKE NO CHANGES HERE
# The minimum content of this file is the following Definition statement

Definition = {}

# End MAKE NO CHANGES HERE
#**********************************************************************
#####################################################
# Override VariableList if desired
#
#VariableList = []

#----- WFO BOU FWFTable Definition -----
# Definition Statements must start in column 1.

# REQUIRED CONFIGURATION ITEMS
Definition['displayName'] = None
#Definition['displayName'] = "FWF_Tabular"

Definition["defaultEditAreas"] = "Combinations_FWFTable_BOU"
Definition["mapNameForCombinations"] = "FireWxZones_BOU" # Map background for creating Combinations

# MODIFICATION -- Added gustWindDifferenceThreshold
Definition["gustWindDifferenceThreshold"] = 15


# ---------------------------------------------------------------------
# This software is in the public domain, furnished "as is", without
# technical  support, and with no warranty, express or implied, as to
# its usefulness for any purpose.
#
# FWFTable_BOU_Overrides.TextUtility
#
#  This file is used for WFO specific overrides of the FWFTable
#  formatter. 
#
#
# Methods:
#   Overrides:
#
#   Additions:
#
# ---------------------------------------------------------------------

import string, time, re, os, types, copy
import TimeRange
import TextRules

#**********************************************************************
# MAKE NO CHANGES HERE
# The minimum contents of this file are the following class definition
# and the __init__ method with only "pass" line in it.

class FWFTable_BOU_Overrides:
    def __init__(self):
        pass

# End MAKE NO CHANGES HERE
#**********************************************************************
    # Make sure to indent methods inside the class statement.
    #----- WFO BOU FWFTable Overrides -----

    # It is helpful to put a debug statement at the beginning of each
    # method to help with trouble-shooting.
    #def _method(self):
        #self.debug_print("Debug: _method in FWFTable_CR_Overrides")

    # Example of Overriding a dictionary from TextRules
    #def phrase_descriptor_dict(self, tree, node):
        #dict = TextRules.TextRules.phrase_descriptor_dict(self, tree, node)
        #dict["PoP"] = "chance of"
        #return dict
  
    def _rowList(self):
        # The rowList is controls what parameters go into the table.
        # The list is a set of (label:method) pairs.
        # You may change the label if you like.
        # The order of the list determines the order of the rows in the table
        # so you may re-arrange the order if you like.
        return [
            # Directive requirements

            ("CLOUD COVER", self._cloudCover_row),
            ("PRECIP TYPE", self._precipType_row),           
            ("CHANCE PRECIP (%)", self._chancePrecip_row),
            ("TEMP (24H TREND)", self._tempWithTrend_row),
            ("RH % (24H TREND)",self._rhWithTrend_row),
            # Use these if you do not want trends
            #("TEMP", self._temp_row),
            #("RH %", self._rh_row),
            ("20FTWND-VAL/AM(MPH)", self._windValleyMph_row),
            ("20FTWND-RDG/PM(MPH)", self._windRidgeMph_row),
            # Directive optional products
           
            # MODIFICATION -- Added Wind Gust row
            ("WIND GUST",           self._windGust_row),
           
##            ("PRECIP AMOUNT", self._precipAmount_row),
##            ("PRECIP DURATION", self._precipDuration_row),
##            ("PRECIP BEGIN", self._precipBegin_row),
##            ("PRECIP END", self._precipEnd_row),
##            ("MIXING HGT(M-AGL/MSL)", self._mixHgtM_row),
##            ("MIXING HGT(FT-AGL/MSL)", self._mixHgtFt_row),
##            ("TRANSPORT WND (KTS)", self._transWindKts_row),
##            ("TRANSPORT WND (M/S)", self._transWindMS_row),
##            ("TRANSPORT WND (MPH)", self._transWindMph_row),
##            ("VENT RATE (KT-FT)", self._ventRateKtFt_row),
##            ("VENT RATE (M/S-M)", self._ventRate_row),
##            ("DISPERSION", self._dispersion_row),
##            ("DSI", self._dsi_row),
##            ("SUNSHINE HOURS", self._sunHours_row),
##            # If you need Ceiling, uncomment the Ceiling line in _getAnalysisList
##            ("CEILING", self._ceiling_row),
##            ("CWR", self._cwr_row),
##            ("LAL", self._lal_row),
##            ("HAINES INDEX", self._haines_row),
##            ("RH RECOVERY", self._rhRecovery_row),
##            # If you need 500m Mix Hgt Temp, uncomment the MixHgt500
##            # line in _getAnalysisList
##            ("MIX HGT 500", self._mixHgt500_row),
##            ("STABILITY CLASS", self._stability_row),
            ]

    def _getAnalysisList(self):
        return[
          ("Sky", self.avg),
          ("PoP", self.stdDevMaxAvg),
          ("Wx", self.dominantWx, [12]),
          ("Wx", self.dominantWx, [0]),
          ("MaxT", self.minMax),
          ("MinT", self.minMax),
          ("T", self.minMax),
          ("Wind", self.vectorAvg, [6]),
          ("Wind20ft", self.vectorAvg, [6]),
          ("QPF", self.minMaxSum),
          ("MaxRH", self.minMax),
          ("MinRH", self.minMax),
          ("RH", self.minMax),
          ("MixHgt", self.minMax, [0]),
          ("TransWind", self.vectorAvg, [0]),
          ("VentRate", self.minMax, [0]), # aka "Dispersion" prior to RPP20
          ("DSI", self.avg),
          ("HrsOfSun", self.avg),
          # Uncomment the next line if you're carrying Cig Height
          #("Ceiling", self.minMax),
          ("CWR", self.stdDevMaxAvg),
          ("Haines", self.minMaxAvg),
          ("LAL", self.maximum),
          ("Ttrend", self.minMax),
          ("RHtrend", self.minMax),
          ("Stability", self.avg),
          # Uncomment the next line if you're carrying 500m mix height temp
          #("MixHgt500", self.avg),

          # MODIFICATION -- Added entries for Wind Gust
          ("WindGust", self.minMax),
          ("Wind", self.vectorMinMax),
          ("Wind20ft", self.vectorMinMax),
          ]

    def _windGust_row(self, fcst, label, statList, priorStatDict):
        fcst = fcst + self.makeRow(
            label, self._colWidth, self._timeRangeList, statList,
            self._windGust, None, self._rowLabelWidth, self._fixedValueWidth,
            self._columnJustification)
        return fcst

    def _windGust(self, statDict, timeRange, argList):
        # Use Wind20ft if available, otherwise adjust Wind
        # Stats vectorAvg

        # See if there is any WindGust data
        windGust = self.getStats(statDict, "WindGust")
        if windGust is None:
            return ""

        # Get the wind information and adjust if using Wind instead
        # of Wind20ft
        windStats = self.getStats(statDict,"Wind20ft__vectorMinMax")
        if windStats is None:
            windStats = self.getStats(statDict,"Wind__vectorMinMax")
            if windStats is None:
                return ""
            mag, dir = windStats
            maxWind = self.getValue(mag, "Max")
            maxWind = maxWind * self._windAdjustmentFactor
            windStats = (maxWind, dir)

        # Check the gustWindDifferenceThreshold
        mag, dir = windStats
        maxWind = self.getValue(mag, "Max")
        maxGust = self.getValue(windGust, "Max")
        if maxGust - maxWind <= self._gustWindDifferenceThreshold:
            return ""
        else:
            return self.getScalarVal(maxGust)


Answer to Phrase Exercise 1

# ---------------------------------------------------------------------
# This software is in the public domain, furnished "as is", without
# technical  support, and with no warranty, express or implied, as to
# its usefulness for any purpose.
#
# ZFP_BOU_Overrides.TextUtility
#
#  This file is used for WFO specific overrides of the ZFP
#  formatter. 
#
#
# Methods:
#   Overrides:
#
#   Additions:
#
# ---------------------------------------------------------------------

import string, time, re, os, types, copy
import TimeRange
import TextRules

#**********************************************************************
# MAKE NO CHANGES HERE
# The minimum contents of this file are the following class definition
# and the __init__ method with only "pass" line in it.

class ZFP_BOU_Overrides:
    def __init__(self):
        pass

# End MAKE NO CHANGES HERE
#**********************************************************************
    # Make sure to indent methods inside the class statement.
    #----- WFO BOU ZFP Overrides -----

    # It is helpful to put a debug statement at the beginning of each
    # method to help with trouble-shooting.
    #def _method(self):
        #self.debug_print("Debug: _method in ZFP_CR_Overrides")

    # Example of Overriding a dictionary from TextRules
    #def phrase_descriptor_dict(self, tree, node):
        #dict = TextRules.TextRules.phrase_descriptor_dict(self, tree, node)
        #dict["PoP"] = "chance of"
        #return dict
   
    def snow_words(self, tree, node):
        "Create phrase for snow accumulation"

        # First check if the pop threshold has been met.
        # If not, then do not generate phrase.
        threshold = self.pop_snow_lower_threshold(tree, node)
        lowPopFlag = self.lowPop_flag(tree, node, threshold)
        if lowPopFlag == 1:
            return self.setWords(node, "")

        # Second, check to see if it is snowing based on a configurable
        # list called accumulatingWx. If not, then do not generate phrase.
        timeRange = node.getTimeRange()
        areaLabel = node.getAreaLabel()
        wxStats =  tree.stats.get("Wx", timeRange, areaLabel, mergeMethod="List")
        if wxStats is None:
            return self.setWords(node, "")
        accumulatingWx = ['S', 'SW', 'IP', 'IC']
        accumulatingWxFlag = 0
        for rankList, tr in wxStats:
            subkeys = self.getSubkeys(rankList)
            for subkey in subkeys:
                if subkey.wxType() in accumulatingWx:
                    accumulatingWxFlag = 1
        if accumulatingWxFlag == 0:
            return self.setWords(node, "")

        # Third, load in the SnowAmt statistics.
        threshold = 1
        statDict = node.getStatDict()
        stats = self.getStats(statDict, "SnowAmt")
        if stats is None:
            return self.setWords(node, "")
        min, max = self.getValue(stats, "MinMax")
           
        # Fourth, round accumulation and create accumulation strings.
        if min%1 == 0:
            min = int(min)
            minStr = `min`
        else:
            minStr = `int(min+0.5)`
        if max%1 == 0:
            max = int(max)
            maxStr = `max`
        else:
            maxStr = `int(max+0.5)`

        #print "min, max", min, max, node.getTimeRange(), node.getAreaLabel()

        # Finally, generate the snow accumulation phrase.   
        if min == 0 and max == 0:
            return self.setWords(node,"no")
        elif min < 0.5 and max < 0.5:
            return self.setWords(node,"little or no")

        outUnits = self.element_outUnits(tree, node, "SnowAmt", "SnowAmt")
        unit = self.units_descriptor(tree, node,"unit", outUnits)
        units = self.units_descriptor(tree, node,"units", outUnits)
        min = int(min+0.5)
        max = int(max+0.5)
                       
        # Single Value input
        if  min == max:
            # Handle case of 1 inch
            if min == 1:
                units = unit
            value = "around " + minStr

        # Range
        else:
            value = "of " + minStr + "-" + maxStr
            # Handle case when lower value is 0
            if min == 0:
                value = "up to " + maxStr
            if max == 1:
                units = unit
               
        snowPhrase = value + " " + units       
        return self.setWords(node, snowPhrase)

Answer to Phrase Exercise 4

# ---------------------------------------------------------------------
# This software is in the public domain, furnished "as is", without
# technical  support, and with no warranty, express or implied, as to
# its usefulness for any purpose.
#
# ZFP_BOU_Overrides.TextUtility
#
#  This file is used for WFO specific overrides of the ZFP
#  formatter. 
#
#
# Methods:
#   Overrides:
#
#   Additions:
#
# ---------------------------------------------------------------------

import string, time, re, os, types, copy
import TimeRange
import TextRules

#**********************************************************************
# MAKE NO CHANGES HERE
# The minimum contents of this file are the following class definition
# and the __init__ method with only "pass" line in it.

class ZFP_BOU_Overrides:
    def __init__(self):
        pass

# End MAKE NO CHANGES HERE
#**********************************************************************
    # Make sure to indent methods inside the class statement.
    #----- WFO BOU ZFP Overrides -----

    # It is helpful to put a debug statement at the beginning of each
    # method to help with trouble-shooting.
    #def _method(self):
        #self.debug_print("Debug: _method in ZFP_CR_Overrides")

    # Example of Overriding a dictionary from TextRules
    #def phrase_descriptor_dict(self, tree, node):
        #dict = TextRules.TextRules.phrase_descriptor_dict(self, tree, node)
        #dict["PoP"] = "chance of"
        #return dict
   
    def phrase_descriptor_dict(self, tree, node):
        # Descriptors for phrases
        dict = TextRules.TextRules.phrase_descriptor_dict(self, tree, node)
        # This is the default. Triggers if ALL coverage terms are areal
        #dict["PoP"] = self.allAreal_or_chance_pop_descriptor,
        # Uncomment this line for invoking areal or chance pop descriptor
        #    Triggers if ANY coverage terms are areal
        #dict["PoP"] = self.areal_or_chance_pop_descriptor,
        # Uncomment this line to use "chance" descriptor in all cases
        #dict["PoP"] = "chance of"
        dict["QPF"] = "qualitative precipitation"
        return dict


    def qpf_phrase(self):
        return {
            "setUpMethod": self.qpf_setUp,
            "wordMethod": self.qpf_words,
            "phraseMethods": self.standard_phraseMethods() 
            }   
    def qpf_setUp(self, tree, node):
        elementInfoList = [self.ElementInfo("QPF", "List")]
        self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)     
        return self.DONE()
   
    def qpf_words(self, tree, node) :
        "Create phrase for QPF"
        # Wait for weather phrase to complete
        statDict = node.getStatDict()
        qpf = self.getStats(statDict, "QPF")
        if qpf is None:
            return self.setWords(node, "")

        # Analysis-driven
        min, max = self.getValue(qpf, "MinMax")
        units = self.units_descriptor(tree, node, "units", "in")
        min = int(min)
        max = int(max)
        minStr = self.fformat(min, .01)
        maxStr = self.fformat(max, .01)
        #print "min, max", min, max
        if min==max:
            words = minStr + " " + units
        else:
            words =minStr + "-" + maxStr + " " + units
       
        return self.setWords(node, words)

    def Period_1(self):
        component =  {
            "type": "component",
            "methodList": [
                          self.orderPhrases,
                          self.assemblePhrases,  
                          self.wordWrap,         
                          ],
            "analysisList": [
                       #("MinT", self.avg),
                       #("MaxT", self.avg),
                       ("MinT", self.stdDevMinMax),
                       ("MaxT", self.stdDevMinMax),
                       ("T", self.hourlyTemp),
                       ("T", self.minMax),
                       ("Sky", self.median, [3]),
                       ("PoP", self._PoP_analysisMethod("Period_1"), [3]),
                       ("PoP", self.binnedPercent, [3]),
                       ("SnowAmt", self.accumMinMax),
                       ("StormTotalSnow", self.accumMinMax),
                       ("IceAccum", self.accumMinMax),
                       ("SnowLevel", self.avg),
                       ("Wind", self.vectorMedianRange, [6]),
                       ("Wind", self.vectorMinMax, [6]),
                       ("WindGust", self.maximum, [6]),
                       ("Wx", self.rankedWx, [3]),
                       ("WindChill", self.minMax, [6]),
                       ("HeatIndex", self.minMax, [6]),
                       ("QPF", self.accumMinMax, [6]),
                       ],
            "phraseList":[
                   self.sky_phrase,
                   self.skyPopWx_phrase,
                   self.wind_summary,
                   self.reportTrends,
                   self.weather_phrase,
                   self.severeWeather_phrase,
                   self.heavyPrecip_phrase,
                   self.visibility_phrase,
                   self.snow_phrase,
                   self.total_snow_phrase,
                   self.snowLevel_phrase,
                   self.iceAccumulation_phrase,
                   self.highs_phrase,
                   self.lows_phrase,
                   #self.highs_range_phrase,
                   #self.lows_range_phrase,
                   self.temp_trends,
                   self.wind_withGusts_phrase,
                   self.lake_wind_phrase,
                   self.popMax_phrase,
                   self.windChill_phrase,
                   # Alternative
                   #self.windBased_windChill_phrase,
                   self.heatIndex_phrase,
                   self.qpf_phrase,
                   ],
##            "additionalAreas": [
##                   # Areas listed by weather element that will be
##                   # sampled and analysed.
##                   # E.g. used for reporting population centers for temperatures.
##                   ("MaxT", ["City1", "City2"]),
##                   ("MinT", ["City1", "City2"]),
##                   ],
##            "intersectAreas": [
##                   # Areas listed by weather element that will be
##                   # intersected with the current area then
##                   # sampled and analysed. 
##                   # E.g. used in local effects methods.
##                   ("MaxT", ["Mountains"]),
##                   ("MinT", ["Valleys"]),
##             ],
        }
        if self._arealSkyAnalysis:
            component["analysisList"].append(("Sky", self.binnedPercent, [6]))
        if self._useStormTotalSnow:
            phraseList = component["phraseList"]
            index = phraseList.index(self.total_snow_phrase)
            phraseList[index] = self.stormTotalSnow_phrase
            component["phraseList"] = phraseList
        return component