Infrastructure Reference

   Arguments in the Standard File
   Naming Conventions
   Sample Analysis
        Statistics By Time Range
        Arguments to SampleAnalysis methods
       Checking for Edit Areas
        Global Thresholds and Variables
        Scalar Analysis methods
        Vector Analysis methods
        DiscreteAnalysis methods
        Weather Analysis methods
           Algorithm for rankedWx and dominantWx
   Text Rules Libraries for Narrative Products
        Setting Day and Night Times
        Analysis-Driven Phrases
        Arguments to Narrative methods
        Checking for Edit Areas
        Global Thresholds and Variables
        Narrative Phrases
           Scalar Weather Element Phrases
           Vector-Related Phrases (Wind and WindGust)
           Sea Breeze Identification
           Weather Phrases
           Weather Subkey Attributes "Primary" and "Mention"
           Customizing Weather Phrases
           Pop/Wx Consistency
           Combined Element Phrases
           Marine Phrases
         Algorithm for WaveHeight and WindWaveHgt Reporting
           Fire Weather Phrases
           Discrete Phrases (Headlines)
        Narrative Strategies
           Non-Linear Thresholds
           DataValue Extraction from the Grids
            Unit Conversion
            Standard and Non-Standard Rounding
            Range  Adjustment
           Null Values
           Local Effects
            Local Effects and Compound Phrases
            Consolidation Rules for Local Effects
            Local Effect Examples
              Null Values and Local Effects
             "Except" vs. "Otherwise" wording
             More Local Effect Area Features
             Local Effects for Snow Accumulation and Total Snow Accumulation -- Using a Method for Checking the Local Effect Threshold
             Local Effects for the Combined SkyPopWx Phrase
           Period Combining
           Phrase Consolidation
           Phrase Ordering
           "Until" Phrasing
           Visibility
           Weather Phrasing
   Appendix A:  Local Effects Design


Arguments in the Standard Product File

 


ARGUMENT ACCESSING INFORMATION
argDict  Dictionary containing system information about the product being created. In most cases, you will not need to access information within argDict, but it must be passed for internal system use.
fcst The text string representing the product. It will be in a partially completed state as it is passed to each method in turn. Be sure to "return fcst" at the end of a method that is passed the fcst.
editArea Contains the name and polygon points that define an edit area. 
      editAreaName = editArea.id().name()
areaLabel The label given to the current edit area (or current combination of areas).   This label is defined via the "defaultEditAreas" entries OR within the Combinations file entries.
timeRange Time Range over which statistics are being generated.  This is always in GMT. 
    timeRange.startTime() 
    timeRange.startTime().day(),   timeRange.startTime().month(),  timeRange.startTime().hour() 
    timeRange.endTime()

Naming conventions

Throughout the infrastructure, thresholds, variables, and methods are named with suffixes that indicate the return value expected.   For example,  the method, pop_lower_threshold,returns a weather element value for PoP.   The table below lists the suffixes and their meaning (in alphabetical order):
 
 


METHOD SUFFIXES METHOD RETURN VALUE
_dict Python dictionary
_difference a difference in weather values e.g. vector_mag_differenceis used to determine if wind magnitudes are significantly different
_element weather element name e.g. "WindWaveHgt" returned as the  seasWindWave_element
_flag 0 or 1
_list Python list
_percentage number between 0 and 100
_phrase text string phrase
_phrase_connector text string for connecting phrases e.g. " then " as in "Mostly cloudy in the morning then clearing."
_threshold weather element value
_weight number between 0 and 1
_valueStr string representing a weather element value
_valueList list of thresholds corresponding to value strings for weather element values
_value_connector text string for connecting values e.g. " to " as in "25 to 35 mph"
_nlValue This suffix indicates that the method can return either a single value, a method, OR a dictionary of (lowValue, highValue) : returnValue  entries.  See the section on "Non-linear Thresholds" for more information.

Sample Analysis

The Python methods described in this section can be found in the SampleAnalysis module which appears in the Utilities window of the Define Text Products Dialog.  Each method is listed with the value it returns.  All methods return "None" if there is no data available.

Statistics By Time Range

 Methods that are starred (**) below  (e.g. **avg) can be listed in the analysisList with an optional argument list  indicating how the time range is to be divided in reporting statistics.  A typical analysisList entry might look like this:

       "analysisList" : [
                                   ("MaxT", self.avg),
                                    ]

The result of the analysis will be a simple average value for the entire time range being examined.   Using the optional "divide" argument, the analysisList might look like this:

       "analysisList" : [
                                   ("MaxT", self.avg, [6]),
                                    ]

In this instance, the time range will be divided into 6-hour sub-ranges and an average value for MaxT will be reported for each sub-range. This information will be reported as a list of tuples. For example, a 12-hour time range would produce:

                [ (avg1, subRange1), (avg2, subRange2)]

The time range is divided from the endtime backward so that "odd" sub-ranges occur at the beginning of the time range.  For example, suppose we are dividing an 8 hour time range into 6-hour sub-ranges.  This will result in a 2-hour sub-range followed by a 6-hour sub-range.

The Text Rules methods are built to handle varying types of Sample Analyis return values.

If the time-divide argument is zero,  the statistics will be reported "byTimeRange" returning a list of (statistics, subRange) tuples,  one for each grid overlapping timeRange being examined.

In summary, the following table shows analysisList methods and their corresponding results:
 
 



ANALYSIS LIST SPECIFICATION RESULT
("MinT", self.avg) Average scalar value over the entire time range
("MinT", self.avg, [0])  List of (avg, subRange) tuples, one for each grid overlapping timeRange
("MinT", self.avg, [3])  TimeRange is divided into 3-hour sub-ranges and returns: 
 List of (avg, subRange) tuples, one for each sub-range
("MinT", self.avg, [6])  TimeRange is split into 6-hour sub-ranges and returns: 
 List of (avg, subRange) tuples, one for each sub-range

NOTE that if there is no data for ANY ONE  of the subRanges,  None is returned for the entire time range.

Arguments to Sample Analysis Methods

The SampleAnalysis thresholds and variables take several arguments which contain useful information allowing you to return values based on product type, edit area, weather element or time range.  The following table lists the arguments and shows how to access the information they contain.
 
 


ARGUMENT ACCESSING INFORMATION
parmHisto Weather Element:    elementName = parmHisto.parmID().parmName() 
Edit Area:                   editAreaName = parmHisto.area().id().name()
timeRange Time Range over which statistics are being generated.  This is always in GMT. 
    timeRange.startTime() 
    timeRange.startTime().day(),   timeRange.startTime().month(),  timeRange.startTime().hour() 
    timeRange.endTime()
componentName Current Text Product component being analyzed

 

Checking for Edit Areas

Sometimes you might want to check to see if you are processing a particular edit area or set of edit areas.  For example, you might want to base the analysisMethod on the current Edit Area.  To do this,  set up an analysis method in the "analysisList" as follows:

           ("Wind", self._wind_analysisMethod),

Then include this analysis method in your override file:

     def _wind_analysisMethod(self, parmHisto, timeRange, componentName):
            ChannelZone = ["PHZ130"]
            argDict = self._argDict
            argDict["editArea"] = (parmHisto.area(), "")
            if self.currentAreaContains(argDict, ChannelZone):
                 return self.vectorModeratedMax(parmHisto, timeRange, componentName)
            else:
                 return self.vectorModeratedMinMax(parmHisto, timeRange, componentName)      

GLOBAL THRESHOLDS AND VARIABLES

There are four categories of analysis methods: absolute, cumulative, standard deviation (stdDev) and moderated (percentage).  The absolute and cumulative categories do not filter the sampled data. All statistics are based on the entire  sampled data set.  The standard deviation (stdDev) and moderated methods do filter the sampled data set, however.  The stdDev methods ignore values that lie outside a configurable number of standard deviations from the mean.  Low and high thresholds are defined independently. The moderated methods work exactly the same way as stdDev methods, except that they filter data based on a ranked percentage.  Values are ranked, low to high, and outliers are excluded from the data set (e.g., the upper 10%).

Most analysis methods screen grids for temporal coverage of the given time range before including their data values in the analysis.    The exceptions to this are the methods that work with accumulative elements. The following table describes the temporal coverage thresholds you can override for this screening process.  To override a threshold, simply copy the method from the SampleAnalysis utility into your _Overrides file and change desired values.  Most methods weight grids temporally as well. Each grid will contribute proportional to its time duration for the sampled period.


temporalCoverage_percentage This is the percentage of the given time range COVERED by the grid in order to include it in the analysis.  This is the value used IF a value is not found in the temporalCoverage_dict (see below) 
NOTE: The temporalCoverage_hours (see below) must also be met by the grid for it to be included.
In addition, if a grid is completely contained within the time range, it will automatically be included. 
Default is 20 percent.
temporalCoverage_dict Temporal Coverage percentage specified by Weather Element.
temporalCoverage_hours This is the hours of the given time range COVERED by the grid in order to include it in the analysis.  This is the value used IF a value is not found in the temporalCoverage_hours_dict (see below) 
NOTE: The temporalCoverage_percentage (see above) must also be met by the grid for it to be included.
In addition, if a grid is completely contained within the time range, it will automatically be included. 
Default is 0 percent.
temporalCoverage_hours_dict Temporal Coverage hours specified by Weather Element.
moderatedDefault The value used by all "moderated" methods by default if the weather element does not appear in the moderated_dict. The moderated_dict value always overrides this default value.  This value is used for the min and max values.
moderated_dict Returns a dictionary that defines the min and max filter values for all "moderated" methods. For each weather element, a tuple defines the minimum threshold and the maximum threshold respectively.  All moderated methods rank all sampled values from lowest to highest and then exclude values that are below the minimum threshold and above the maximum threshold.
stdDevDefault The value used by all "stdDev" methods by default if the weather element does not appear in the stdDev_dict. The stdDev_dict value always overrides this default value.  This value is used for the min and max values.
stdDev_dict Returns a dictionary that defines the low and high limit at which outliers will be removed when calculating stdDev stats. These tuples represent the (low, high) number of standard 
deviations.  Any values falling outside this range will not be included in the calculated statistic. 
bin_dict Returns a dictionary that defines the bin values for "binnedPercent". Bin tuples represent (low, high) values for each bin. Bin values are inclusive.
maxMode_increment_dict Returns the increment to be used for "maxMode" when "binning" values before computing mode.  For example, the default "maxMode_increment" for "PoP" is 10. 

Here is how the standard deviation SampleAnalysis methods work:
1) compute the standard deviation of all grid values
2) the stdDev_dict contains a threshold, i.e., number of sigmas for each the min and the max
3) the routine calculates the min and max from the std deviation and the sigmas
4) in some cases, the result may fall outside of the actual data range.  In this case, the returned values will be the min or max of the actual data points.

Here is how the moderated SampleAnalysis methods work:
1) compute the "histogram" of the data values
2) the moderated_dict gives a percentage for min, and a percentage for max
3) we return values that are % percent from the min and % percent from the max, not numerically, but from a sorted list.  Thus if the histogram is
4,4,5,6,7,8,9,10,15,19, and your percent is 20% for the min and 10% for the max, you will pick out the 20% of the number and the 90% of the number in the
sequence.  Since there are exactly 10 samples in my exmaple, the 20% point will be 5, and the 90% point will be 15.  It isn't numerical.

Here is some further explanation (from Bill Schneider) on the moderated methods:

The moderated methods look at all the points in the grid, then they throw away highest (for max) or lowest (for min) percentage you have specified in the moderated dictionary. Then it uses the max or min of the remaining gridpoints.

So for example if you use moderatedMaximum for temperature and have defined the moderated percentages fore temperature as (10,15) in the dictionary, the method will take the grid points that have the highest values and throw away the top 15 percent of them. The remaining maximum value will be reported. For moderatedMinimum, the method will take the grid points that have the lowest values and throw away 15 % of the lowest values before reporting the minimum.
 

SCALAR WEATHER ELEMENTS


METHOD  RETURN VALUE:
**avg  average value
**minMax  (min, max)
**minimum minimum value
**maximum maximum value
**median  median value for given time range
**medianRange  2-value range around the median value for given time range
**mode  mode value for given time range
**modeRange  2-value range around the mode value for given time range
firstAvg  average value for first grid only
firstMinMax minmax value for the first grid only
minMaxAvg   (min, max, avg)
minMaxSum   ( min, max, sum)
Time-weighted min/max/ave of  the multiple grids overlapping the time range. This is used for rate-dependent weather elements such as QPF.
accumMinMax  (min, max,  sum)
Returns the min, max, and sum for rate-dependent weather elements such as QPF.
accumSum sum
Returns the sum for rate-dependent weather elements such as QPF.
**maxAvg  average value.
Calculates the average value  in each of the overlapping grids, and then returns the maximum of the averages.
**maxMode Calculates the mode value in each of the overlapping grids, and thern returns the maximum of the modes.  Before calculating the mode, the values are rounded to the nearest increment as given in the maxMode_increment_dict
**stdDevMaxAvg average value. 
Calculates the average value after filtering by standard deviation and then returns the maximum of the averages.
**moderatedMaxAvg average value. 
Calculates the average value after filtering by moderated (percentage) and then returns the maximum of the averages.
**stdDevAvg average value after filtering based on stdDev
stdDevFirstAvg average value for the first grid only
**stdDevMinMax  (min, max)
min, max set at standard deviations around the mean.
The stdDev_dict, defines the number of standard deviations to compute min, max around the mean. Default is 1.0 standard deviations. Both min and max can be defined independently per weather element.
**stdDevMin same as stdDevMinMax except just returns the minimum value
**stdDevMax same as stdDevMinMax except just returns the maximum value
stdDevFirstMinMax (min,max) 
stdDevMinMax (see above) for the first grid only
**moderatedAvg Returns the average value after filtering based on percentage defined in moderated_dict
**moderatedMinMax (min, max) 
Returns the min and max values after filtering based on the percentage defined in moderated_dict.
**moderatedMin same as moderatedMinMax except just returns the minimum
**moderatedMax same as moderatedMinMax except just returns the maximum
moderatedFirstAvg same as moderatedAvg except only the first grid is sampled
moderatedFirstMinMax same as moderatedMinMax except only the first grid is sampled.
hourlyTemp  return hourly T values in tuples;  returns a list of tuples:    (avgValue, startHour)
binnedPercent return a list of tuples representing bins and corresponding percentages of values in each bin. The bins are defined in the bins_dict.NOTE: When using this method, make sure you have grids covering the entire time range of interest so that the percentages which are time-weighted will add up to 100.

 

VECTOR WEATHER ELEMENTS

Note: all vector directions are returned as numeric.   You can set the vectorDirection_algorithm to be "Average" or "MostFrequent".  It will then apply to all vector analysis methods. 


**vectorAvg  (mag, dir) 
**vectorMinMax ((minMag, maxMag), dir) 
**vectorMin  (minMag, dir)
**vectorMax (maxMag, dir)
vectorMagMinMax  (minMag, maxMag) 
vectorMagMin minMag 
vectorMagMax maxMag 
**vectorMode  (mag, dir)
**vectorMedian  (mag, dir) 
**vectorModeRange  ((minMag, maxMag), dir) 
**vectorMedianRange ((minMag, maxMag), dir) 
**vectorStdDevAvg (mag, dir) 
**vectorStdDevMinMax ((minMag, maxMag), dir) 
**vectorStdDevMin  (minMag, dir)
**vectorStdDevMax  (maxMag, dir)
**vectorModeratedAvg (mag, dir) 
**vectorModeratedMinMax ((minMag, maxMag), dir) 
**vectorModeratedMin  (minMag, dir)
**vectorModeratedMax  (maxMag, dir)
vectorRange (mag1, mag2, dir1, dir2) 
returns mag and dir for two specified periods
vectorBinnedPercent return a list of tuples representing bins and corresponding percentages of magnitude values in each bin. The bins are defined in the bins_dict.NOTE: When using this method, make sure you have grids covering the entire time range of interest so that the percentages which are time-weighted will add up to 100.

DISCRETE ELEMENTS


METHOD RETURN VALUE THRESHOLDS AND VARIABLES 
(To override, find in SampleAnalysis and copy to Overrides file)
discreteTimeRangesByKey  List of (discreteKey, timeRange) pairs ordered in ascending order by timeRange and then by priority of discrete keys as defined in the serverConfig files. discreteKey_coverage_percentage-- required  coverage by discete key
discreteTimeRangesByKey_withAux Same as above, but including the auxiliary field

**dominantDiscreteValue List of most common discrete keys ranked from most frequent to least frequent.   discreteKey_coverage_percentage--  required coverage by discete key
dominantDiscreteKeys_threshold-- maximum number of discrete keys to be returned from dominantDiscreteValue
cleanOutEmptyValues -- whether or not to leave <None> values out of list of keys
**dominantDiscreteValue_withAux Same as above, but including the auxiliary field
**rankedDiscreteValue List of most common (discrete keys, rank) pairs listed from most frequent to least frequent.   discreteKey_coverage_percentage--  required coverage by discete key
dominantDiscreteKeys_threshold-- maximum number of discrete keys to be returned from dominantDiscreteValue
cleanOutEmptyValues -- whether or not to leave <None> values out of list of keys (default 0)
**rankedDiscreteValue_withAux Same as above, but including the auxiliary field

discrete_percentages list of (discrete key, percentage) pairs.  Time-weighted / areal-weighted percentage is calculated for all overlapping grids None
discrete_percentages_withAux Same as above, but including the auxiliary field

 

WX  ELEMENT  


METHOD RETURN VALUE THRESHOLDS 
AND VARIABLES 
(To override, find in SampleAnalysis and copy to Overrides file)
**rankedWx
list of (subkey, rank) pairs found for a given time range.  rank is based on temporal and areal coverage of the subkey in the time range
coverage_weights_dict - weights per coverage term
wxkey_coverage_percentage-- areal coverage over the time period by weather key
attribute_coverage_percentage-- areal coverage over the time period for attributes
dominantKeys_threshold-- maximum number of weather keys to be returned from dominantWx
aggregateCov_algorithm - can use highest ranked coverage or a weighted scheme
cleanOutEmptyValues -- whether or not to leave <NoWx> values out of list of keys (default 0)
**dominantWx  list of subkeys found for the given time range same as rankedWx

Algorithm for rankedWx and dominantWx

The rankedWx and dominantWx algorithms use configurable thresholds and weights to give you control over the weather that you choose to submit to Text Rules for phrase generation.  The only difference between rankedWx and dominantWx is that the rankedWx method returns tuples of (subkey, rank) pairs while the dominantWx method simply returns the list of subkeys.  The algorithm is as follows:  We use the rank when combining subkeys and in weather phrasing and wording as follows:

    def rankFuzzFactor(self):
        # Used when combining weather subkeys.
        # If the difference between the subkey rankings is
        # less than this amount, the subkeys will be combined and
        # the dominant coverage or intensity will be chosen
        #
        # Also used in weather wording to determine if
        # subkeys have significantly different ranks.
        # If so, then wording such as "possibly mixed with" or
        # "with pockets of" could be used.
        #
        return 10  

For more information, see the section on Weather Phrasing.

Text Rules Libraries for Simple Table Products

(To be supplied)

Text Rules Libraries for Smart Tabular Products

(To be supplied)

Text Rules Libraries for Narrative Products

Narrative Tree and Phrase Builder Infrastructure

(to be supplied)
 

The Text Rules and all of it's inherited libraries can be found in the Utilities window of the Define Text Products Dialog.   This section describes the threshold, variable and phrase methods in the TextRules libraries to support Narrative Products.  This information will help you customize your narrative product by looking up the phrases that are being generated in the "phraseList" of your product components.  For each weather element (or combinations of weather elements), we list Example Phrases, corresponding SampleAnalysis methods, and Thresholds and variables you can set to customize the phrase.
 

Setting DAY and NIGHT times

Most formatters work on the assumption that "daytime" is from 6 am to 6 pm local time and "nighttime" is 6 pm to 6 am the next day local time.  If you want to change that assumption, you can override the following variables simply by including them in your local file:

            def DAY(self):
                return 6
            def NIGHT(self):
                return 18
 

Analysis-Driven Phrases

Most phrase methods accept results from any SampleAnalysis method for the appropriate data type (Scalar, Vector, Wx), and will report with an appropriate temporal resolution.  Some phrase methods are expecting certain statistics.  In the tables below, if the statistics method is starred, any temporal resolution is accepted  -- otherwise only what's shown.

If a threshold or variable appears in bold, then it MUST be set in order for the phrase to appear.

Arguments to Narrative Methods

The TextRules thresholds, variables, and methods take arguments which contain useful information allowing you to return values based on product type, edit area, weather element or time range.  The following table lists the arguments and shows how to access the information they contain.
 
 


ARGUMENT DESCRIPTION ACCESSING INFORMATION
tree Narrative tree contains the structure and information for the entire narrative product tree.stats.get(elementName, timeRange, areaLabel, statLabel, mergeMethod) -- allows you to get statistics  e.g. 

stats = tree.stats.get("Wind", timeRange, "CO_Boulder", "vectorMinMax", "List")

productTimeRange = tree.getTimeRange()
productStartTime = productTimeRange.startTime()

node The node of the tree at which we are currently executing methods.  The current time range: 
           nodeTimeRange = node.getTimeRange()
           nodeStartTime = nodeTimeRange.startTime()

The current edit area: 
           node.getAreaLabel() 

The current product component,  name, definition, or position: 
           component = node.getComponent() 
           componentName = node.getComponentName() 
           componentDef = component.get("definition")
           componentPosition = node.getComponentPosition()
                       # tells it's position (1,2,3...) in the Narrative Definition

To see if the current edit area (which could be a Combination) contains any of a list of edit areas: 
         inlandWaters = ["TampaBayWaters"] 
         if self.currentAreaContains(tree, inlandWaters): 
               #  do special case for TampaBayWaters 

The current phrase: 
         node.getAncestor("name") 
Note: This will only work for a node within a "phrase" method. 

The "getAncestor" method finds the first ancestor with the given attribute and returns the attribute's value.

elementName Weather element name -- useful information for returning threshold or variable values based on weather element

Checking for Edit Areas

Sometimes you might want to check to see if you are processing a particular edit area or set of edit areas.  For example, if you running a CWF product and setting a threshold for WindWaveHgt, you might want to know if you processing an "inland waters area" or not.   There is an EditAreaUtils library method to help you do this which is illustrated through the following example:

                                inlandWatersAreas = ["TampaBayWaters", "InlandWaters"]
                                if self.currentAreaContains(tree, inlandWatersAreas):
                                         # Return a value for the inland waters areas
                                else:
                                        # Return a value for the  other areas

GLOBAL THRESHOLDS AND VARIABLES

To override these thresholds and variables, find them in the designated library module and copy them into the Overrides product file.
NOTE:  There are many more thresholds and variables in the ConfigVariables module which, to avoid redundancy, are not listed here.  Please refer to that module directly to learn of the many ways you can customize the formatters.


THRESHOLD OR VARIABLE DESCRIPTION LIBRARY
phrase_descriptor_dict Descriptor words for weather elements in phrases e.g. "North winds 5-10 mph." ConfigVariables
phrase_connector_dict Connecting words in phrases e.g. "North winds 5-10 mph becoming20-25 mph. ConfigVariables
value_connector_dict Connectors for ranges of values e.g. "North winds 5 to 10 mph." ConfigVariables
units_descriptor_dict Words for units used in reporting data values e.g. "North winds 5-10 knots." ConfigVariables
timePeriod_descriptor_list Phrases for sub-ranges of a time period e.g. "Partly cloudy in the afternoon." TimeDescriptor
scalar_difference_nlValue_dict If the difference between scalar values for 2 sub-periods is greater than or equal to this value, the different values will be noted in the phrase.
ConfigVariables
vector_mag_difference_nlValue_dict
vector_dir_difference_nlValue_dict
If the difference between vector values for 2 sub-periods is greater than or equal to these values, the different values will be noted in the phrase. ConfigVariables

NARRATIVE PHRASES

The following tables list the various phrases definitions found in Text Rules library modules and used by the standard text narrative text products.

SCALAR WEATHER ELEMENT PHRASES -- ScalarPhrases module



ELEMENT TEXT RULES 
METHOD
EXAMPLE PHRASES SAMPLE 
ANALYSIS
THRESHOLDS AND VARIABLES 
(To override, find in ScalarPhrases or ConfigVariables and copy to Overrides file)
ADD'L 
INFO
Hazards makeHeadlinesPhrases .WINTER STORM WARNING IN EFFECT
No sampling by SampleAnalysis
 allowedHazards Found in DiscretePhrases module
Sky sky_phrase "Partly cloudy in the 
morning, then clearing."
Sky: 
**Scalar  Stats 
**binnedPercent 

PoP: (optional) 
**Scalar Stats 

pop_sky_lower_threshold
clearing_threshold
sky_valueList
sky_value
areal_sky_flag
areal_sky_value
areal_skyPercentage
areal_skyRelatedWx
similarSkyWords_list
reportIncreasingDecreasingSky_flag
reportClearSkyForExtendedPeriod_flag

PoP popMax_phrase "Chance of 
snow 40 percent."
PoP: **maxAvg phrase_descriptor_dict for "PoP" 
May use this method in the phrase_descriptor_dict: 
 areal_or_chance_pop_descriptor
units_descriptor_dict for "percent" 
pop_lower_threshold
pop_upper_threshold
wxQualifiedPoP_flag
matchToWxInfo_dict

MaxT 
MinT
reportTrends "Warmer." 
"Much colder."
MaxT, MinT 
avg, minMax, 
stdDevMinMax
reportTrends_valueStr to set up thresholds for trend phrases
Phrases are based on 24 hour prior statistics

MaxT 
MinT 
HeatIndex 
WindChill
extremeTemps_phrase "Very hot." 
"Hot." 
"Bitterly cold."
MaxT, MinT, HeatIndex, WindChill: **ScalarStats heatIndex_threshold
windChill_threshold
This phrase may be used in place of "reportTrends"
MinT lows_phrase "Lows in the lower 40s to lower 50s." MinT: 
minmax  stdDevMinMax
phrase_descriptor_dict for "lows" 
minimum_range_nlValue_dict
maximum_range_nlValue_dict
range_nlValue_dict
tempPhrase_exceptions
tempPhrase_boundary_dict
Only generated if Night time
MaxT highs_phrase "Highs in the upper 70s to lower 80s." MaxT: 
minMax  stdDevMinMax
phrase_descriptor_dict for "highs" 
minimum_range_nlValue_dict
maximum_range_nlValue_dict
range_nlValue_dict
tempPhrase_exceptions
tempPhrase_boundary_dict
Only generated if Day time
MinT lows_range_phrase "Lows 45 to 50." MinT: 
minMax  stdDevMinMax
phrase_descriptor_dict for "lows"
minimum_range_nlValue_dict
maximum_range_nlValue_dict
range_nlValue_dict
value_connector_dict
Only generated if Night time
MaxT highs_ range_phrase "Highs 70 to 75." MaxT: 
minmax  stdDevMinMax
phrase_descriptor_dict for "highs"
minimum_range_nlValue_dict
maximum_range_nlValue_dict
range_nlValue_dict 
value_connector_dict
Only generated if Day time
MinT extended_lows 
_phrase
"Lows in the 40s." 
"Lows 55 to 65."
MinT: 
firstAvg
extended _temp_range C11 only
MaxT extended_highs 
_phrase
"Highs in the 70s." 
"Highs 55 to 65."
MaxT: 
avg
extended _temp_range C11 only

MinT 
MaxT
temp_trends "Temperatures 
falling overnight."
T: hourlyTemp 
MinT and MaxT: 
avg, minMax, stdDevMinMax
temp_trend_threshold NOTE: There is an alternate method for "temp_trends_words" in the ScalarPhrases module.
SnowAmt 
snow_phrase "Snow accumulation 2 inches." 

 

SnowAmt: accumMinMax
 
phrase_descriptor_dict for "SnowAmt" 
units_descriptor_dict  for "inches" and "inch" 
pop_snow_lower_threshold
increment_dict for "SnowAmt" 

SnowAmt descriptive_snow 
_phrase
"Moderate snow accumulations." SnowAmt: accumMinMax

StormTotalSnow
stormTotalSnow_phrase
"Storm total snow accumulation around 9 inches."
StormTotalSnow:
accumMinMax
phrase_descriptor_dictfor "TotalSnow" 
units_descriptor_dict  for "inches" and "inch" 
pop_snow_lower_threshold
increment_dict for "SnowAmt" 

SnowAmt
total_snow_phrase
"Total snow accumulation 6 inches."
SnowAmt:
accumMinMax
phrase_descriptor_dictfor "SnowAmt" 
units_descriptor_dict  for "inches" and "inch" 
pop_snow_lower_threshold
getSnowReportEndDay
increment_dict for "SnowAmt" 

SnowLevel snowLevel_phrase "Snow level 7600 feet." SnowLevel: **ScalarStats phrase_descriptor_dict for "SnowLevel" 
units_descriptor_dict  for "feet" 
pop_snowLevel_upper_threshold
snowLevel_maximum_phrase
snowLevel_upper_topo_percentage
snowLevel_lower_topo_percentage
increment_dict  for "SnowLevel"
Be sure and set the  snowLevel_maximum_phrasefor your local area
IceAccum
iceAccumulation_phrase
"Ice accumulation of  up to one half inch."
IceAccum: accumMinMax
ice_accumulation_threshold
phrase_descriptor_dict
for "IceAccum"
increment_dict for "IceAccum"

WindChill windChill_phrase "Wind chill readings 5 to 10." WindChill: 
**ScalarStats 
T:minMax
phrase_descriptor_dict for "WindChill" 
windChill_threshold
windChillTemp_difference
windChill_range

WindChill windBased_windChill_phrase "Wind chill readings 5 to 10." WindChill:**ScalarStats 
Wind: **VectorStats
phrase_descriptor_dict for "WindChill" 
windChill_threshold
windChill_wind_threshold
minimum_range_threshold_dict
range_threshold_dict
value_connector_dict
windChill_range
Checks against Wind instead of T
HeatIndex heatIndex_phrase "Heat index readings 10 to 15." HeatIndex: 
**ScalarStats 
T: minMax
phrase_descriptor_dict for "HeatIndex" 
heatIndex_threshold
heatIndexTemp_difference
minimum_range_threshold_dict
range_threshold_dict
value_connector_dict

MinRH
rh_phrase
"Minimum RH 30 percent."
**ScalarStats MinRH
rh_threshold

WIND AND WINDGUST PHRASES -- VectorRelatedPhrases module



ELEMENT TEXT RULES 
METHOD
EXAMPLE 
PHRASES
SAMPLE 
ANALYSIS
THRESHOLDS AND VARIABLES 
(To override, find in VectorRelatedPhrases or ConfigVariables and copy to Overrides file)
ADD'L 
INFO
Wind wind_phrase "Northeast winds 5 to 15 mph in the afternoon." 

"Southwest winds 5-15 mph." 

 

Wind: **Vector Stats phrase_descriptor_dict for "Wind" 
units_descriptor_dict for "mph" 
value_connector_dict for "Wind" e.g. 5 to 10 mph 
increment for "Wind" e.g. round wind magnitudes to nearest 5 
null_nlValue_dict
first_null_phrase_dict
null_phrase_dict

maximum_range_nlValuefor "Wind" e.g. do not report winds with more than a 5 mph range e.g. 5 to 10 mph 
minimum_range_nlValue for "Wind" e.g. report winds with at least this range e.g. 5 to 10 mph 

Differences used for reporting Wind in different time periods: 
"Wind NE 20 to 35 mph in the morning becoming S 10 to 15 mph in the afternoon." 
vector_mag_difference_nlValue_dict e.g. 10 mph 
vector_dir_difference_nlValue_dict e.g. 60 degrees 

phrase_connector_dict for "becoming", "increasing to", "decreasing to", "shifting to the" 

embedded_vector_descriptor_flag_dict -- default is 1 -- if 0, descriptor is first e.g. "Winds east 5 to 10 mph."

include_timePeriod_descriptor_flag  -- moderates the time qualification on the phrase e.g. "in the afternoon"
marine_wind_flag

mph
Wind wind_withGust_phrase "East winds 10 to 20 mph with gusts to around 70 mph in the night becoming southwest 5 to 10 mph in the early morning." Wind: **Vector  Stats  for Wind 
Wind: **Vector MinMax (must be there as a secondary analysis method for comparison in the gust_phrase) 
WindGust: 
**Scalar Stats 
Same as above PLUS: 
phrase_descriptor_dict  for "WindGust" 
gust_threshold -- gusts reported if above this threshold
gust_wind_difference_threshold -- gust reported if difference between max Wind and WindGust is greater than this threshold
useWindsForGusts_flag -- if a WindGust grid is not found, the absolute max wind will be used for gusts
mph
Wind marine_wind_phrase "West winds 10 to 20 knots in the morning shifting to the southwest in the afternoon." **Vector  Stats Same as for public_windRange_phrase except units_descriptor_dict for "kt" 
marine_wind_flag set to 1 for marine descriptors e.g. gales, storm force winds 
seaBreeze_thresholds and local effects areas (see below)
marine_wind_descriptor for "Wind" 
phrase_connector_dict  for "becoming", "rising  to", "easing to", "veering", "backing", "becoming onshore" 
useWindsForGusts_flag -- if a WindGust grid is not found, the absolute max wind will be used for gusts
knots 

See description for SeaBreeze below

Wind marine_wind 
_withGusts_phrase
"East winds 10 to 20 knots with gusts to around 70 knots in the night veering southwest 5 to 10 knots in the early morning." Wind: **Vector  Stats 
Wind: **Vector MinMax (must be there for comparision in the Gust phrase) 
WindGust: 
**ScalarStats 
Same as above PLUS 
phrase_descriptor_dict  for "WindGust" 
gust_threshold -- gust reported if above this threshold
gust_wind_difference_threshold-- gust reported if difference between max Wind and WindGust is greater than this threshold
knots
Wind lake_wind_phrase "Caution advised on area lakes." Wind: **Vector  Stats lake_wind_areaNames
phrase_descriptor _dict for "lakeWinds" 
lake_wind_thresholds

Wind wind_summary "Breezy"  "Windy" Wind: **Vector  Stats vector_summary_valueStr
WindGust gust_phrase "Gusts up to 70 mph." WindGust: **Scalar Stats phrase_descriptor_dict  for "WindGust" 
gust_threshold
null_nlValue_dict
first_null_phrase_dict
null_phrase_dict

gust_wind_difference_nlValue -- gust reported if difference between max Wind and WindGust is greater than this threshold
scalar_difference_nlValue_dict -- for "WindGust": if gusts for 2 consecutive sub-ranges differ by more than this amount, they will be reported separately.
include_timePeriod_descriptor_flag  -- moderates the time qualification on the phrase e.g. "in the afternoon"


Sea Breeze Identification

Sea breezes can be identified to produce phrases such as the following:

         NW winds 15 to 25 knots becoming onshore 10 to 15 knots.

To set up sea breeze indentification, you need to:

          def CWFPeriod(self):

WEATHER PHRASES -- WxPhrases module



ELEMENT TEXT RULES 
METHOD
EXAMPLE 
PHRASES
SAMPLE 
ANALYSIS
THRESHOLDS AND VARIABLES 
(To override, find in WxPhrases, PhraseBuilder,  or ConfigVariables and copy to Overrides file)
ADD'L 
INFO
Wx weather_phrase
Wx: **rankedWx Pop/Wx Consistency
coveragePoP_value
pop_related_flag
pop_wx_lower_threshold pop-related Wx types are not reported if PoP is below this threshold 

Combining and consolidating

filter_subkeys_flag set to filter subkeys by combining duplicates and similar Wx types as specified in:
wxCombinations Wx types WITHIN sub-phrases can be combined to prevent redundant reporting e.g. combine "R" and "RW" to prevent:   "Scattered rain showers and rain." 
similarWxTypes, similarIntensities, similarCoverages, similarCoverageLists  Used when combining weather subkeys ACROSS sub-phrases for weather phrases and for determining local effects.
wxHierarchies  Used when sorting weather subkeys into dominant coverage or weather type order.

Wording
useSimple
subPhrase_limit

rankFuzzFactor  (CommonUtils)
precip_related_flag
wxTypeDescriptors
wxIntensityDescriptors
wxAttributeDescriptors

rankWordingFuzzFactor
possiblyMixedWith
mixedWith
withPocketsOf
wxConjunction

Visibility
null_nlValue for "Visibility"
embedded_visibility_flag
visibility_weather_phrase_nlValue
element_outUnits_dict for "Visibility"
visibility_wx_threshold
significant_wx_visibility_subkeys

include_timePeriod_descriptor_flag  -- moderates the time qualification on the phrase e.g. "in the afternoon"

See section below on the meaning of Weather Subkey Attributes "Primary" and "Mention."

See section below on Customizing Weather Phrases

See the section on Handling Visibility

Wx severeWeather_phrase "Some thunderstorms may be severe."
"Some storms could be severe with damaging hail."
"Some storms could be severe with large hail and damaging winds possible."

Wx:**rankedWx
Reports if there is T+ in the time period.
Also reports large hail and damaging winds.
Wx heavyPrecip_phrase "Rain may be heavy at times." 
"Locally heavy rainfall possible." 
"Snow may be heavy at times."
Wx: **rankedWx phrase_descriptor_dict  for "heavyRainfall", "heavyRain", "heavySnow", "heavyPrecip"
heavySnowTypes
heavyOtherTypes

Reports if intensity is + for R, RW, S, SW,
IP, ZR, ZL, L
Wx
heavyRain_phrase
"Locally heavy rain possible."
Wx: **rankedWx phrase_descriptor_dict for "heavyRains" Reports if T has attribute HvyRn
Wx visibility_phrase "Visibility less than 1 nautical mile." Wx: **rankedWx null_nlValue for "Visibility" below which visibility is not reported
phrase_descriptor for "Visibility"
increment_nlValue for "Visibility"
visibility_phrase_nlValue
element_outUnits_dict for "Visibility"
See the section on Handling Visibility

Weather Subkey Attributes "Primary" and "Mention"

Many weather subkeys can have attributes of "Primary" and "Mention" which allow the forecaster to influence the resulting words as follows:

Customizing Weather Phrases

You can override the "wxCoverageDescriptors", "wxTypeDescriptors" , "wxIntensityDescriptors" , and "wxAttributeDescriptors" in your Overrides file to customize the wording for weather types, coverages,  intensities, and attributes.  For example, the following default settings express very light rain and snow showers as "sprinkles" and "flurries", respectively.

    def wxTypeDescriptors(self):
        # This is the list of coverages, wxTypes, intensities, attributes for which special
        # weather type wording is desired.  Wildcards (*) can be used to match any value.
        # If a weather subkey is not found in this list, default wording
        # will be used from the Weather Definition in the server.
        # The format of each tuple is:
        #    (coverage, wxType, intensity, attribute, weatherType descriptor)
        return [
                ("*", "RW", "--", "*", "sprinkles"),
                ("*", "SW", "--", "*", "flurries"),
            ]

In many cases, more than one of these methods will have to be overridden to accomplish the desired result.  We might have to "turn on" one kind of wording and "turn off" other default wording. In the above example, we have to "turn off" the default descriptor for "very light" using the wxIntensityDescriptors:

    def wxIntensityDescriptors(self):
        # This is the list of coverages, wxTypes, intensities, attribute for which special
        # weather intensity wording is desired.  Wildcards (*) can be used to match any value.
        # If a weather subkey is not found in this list, default wording
        # will be used from the Weather Definition in the server.
        # The format of each tuple is:
        #    (coverage, wxType, intensity, attribute, intensity descriptor)
        return [
                ("*", "RW", "--", "*", ""),
                ("*", "RW", "-", "*", ""),
                ("*", "R", "--", "*", "very light"),
                ("*", "R", "-", "*", "light"),
                ("*", "R", "+", "*", "heavy"),
                ("*", "RW", "+", "*", "heavy"),
                ("*", "SW", "--", "*", ""),
                ("*", "SW", "-", "*", ""),
                ("*", "SW", "+", "*", "heavy"),
                ("*", "S", "--", "*", "very light"),
                ("*", "S", "-", "*", "light"),
                ("*", "S", "+", "*", "heavy"),
                ("*", "ZR", "--", "*", "light"),
                ("*", "ZR", "+", "*", "heavy"),
                ("*", "L", "*", "*", ""),
                ("*", "F", "+", "*", "dense"),
            ]

Suppose we want to report "heavy snow" as "snow...heavy at times", we would include the following in our Overrides file:

    def wxTypeDescriptors(self):
         list = TextRules.TextRules.wxTypeDescriptors(self)
         list.append( ("*", "S", "+", "*", "snow...heavy at times") )
         return list

    def wxIntensityDescriptors(self):
         list = TextRules.TextRules.wxIntensityDescriptors(self)
         list.append( ("*", "S", "+", "")
         return list

As another example, suppose we want to describe thunderstorms with an attribute of "dry" as "dry thunderstorms."  Including the following in our Overrides file could accomplish this:

    def wxTypeDescriptors(self):
         list = TextRules.TextRules.wxTypeDescriptors(self)
         list.append( ("*", "T", "*", "Dry", "dry thunderstorms") )
         return list

    def wxAttributeDescriptors(self):
         list = TextRules.TextRules.wxAttributeDescriptors(self)
         list.append( ("*", "T", "*", "Dry", "") )
         return list

It is possible to specify a method instead of text string for the descriptor.  For example, the default wxTypeDescriptor for rain showers uses a method as follows:

    def wxTypeDescriptors(self):
        return [
                ("*", "SW", "--", "*", "flurries"),
                ("*", "RW", "*", "*", self.rainShowersDescriptor),
            ]

    def rainShowersDescriptor(self, tree, node, subkey):
        if subkey.intensity() == "--":
            return "sprinkles"
        if tree is None:
            return "showers"
        t = tree.stats.get(
              "T", node.getTimeRange(),
              node.getAreaLabel(), statLabel="minMax",
              mergeMethod="Min")
        if t is None:
            return "showers"
        if t < 40:
            return "rain showers"
        else:
            return "showers"

These methods are  used in Table products as well as Narratives.  NOTE:  IF you are working with a narrative product, you can include the "tree, node" arguments and use them to qualify your customizations.  For example, we could check the node time range and qualify fog as "morning fog" as in the example below:

    def wxTypeDescriptors(self, tree, node):
         list = TextRules.TextRules.wxTypeDescriptors(self)
         # Find out what time range we are working with
         timeRange = node.getTimeRange()
         # See if this time range is for the Morning
         # First, shift to Local time from GMT
         timeRange = self.shiftedTimeRange(timeRange)
         startHour = timeRange.startTime().hour()
         endHour = timeRange.endTime().hour()
         if startHour >= 6 and endHour <= 12:
                list.append( ("*", "F", "*", "*", "morning fog") )
         return list

For more information on customizing Weather Phrases, see the sections on Weather Phrasing  and Phrase Consolidation.

Pop/Wx Consistency

By default, PoP (as well as LAL) is matched to the highest ranking weather subkey.  The default algorithm ensures that the reported PoP matches the reported Wx.  This algorithm assumes that the grids have been made consistent between PoP and Wx, perhaps via a Smart Tool. Thus, for each weather subkey, there is a corresponding PoP in the grid.  The algorithm reports the PoP that matches the highest ranked weather.  So you might have a large area of SChc T with PoP of 20 and a very small area of Lkly T with a PoP of 60.  This will get reported as

    SLIGHT CHANCE OF THUNDERSTORMS.  CHANCE OF THUNDERSTORMS 20 PERCENT.

since SChc T is computed to be the highest ranking subkey. The algorithm does not report values that are not in the grids.  Instead, the PoP value in the grids that gives the closest match to the Wx coverage is used.  The configurable coveragePoP_table (CommonUtils) defines the PoP ranges associated with each weather coverage.

The algorithm and matching can be controlled by adjusting the "matchToWxInfo_dict" (ConfigVariables) in various ways shown and described below:

    def matchToWxInfo_dict(self, tree, node):
        # The system will automatically match the following elements to
        # the highest ranking weather subkey coverage.
        # Each entry is a tuple of (increment, algorithm, noPrecipValue, percentThreshold, wxTypes filter) where
       
        #  increment: This is the increment from the low "bin" value
        #    to be added.  For example, PoP has a bin of 55-65, so
        #    its increment is 5 to end up with values as multiples of 10.
       
        #  algorithm: Can be
        #    Max:  The MAXIMUM value that falls within the coverage range
        #          for the highest ranking subkey will be chosen.
        #    Mode: The MOST FREQUENT (over space and time) value that
        #          falls within the coverage range for the highest ranking
        #          subkey will be chosen.
        #    MaxMode: This is the MAXIMUM value over time of the MOST
        #         FREQUENT values over area for each of the grids in the timeRange.
        #         In other words, for each grid, we find the Mode i.e. MOST FREQUENT
        #         value that falls within the coverage range for the highest
        #         ranking subkey.  Then we find the MAXIMUM of these values
        #         over the grids again falling within the coverage values.
        #    AnalysisMethod: This will simply use whatever analysis method
        #         is specified as the first entry in the product component
        #         for the element. For example, if you have
        #
        #         ("PoP", self.stdDevMaxAvg, [3]),
        #         ("PoP", self.binnedPercent, [3]),
        #
        #         the "stdDevMaxAvg" method will be used.
        #
        #  noPrecipValue: The value that should be returned if there is
        #         no precipitating weather.  Can be:
        #     None
        #     an actual data value
        #     "Max": The maximum value found that has a greater > 0% occurrence.
        #     "AnalysisMethod": As above, will return the result of the product
        #         component analysis method e.g. stdDevMaxAvg or maximum.
        #
        #  percentThreshold: (optional) Percent of areal coverage a value must have in
        #     order to be considered for the element value returned.
        #     Default is 0.
        #
        #  wxTypes filter: (optional) Match only to weather keys of these wxTypes e.g.
        #     match LAL only to "T"
        #
        #  EXAMPLE 1:  Suppose we have:

        #       Wx  Hours 1-12:  Chc R  (coverage range is 30-60)
        #       PoP Hours 1-3:   40% (over 70% of area), 50% (over 30% of area)
        #           Hours 4-12:  30

        # For the 12-hour PoP,
        #    If set to Max, we will get PoP:      50
        #    If set to Mode, we will get PoP:     30
        #    If set to MaxMode, we will get PoP:  40
       
        # For the Hours 1-3 PoP:
        #    If set to Max, we will get PoP:      50
        #    If set to Mode, we will get PoP:     40
        #    If set to MaxMode, we will get PoP:  40
     
        #  NOTE: IF you add a new element to this list, you MUST include
        #  a coverage table named "coverage<elementName>_value".  Follow
        #  the example for "coveragePoP_value" in CommonUtils.  You can
        #  then access the element value by calling "matchToWx" (WxPhrases).
        #
        return {
            "PoP": (5, "Max", None, 0),
            # If there's no precipitating weather, return LAL 1
            "LAL": (0, "Max", 1, 0, ["T"]),
            }

COMBINED ELEMENT PHRASES -- CombinedPhrases module


ELEMENT TEXT RULES 
METHOD
EXAMPLE 
PHRASES
SAMPLE 
ANALYSIS
THRESHOLDS AND VARIABLES 
(To override, find in ConfigVariables, WxPhrases, or CombinedPhrases and copy to Overrides file)
ADD'L 
INFO
Sky, PoP, Wx skyPopWx_phrase "Partly cloudy with a 20 percent chance of showers and thunderstorms."

"Sunny in the morning then partly cloudy with a 20 percent chance of rain showers in the afternoon."
Wx:**rankedWx 
(See add'l info). 
Sky: **median 
PoP: **binnedPercent, 
The same thresholds and variables described for the weather_phrase apply to this combined phrase.

In addition:
useCombinedSkyPopWx
useSkyPopWx_consolidation

sky_valueList
matchToWxInfo_dict

NOTE: You should also include the sky_phrase, weather_phrase, and popMax_phrase in your phraseList when you are using the skyPopWx_phrase.

For more information on the algorithm used and examples, see the documentation in the CombinedPhrases module.

Wx, Sky weather_orSky_phrase If no weather, then return sky phrase Sky: **median 
Wx: **rankedWx
same as for weather_phrase and sky_phrase

 

MARINE PHRASES -- MarinePhrases module


ELEMENT TEXT RULES 
METHOD
EXAMPLE 
PHRASES
SAMPLE 
ANALYSIS
THRESHOLDS AND VARIABLES 
(To override, find in ConfigVariables or MarinePhrases and copy to Overrides file)
ADD'L 
INFO
WaveHeight 
WindWaveHgt
wave_phrase "Combined seas 15 to 25 feet." 
"Wind waves 5 feet."
WaveHeight or WindWaveHeight: 
** Scalar Stats

maximum_range_nlValue
inlandWatersAreas
inlandWatersWave_element
waveHeight_wind_threshold
combinedSeas_threshold
seasWaveHeight_element 
seasWindWave_element 
noWaveHeight_phrase
null_nlValue_dict
first_null_phrase_dict
null_phrase_dict
include_timePeriod_descriptor_flag  -- moderates the time qualification on the phrase e.g. "in the afternoon"
See algorithm 
below 
WaveHeight 
WindWaveHgt
wave_withPeriod 
_phrase
"Combined seas 15 to 25 feet dominant period 11 seconds." Same as above PLUS: 
Period: **Scalar Stats 
Period2: **Scalar Stats
same as above PLUS 
phrase_descriptor_dict  for "dominant period" 
units_descriptor_dict for "ft"
See algorithm 
below
Wind see Wind and WindGust Phrases above



Wind chop_phrase "Bay and inland waters a light chop." Wind: **Vector  Stats phrase_descriptor_dict  for "chop" 
include_timePeriod_descriptor_flag  -- moderates the time qualification on the phrase e.g. "in the afternoon"

Swell swell_phrase "Northeast swells 5 to 10 feet and 10 to 15 feet." Swell: **Vector Stats 
Swell2: **Vector Stats 
Wind: **Vector Stats
phrase_descriptor_dict for "Swell" 
units_descriptor_dict for "ft" 
units_descriptor_dict for "foot" 
value_connector_dict for "Swell" e.g. 5 to 10 feet 
increment for "Swell" e.g. round swell magnitudes to nearest 1 

null_nlValue_dict
first_null_phrase_dict
null_phrase_dict

maximum_range_nlValue_dict
minimum_range_nlValue_dict

waveHeight_wind_threshold
combinedSeas_threshold

vector_mag_difference_nlValue_dict e.g. 2 feet 
vector_dir_difference_nlValue_dict e.g. 60 degrees 

phrase_connector_dict for "becoming", "increasing to", "decreasing to", "shifting to the" 

embedded_vector_descriptor_flag_dict -- default is 1 -- if 0, descriptor is first e.g. "Swells east 5 to 10 feet."

include_timePeriod_descriptor_flag  -- moderates the time qualification on the phrase e.g. "in the afternoon"

Note: The Swell phrase will not be produced for InlandWaters areas OR if a "seas" wave phrase is produced (See algorithm below.)
Swell swell_withPeriod 
_phrase
"Northeast swells 5 to 10 feet at 9 seconds and 10 to 15 feet at 12 seconds." Swell: **Vector Stats 
Swell2: **Vector Stats 
Period: **Scalar Stats 
Period2: **Scalar Stats 
Wind: **Vector  Stats
Same as above PLUS: 
phrase_descriptor_dict  for "Period" 
units_descriptor  for "seconds" 

Period period_phrase "Period 15 seconds." Period: **Scalar Stats phrase_descriptor_dict  for "Period" 
units_descriptor  for "seconds"
include_timePeriod_descriptor_flag  -- moderates the time qualification on the phrase e.g. "in the afternoon"

Algorithm for WaveHeight and/or WindWaveHgt Reporting

NOTE:  The Swell phrase will not be produced for inland waters OR if a "seas" phrase is reported.

FIRE WEATHER PHRASES


ELEMENT TEXT RULES 
METHOD
EXAMPLE 
PHRASES
SAMPLE 
ANALYSIS
THRESHOLDS AND VARIABLES 
(To override, find in ConfigVariables orFirePhrases and copy to Overrides file)
ADD'L 
INFO
CWR cwr_phrase CHC OF WETTING RAIN....20 percent CWR: **ScalarStats  phrase_descriptor_dict
Dispersion smokeDispersal_phrase SMOKE DISPERSAL.... Dispersion: **ScalarStats phrase_descriptor_dict
smokeDispersal_valueStr

Haines haines_phrase HAINES INDEX...3 Very low potential for large plume dominated fire growth. Haines: **ScalarStats phrase_descriptor_dict
hainesDict

LAL lal_phrase LAL...2. LAL:  avg [0]  phrase_descriptor_dict
MaxRH humidity_recovery_phrase HUMIDITY RECOVERY...POOR MaxRH: mode phrase_descriptor_dict
humidityRecovery_percentage
humidityRecovery_valueList

MixHgt mixingHgt_phrase     MIXING HEIGHT...600-1000 FT AGL MixHgt: **ScalarStats phrase_descriptor_dict
Sky/Wx skyWeather_byTimeRange_compoundPhrase SKY/WEATHER...Mostly sunny until 1100...then partly cloudy.  Chance of showers. Sky: **Scalar Stats 
Wx:  **dominantWx
phrase_descriptor_dict
includeSkyRanges_flag
All the thresholds and varables from the weather_phrase and the fireSky_phrase apply
To change the format of the sky ranges, override the addSkyRange method from FirePhrases
TransWind transportWind_phrase    TRANSPORT WINDS...West 3-10 mph. TransWind: **Vector Stats phrase_descriptor_dict
Wind fireWind_compoundPhrase 20-FT WINDS...Winds northeast around 10 mph in the evening becoming light after midnight. Wind20ft OR Wind: **VectorStats phrase_descriptor_dict
All the threhsolds and variables from the wind_summary and wind_phrase apply.
If the Wind20ft grid is available, it is reported. Otherwise, the Wind grid values are multiplied by the windAdjustmentFactor(set in the Overrides file) and reported.
Variable trend_DayOrNight_phrase "24-HOUR TREND...2 degrees warmer." 
"24-HOUR TREND...10 percent wetter."
MinT, MaxT: mode 
MinRH, MaxRH: mode 

Optional: 
Ttrend: self.avg 
RHtrend: self.avg 

IF the trend grids are present, they are used, otherwise, the MinT or MaxRH (nighttime) or MaxT or MinRH (daytime) grids are used.

phrase_descriptor for "higher", "lower", "missing", "unchanged" 
trend_threshold_dict
units_descriptor
Arguments: dayElement, nightElement, 
trendElement, 
indent_flag, 
endWithPeriod_flag 
The flags are set to 1 for FWF-type phrases.
Variable dayOrNight_phrase "MAX TEMPERATURE....45-55." 
"MIN HUMIDITY...........20-25."
avg, minmax, stdDevMinMax, median, mode minimum_range_threshold_dict
range_threshold_dict
value_connector_dict
Arguments: dayElement, nightElement, indent_flag, endWithPeriod_flag 
These flags are set to 1 for FWF-type phrases.

Ridge versus Valley Winds

For some edit areas, you may want to break out  the 20-foot winds into Ridges and Valleys. To do so, in the FWF_<site>_Overrides file, set up the names of areas for which you want this break-out:

    def ridgeValleyAreas(self, tree, node):
        # List of edit area names for which we want
        # ridge/valley winds reported:
        #
        # 20-FOOT WINDS...
        #     VALLEYS/LWR SLOPES...
        #     RIDGES/UPR SLOPES....
        #
        # e.g.
        # return ["Area1"]
        return []

Next, make sure you have edit areas defined for "Ridges" and "Valleys" to intersect with the current area.  If you want to use different edit area names, you can include the following override (from the FirePhrases module) in your Overrides file:

    def valleyRidgeAreaNames(self, tree, node):
        # These are the areas for valleys and ridges, respectively,
        # to be intersected with the current edit area for
        # reporting valley winds and ridge winds, respectively.
        # NOTE: If you change these area names, you will also
        # need to change the names in the FirePeriod "intersectAreas"
        # section.
        return "Valleys", "Ridges"
 

 DISCRETE PHRASES (DiscretePhrases module)


ELEMENT TEXT RULES 
METHOD
EXAMPLE PHRASES SAMPLE 
ANALYSIS
THRESHOLDS AND VARIABLES 
(To override, find in designated module and copy to Overrides file)
ADD'L 
INFO
Hazards makeHeadlinePhrases
.WINTER STORM WARNING IN EFFECT
Not sampled by SampleAnalysis  allowedHazards
Found in DiscretePhrases module

NARRATIVE STRATEGIES

Narrative Tree methods coordinate to produce a desired result.  The strategy behind this coordination is not obvious from looking at the individual methods, so we provide documentation for this coordination in the following "Strategy" sections.
 

NON-LINEAR THRESHOLDS

We support non-linear thresholds and variables by allowing a method to return a dictionary of ranges and values. The code applying the threshold is responsible for applying it to each value using the nlValue method:

     threshold = self.nlValue(threshold, value)

Any variable that is named with the suffix _nlValue can operate in this way returning EITHER a single value or a dictionary of values.  If a dictionary is returned, it is of the form:

      {
      'default': <default value>,
      (lowVal, highVal) :  <value>,
      }
where lowVal and highVal consitute a range for applying the given value.  The lowVal is inclusive while the highVal is exclusive.  For example:

    def scalar_difference_nlValue_dict(self, tree, node):
        # Scalar difference.  If the difference between scalar values
        # for 2 sub-periods is greater than this value,
        # the different values will be noted in the phrase.
        return {
            "WindGust": {
                        'default': 10,
                        (0, 30):   10,
                        (30, 50):  15,
                        (50, 200): 20,
                        },
             }

In this case, if the WindGust is greater or equal to 0 and less than 30, the scalar_difference will be 10; if WindGust is greater or equal to 30  and less that 50, the scalar_difference will be 15; etc.

You always have the additional option of basing the return value on the current edit area.  For example:

    def scalar_difference_nlValue_dict(self, tree, node):
        # Scalar difference.  If the difference between scalar values
        # for 2 sub-periods is greater than this value,
        # the different values will be noted in the phrase.
        return {
            "WindGust": self.windGust_scalar_nlValue_difference,
            }

     def windGust_scalar_nlValue_difference(self, tree, node):
         mountains = ["Area1", "Area2"]
         if self.currentAreaContains(tree, mountains):
            return 15
         else:
            return {
                        'default': 10,
                        (0, 30):   10,
                        (31, 50):  15,
                        (51, 200): 20,
                        }

The method applying the threshold must do the following before using it:

   threshold = self.scalar_difference_threshold(
                    tree, node, element, element)
   for each value to check:
        curThreshold = self.nlValue(threshold, value)
        # Use curThreshold for the given value
 

DATA VALUE EXTRACTION FROM THE GRIDS

The following steps are taken to extract the raw data values from the grids for reporting in narrative products: All of this is done prior to generating phrases.  In addition, if sub-phrases or components are combined, Range Adjustment is re-done if necessary.  There are user-configurable thresholds and variables to control this processing, and it is very important to understand the relationships between them to produce acceptable results.

Unit Conversion

The input and output units are specified in the following dictionaries.  Unit conversion is done automatically

    def element_inUnits_dict(self, tree, node):
        # Dictionary of descriptors for various units

    def element_outUnits_dict(self, tree, node):
        # Dictionary of descriptors for various units

 Standard and Non-Standard rounding

The system does automatic unit conversion and rounding when statistics are generated for the narrative product.  Rounding is normally done using a simple increment value (which can be non-linear as described above).  In lieu of that, a non-standard rounding method may be specified per weather element.

Example of standard rounding increment (from ZFP_<site>_Overrides file, overriding increment_nlValue_dict in ConfigVariables module):

    def increment_nlValue_dict(self, tree, node):
        # Increment for rounding values
        # Units depend on the product
        dict = TextRules.TextRules.increment_nlValue_dict(self, tree, node)
        dict["Wind"] = 5
        return dict

Example of non-standard rounding method (found in ConfigVariables module):

    def rounding_method_dict(self, tree, node):
        # Special rounding methods
        #
        return {
            "Wind": self.marineRounding,
            }

    def marineRounding(self, value, mode, increment, maxFlag):
        # Rounding for marine winds
        mode = "Nearest"
        if maxFlag:
            if value > 30 and value < 34:
                mode = "RoundDown"
            elif value > 45 and value < 48:
                mode = "RoundDown"
            else:
                mode = "Nearest"
        return self.round(value, mode, increment)

Range Adjustment

Range adjustment is a feature that allows you to set required ranges on data values coming from the grids.  For example, if you would always like to see at least a 5 mph range in the wind phrases, you can set the "minimum_range_nlValue_dict" for "Wind" to 5.  Then you will see phrases such as "Winds 5 to 10 mph" instead of "Winds 10 mph." Range adjustment is rather complicated logically and this leads to some confusion.  Purists would argue against using range adjustment as it is not 100% true to the grids -- however, with the current suite of zone-based products, it is sometimes desirable.  For a discussion, see the FAQ Range Adjustment Questions/Answers section.

The basic premise of range adjustment is that you have to commit to the ranges you choose (and rounding increments as well) from the beginning of processing.  In other words, we can't just slap on a range at the very end when we are building the phrase; ranges need to be applied from the moment the grids are analyzed so that phrase combining and period combining are comparing pre-adjusted values.  Then, if we do combine two sub-phrases or two periods, we have to combine their statistics and, then re-apply the range adjustments to the new values. The system is set up to recognize these cases when you set the range values listed below.

The following variables are used to adjust the range reported for the data value:

    def range_nlValue_dict(self, tree, node):
        # If the range of values less than this amount, return as a single value

    def minimum_range_nlValue_dict(self, tree, node):
        # This threshold is the "smallest" min/max difference allowed between values reported.
        # For example, if threshold is set to 5 for "MaxT", and the min value is 45
        # and the max value is 46, the range will be adjusted to at least a 5 degree
        # range e.g. 43-48.  These are the values that are then submitted for phrasing
        # such as:
        #   HIGHS IN THE MID 40S

    def maximum_range_nlValue_dict(self, tree, node):
        # Maximum range to be reported within a phrase
        #   e.g. 5 to 10 mph
        # Units depend on the product

    def range_bias_nlValue_dict(self, tree, node):
        # "Min", "Average", "Max"
        #  Should the range be taken from the "min" "average" or "max" value of the current range?
        return {'default': "Average"}

    def maximum_range_bias_nlValue_dict(self, tree, node):
        # "Min", "Average", "Max"
        #  Should the maximum_range be taken from the "min" "average" or "max" value of the current range?
        return {
                      "default": "Average",
                      "Wind": "Max"
                     }

     def minimum_range_bias_nlValue_dict(self, tree, node):
        # "Min", "Average", "Max"
        #  Should the minimum_range be taken from the "min" "average" or "max" value of the current range?
        return {
                      "default": "Average",
                      "Wind": "Max"
                     }

NOTE: The range_nlValue is  applied first and thus supercedes range adjustments (minimum/maximum ranges).  So if your minimum or maximum ranges are not taking effect, check to make sure there is not a range_nlValue set for the weather element in question.

Ranges are adjusted at several points in the processing of phrases:

Example: Using Maximum Range and Bias

Fire weather users are most concerned with the minimum RH/maximum Temp during the day, and the maximum RH/minimum Temp at night. So, rather than give them the full range of values for a particular zone, we'd like to be able to weight the output range of values to the lower end of MinRH and MinT, and the higher end of MaxRH and MaxT.  Sampling could be manipulated to do this through the moderated_dict, but then we would want those results turned into a 5 degree range (rounded to the nearest 5, if possible) returned for non-mountain zones and a 10 degree range (rounded to the nearest 5) for mountain zones.

To accomplish this, include the following in the  Overrides file:

    def maximum_range_nlValue_dict(self, tree, node):
        # Maximum range to be reported within a vector phrase
        #   e.g. 5 to 10 mph
        # Units depend on the product
        dict = TextRules.TextRules.maximum_range_nlValue_dict(self, tree, node)
        mountainZones = ["area3"]
        if self.currentAreaContains(tree, mountainZones):
            range = 10
        else:
            range = 5
        dict["MaxT"] = range
        dict["MinT"] = range
        dict["MaxRH"] = range
        dict["MinRH"] = range
        return dict

    def maximum_range_bias_nlValue_dict(self, tree, node):
        # "Min", "Average", "Max"
        #  Should the maximum_range be taken from the "min" "average" or "max"
        #  value of the current range?
        dict = TextRules.TextRules.maximum_range_bias_nlValue_dict(self, tree, node)
        dict["MaxT"] = "Max"
        dict["MinT"] = "Min"
        dict["MaxRH"] = "Max"
        dict["MinRH"] = "Min"
        return dict

    def increment_nlValue_dict(self, tree, node):
        # Increment for rounding values
        # Units depend on the product
        dict = TextRules.TextRules.increment_nlValue_dict(self, tree, node)
        dict["MaxT"] = 5
        dict["MinT"] = 5
        dict["MaxRH"] = 5
        dict["MinRH"] = 5
        return dict

Data Value Extraction and Combining

It is very important that the combining criteria be consistent with the data value extraction thresholds and variables.  Combining for scalar and vector magnitudes is based upon the following thresholds:

    def scalar_difference_nlValue_dict(self, tree, node):
        # Scalar difference.  If the difference between scalar values
        # for 2 sub-periods is greater than this value,
        # the different values will be noted in the phrase.

    def vector_mag_difference_nlValue_dict(self, tree, node):
        # Replaces WIND_THRESHOLD
        # Magnitude difference.  If the difference between magnitudes
        # for sub-ranges is greater than this value,
        # the different magnitudes will be noted in the phrase.
        # Units can vary depending on the element and product

Data Value Extraction Consistency

Certain relationships must hold among the thresholds and variables used for Data Extraction. In particular: If this is not the case, the system will automatically set the differences accordingly.

NULL VALUES

"Null" values are those that are below the  "null_nlValue" or, in the case of Wx, <"NoWx">.  Here area the default thresholds from the ConfigVariables module:

    def null_nlValue_dict(self, tree, node):
        # Magnitude threshold below which values are considered "null" and  not reported.
        return {
            "Wind": 5,  # mph
            "TransWind": 0,  # mph
            "FreeWind": 0,  # mph
            "Swell": 5,  # ft
            "Swell2": 5,  # ft
            "WaveHeight": 3,  # ft
            }

You may specify the phrases that will be used to report Null values.  These phrases may be the empty phrase, "".   You may want to specify a different phrase if the the null value occurs for the entire time period or the for the first sub-phrase versus in subsequent sub-phrases.  Here are the default null phrase dictionaries from the ConfigVariables module:

    def first_null_phrase_dict(self, tree, node):
        # Phrase to use if values THROUGHOUT the period or
        # in the first period are Null (i.e. below threshold OR NoWx)
        # E.g.  LIGHT WINDS.    or    LIGHT WINDS BECOMING N 5 MPH.
        return {
            "Wind": "light winds",
            "TransWind": "",
            "FreeWind": "",
            "Swell": "light swells",
            "Swell2": "",
           "Wx": "",
            "WindGust": "",
            "Wave": "2 feet or less",
            }

    def null_phrase_dict(self, tree, node):
        # Phrase to use for null values in subPhrases other than the first
        # Can be an empty string
        #  E.g.  "NORTH WINDS 20 to 25 KNOTS BECOMING LIGHT"
        return {
            "Wind": "light",
            "TransWind": "light",
            "FreeWind": "light",
            "Swell": "light",
            "Swell2": "",
            "Wx":"",
            "WindGust": "",
            "Wave": "2 feet or less",
            }

LOCAL EFFECTS

Local effects are set up on a per-Product Component, per-phrase basis. When you set up a phrase to look for local effects, the system checks for significant differences in weather element values between contrasting edit areas. If differences are found, they will be reported in the phrase.  For example, through the GFE GUI we can define contrasting edit areas, "AboveElev" e.g. above 6000 feet and "BelowElev" e.g. below 6000 feet.  Then we can set up local effects for a particular phrase within our Component Product definition by doing the following: The following Component definition sets up local effects for the highs_phrase, lows_phrase, and wind_withGusts_phrase:

   def Period_1(self):
        return {
            "type": "component",
            "methodList": [
                          self.orderPhrases,
                          self.assemblePhrases,

                          self.wordWrap,
                          ],
            "analysisList": [
                       ("MinT", self.stdDevMinMax),
                       ("MaxT", self.stdDevMinMax),
                       ("T", self.hourlyTemp),
                       ("Wind", self.vectorMedianRange, [6]),
                       ("Wind", self.vectorMinMax, [6]),
                       ("WindGust", self.avg, [6]),
                       ],
            "phraseList":[
                   self.wind_summary,
                   self.reportTrends,
                   #self.highs_phrase,
                   #self.lows_phrase,
                   (self.highs_phrase, self._tempLocalEffects_list()),
                   (self.lows_phrase, self._tempLocalEffects_list()),
                   #self.highs_range_phrase,
                   #self.lows_range_phrase,
                   #self.wind_withGusts_phrase,
                   (self.wind_withGusts_phrase, self._windLocalEffects_list()),
                   ],
            "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", "Valleys"]),
                   ("MinT", ["Mountains", "Valleys"]),
                   ("Wind", ["Coast", "Inland"]),
                   ("WindGust", ["Coast", "Inland"]),
 ]

    def _windLocalEffects_list(self):
        leArea1 = self.LocalEffectArea("Coast", "near the coast")
        leArea2 = self.LocalEffectArea("Inland", "inland")
       return [self.LocalEffect([leArea1, leArea2], 10, " and ")]

    def _tempLocalEffects_list(self):
        leArea1 = self.LocalEffectArea("Valleys", "")
        leArea2 = self.LocalEffectArea("Mountains", "in the mountains")
        return [self.LocalEffect([leArea1, leArea2], 8, "...except ")]

As a result,   the system will automatically calculate the intersection of the Mountains, Valleys, (for MaxT, MinT) and Coast, Inland (for Wind and WindGust) areas with each edit area as it processes it.   Sampling and Analysis statistics will be calculated for both intersection areas for the appropriate weather elements. Then, the local effects phrase can examine the statistics for differences and report according to the methods and words given in the local effects definition. For example, the following phrases could be generated depending on the data:

    "Highs 45-55."
    "Highs 45-55...except 35-45 above timberline."
    "North winds 20 mph."
    "North winds 20 mph near the coast and  west winds 10 mph inland."
    "Near the coast...North winds 20 mph becoming 10 mph in the afternoon. Inland...North winds 10 mph becoming light in the afternoon."

Local Effects and Compound Phrases

Some phrases are "compound" i.e. they are composed of other phrases. In this case, you must specify the Local Effect per phrase within the compound phrase definition as in the following example:

    def skyWeather_byTimeRange_compoundPhrase(self):
        return {
            "phraseList": [
                self.fireSky_phrase
                #self.weather_phrase,
                (self.weather_phrase, self._generalLocalEffects_list()),
                ],
            "phraseMethods": [
                self.assembleSentences,
                self.skyWeather_finishUp,
            ],
            }

     def _generalLocalEffects_list(self):
        leArea1 = self.LocalEffectArea("Coast",  "on the coast")
        leArea2 = self.LocalEffectArea("Inland", "inland")
        leArea3 = self.LocalEffectArea("City", "in the city")
        return [self.LocalEffect([leArea1, leArea2, leArea3], 5, " ")]

Remember, as always, to include the local effect areas in the "intersectAreas" list within the component definition:

     def getFirePeriod_intersectAreas(self):
        return [
            ("MinT", ["BelowElev", "AboveElev"]),
            ("MaxT", ["BelowElev", "AboveElev"]),
            ("MinRH", ["BelowElev", "AboveElev"]),
            ("MaxRH", ["BelowElev", "AboveElev"]),
            ("RH", ["BelowElev", "AboveElev"]),
            ("Wx", ["BelowElev", "AboveElev"]),
            ]
 

Consolidation Rules for Local Effects

Local effect phrases with multiple weather elements and time periods could become complicated and unduly wordy.  To avoid this, we implement the following consolidation rules for local effect phrases: NOTE: If you are setting up a local effect for a multiple-element phrase, you MUST include all the weather elements in the "intersectAreas" and/or "additionalAreas" lists.

Local Effect Examples

Null Values and Local Effects

Below area local Effect phrases where one area has a null (below threshold) value so that only one local effect area is mentioned.

Set Up:

    def _windLocalEffects_list(self):
        srnInland = self.LocalEffectArea("Inland","")
        srnBeaches = self.LocalEffectArea("Beaches","at the beaches")
        return [self.LocalEffect([srnInland, srnBeaches], 5, "...except ")]

The local effect descriptor will appear at the end of the sentence IF there is only one subphrase.

SOUTHWEST WINDS 25 TO 35 MPH AT THE BEACHES.

Otherwise, the sentence would be  ambiguous:

SOUTHWEST WINDS 25 TO 35 MPH INCREASING TO 40 TO 50
MPH IN THE AFTERNOON AT THE BEACHES.

So the system  produces:

AT THE BEACHES...SOUTHWEST WINDS 25 TO 35 MPH INCREASING TO 40 TO 50
MPH IN THE AFTERNOON.

Null values and Wx: For periods in which you have Local Effects for Wx, you might want to specify afirst_null_nlValue and null_nlValue of "dry" so that the local effect can be correctly reported.  For example:

def null_nlValue_dict(self, tree, node):
        # Descriptors for phrases
        dict = TextRules.TextRules.null_nlValue_dict(self, tree, node)
        componentName = node.getComponentName()
        if componentName == "Period_1":
             dict[Wx"] = "dry"
        return dict

Then you will get phrases like:

SCATTERED SHOWERS...EXCEPT DRY IN THE BLUE MOUNTAINS.

instead of just:

SCATTERED SHOWERS.

"Except" versus "Otherwise" Wording

Below area examples of how to set up "except" types wording versus "otherwise" wording in a local effect phrase.
Set-up:

    def _windLocalEffects_list(self):
        srnInland = self.LocalEffectArea("BelowElev","")
        srnBeaches = self.LocalEffectArea("AboveElev","at the beaches")
        return [self.LocalEffect([srnInland, srnBeaches], 5, "...except ")]

SOUTHWEST WINDS 5 TO 15 MPH...EXCEPT SOUTHWEST 25 TO 35 MPH AT THE BEACHES.

SOUTHWEST WINDS 5 TO 15 MPH. AT THE BEACHES...SOUTHWEST WINDS 25 TO
35 MPH INCREASING TO 40 TO 50 MPH IN THE AFTERNOON.

Set-up:

    def _windLocalEffects_list(self):
        srnInland = self.LocalEffectArea("BelowElev","", "inland" )
        srnBeaches = self.LocalEffectArea("AboveElev","at the beaches")
        return [self.LocalEffect([srnBeaches, srnInland,], 5, "...otherwise ")]

SOUTHWEST WINDS 25 TO 35 MPH AT THE BEACHES...OTHERWISE SOUTHWEST 5 TO
15 MPH.

AT THE BEACHES...SOUTHWEST WINDS 25 TO 35 MPH INCREASING TO 40 TO 50
MPH IN THE AFTERNOON. INLAND...SOUTHWEST WINDS 5 TO 15 MPH.

More Local Effect Features

The following example shows several features of the Local Effects capability: In the following set-up, we have a small area (area1) which is a subset of the larger zone.  This area is used instead of the larger zone for phrase reporting.  However, in the case of temperature, we want to identify if certain regions (the Rush Valley or the Benches) within the zone exhibit temperatures significantly different from the small area.   This applies to only one area (area1) .  For other areas in the product , we do not want to identify any Local Effects.  Since the comparison areas (Rush_Valley and Benches) do not intersect area1, we specify them in the product component as "additionalAreas" instead of "intersectAreas" AND we set the "intersectFlag" to zero when defining them as LocalEffectAreas.

Set-up:

In the product component definition, we specify the local effects list as a method rather returning a list.  This is necessary since we want to define the list "on-the-fly" based on the current edit area:

            (self.highs_phrase, self._tempLocalEffects_list),
                (self.lows_phrase, self._tempLocalEffects_list),

Then we add in the additonal areas to be sampled and analyzed:

            "additionalAreas": [
                   # Areas listed by weather element that will be
                   # intersected with the current area then
                   # sampled and analyzed.
                   # E.g. used in local effects methods.
                   ("MaxT", ["Benches", "Rush_Valley"]),
                   ("MinT", ["Benches", "Rush_Valley"]),
             ],

Finally, we provide the method for defining the local effects list "on-the-fly."

    def _tempLocalEffects_list(self, tree, node):
        if self.currentAreaContains(tree, ["area1"]):
           leArea1 = self.LocalEffectArea(
                    "__Current__","",intersectFlag=0)
           leArea2 = self.LocalEffectArea("Rush_Valley",
                    "in the rush valley", intersectFlag=0)
           leArea3 = self.LocalEffectArea(
                    "Benches", "in the benches",intersectFlag=0)
           return [
               self.LocalEffect([leArea1,leArea2],5,"...except "),
               self.LocalEffect([leArea1,leArea3],5,"...except "),
               ]
        else:
           return []

Note that the Rush Valley will be checked first.  If a local effect is found, it will be reported:

HIGHS IN THE UPPER 50S...EXCEPT IN THE UPPER 20S TO UPPER 40S IN THE RUSH VALLEY.

If not, the Benches will be checked and if a local effect is found, it will be reported:

HIGHS IN THE UPPER 50S...EXCEPT IN THE UPPER 20S TO UPPER 40S IN THE BENCHES.

Finally, if neither area triggers a local effect, a simple phrase will be reported:

HIGHS IN THE UPPER 50S.
 

Local Effects for Snow Accumulation and Total Snow Accumulation -- Using a Method for the Checking the Local Effect Threshold

This section outlines how to produce local effect phrases such as the following:

SNOW ACCUMULATION 4 INCHES...EXCEPT 7 INCHES ABOVE
TIMBERLINE. TOTAL SNOW ACCUMULATION 9 INCHES...EXCEPT 18 INCHES
ABOVE TIMBERLINE.

In your overrides file, you must override the product component(s) for which you want to report local effects for the snow_phrase and/or total_snow_phrase. In these component definitions, specify the local effect as follows:

                   (self.snow_phrase,self._snowAmtLocalEffects_list()),
                   (self.total_snow_phrase,self._totalSnowAmtLocalEffects_list()),

Next, add the "intersectAreas" to these components for SnowAmt and IceAmt (note that IceAmt is handled by the SnowAmt phrase):

            "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.
                   ("SnowAmt", ["BelowElev", "AboveElev"]),
                   ("IceAmt", ["BelowElev", "AboveElev"]),
                   ],

IMPORTANT NOTE:  The total_snow_phrase looks AHEAD one period for information about snow ending (SnowAmt = 0). Therefore, you must include the "intersectAreas" in the component FOLLOWING the ones in which you are specifying a local effect for the total_snow_phrase. For example, in the ZFP_<site>_Overrides file using the 10-503 directive, if I set up a total_snow_phrase local effect for Period_1 and Period_2_3, I must also include the "intersectAreas" for Period_4_5.

Finally,  include the following local effect lists in your Overrides file:

    def _snowAmtLocalEffects_list(self):
        leArea1 = self.LocalEffectArea("BelowElev", "")
        leArea2 = self.LocalEffectArea("AboveElev", "above timberline")
        return [self.LocalEffect([leArea1, leArea2], 2, "...except ")]

    def _totalSnowAmtLocalEffects_list(self):
        leArea1 = self.LocalEffectArea("BelowElev", "")
        leArea2 = self.LocalEffectArea("AboveElev", "above timberline")
        return [self.LocalEffect(
            [leArea1, leArea2], self._checkTotalSnow, "...except ")]

    def _checkTotalSnow(self, tree, node, localEffect, leArea1Label, leArea2Label):
        totalSnow1 = self.getTotalSnow(tree, node, leArea1Label)
        totalSnow2 = self.getTotalSnow(tree, node, leArea2Label)
        if totalSnow1 is None or totalSnow2 is None:
            return 0
        if abs(totalSnow1 - totalSnow2) > 3:
            return 1
        return 0

Local Effects for the Combined SkyPopWx Phrase

You can set up Local Effects for the combined skyPopWx_phrase just like any other phrase.  This phrase has  primary element of Wx with Sky and PoP as secondary elements.  So local effects (as well as sub-phrase combining) is based upon Wx.  If  you are using the skyPopWx_phrase, you must also include the independent Sky, PoP, and Wx phrases in your phraseList and then include local effects for these phrases as well.  It is recommended that you use the following Local Effect Lists.    So, you would have:
 

     "phraseList":[
                   (self.sky_phrase, self._skyLocalEffects_list()),
                   (self.skyPopWx_phrase, self._skyPopWxLocalEffects_list()),
                   (self.weather_phrase,self._wxLocalEffects_list()),
                   (self.popMax_phrase, self._popLocalEffects_list()),
                  ...
                   ],                 
               

And also within the product component definition:

            "intersectAreas": [
                   ("Sky", ["AboveElev", "BelowElev"]),
                   ("Wx",  ["AboveElev", "BelowElev"]),
                   ("PoP", ["AboveElev", "BelowElev"]),
                   ], 

Set up the Local Effects lists as follows:

    def _skyLocalEffects_list(self):
        leArea1 = self.LocalEffectArea("AboveElev", "windward")
        leArea2 = self.LocalEffectArea("BelowElev", "leeward")
        return [self.LocalEffect([leArea1, leArea2], self.checkSkyDifference, "...")]
 
    def _wxLocalEffects_list(self):
        leArea1 = self.LocalEffectArea("AboveElev", "windward")
        leArea2 = self.LocalEffectArea("BelowElev", "leeward")
        return [self.LocalEffect([leArea1, leArea2], 0, "...")]

    def _popLocalEffects_list(self):
        leArea1 = self.LocalEffectArea("AboveElev", "windward")
        leArea2 = self.LocalEffectArea("BelowElev", "leeward")
        return [self.LocalEffect([leArea1, leArea2], 20, "...")]

    def _skyPopWxLocalEffects_list(self):
        leArea1 = self.LocalEffectArea("AboveElev", "windward")
        leArea2 = self.LocalEffectArea("BelowElev", "leeward")
        # Set threshold to be used by checkSkyWxDifference
        self._skyLocalEffectThreshold = 38
        return [self.LocalEffect([leArea1, leArea2],
                                 self.checkSkyWxDifference, "...")]

 PERIOD COMBINING

Narrative products include a period combining capability that attempts to combine periods with similar weather forecast values. To activate period combining uncomment the following line in your Overrides file.

    Definition["periodCombining"] = 1     # If 1, do period combining
 

Period combining is set up by methods found in the PhraseBuilder module:

1) If the periodCombining flag is set to 1 in the product, we add a method to the product narrative tree top level node called  "combineComponents" (in PhraseBuilder module).
2) The "combineComponents" method is called at the top of the tree and compares all consecutive components for similarity.  Also, we are only combining components that begin 36 hours after the issuance time of the product.
3) If two components are deemed similar (according to the "periodCombining_elementList" (in ConfigVariables)), then the component nodes are collapsed into one node with the time range spanning the original nodes.
4) From here all phrases and processing pretty much the same as it would have been for the original nodes.  To simplify combined period phrasing, there is a threshold "collapsedSubPhrase_hours" (in ConfigVariables) which is set to 12 hours as the default. If the period is longer than these hours, subphrases will automatically be collapsed.  

Periods can be combined providing the differences between the adjacent periods are small.  There is one method per weather element that determines whether two periods should be combined or not.  These methods are found in the PhraseBuilder module.  Each of them begin with "similar" followed by the weather element name.  The table below shows the methods that have been implemented thus far and their thresholds.
 

  Element Name as listed in the periodCombining_elementList   Method Name (in PhraseBuilder module)   Thresholds
  Sky   similarSky   Sky words are similar
  Wind   similarWind   mag within 10 knots 
  dir within 45 degrees
  Wx   similarWx   Identical wx keys per each analyzed sub-range
 PoP
  similarPoP
PoPs are equal or both below pop_lower_threshold or both above pop_upper_threshold
 MaxT
similarMaxT MaxT within 5 degrees
 MinT similarMinT MinT within 5 degrees
WaveHeight
similarWaveHeight WaveHeights within 4 feet
DiurnalSkyWx similarDiurnalSkyWx DiurnalSkyWx will combine if there is a diurnal pattern of sky and wx. For example,  "CLOUDY IN THE NIGHT AND MORNING...OTHERWISE CLEAR." or "LOW CLOUDS AND FOG IN THE NIGHT AND MORNING...OTHERWISE CLEAR."
Note that it will also combine if the sky and wx are similar without a diurnal pattern. 

Each of these methods can be overridden in your Overrides file.  In general, the threshold values are defined near the top of the method, so you won't need to hunt for them or change them in several places.  If you desire to change the thresholds, copy the method into your Overrides file and modify the threshold value.  You can also modify the algorithm, if you like.  The default method retrieves the statistics and examines them to see if the values between each component are close enough to combine.  You will likely want to leave the code that retrieves the statistics unmodified.  But you can choose to change the logic that decides if the values are "similar".

When components are collapsed, we do not want phrases to have detailed sub-phrases as we would in a normal 12-hour period.  Sub-phrases are then automatically collapsed if the combined period exceeds the collapsedSubPhrase_hours  found in the ConfigVariables module.   When sub-phrases are collapsed, the data is summarized according to the mergeMethod for the weather element being collapsed.  These settings can be overridden in your Overrides file.

    def collapseSubPhrase_hours_dict(self, tree, node):
        # If the period is longer than these hours, subphrases will automatically
        # be collapsed.
        return {
            "otherwise": 24,
            #"Wx": 24,
            }

    def mergeMethod_dict(self, tree, node):
        # Designates the mergeMethod to use when sub-phrases are automatically collapsed.
        return {
            "otherwise": "Average",
            "MinT": "Min",
            "MaxT": "Max",
            "PoP": "Max",
            "Wx": "List",  # Still want Wx to be broken out
            }

 PHRASE CONSOLIDATION

Phrases with multiple weather elements and complex temporal resolution can become very complicated and unduly wordy.  To avoid these situations, we implement a set of consolidation rules described in this section.  These rules are for Scalar and Vector elements.  For information on Weather consolidation see the Section on Weather Phrasing.

Definitions: Phrases are composed of sub-phrases depending on the temporal resolution of the statistics.  For example, the Wind phrase in the ZFP is composed of 2 6-hour sub-phrases, e.g. "North wind 10-15 mph increasing to 20 mph in the afternoon." A phrase can report on multiple elements (e.g. Wind and WindGust).  "North wind 10-15 mph with gusts up to 35 mph."  Weather elements in these phrases are considered "primary" and "secondary".

PHRASE ORDERING

You might want to re-order the phrases of a component based on the forecast data.  For example, suppose we have the following:

.TODAY...STRONG WINDS. THUNDERSTORMS WITH HEAVY RAINFALL.  HIGHS
AROUND 80. SOUTHEAST WINDS AROUND 70 MPH BECOMING SOUTH AROUND 105
MPH IN THE AFTERNOON.

The winds are the most dominant feature and thus you might want to move the "wind_withGusts_phrase" closer to the beginning of the forecast.  To re-order phrases on-the-fly, follow steps similar to this example:

--Make sure the Product Component Definition for any Periods in which you'd like to order the phrases has the method, "orderPhrases", prior to "assembleSubPhrases":

    def Period_1(self):
        component =  {
            "type": "component",
            "methodList": [
                          self.orderPhrases,
                          self.assemblePhrases,
                          self.wordWrap,
                          ],
            ....

 --Modify the "orderPhrases"(PhraseBuilder) method, for example:

    def orderPhrases(self, tree, component):
        reorderList = []
        timeRange = component.getTimeRange()
        areaLabel = component.getAreaLabel()

        # Put in some logic to see if you want to re-arrange the order
        # of the phrases.  For example:
        # Check for high winds
        windMax, dir = tree.stats.get("Wind", timeRange, areaLabel, mergeMethod="Max")
        if windMax > 50:
           # Put wind phrase first
           reorderList.append(("wind_withGusts_phrase", "weather_phrase"))

        # Apply any reorderings that were identified
        for name1, name2 in reorderList:
           self.moveAbove(tree, component, name1, name2)
        return self.DONE()

"UNTIL" PHRASING

In some phrases, you might want more resolution on the time descriptors such as in the following:

        "North winds 20 mph until 10 AM then 35 mph."

To accomplish this, override the flags below from the ConfigVariables module.  Be sure that if you want higher temporal resolution in your phrase, to set the temporal resolution in the analysisList accordingly.  For example:

                       ("Wind", self.vectorMedianRange, [0]),

    def untilPhrasing_flag_dict(self, tree, node):
        # If set to 1, "until" time descriptor phrasing will be used.
        # E.g. "NORTH WINDS 20 MPH UNTIL 10 AM...THEN 35 MPH"
        return {
            "otherwise": 0,
            #"Wind" : 1,
            }

    def onTheFly_untilPhrasing_flag_dict(self, tree, node):
        # If set to 1, "until" time descriptor phrasing will be used ONLY if
        # the time range for a sub-phrase does not end on a 3-hour boundary.
        return {
            "otherwise": 1,
            #"Wind" : 1,
            }

    def untilPhrasing_format_dict(self, tree, node):
        # Format for "until" time descriptors.
        # If "military": UNTIL 1000
        # If "standard": UNTIL 10 AM
        return {
            "otherwise": "military",
            #"Wind": "standard",
            }

The untilPhrasing may apply to only certain components of your product.  You can make this distinction as in this  example from the FWF:

    def untilPhrasing_flag_dict(self, tree, node):
        # If set to 1, "until" time descriptor phrasing will be used.
        # E.g. "NORTH WINDS 20 MPH UNTIL 10 AM...THEN 35 MPH"
        # Be sure to increase the temporal resolution if desired:
        # E.g.  ("MixHgt", self.minMax, [0]),
        dict = TextRules.TextRules.untilPhrasing_flag_dict(self, tree, node)
        dict["LAL"] = 1
        componentName = node.getComponent().get("name")
        if componentName == "FirePeriod":
            dict["Sky"] = 1
            dict["Wx"]  = 1
        return dict

VISIBILITY

Visibility is an attribute of the Wx grid and is reported on a sub-phrase level within the weather_phrase.  Since  each sub-phrase can have multiple subkeys, we report  the lowest visibility over the subkeys.    The visibility attribute is specified in the grids in statute miles.  You can specify the output units ("SM" or "NM" for nautical miles) for visibility in the element_outUnits_dict for "Visibility". Here's an example:

Example 1:
  Grids:
      Hour 1-6      Patchy F 1/4SM Chc R 1/2SM
      Hour 7-12   Sct  RW

  Phrase:
CHANCE OF LIGHT RAIN AND PATCHY FOG WITH VISIBILITY LESS THAN 1 NM THEN SCATTERED RAIN
SHOWERS IN THE AFTERNOON.

Example 2:
  Grids:
      Hour 1-6      Patchy F 1/4SM Chc R 1/2SM
      Hour 7-12   Sct  RW 11/2SM

  Phrase:  CHANCE OF LIGHT RAIN AND PATCHY FOG WITH VISIBILITY LESS THAN 1 NM THEN SCATTERED RAIN SHOWERS WITH 2 NM VISIBILITY IN THE AFTERNOON.

The wording for visibility within the weather phrase can be configured by overriding the visibility_weather_phrase_nlValue.

You can set the null_nlValue  for "Visibility" so that only visibilities below the threshold are reported.  The value should be in nautical miles. Suppose we have set this value to 3 nautical miles, Then:

Example 3:
  Grids:
      Hour 1-6      Patchy F 4SM Chc R
      Hour 7-12   Sct  RW

  Phrase:
CHANCE OF LIGHT RAIN AND PATCHY FOG THEN SCATTERED RAIN SHOWERS IN THE AFTERNOON.

You can set the  visibility_wx_threshold  so that weather is only reported if visibility is below the threshold.   Also, in this case you might want to set up a list of significant_wx_visibility_subkeys.   If any of these subkeys appear in the sub-phrase, the weather will be reported regardless of the visibility.  Suppose we have:

    def visibility_wx_threshold(self, tree, node):
        # Weather will be reported if the visibility is below
        # this threshold (in NM) OR if it includes a
        # significant_wx_visibility_subkey (see below)
        return 3

    def significant_wx_visibility_subkeys(self, tree, node):
        # Weather values that constitute significant weather to
        # be reported regardless of visibility.
        # If your visibility_wx_threshold is None, you do not need
        # to set up these subkeys since weather will always be
        # reported.
        # Set of tuples of weather key search tuples in the form:
        #  (cov type inten)
        # Wildcards are permitted.
        return [("* T"), ("* ZY")]

then:

Example 4:
  Grids:
      Hour 1-6      Patchy F 1/4SM Chc R 4SM
      Hour 7-12   Sct  RW

  Phrase:  CHANCE OF LIGHT RAIN AND PATCHY FOG WITH VISIBILITY LESS THAN 1 NM IN THE MORNING.

Example 5:
   Hour 1-6      F 1/4SM  RW 1/2SM
   Hour 7-12   TRW

  Phrase:   CHANCE OF LIGHT RAIN AND PATCHY FOG WITH VISIBILITY LESS THAN 1 NM THEN SCATTERED SHOWERS AND
THUNDERSTORMS IN THE AFTERNOON.

Finally, there is a consolidation method (consolidateVisibility in WxPhrases).  If the low visibility is constant across sub-phrases, visibility is separated out into its own phrase:

Example 6:
   Hour 1-6      F 1/4SM  RW 1/2SM
   Hour 7-12   TRW  1/4SM

  Phrase:   CHANCE OF LIGHT RAIN AND PATCHY FOG THEN SCATTERED SHOWERS AND THUNDERSTORMS IN THE AFTERNOON. VISIBILITY LESS THAN 1 NM.

The wording for visibility within the visibility_phrase can be configured by overriding the visibility_phrase_nlValue.

If you always want visibility reported in its own phrase, you can include the visibility_phrase in your phrase list ANDmake sure to set the  embedded_visibility_flag to zero so that it does not appear redundantly within the weather phrase.
 

WEATHER PHRASING

Also see sections on
     Algorithm for rankedWx and dominantWx
     Customizing Weather Phrases
     PoP/Wx Consistency
     Local Effects for the Combined SkyPopWx Phrase

Weather phrasing in the formatters involves many modules and logic threads as outlined in the following diagram:

Weather Phrase Design

Weather phrasing is complex, but centers around two primary methods:  "makeAggregateSubkey" (CommonUtils) and "checkWeatherSimilarity" (PhraseBuilder).  These methods are ultimately employed throughout the many phases of text generation: Analysis, Merging of grid statistics, Sub-phrase Combining, Local Effects, Period Combining and Weather Wording.  This ensures consistency throughout the process and makes customizing easier.

The makeAggregateSubkey method consolidates two subkeys according to the following:
It uses the rankFuzzFactor (CommonUtils) to determine if 2 ranks are similar.  It refers to the wxHierarchies (WxPhrases) to determine which coverage or intensity is stronger.

The checkWeatherSimilarity method compares two rankLists i.e. lists of (subkey, rank) tuples.  It uses similarWxTypes, similarCoverageLists and similarIntensities to determine if subkeys are similar.   To be deemed similar, the rankLists must meet the following criteria:
      
Analysis:  The "rankedWx" method is used for weather phrasing and is discussed in detail in the section Algorithm for rankedWx and dominantWx. Of note, here, is that the algorithm uses the "aggregateCov_algorithm" (default is "getAggregateCov")  to compute an aggregate coverage, and the "getAggregate" method to determine the aggregate intensity,  visibility, and attributes according to the rules of the "makeAggregateSubkey" (CommonUtils) method. 

In the Analysis phase, we also Filter the subkeys.  Just as Scalar and Vector statistics are automatically converted and rounded, weather subkeys are automatically filtered according to the following criteria:
    def wxCombinations(self):
        # This is the list of which wxTypes should be combined into one.
        # For example, if ("RW", "R") appears, then wxTypes of "RW" and "R" will
        # be combined into one key and the key with the dominant coverage will
        # be used as the combined key.
        # You may also specify a method which will be
        #  -- given arguments subkey1 and subkey2 and
        #  -- should return
        #     -- a flag = 1 if they are to be combined, 0 otherwise
        #     -- the combined key to be used
        #  Note: The method will be called twice, once with (subkey1, subkey2)
        #  and once with (subkey2, subkey1) so you can assume one ordering.
        #  See the example below, "combine_T_RW"
        #
        return [
                ("RW", "R"),
                ("SW", "S"),
                self.combine_T_RW,
            ]

     def combine_T_RW(self, subkey1, subkey2):
        # Combine T and RW only if the coverage of T
        # is dominant over the coverage of RW
        wxType1 = subkey1.wxType()
        wxType2 = subkey2.wxType()
        if wxType1 == "T" and wxType2 == "RW":
            order = self.dominantCoverageOrder(subkey1, subkey2)
            if order == -1:
                return 1, subkey1
        return 0, None
 

All weather filtering variables are located in the WxPhrases module and can be overridden by copying them into your Overrides file.

Merging of statistics occurs when a tree.stats.get command requests statistics for a time period spanning more than one in the Statistics Dictionary.  For example, if we request Wx statistics with a 3-hour temporal resolution and request the statistics for a 6-hour period, we must merge the 3-hour statistics.  Again, we must filter  subkeys and ultimately employ  the "makeAggregateSubkey" method.

Sub-phrase Combining.  There are various weather phrases for which we must combine sub-phrases.  The "skyPopWx_phrase" is used for simpler cases in which the resulting phrase only requires 1 or 2 sub-phrases.  For more complex scenarios, the "weather_phrase" is triggered.  The "severeWeather_phrase" and "heavyPrecip_phrase" are special cases, but use the same combining scheme to determine the appropriate sub-phrasing.
           you would end up with
           and wording:  "Widespread rain in the early morning.  Slight chance of snow through the day.  Freezing rain likely in the afternoon."
Local Effects.  For consistency, local effects for the weather phrases uses the same underlying "checkWeatherSimilarity" method.   Local effects for the weather_phrase will use the general "checkLocalEffectDifference" (PhraseBuilder) method which in turn calls "checkWeatherSimilarity".  For information on setting up local effects for the "skyPopWx_phrase", see the section: Local Effects for the Combined SkyPopWx Phrase.

Period Combining.  
If you choose to examine Wx  similarities as a basis for period combining, the "similarWx" method will call "checkWeatherSimilarity" for it's comparison of Wx across periods.

Weather Words.  
The words for each weather sub-phrase are created by the following steps:
          Only switch once to "mixed with", "possibly mixed with" or "with pockets of".  The rankFuzzFactor (CommonUtils) is used to determine if ranks are similar and   the similarCoverages  method is used to determine if coverages are similar.  The wording can be adjusted for mixedWith, possiblyMixedWith, withPocketsOf  and wxConjunction.


Appendix A

Local Effects Re-design/Design to Handle Combined Phrases

Phrase Processing Steps including Local Effects

Examples of Phrase Processing Steps

The following examples show the progression through the phrase processing steps.  They are rough sketches, not intended to be complete. They use these abbreviations:

Test Case 3_3 F20

Grids:
        Area1:                Iso T (hours 0-6)  Chc R Lkly S    Sky 90  (hours 0-12)
                                         (The Wx is the result when we sample the combined Windward and Leeward values).
        Windward:        Iso T (hours 0-6)   Lkly R Lkly S  PoP 90    Sky 90  (hours 0-12)
        Leeward:            Iso T (hours 0-6)   Chc R                PoP 40    Sky 90  (hours 0-12)

Words:
       ISOLATED THUNDERSTORMS IN THE MORNING.
       RAIN AND SNOW LIKELY WINDWARD...A 40 PERCENT CHANCE OF RAIN LEEWARD
       ...CHANCE OF PRECIPITATION 70 PERCENT WINDWARD.

Note:  The PoP is reported separately for WW because it is greater than 60.

Steps:

Test Case 3_3 F6 

Grids:
        Area1:               
        Windward:         Sct RW  Sky 70   PoP 50
        Leeward:            NoWx     Sky 20   PoP   0

Words:
       SUNNY LEEWARD...MOSTLY CLOUDY WITH SCATTERED SHOWERS WINDWARD...CHANCE OF SHOWERS 50 PERCENT WINDWARD
Steps:

Test Case 3_3 F30  Sub-phrase consolidation Case 1

This is an example in which we need the Consolidate Sub-phrases step.
Case 1: duplicate sub-phrases are for the same areaLabel.

Grids:
        Area1:               
        Windward:        (hours 9-12) Wx: Patchy:F  PoP 20  Sky 50
        Leeward:            (hours 9-12) Wx: Wide:F    PoP 0    Sky 50

Words:
       MOSTLY SUNNY.
       WINDWARD...PATCHY FOG LATE IN THE AFTERNOON.
       LEEWARD...WIDESPREAD FOG LATE IN THE AFTERNOON.

Test Case 3_3 F22  Sub-phrase consolidation Case 2

This is an example in which we need the Consolidate Sub-phrases step.
Case 2:  duplicate subphrases are for a local effect and cover all possible local effect areas for their phrase, create a new phrase for component.getAreaLabel() with this subPhrase wording. Remove the local effect subPhrases.

Grids:
        Area1:               
        Windward:        Chc T (hours 0-6)   Lkly RW    PoP 70
        Leeward:            Chc T (houts 0-6)   Chc RW     PoP 40

Words:
       CLOUDY.  CHANCE OF THUNDERSTORMS IN THE MORNING.
       WINDWARD...SHOWERS LIKELY...CHANCE OF SHOWERS AND THUNDERSTORMS 70 PERCENT.
       LEEWARD...CHANCE OF SHOWERS IN THE AFTERNOON...CHANCE OF SHOWERS AND THUNDERSTORMS
      40 PERCENT.
      
Steps:
       Note:  The leeward showers are "in the afternoon" because T and RW are combined if their
       the coverage of T is greater or equal to that of RW.

Test Case 3_3 F45 Need checkSkyPopWx to remove pop phrases for component as well as local effect area.

Grids:
        Windward:        Sky: Cloudy            SChc S    PoP 20
        Leeward:            Sky: Partly Sunny Chc R      PoP 30

Words:
      CLOUDY WITH A 20 PERCENT CHANCE OF SNOW WINDWARD...
      PARTLY SUNNY WITH A 30 PERCENT CHANCE OF RAIN LEEWARD

(No extra PoP phrase)
      
Steps:

Design Changes Made from Original Local Effect Strategy to New

Basically, we were trying to do too much upstream,  making assumptions that were ok for simple 1-dimensional phrases, but did not hold for multi-element phrases. Now we have moved much of the decision-making downstream when we know more about the outcome.
        ##  Timing: This method runs at the component level
        ##  AFTER all sub-phrase words have been set and
        ##  BEFORE they have been assembled into phrases at the phrase level.
        ##
        ##  Purpose: If for all possible areaLabels per phrase or phrase type
        ##        we repeat a particular subPhrase and timeRange,
        ##      Factor it out into an un-qualified phrase
        ##
        ##  For example:
        ##    Chance of thunderstorms in the morning (windward)
        ##    Chance of thunderstorms in the morning (leeward)
        ##    Chance of rain in the afternoon (windward)
        ##    Chance of snow in the afternoon (leeward)
        ##
        ##  becomes:
        ##    Chance of thunderstorms in the morning (unqualified)
        ##    Chance of rain in the afternoon (windward)
        ##    Chance of snow in the afternoon (leeward)
                        # Organize the local effect and non-local effect phrases.
                        # "node" can be a component or a compound phrase.
                        # Convert to embedded local effect phrases if appropriate.
                        # Apply the Local Effect thresholds:
                        #    repeatingEmbedded_localEffect_threshold
                        #    repeatingPhrase_localEffect_threshold

    #### Component-Level Local Effect thresholds
    def repeatingEmbedded_localEffect_threshold(self, tree, component):
        # Number of embedded local effect phrases allowed in a component
        # before they are gathered together into a conjunctive local
        # effect clause.  For example, with the threshold set to 2:
        #
        # Instead of:
        #     Cloudy windward and partly cloudy leeward.
        #     Rain likely windward and scattered showers leeward.
        #     Chance of precipitation 50 percent windward and 30
        #     percent leeward.
        #
        # We will produce:
        #     Windward...Cloudy...Rain likely...Chance of precipitation 50 percent.
        #     Leeward...Partly cloudy...Scattered showers...Chance of precipitation
        #     30 percent.
        #
        # NOTE:  If we have even one conjunctive local effect, however, all will be left
        #     conjunctive.  For example, instead of:
        #
        #     Cloudy windward and partly cloudy leeward.
        #     Windward...Rain likely in the morning.
        #     Leeward...Scattered showers in the afternoon.
        #
        #  We will produce:
        #     Windward...Cloudy...Rain likely in the morning.
        #     Leeward...Partly cloudy...Scattered showers in the afternoon.
        #
        return 2

    def repeatingPhrase_localEffect_threshold(self, tree, component):
        # Number of repeating local effect phrases allowed inside a
        # set of conjunctive local effects for each of the
        # repeatingPhrase_categories (see below).
        #
        # For example, with the default of 1 and the categories below,
        #
        # Instead of:
        #    Chance of thunderstorms in the morning.
        #    Windward...Cloudy...Rain likely...Chance of precipitation 70 percent.
        #    Leeward...Partly cloudy...Scattered showers...Chance of precipitation 30
        #    percent. Highs in the 40s.  Winds 20 mph.
        #
        # We will produce:
        #    Windward...Cloudy....Rain likely...Chance of thunderstorms in the morning...
        #    Chance of precipitation 70 percent.
        #    Leeward...Partly cloudy...Scattered showers...Chance of thunderstorms in
        #    the morning...Chance of precipitation 30 percent. Highs in the 40s.
        #    Winds 20 mph.
        #
        # Note that if we had:
        #    Windward...Cloudy....Rain likely...Chance of precipitation 70 percent.
        #    Leeward...Partly cloudy...Scattered showers......Chance of precipitation
        #    30 percent. Highs in the 40s. Winds 20 mph.
        #
        # The phrasing would remain unchanged since there are 2 phrases (Temps and Winds)
        # in the "ALL OTHER PHRASES" category that would have to be repeated within the
        # conjunctive local effects.
        return 1
 
    def repeatingPhrase_localEffect_categories(self, tree, component):
        return [
            ["skyPopWx_phrase", "sky_phrase", "weather_phrase", "popMax_phrase"],
            ["ALL OTHER PHRASES"],
            ]

    def lePhraseNameGroups(self, tree, component):
        # Groups of phrase names that can be combined into embedded local effect phrases.
        # If the phrase is not listed here, it is assumed that only phrases with the
        # same name can be combined with it into an embedded local effect phrase.
        # For example:
        #   With the group:  ("skyPopWx_phrase", "weather_phrase"), we will allow:
        #
        #   A 20 percent chance of rain windward and areas of fog leeward.
        #
        #   Since "skyPopWx_phrase" and "wind_phrase" do not appear as group, we will
        #   not allow:
        #
        #    A 20 percent chance of rain windward and north winds 20 mph leeward.
        #
        #
        return [("skyPopWx_phrase", "weather_phrase")]