contents  prev top bottom next

Chapter 3: Exercises and Examples    contents  ↑  ^   v   

3.0 Configuration    contents    ^   v   

3.0.1 Settings     contents    ^  ─  +   v   

By default initial setting, we mean the setting file that is used on startup in the situation where this had not been specifically defined. The initial setting can be specified by creating a user version of StartUpConfig.py where the item "defaultSettings" is set to the desired initial settings file. This can now also be done using SETUP->Manage Settings->Make My Current Default in the console.


Be aware that there is now a ‘universal generic base’ for all settings files, CommonSettings.py, found in /awips2/edex/data/utility/common_static/base/HazardServices/settings/config/. Also be aware that the way specific settings files interoperate with CommonSettings.py is still in flux.

It is possible to use the localization perspective to manipulate settings. However, for most things one might want to do with settings it makes more sense to use the Edit Default Setting: GUI (at SETUP->Manage Settings->Edit/Filter… in the console). See section 5.0 of the User Guide for more information about this. There are exceptions to this, including promoting custom settings to site level , configuring CommonSettings.py, and removing a base level default setting file.  For completeness, we will show some examples of using the localization perspective to manipulate settings.

3.0.1.1 Modifying an existing Settings file    contents    ^  ─  +   v   

   "visibleColumns": [
       "Event ID",
       "Lock Status",
       "Hazard Type",
       "Status",
       "Stream",
       "Point ID",
       "Start Time",
       "End Time",
       "Expiration Time",
       "VTEC Codes",
   ],

Hydrology_All = {
   "visibleColumns": [
       "Event ID",
       "Lock Status",
       "Hazard Type",
       "Status",
       "Point ID",
       "Start Time",
       "End Time",
       "Expiration Time",
       "VTEC Codes",
   ],
}

Now you can restart Hazard Services and verify that the Stream column is gone, right? Wait a minute, the Stream column is still there...what happened? This is a result of the fact that this file is subject to incremental override. Incremental override is why one does not have to copy the whole base file to the site file. However, for incremental override, the default behavior for two lists at the same namespace is to merge the lists rather than to replace the list in the base file with the list in the site file. You have two options in the site file that can change this default behavior, as follows:

     "visibleColumns": [
       "_override_replace_",
       "Event ID",
       "Hazard Type",
       "Status",
       "Point ID",
       "Start Time",
       "End Time",
       "Expiration Time",
       "VTEC Codes",
   ]

    "visibleColumns": [

        "_override_remove_list_",

        "Stream",

    ],

 

3.0.1.2 Creating a new Settings file   contents    ^    +  v   


This can be done in two ways; through the
Edit Default Settings: dialog or through the localization perspective. Using the Edit Default Settings: dialog has the advantage of it being very hard to shoot yourself in the foot. Using the localization perspective has the advantage that the resulting settings file can be made ‘incremental’ (e.g. you only define the things you want to change.)

Here is how this can be done through the Edit Default Settings: dialog.


Here is how this can be done through the localization perspective:

3.0.1.3 Promoting a settings file to Site level.  contents    ^    +  v   

It is perfectly reasonable to use the dialog to initially create a new settings file, and then to later go into the localization perspective to further modify the settings information. Also, be advised that both of these means for creating a new settings file will create a USER level settings file. If it is desired to make the new settings file a SITE level settings file, this can be accomplished through the localization perspective as follows:

3.0.1.4 Console Time Window.  contents    ^    +  v   

A recently added feature is to allow a setting file to specify the range of hours offset from the current time for which to display hazards in the Console. This currently defaults to essentially an infinite time range in all default settings files. Some sites or users may find it more convenient now for default settings files to make ALL Status values visible and apply a reduced size console time window. What follows is an override for CommonSettings.py that can accomplish this.

CommonSettings = {

   "consoleTimeWindow": ("_override_replace_", -8, 999),

   "visibleStatuses": [

        "ended",

        "elapsed"

    ],

}

With this example override file, hazards valid in the future will appear in the Console essentially forever, but previous hazards will appear in the Console only for the last 8 hours (e.g. one shift). If there is a desire to have this apply to all users, installing this as a Region level override is a reasonable way to do this.

Note that the Console Time Window can be controlled using the Console tab of the Edit Default Settings: dialog.

3.0.1.5 Remove default base setting file.  contents    ^    +  v   

New settings files created by the user that are not overrides of an existing default base setting file are straightforward to remove.  If they are implemented at the User level, this can be done either using the localization perspective or by way of SETUP->Manage Settings->Delete.  At the Site level, one can still use the localization perspective, but at the Configured or Region level this needs to be done using the file system.  However, consider the situation where one wants to remove a settings file that was delivered as Base level default settings file.  This cannot be done by overriding or undoing an override of the setting file itself; this must be done using an override of StartUpConfig.py.  Suppose one wanted to remove the Hydrology_ESF settings file.  Here is an override of StartUpConfig.py that can accomplish this:

StartUpConfig = {

    "turnOffFeatures_practiceMode":[

         {

         "settings": ["Hydrology_ESF"],

         }

         ],

    "turnOffFeatures_testMode" : [

         {

         "settings": ["Hydrology_ESF"],

         }

         ],

    "turnOffFeatures_operationalMode" : [

         {

         "settings": ["Hydrology_ESF"],

         }

         ],

}

3.0.2 StartUpConfig      contents    ^    +  v   

Here we show how to modify an entry in StartUpConfig.py. Note that StartUpConfig.py is subject to incremental override, and so one only need specify the items to change; there is no need to copy the whole base file.

StartUpConfig = {
   "defaultSettings" : "
Hydrology_All",
}

Note that the console control SETUP->Manage Settings->Make My Current Default is just like entering your current setting as the default setting for an override of StartUpConfig.py. Also note that this operation results in an incremental version of StartUpConfig.py being written out.

3.0.3 Hazard Types      contents    ^  ─  +  v   

3.0.3.1 Modifying a Hazard Type entry     contents    ^  ─  +  v   

Here we show how to modify a Hazard Type entry. These entries occur in the file HazardTypes.py. Note that HazardTypes.py is subject to incremental override, and so one only need specify the items to change; there is no need to copy the whole base file.

HOURS = 3600000
HazardTypes = {
   "FA.A": {
        'defaultDuration': 6 * HOURS,
   },
}

Here is a related example for a hazard type that has a list of durations to pick from. Suppose that it is desired to allow Flash Flood warnings of a shorter duration (this is often a convenient way to test the behavior of hazards as they approach their end time). Here are two possibilities for what the content of the override file could look like in that case:

HazardTypes = {

  'FF.W.Convective' : {

         'durationChoices' :

           [ "_override_prepend_",

             "15 min", "30 min", "45 min" ]

  }

}

HazardTypes = {

  'FF.W.Convective' : {

         'durationChoices':

           ["_override_replace_",

            "15 min", "30 min", "45 min",

            "60 min", "90 min", "120 min",

            "2 hrs 30 min", "3 hrs",

            "3 hrs 30 min", "4 hrs",

            "4 hrs 30 min", "5 hrs",

            "5 hrs 30 min", "6 hrs",

            "6 hrs 30 min", "7 hrs",

            "7 hrs 30 min", "8 hrs",

             ]

  }

}

Because HazardTypes.py is subject to incremental override, the default behavior for two lists at the same namespace is to merge the lists rather than to replace the list in the base file with the list in the site file. The first example override file tells the system to prepend  '15 min',  

‘30 min’, and  '45 min' to the list of durations, whereas the second example starts by stipulating that the supplied override list should replace the base list.

3.0.3.2 Completely removing an existing Hazard Type.     contents    ^   +  v   

In this section we describe how to completely remove an existing default hazard type from Hazard Services. We make the assumption that if one wants to remove a user defined hazard type from Hazard Services, one can simply undo the steps described in section 4.2 which were used to add that hazard type in the first place.

In the section entitled disabling the ability to issue a hazard type, we describe how to make a hazard type unissuable. Making a hazard type unissuable retains the ability to view hazards of that type from neighboring offices and the ability to manage that hazard type in the settings. The changes documented in this section result in eliminating all ability for Hazard Services to interoperate with hazards of that type. This means hazards of that type from neighboring offices cannot be viewed, nor can this hazard type be managed in the settings.

Here we will demonstrate specifically how to eliminate the Flood Advisory (FL.Y) hazard type.
We will start by eliminating it from HazardCategories.py. The approach is to create a site override for HazardCategories.py containing the following:

import collections

HazardCategories = collections.OrderedDict(
       {
       "Hydrology": [ "_override_remove_", ("FL", "Y") ]
       }
)


HazardCategories.py is subject to
incremental override, and so there is no need to replicate the whole file, we just need to mark FL.Y for deletion from its category, "Hydrology". Were an override for HazardCategories.py already present, its new content would need to be a merge of the immediately preceding example and its current content. If an override file for HazardCategories.py had been used to reassign FL.Y to a different category, updating the override file would need to include removing FL.Y from its new category as well.

Hazard Service’s decisions about which hazard types are important to the console are completely driven by the content of your current setting, and the code that manages the settings files is smart enough to completely throw on the floor any hazard type that does not have a category.  Functionally removing a hazard type from Hazard Services does not require removing the hazard type from HazardTypes.py. In fact, attempting to do this would likely cause more problems than it would solve and therefore is not recommended. The same cannot be said, however, for any recommenders that might generate hazards of that type. Nor is this true for the logic that displays hazards.


For the sake of argument, let us stipulate (not true) that the RiverFloodRecommender recommends hazards
only of type FL.Y and no other types. Therefore, all references to that recommender need to be removed from all our settings files. Plus, we want to remove all references to “FL.Y” from the settings files. These four commands allow one to easily identify any setting files for which this is an issue:

> touch /tmp/bbb

> cd /awips2/edex/data/utility/common_static

> find . -name '*.py' -path '*/settings/*' -exec grep 'RiverFloodRecommender' '{}' /tmp/bbb \;

> find . -name '*.py' -path '*/settings/*' -exec grep 'FL.Y' '{}' /tmp/bbb \;

For any base settings file identified where there is no corresponding site override, one needs to create a site override containing what follows. The italicized text is not literal, but rather is the name of the settings file minus the .py extension. Please note that while we are using the file system to identify those settings files that we need to change, creating and/or updating the site override file should be done in the localization perspective.

Settings_File_Name = {

   "visibleTypes": [ "_override_remove_", "FL.Y" ],

   "toolbarToolNames": [

       "_override_remove_",

       "RiverFloodRecommender"

   ]

}

For any override settings file where there is no corresponding base file, one simply needs to remove any instance of "RiverFloodRecommender" from the "toolbarToolNames" , plus any occurrence of “FL.Y”. The same goes for any override settings file where there is an entry of “_override_replace_”: True, at the top level. Otherwise the modified override settings file needs to be a merge of the immediately preceding example and its current content.

As previously mentioned, it is not actually true that the RiverFloodRecommender recommends hazards only of type FL.Y. Therefore, if one were actually completely removing the hazard type FL.Y from Hazard Services, one needs to go into the recommender itself and disable its ability to interoperate with hazards of type FL.Y. For this particular recommender, this is detailed in section 3.3.3. For other recommenders that recommend multiple hazard types, the details would be different. It is also possible for a hazard type to have no associated recommender. In that case, none of the override settings files would need any entries for "toolbarToolNames".

3.0.4 FilterIcons.py    contents    ^   +  v   

The purpose of FilterIcons.py is to allow icons to be placed on the left side of the console tool bar that indicate when hazard events meeting certain criteria have been filtered out. One can see the default version of FilterIcons.py in the localization perspective under Hazard Services -> Hazard Categories. What follows is a sample override for this file. This file is subject to incremental override, and so to eliminate the default entry the list of entries begins with '_override_replace_' . If there was a desire to only add new entries, those entries could be placed in the override file and the default entry would still be in effect as well. The image referred to with the 'normalIcon' element is what shows up when no events meeting the criteria have been filtered out.; the 'coloredIcon' element indicates the image shown when there ARE events being filtered out that meet the criteria.

FilterIcons = [
   '_override_replace_',
   {
   'label': 'Hydrology Watches',
   'normalIcon': 'waveIcon.png',
   'coloredIcon': 'waveIconYellow.png',
   'hazardTypes': [
                   'FF.A', 'FA.A',],
   'status': ['potential', 'pending', 'issued', 'ending']
   },
   {'label': 'Hydrology Warnings',
   'normalIcon': 'unlocked.png',
   'coloredIcon': 'locked.png',
   'hazardTypes': ['FF.W.Convective', 'FF.W.NonConvective', 'FF.W.Burnscar',
                   'FA.W','FL.W'],
   'status': ['potential', 'pending', 'issued', 'ending']
   },
   {'label': 'Hydrology Advisory',
   'normalIcon': 'MultiValueScaleNormalThumbImage.png',
   'coloredIcon': 'MultiValueScaleActiveThumbImage.png',
   'hazardTypes': [
                   'FA.Y', 'FL.Y',],
   'status': ['potential', 'pending', 'issued', 'ending']
   },
]


3.0.5 Highway Mile Markers in Flash Flood Warnings    contents    ^   v   

Hazard Services by default supports the ability to list the mile markers of impacted highways in flash flood warnings. Hazard Services leverages much of the same scripting and data structures to do this that warnGen does. For both Hazard Services and warnGen, this functionality is dependent on creating a table in postgres for each route.   These tables appear in the mapdata schema of the maps database.  For warnGen, the name of each table that supports the mile marker functionality is placed in an xml file; for Hazard Services, each table needs to be inserted into an override for the file PointMarkerConfigs.py.  

Here is an example of an entry in PointMarkerConfigs.py that supports the mile marker functionality for one route:

PointMarkerConfigs = {

   "mileMarkers": {

        "tables": [

            {

                "displayName": "Interstate 29 in Nebraska",

                "tableName": "i29_oax",

                "returnFields": [

                    "id",

                    "name"

                ],

                "maxResults": 10,

                "simplify": True,

                "sortBy": [

                    "gid",

                    "asc"

                ],

                "type": "mile marker"

            },

        ],

    },

}

Note that the content in blue is the structure within which the content for each route exists, the content in black is what is specific to the route for this example.  Also note that the site ‘oax’ is embedded in the table name entry. Unlike dam break or burn scar information, there is no formal segregation in postgres according to CWA for this information.  Therefore it is a good idea to always embed the CWA name that the route exists within as part of the table name.

For now lets assume the mile marker functionality is already working in warnGen, and has been implemented for all the markers along all the routes you care about at your site.  If this is the case, there should already be a postgres table for each route, and so one only need create an override for PointMarkerConfigs.py to leverage those.  A python script has been updated to do most of this work for you.  To invoke this script, become user awips on dx3, and run these commands:

> cd /awips2/edex/scripts/HazardServices/

> /awips2/python/bin/python parseWarngenTemplate.py -m

With the -m option, parseWarngenTemplate.py reads the files mileMarkers.vm and mileMarkers.xml from site/LLL/warngen/ and outputs an override for PointMarkerConfigs.py in the following directory (LLL is not literal, but is your primary site):

common_static/configured/LLL/HazardServices/python/events/productgen/geoSpatial
/

You can run parseWarngenTemplate.py with no arguments to get usage information.  You can also supply a backup site id on the command line to run it for any arbitrary backup site. Note that if this conversion step fails to create a useable override of PointMarkerConfigs.py, it is always possible to compose these entries for yourself, following the example shown.  If for some reason you want to add mile markers for an additional route not yet implemented in your warnGen, that will also require composing one or more of these entries.

In the case where the mile marker functionality is already working in warnGen, all the postgres tables referred to by the version of PointMarkerConfigs.py created here should already be in postgres.  If mile markers for some or all of the routes are not showing up in FFW text, this means that some or all of the postgres tables are not installed.  If this is the case, here is what to do.  First, identify all the tables referred to in all your overrides of PointMarkerConfigs.py using these commands, best run as user awips on dx3:

> cd /awips2/edex/data/utility/common_static

> grep tableName */LLL/HazardServices/python/events/productgen/geoSpatial/PointMarkerConfigs.py


Next, identify all the tables in the
mapdata schema of the maps database.  The command that follows will do this (leave off the -U awips -hdx1f in a dev environment):

> psql -U awips -hdx1f -d maps -c '\dt mapdata.*'

For any table referred to in an override of PointMarkerConfigs.py that is not in postgres, one either needs to remove all entries that refer to it in any overrides of PointMarkerConfigs.py, or add it to postgres.  To add such a table to postgres, one first needs a file that contains a list of mile markers for a single route in a very specific format.  What follows is an example of such a file.  The name of the file does not matter except that it helps keep this information organized in a meaningful way for the person managing this.


highway92_oax.id :

7
1        41.192        -97.354        p        1        409|1
2        41.192        -97.296        p        2        412|1
3        41.192        -97.238        p        3        415|1
4        41.192        -97.162        p        4        419|1
5        41.206        -97.096        p        5        423|1
6        41.206        -97.057        p        6        425|1
7        41.206        -97.000        p        7        428|1


The script used to install this information as a postgres table is available only on dx1, and should be run as user awips. The full path to this script is: /awips2/database/sqlScripts/share/sql/maps/importMarkersInfo.sh

The importMarkersInfo.sh script is very particular about this format, so it is recommended to not deviate from it. If you have already successfully installed mile markers for warnGen, then you will likely already have a set of these ID files that you can reuse for creating mile marker postgres tables for Hazard Services if necessary. The first line in the ID file always contains the number of entries in the file. The ‘p’ and the ‘|1’ are literal and should always be placed in each line exactly as shown. The first and fifth whitespace delimited columns are sequence numbers and should always count up. Just before the vertical bar are the mile marker values; these are arbitrary strings. The latitude and longitude values are self explanatory.

Here is how this script is run to create a postgres table:

> importMarkersInfo.sh /path/to/markerfile.id mapdata tableNameInPointMarkerConfigs.py

mapdata is literal. This results in a new table being created in the mapdata schema of the maps database with the same name as the final argument. Unlike dam break or burn scar information, there is no formal segregation in postgres according to CWA for this information.  Therefore it is a good idea to always embed the CWA name that the route exists within as part of the table name.

The very first time importMarkersInfo.sh is called for a given table name, you might see a message like:
   NOTICE:
 table "highway92_oax" does not exist, skipping
This seems bad, but does not necessarily mean the script failed. It may mean simply that it ‘skipped’ the step of deleting the table before reinstalling it.

3.0.6 Other arbitrary Markers in Flash Flood Warnings.    contents    ^   v   

Hazard Services implements the ability to mention when the location of other arbitrary markers occur within the bounds of a Flash Flood Warning. Here we will present an idealized example of this. It starts with a site override of PointMarkerConfigs.py:

PointMarkerConfigs = {

                       "publicevents" : {

                           "tableName" : "publicevents",

                           "returnFields" : ["name"],

                           "pointSpecificLabel" : "Public Events"

                           },

                 }

With this in place, the Additional Locations: section of the Hazard Information Dialog for a Flash Flood Warning will have a checkbox labeled "Public Events" (e.g., the value of the “pointSpecificLabel" key). In order for this to have useful functionality, the importMarkersInfo.sh script must be used to create a new table in the mapdata schema of the maps database with the name "publicevents" (e.g. the value of the "tableName" key). Suppose the following invocation of importMarkersInfo.sh was used:
> importMarkersInfo.sh /tmp/publicEvents.id mapdata publicevents


Furthermore, suppose that the contents of publicEvents.id were the following:

2
1  41.22  -96.11 p 1 Little League Finals|1
2  41.33  -96.27 p 1 Regional Tractor Pull|1

Finally, suppose that one created an FFW where the warning box contained both of those latitude/longitude points and the "Public Events" checkbox was activated in the Hazard Information Dialog. The resulting text product would contain the following small paragraph:


This includes the following Public Events...

        Little League Finals and Regional Tractor Pull.

3.1 Recommenders    contents    ^   +  v   

Please see this presentation to become familiar with the Recommender Framework. Also, please see the section on how to configure a Recommender Dialog using megawidgets.

3.1.1 Modifying an existing Recommender     contents    ^  v   

class Recommender(RecommenderTemplate.Recommender)

    def _getWarningThreshold(self):

        return 20 

3.1.2 Setting up a “Modify Call Back” Recommender    contents    ^  v   

Recommenders by design are allowed to set up a “callback” Recommender to be invoked automatically when a Hazard Event is modified. The line of python that follows is an excerpt from the recommender StormTrackTool.py. This designates that the recommender ModifyStormTrackTool.py should be called when events created by StormTrackTool.py are modfied.

                hazardEvent["modifyCallbackToolName"] = "ModifyStormTrackTool"

When the hazard geometry is modified by the user, the designated tool can be called to update the hazard event.

In actuality, the ability to specify in a recommender the name of a “callback” Recommender is broken. The name of the callback Recommender to use is currently hardcoded in the java for the one default recommender that uses a callback Recommender; specifically the Storm Track recommender. The indefinitely deferred Hazard Services ticket
#10414 was written to address this problem.

 

3.1.3 Creating a new Recommender    contents    ^  v   


Please note that as of May 2018, the content of this section is not in a very useful state. It is being left in for now as a place holder.

To create a new Recommender, follow these steps:

        # Get the attributes from the EventSet as a Python dictionary

        sessionAttributes = eventSet.getAttributes()  

        # If an event has been sent into the Recommender, then it can be detected and

        # extracted as follows:

        # Pick up the existing event from what is passed in.

        layerEventId = spatialInputMap.get("eventID")

        events = eventSet.getEvents()

        haveEvent = False

        for event in events :

            if event.getEventID() == layerEventId :

                hazardEvent = event

                haveEvent = True

                break

   

Then the ‘haveEvent’ flag and resulting hazardEvent can be used accordingly.

 

        selectedPointID = None

        try:

            selectedPointID = dialogInputMap.get(SELECTED_POINT_ID)

        except:

            pass        

        if selectedPointID is not None:

              inputMap.put(SELECTED_POINT_ID, selectedPointID)

    def getQPEValues(self):

        '''        

        Strategy method for reading and accumulating data

        from preprocessed FFMP QPE datasets.

        '''

        request = DataAccessLayer.newDataRequest()

        request.setDatatype(GRID_KEY)

        request.setParameters(self._sourceName)

        availableTimes = DataAccessLayer.getAvailableTimes(request)

        # determine correct times

        latestTime = 0

        useTime = availableTimes[0]

        for time in availableTimes :

            tm = time.getRefTime().getTime()

            if tm > latestTime :

                latestTime = tm

           useTime = time

        usedTimes = []

        if latestTime:

            usedTimes.append(useTime)

            grids = DataAccessLayer.getGridData(request, usedTimes)        

            if grids:

                     self.dataGrid = grids[0]

                return True

        return False

Then you can access the dataGrid information as follows:

lonLats = self.dataGrid.getLatLonCoords()

        values = self.dataGrid.getRawData()

from ufpy.dataaccess import DataAccessLayer as DAL

self._site = "MFL"

weatherElement = "Wind"

req = DAL.newDataRequest("gfe", parameters = [weatherElement], siteId = self._site)

gridTimes = DAL.getAvailableTimes(req)

grids = DataAccessLayer.getGridData(req, gridTimes)

firstGrid = grids[0].getRawData()

We have found that attempting to fetch more than 20 grids at a time causes a memory error on the server side. Apparently there's some fixed size buffer limit that gets exceeded when you ask for more than 20 or so.

import GeometryFactory

...

    def getFloodPolygonForRiverPointHazard(self, hazardEvent):

        """

        Returns a user-defined flood hazard polygon for

        a river forecast point flood hazard. The base version

        of this tool does nothing. It is up to the implementer

        to override this method.

       

        @param  hazardEvent: A hazard event corresponding to

                           a river forecast point flood

                           hazard

        """

        id = hazardEvent.get(POINT_ID)

       

        # Always create the point representing the

        # The river forecast point location.

        # Then check to determine if there

        # is an additional inundation map available.

        forecastPointDict = hazardEvent.get(FORECAST_POINT)

        point = forecastPointDict.get(POINT)

        coords = [float(point[0]), float(point[1])]

        pointGeometry = GeometryFactory.createPoint(coords)

        geometryList = [pointGeometry]

               

        if id in self.hazardPolygonDict:

            hazardPolygon = self.hazardPolygonDict[id]

            geometry = GeometryFactory.createPolygon(hazardPolygon)

            geometryList.append(geometry)     

           

        geometryCollection = GeometryFactory.createCollection(geometryList)

        hazardEvent.setGeometry(geometryCollection)

3.2 Hazard Metadata    contents    ^  v   

At the beginning of hazard metadata modules, generally a variable self.hazardEvent is set for the class; this contains a local working copy of the hazard event the metadata is being generated on behalf of. It is very common for metadata logic to inspect the contents of this data structure during the creation of the metadata. However, metadata logic should never change the contents of self.hazardEvent.


Another caution; if one is using the “refreshMetadata” feature to modify an existing megawidget that retains the same "fieldName" attribute, great care must be taken if one is also changing the "fieldType". In general this is not recommended.  More about this in section 3.2.7.

3.2.1 Examples of changes in Calls to Action    contents    ^  ─  +  v   

In this section we will present some customization examples for calls to action. When making modifications to calls to action, it is very important to be mindful of the distinction between the getCTA_Choices() method and the getCTAs() method. getCTA_Choices() is where the list of all calls to action that apply to a hazard is specified. getCTAs() packages those choices as a checkbox menu and controls which are activated by default.  Also be aware that the methods that create the megawidget element for each specific CTA are neither in CommonMetaData.py nor in any of the individual metadata modules; they are all in the module CallsToActionAndImpacts.py.

3.2.1.1 Change the wording of a Call to Action  contents    ^  ─  +  v   

This example is to change the wording of the “Nightime flooding” call to action. First, one needs to find which python module this call to action is defined in. These commands will accomplish this (see Introduction for more information about searching code for functionality):

> cd /awips2/edex/data/utility/common_static/base/HazardServices

> find . -name '*.py' -exec grep -i 'Nighttime flooding' '{}' \; -print

In this case the file identified will be CallsToActionAndImpacts.py; the base version of all call to action methods are defined here. Search the module CallsToActionAndImpacts.py for the string “Nightime flooding”, and you will see that the method involved is ctaNightTime(). To implement the wording change for all hazard types, create an override file for CallsToActionAndImpacts.py with the following (change from base in green):

class CallsToActionAndImpacts(object):

   def ctaNightTime(self):
       return  {"identifier": "nighttimeCTA",
               "displayString": "Nighttime flooding",
               "productString":
               '''Be especially cautious at night when it is harder to recognize the
               dangers of flooding.
Do not stay in areas subject to flooding when
               water begins rising.
'''}

Now suppose there was a desire to change the wording of this call to action for only one hazard type; for sake of argument let’s say convective FFWs. Here is what this override would look like in this case:

class CallsToActionAndImpacts(object):

   def ctaNightTime(self):
       
prodString = '''Be especially cautious at night when it is harder to recognize the
                       dangers of flooding.'''
        import traceback
       tbData = traceback.format_stack()
       i = len(tbData)-1
       while i>=0 :
           onetb = tbData[i]
           i -= 1
           if onetb.find("MetaData")<0 :
               continue
           if onetb.find("FF_W_Convective")>=0 :
               prodString +=  ''' Do not stay in areas subject to flooding when
                                  water begins rising.'''
           break
        return  {"identifier": "nighttimeCTA",
               "displayString": "Nighttime flooding",
               "productString":
prodString }

What is happening here is that the traceback utility is used to determine whether this method is being invoked by the MetaData_FF_W_Convective.py metadata class, and updating the productString accordingly if this is the case.

3.2.1.2 Change list of Calls to Action for a Hazard    contents    ^    +  v   

This customization example will be to change the the default call to action choices for a particular hazard type. Specifically, we will add the "Turn around...don't drown" call to action to the list available for the Areal Flood Watch. Use commands similar to those in the previous example to learn that the default version of this call to action method is implemented CallsToActionAndImpacts.py; inspect CallsToActionAndImpacts.py to learn that the method name is ctaTurnAround(). The specific metadata method for Areal Flood Watch is MetaData_FA_A.py. To make this change, you need to provide an override for the getCTA_Choices() method of MetaData_FA_A.py as follows (change from base in green):

class MetaData(CommonMetaData.MetaData):

   def getCTA_Choices(self):
       return [
           self.ctaImpact.ctaSafety(),
           self.ctaImpact.ctaStayAway(),
           
self.ctaImpact.ctaTurnAround(),
            self.ctaImpact.ctaFloodWatchMeans()
           ]

3.2.1.3 Change Calls To Action activated by default    contents    ^    +  v   

This customization example will be to make the "Stay away" call to action be activated by default for an Areal Flood Watch. First, one needs to locate the call to action method in CallsToActionAndImpacts.py (ctaStayAway) and note the value of the identifier specified in that method, which is "stayAwayCTA". Then we need to create an override for the getCTAs() method of MetaData_FA_A.py. Note that the default instance of the getCTAs() method for MetaData_FA_A.py is actually in CommonMetaData.py. The content of this override follows (change to base instance in green):

class MetaData(CommonMetaData.MetaData):

   def getCTAs(self, values=None):
       pageFields = {
            "fieldType":"CheckBoxes",
            "fieldName": "cta",
            "showAllNoneButtons" : False,
            "choices": self.getCTA_Choices()
        }

       if values is not None:
           pageFields["values"] = values
       
else :
           pageFields['values'] = [ "stayAwayCTA" ]

       return {
               
              "fieldType": "ExpandBar",
              "fieldName": "CTABar",
              "expandHorizontally": True,
              "pages": [
                           {
                            "pageName": "Calls to Action",
                            "pageFields": [pageFields]
                           }
                        ],
               "expandedPages": ["Calls to Action"]
               }

Of course, if there were already a site version of MetaData_FA_A.py, the instance of getCTAs() shown would be added to that. The only code added to this is the code in green, which makes the CTA with the identifier of "stayAwayCTA" the one that is turned on by default for a new hazard. The two lines of code immediately preceding that allows an existing set of activated CTAs selected by the forecaster to be propagated to the next product in the life cycle.

3.2.1.4  Remove an existing default Call To Action  contents    ^    +  v   

In this example we remove any calls to action referring to arroyos from those available for convective Flash Flood warnings. For this example this will be approached differently from other examples where the set of available calls to action were changed; we will show how to implement this by overriding getCTAs(). Note that the base version we are overriding here is originally from CommonMetaData.py.

What follows is new site version of MetaData_FF_W_Convective.py for this purpose. The code added or changed is in green as before. The point here is not to necessarily recommend this as a superior approach to implementing this in getCTA_Choices(); rather to show that this is possible and to point out an advantage of this approach. The main advantage is that any future update of the getCTA_Choices() method in the base MetaData_FF_W_Convective.py will be far less likely to require a manual merge to both pull in any new CTAs but still filter out arroyo related CTAs. Note again that the default instance of getCTAs() for this hazard type is actually in CommonMetaData.py.

class MetaData(CommonMetaData.MetaData):

    def getCTAs(self, values=None):

        defChoices =  self.getCTA_Choices()

        myChoices = [ ]

        for defChoice in defChoices :

            if defChoice.has_key("identifier") and \

               defChoice["identifier"].lower().find("arroyos")<0 :

                myChoices.append(defChoice)

        pageFields = {

             "fieldType":"CheckBoxes",

             "fieldName": "cta",

             "showAllNoneButtons" : False,

             "choices": myChoices

         }

        if values is not None:

            pageFields["values"] = values

        return {

                 

               "fieldType": "ExpandBar",

               "fieldName": "CTABar",

               "expandHorizontally": True,

               "pages": [

                            {

                             "pageName": "Calls to Action",

                             "pageFields": [pageFields]

                            }

                         ],

                "expandedPages": ["Calls to Action"]

                }

3.2.1.5 Add a new Call To Action  contents    ^    +  v   

In this final customization example related to calls to action, we show how to add a new call to action. First we need to add a new method for the content of the call to action. These methods always go into an override of CallsToActionAndImpacts.py:

class CallsToActionAndImpacts(object):

   def ctaDrivingInstruction(self):
       return {"identifier": "drivingCTA",
               "displayString": "Driving instructions",
               "productString":
               '''Do not drive your vehicle into areas where the water covers the
               roadway. The water depth may be too great to allow your car to
               cross safely. Move to higher ground. Motorists should not attempt
               to drive around barricades or drive cars through flooded areas.''' }

To complete this, the new call to action content method needs to be added to the getCTA_Choices() method in the metadata file for the desired hazard type(s). Here is what such an override would look like added in MetaData_FA_A.py:

class MetaData(CommonMetaData.MetaData):

   def getCTA_Choices(self):
       return [
           self.ctaSafety(),
           self.ctaStayAway(),
           
self.ctaDrivingInstruction(),
           self.ctaFloodWatchMeans()
           ]

3.2.2 Changing default Hydrologic Cause     contents    ^    +  v   

The purpose of this customization example is to change the default hydrologic cause for non-convective flash flood warnings to be “Ice Jam” in winter and “Snow Melt” otherwise. The file being overridden here is MetaData_FF_W_NonConvective.py (changes to base in green).

class MetaData(CommonMetaData.MetaData):

    def execute(self, hazardEvent=None, metaDict=None):

        self.initialize(hazardEvent, metaDict)

        # Adapt value of self.HYDROLOGIC_CAUSE_DEFAULT to day of year

        creationTime = self.hazardEvent.getCreationTime()

        date0 = creationTime.date()

        month = date0.month

        day = date0.day

        if month in [1, 2] or (month == 12 and day >= 22) or (month == 3 and day < 22):

            self.HYDROLOGIC_CAUSE_DEFAULT = 'icejam'

        else:

            self.HYDROLOGIC_CAUSE_DEFAULT = 'snowMelt'

        self.leveeCause = self.hydrologicCauseLevee()["identifier"]

        self.floodgateCause = self.hydrologicCauseFloodGate()["identifier"]

        self.icejamCause = self.hydrologicCauseIceJam()["identifier"]

        self.ijbreakCause = self.hydrologicCauseIceJamBreak()["identifier"]

        self.glacierCause = self.hydrologicCauseGlacialOutburst()["identifier"]

        self.addDamList = [

              self.hydrologicCauseDam()["identifier"],

              self.hydrologicCauseSiteImminent()["identifier"],

              self.hydrologicCauseSiteFailed()["identifier"],

              self.leveeCause,

              self.floodgateCause

              ]

        self.addVolcanoList = [

                  self.hydrologicCauseVolcano()["identifier"],

                  self.hydrologicCauseVolcanoLahar()["identifier"]

                  ]

        self.featurelessCauses = [

                  self.hydrologicCauseRain()["identifier"],

                  self.hydrologicCauseSnowMelt()["identifier"]

                  ]

        self.hydrologicCause = None

        self.damOrLeveeName = None

        self.hazardLocation = ""

        self.damMetadata = None

        self.riverName = None

        self.recommendedEvent = False

        if hazardEvent is not None:

            self.recommendedEvent = hazardEvent.get('recommendedEvent', False)

            # The dictionary default mirrors the default in getHydrologicCause()

            self.hydrologicCause = hazardEvent.get("hydrologicCause", self.HYDROLOGIC_CAUSE_DEFAULT)

            self.damOrLeveeName = hazardEvent.get('damOrLeveeName')

            if isinstance(self.damOrLeveeName, str) :

                if self.damOrLeveeName.find("|")<0 and self.damOrLeveeName.find("*")<0 :

                    self.hazardLocation = self.damOrLeveeName

                if self.hazardLocation==self.damOrLeveeName or \

                   self.hydrologicCause in self.addDamList :

                    from MapsDatabaseAccessor import MapsDatabaseAccessor

                    mapsAccessor = MapsDatabaseAccessor()

                    self.damMetadata = \

                      mapsAccessor.getDamInundationMetadata(self.damOrLeveeName, True)

            if isinstance(self.damMetadata, dict) :

                self.riverName = self.damMetadata.get("riverName")

                metaHydroCause = self.damMetadata.get("hydrologicCause")

                if metaHydroCause :

                    self.hydrologicCause = metaHydroCause

                altLoc = self.damMetadata.get("breachLocation")

                if altLoc :

                    self.hazardLocation = altLoc

                altLoc = self.damMetadata.get("iceJamLocation")

                if altLoc :

                    self.hazardLocation = altLoc

                altLoc = self.damMetadata.get("glacierLocation")

                if altLoc :

                    self.hazardLocation = altLoc

                altLoc = self.damMetadata.get("volcanoName")

                if altLoc :

                    self.hazardLocation = altLoc

        metaData = self.buildMetaDataList()

        metaData += self.getDoesNotContributeToLockingBolding(False)

        return {

                HazardConstants.METADATA_KEY: metaData

                }

3.2.3 Adapt the metadata to the VTEC life cycle stage     contents    ^  v   

Note that as of July 2019, this is being reworked.  The new logic should make this all much less ambiguous.

At times there might be a desire to have the content of the metadata adapt to the stage in the VTEC life cycle (e.g. NEW, CON, etc.) the product being issued will have. As Hazard Services currently functions, it is not possible to do this directly; one has to deduce the VTEC life cycle stage the product will have from the current hazard status. Depending on which stages one cares about, this is often straightforward, but at times can be somewhat ambiguous.

Current Hazard Status

Impending Product VTEC stage(s)

pending

NEW

issued

CON,EXT

ending

CAN,EXP

ended

CAN,EXP

elapsing

EXP

Were it important to make the distinction between CON and EXT (continue and extension in time), one could in theory compare the end time of the hazard to the end time it previously had to resolve this ambiguity. However, when transitioning from issuing a CON to issuing an EXT, the metadata will never be reexecuted and so there will never be an opportunity to actually change the metadata in response to this.

There is some ability to make a distinction between CAN and EXP (cancellation and expiration). Were it important to make the distinction between CAN and EXP, one could compare the end time of the hazard to the current time. For short fused hazards like FFW, if one is more than 10 minutes from the end of the hazard, it is unambiguous that one was issuing a CAN; if less than 5 minutes it would be unambiguous that one was issuing an EXP.  However, in between there would be no way to resolve the ambiguity.

Issuing a correction adds yet another wrinkle to this.  One cannot distinguish between a correction to a NEW and a typical update to an existing hazard resulting in a CON.

Here we show a customization example where the required data structures are examined in order to switch to a code branch for the proper stage in the life cycle. Here we override the execute() method in MetaData_FF_W_Convective.py, and it is true that for most hazard types this would be the method to override for this type of customization. However, be aware that, for example, in MetaData_FF_W_NonConvective.py the buildMetaDataList() method would be the one to override for this purpose. So we point out here that one needs to be on the lookout for different approaches in the metadata for different hazard types. For this example, no actual new code differences are imposed in the new code branches. As described, keep in mind that in actuality one can never distinguish between a CON and an EXT, and that the ability to distinguish between CAN and EXT reliably is limited.

It is hoped that there may be some utility in showing how to access the data structures in question; some users may find other ways to leverage these data structures to do useful things.

from com.raytheon.uf.common.time import SimulatedTime
import datetime
import HazardDataAccess

class MetaData(CommonMetaData.MetaData):

   def execute(self, hazardEvent=None, metaDict=None):
       self.initialize(hazardEvent, metaDict)
        try :
           prevEvent = HazardDataAccess.getHazardEvent( \
                         hazardEvent.getEventID() , hazardEvent.get('practice', False))
           isExt = hazardEvent.getEndTime()!=prevEvent.getEndTime()
       except :
           isExt = False
       try :
           dtnow = datetime.datetime.fromtimestamp(SimulatedTime.getSystemTime().getMillis()/1000)
           deltaEnd = (self.hazardEvent.getEndTime()-dtnow).total_seconds()
       except :
           deltaEnd = 900

       if self.hazardStatus in ["ending", "ended", "elapsing"]:
           
if deltaEnd>=600 :
               # added code branch for CAN
               metaData = [
                           self.getEndingOption(),
                           ]
           elif deltaEnd<=300 :
               # added code branch for EXP
               metaData = [
                           self.getEndingOption(),
                           ]
           else :
               # existing code branch for CAN/EXP

               metaData = [
                           self.getEndingOption(),
                           ]
       
elif self.hazardStatus=="pending" :
           # added code branch for NEW
           self.ctaImpact = CallsToActionAndImpacts()
           metaData = [
                   self.getIBW_Type(),
                   self.getImmediateCause(),
                   self.getSource(),
                   self.getEventType(),
                   self.getFlashFloodOccurring(),
                   self.getFloodLocation(),
                   self.getRainAmt(),
                   self.getRainRate(),
                   self.getLocationsAffected(),
                   self.getAdditionalLocations(),
                   self.getAdditionalInfo(notation="comments"),
                   self.getCTAs(),
                       ]            
       elif isExt :
           # added code branch for EXT
           self.ctaImpact = CallsToActionAndImpacts()
           metaData = [
                   self.getIBW_Type(),
                   self.getImmediateCause(),
                   self.getSource(),
                   self.getEventType(),
                   self.getFlashFloodOccurring(),
                   self.getFloodLocation(),
                   self.getRainAmt(),
                   self.getRainRate(),
                   self.getLocationsAffected(),
                   self.getAdditionalLocations(),
                   self.getAdditionalInfo(notation="comments"),
                   self.getCTAs(),
                       ]
       else: # existing code branch for CON

           self.ctaImpact = CallsToActionAndImpacts()
           metaData = [
                   self.getIBW_Type(),
                   self.getImmediateCause(),
                   self.getSource(),
                   self.getEventType(),
                   self.getFlashFloodOccurring(),
                   self.getFloodLocation(),
                   self.getRainAmt(),
                   self.getRainRate(),
                   self.getLocationsAffected(),
                   self.getAdditionalLocations(),
                   self.getAdditionalInfo(notation="comments"),
                   self.getCTAs(),
                       ]

       metaData += self.getDoesNotContributeToLockingBolding(False)
       return {HazardConstants.METADATA_KEY: metaData}

3.2.4 Modifying the Product Staging Dialog     contents    ^  v   

The Product Staging Dialog is a dialog that sometimes comes up after choosing Preview but before the Product Editor appears. The purpose of this dialog is to allow the user to make choices that are for the product as a whole rather than for one particular hazard; this only occurs for hazard types where a single product can possibly contain text for multiple specific hazards.

Files that define metadata for a hazard type have the hazard
phensig for the hazard type encoded into the metadata file name (e.g. MetaData_FA_W.py). Files that define metadata for a Product Staging Dialog have the NNN of the product(s) to generate encoded into the metadata file name (e.g. MetaData_FFA_FLW_FLS.py).


Here we have a customization example where all the synopsis options that refer to snow melt are commented out; one might do this if one was at a WFO in a place like Southern Florida. We also make it so the category increase synopsis results in a first guess for the language describing the rivers involved. This is done by overriding the methods execute(), synopsisCategoryIncrease(), and getSynopsisChoices() in MetaData_FFA_FLW_FLS.py (changes from base in
green).

class MetaData(CommonMetaData.MetaData):

   def execute(self, hazardEvents=None, metaDict=None):
       self.hazardEvents = hazardEvents
       productSegmentGroup = metaDict.get('productSegmentGroup')
       self.productCategory = metaDict.get('productCategory')
       self.stagingDialogValues = metaDict.get("stagingDialogValues")

       self.productLabel = productSegmentGroup.get('productLabel')
       self.prevProductLabel = self.hazardEvents[0].get('productLabel', None)
       suffix = "_" + self.productLabel
       prevSuffix = None
       if self.prevProductLabel:
           prevSuffix = "_" + self.prevProductLabel

       geoType = productSegmentGroup.get('geoType')
       productID = productSegmentGroup.get('productID')
       self.eventIDs = productSegmentGroup.get('eventIDs')

       
self.streamNames = set()
        self.immediateCauses = set()
       self.allCAN = True
       if self.hazardEvents:
           for hazardEvent in self.hazardEvents:
               
self.streamNames.add(hazardEvent.get('streamName', ''))
                self.immediateCauses.add(hazardEvent.get('immediateCause', 'ER'))
               if hazardEvent.getStatus() not in ['ENDING', 'ENDED']:
                   self.allCAN = False
                   break

       # Product level CTA's are only for the point-based hazards
       ctas = {}
       if geoType == 'point':
           if not self.allCAN:
               previousValues = set()
               for hazard in self.hazardEvents:
                   tempVals = self.getPreviousStagingValue(hazard, "callsToAction_productLevel")
                   if tempVals:
                       previousValues.update(tempVals)
               ctas = self.getCTAs(previousValues)
       else:
           # There is no overviewSynopsis product part if the segment has only CAN, EXP in it
           if hazardEvent.getStatus() in ['ELAPSED', 'ENDING', 'ENDED']:
               return {
                   HazardConstants.METADATA_KEY:[]
               }

       metaData = []

       # Only add the overview if there is canned choices
       choices = self.getSynopsisChoices()
       if choices:
           # Determine if there is a previous value to use
           previousValues = set()
           for hazard in self.hazardEvents:
               previousValue = self.getPreviousStagingValue(hazard, "overviewSynopsisCanned")
               if previousValue:
                   previousValues.add(previousValue)

           prevValue = None
           if previousValues:
               prevValue = list(previousValues)[0]
           overview = {
                   "fieldType": "ComboBox",
                   "fieldName": "overviewSynopsisCanned" + suffix,
                   "label": "Overview Synopsis for " + self.productLabel,
                   "choices": choices,
                   "values" : prevValue,
                   }
           metaData.append(overview)

       if ctas:
           metaData.append(ctas)
       return {
           HazardConstants.METADATA_KEY: metaData
           }

   def synopsisCategoryIncrease(self):
       
riverDescription = None
       for streamName in self.streamNames :
           if streamName=='' :
               continue
           if riverDescription==None :
               riverDescription = streamName+'.'
           elif riverDescription.find(' and ')>0 :
               riverDescription = streamName+', '+riverDescription
           else :
               riverDescription = streamName+' and '+riverDescription
       if riverDescription==None :
           riverDescription = "|* riverName *|."
       if self.productLabel.find('FFA')>=0:
           productString = "Heavy rainfall may increase the severity of flooding on the "
       else:
           productString = "Heavy rainfall will increase the severity of flooding on the "
       productString += riverDescription

       return {"identifier":"categoryIncrease",
               "displayString":"Increase in Category",
               "productString": productString,
       }

   def getSynopsisChoices(self):
       # No options make sense for CANs
       if self.allCAN:
           return []
       choices = [
                   self.synopsisBlank(),
                   
#self.synopsisTempSnowMelt(),
                   
#self.synopsisDayNightTemps(),
                   
#self.synopsisSnowMeltReservoir(),
                   
#self.synopsisRainOnSnow(),
                   
#self.synopsisIceJam(),
                   self.synopsisCategoryIncrease()
               ]

       identifiers = set()
       for immediateCause in self.immediateCauses:
           if immediateCause == 'SM':
               
#identifiers.add(self.synopsisTempSnowMelt().get('identifier'))
               
#identifiers.add(self.synopsisDayNightTemps().get('identifier'))
               
#identifiers.add(self.synopsisSnowMeltReservoir().get('identifier'))
               identifiers.add(self.synopsisCategoryIncrease().get('identifier'))
           
#elif immediateCause == 'RS':
               #identifiers.add(self.synopsisRainOnSnow().get('identifier'))
           elif immediateCause == 'IC':
               
#identifiers.add(self.synopsisTempSnowMelt().get('identifier'))
               
#identifiers.add(self.synopsisDayNightTemps().get('identifier'))
               
#identifiers.add(self.synopsisSnowMeltReservoir().get('identifier'))
               
#identifiers.add(self.synopsisRainOnSnow().get('identifier'))
               
#identifiers.add(self.synopsisIceJam().get('identifier'))
               identifiers.add(self.synopsisCategoryIncrease().get('identifier'))
         
 #elif immediateCause == 'IJ':
               
#identifiers.add(self.synopsisIceJam().get('identifier'))
           elif immediateCause == 'MC':
               
#identifiers.add(self.synopsisTempSnowMelt().get('identifier'))
               
#identifiers.add(self.synopsisDayNightTemps().get('identifier'))
               
#identifiers.add(self.synopsisSnowMeltReservoir().get('identifier'))
               
#identifiers.add(self.synopsisRainOnSnow().get('identifier'))
               
#identifiers.add(self.synopsisIceJam().get('identifier'))
               identifiers.add(self.synopsisCategoryIncrease().get('identifier'))
           elif immediateCause == 'UU':
               
#identifiers.add(self.synopsisTempSnowMelt().get('identifier'))
               
#identifiers.add(self.synopsisDayNightTemps().get('identifier'))
             
 #identifiers.add(self.synopsisSnowMeltReservoir().get('identifier'))
               
#identifiers.add(self.synopsisRainOnSnow().get('identifier'))
               
#identifiers.add(self.synopsisIceJam().get('identifier'))
               identifiers.add(self.synopsisCategoryIncrease().get('identifier'))
           elif immediateCause == 'DR':
               
#identifiers.add(self.synopsisSnowMeltReservoir().get('identifier'))

       availableChoices = []
       for identifier in identifiers:
           for choice in choices:
               if choice.get('identifier') == identifier:
                   availableChoices.append(choice)
                   break

       if availableChoices:
           # Give a option for no canned text.
           availableChoices.insert(0, self.synopsisBlank())
       return availableChoices

3.2.5 Change default search parameters for impacts section of flood warning products    contents    ^  v   
        
Here we describe how to change the default search parameters for the impacts sections of point flood warning products. First generate any point flood warning recommendation. Click on a recommendation in the Hazard Services console to pop the Hazard Information Dialog. Choose the Impacts Statement tab and then click on Impacts Search Parameters to expand that dialog. Now we want to pick something likely to be somewhat unique from that dialog to search for; for the purpose of this exercise we will pick 'Maximum Depth Below Flood Stage'. So now use the following commands to learn where this dialog is being constructed (see Introduction for more information about searching code for functionality):

> cd /scratch/albatross/awips2/edex/data/utility/common_static/base
> touch /tmp/bbb
> find . -name '*py' -exec grep 'Maximum Depth Below' '{}' /tmp/bbb \;

This tells us that HazardServices/hazardMetaData/CommonMetaData.py is where this dialog is constructed. Inspecting CommonMetaData.py, one learns that the method which constructs this dialog is getStageWindow(). So to change the default search parameters one needs to create a site override of this method. This could be in a site version of CommonMetaData.py, which would impact all hazard types using this dialog, or it could be in MetaData_FL_W.py, which would impact only this particular hazard type. For this exercise, we will implement this in MetaData_FL_W.py. What follows is the user override of MetaData_FL_W.py that implements this (changes from base in
green; note that very little is a copy of the base).

import traceback

class MetaData(CommonMetaData.MetaData):

    # General method for recursively traversing a megawidget structure and
   # updating the default value for the field name containing a substring
   def updateDefaultValues(self, inmeta, fieldNameSub, newvalue, depth=0) :
       if not isinstance(inmeta, dict) :
           return inmeta
       if "values" in inmeta :
           fn = inmeta.get("fieldName")
           if not isinstance(fn, str) :
               return inmeta
           if fn.find(fieldNameSub)>=0 :
               inmeta["values"] = newvalue
           return inmeta
       fldList = inmeta.get("fields")
       if not isinstance(fldList, list) :
           return inmeta
       n = len(fldList)
       i = 0
       depth += 1
       while i<n :
           fldList[i] = self.updateDefaultValues(\
                             fldList[i], fieldNameSub, newvalue, depth)
           i += 1
       return inmeta

    def getStageWindow(self,parm,low=-4,hi=4):

       # Call the base class method to get initial default version of
       # metadata; if not for the impacts we just pass that back.
       basemeta = super(MetaData ,self).getStageWindow(parm, low, hi)
       if parm != 'impacts' :
           return basemeta

       # Set traceNow = True to diagnose the behavior of this routine
       traceNow = True
       if traceNow :
           hhhhh = open("/tmp/getStageWindow.txt", "w")
           hhhhh.write("from MetaData_FL_W.py\n")
           hhhhh.write("parm '"+str(parm)+"'\n")
           hhhhh.write("low '"+str(low)+"'\n")
           hhhhh.write("hi '"+str(hi)+"'\n")
           hhhhh.write(json.dumps(basemeta, indent=4)+"\n")
           hhhhh.write("About to call updateDefaultValues()\n")

       # Change the default for Maximum Depth Below Flood Stage
       try:
           basemeta = self.updateDefaultValues(basemeta, "maxDepthBelow", -4)
           if traceNow :
               hhhhh.write("Updated metadata:\n")
               hhhhh.write(json.dumps(basemeta, indent=4)+"\n")
       except:
           if traceNow :
               hhhhh.write(traceback.format_exc()+"\n")
           else :
               traceback.print_exc()
       if traceNow :
           hhhhh.close()

       return basemeta


There are several things going on here that are unique in comparison to other customization solutions shown in this document. Instead of completely replacing the existing method for obtaining the metadata, we directly call the method in the base class CommonMetaData to get an initial version of the metadata, and then modify that resulting metadata. This has the advantage that if a new base version of CommonMetaData::getStageWindow() appears in a future release, there is a very good chance that no manual merge will be required. We include in the method a simple way for someone to optionally track the behavior of this new version of getStageWindow() in a temporary file. This is set up so that if there is a runtime error in the method updateDefaultValues(), the traceback will also end up in that temporary file. This can be helpful because it then becomes possible for someone not working in eclipse to nonetheless debug the behavior of updateDefaultValues() method. Even if one is working in eclipse, it can sometime be very difficult to identify one traceback of interest from the 'firehose' of information that can appear in the eclipse console.

3.2.6 Change precision for additional rainfall selectors.    contents    ^  v   

In the Hazard Information Dialog for convective Flash Flood Warnings, there are selectors for specifying additional rain that is expected to fall. The current default behavior of those selectors is for them to only allow rainfall to even whole inches. This customization example explains how to change the precision allowed for these selectors. First it is necessary to identify the module where the default behavior for this is invoked. We use the labeling string for those selectors and search the metadata modules using these commands:

> cd /awips2/edex/data/utility/common_static/base/HazardServices/
> touch /tmp/bbb
> find . -name '*Meta*py' -exec grep -i 'additional rain' '{}' /tmp/bbb \;

This tells us that the default megawidget specification for this menu is in CommonMetaData.py. Inspecting the base version of CommonMetaData.py tells us that the method that puts together this megawidget specification is called additionalRain(). We could override this method for CommonMetaData.py, which would change this behavior for all product types. For this example we choose to implement an override for additionalRain() in MetaData_FF_W_Convective.py, so that the change only applies to that hazard type. What follows is the override for MetaData_FF_W_Convective.py, with changes in
green:

class MetaData(CommonMetaData.MetaData):

   def additionalRain(self):
       return  {"identifier":"addtlRain",
                "displayString": "Additional rainfall",
                "productString":
                   '''Additional rainfall amounts of |* additionalRainLowerBound *| to |* additionalRainUpperBound *| inches are possible in the
                   warned area.''',
                "detailFields": [
                       {
                            "fieldType": "FractionSpinner",
                            "fieldName": "additionalRainLowerBound",
                            "label": "of",
                            "sendEveryChange": False,
                            "minValue": 0,
                            "maxValue": 99,
                            "values": 0,
                            "incrementDelta":
0.25,
                            "precision":
2
                       },
                       {
                            "fieldType": "FractionSpinner",
                            "fieldName": "additionalRainUpperBound",
                            "label": " to",
                            "sendEveryChange": False,
                            "minValue": 0,
                            "maxValue": 99,
                            "values": 0,
                            "incrementDelta":
0.25,
                            "precision":
2
                       },
                       {
                            "fieldType": "Label",
                            "fieldName": "additionalRainSuffix",
                            "values": "inches is expected"
                       }
                      ]
                     }

With this override, we specify 2 digit precision, which means it is possible to specify rainfall values to the nearest 0.01 inches. This degree of precision, which is unrealistic for any forecast rainfall amount, is nonetheless required to enable the incrementDelta value of a quarter inch. One could make the argument that it makes no sense to increase the precision of the additional rainfall specifier without also doing the same for the ‘rain so far’ specifier; this is left as an exercise for the user.

3.2.7 Interdependency scripts.    contents    ^  ─  + v   

Interdependency scripts are a means by which a choice made by a user in the Hazard Information Dialog can result in automatically changing the state of other elements in the Hazard Information Dialog. While interdependency scripts are the most efficient way to accomplish this, they are not the only way. One can also define the "refreshMetadata" key to be True for a megawidget element, and this will result in the execute() method for the metadata to be rerun when that element is changed. (To see an example of invoking this refresh logic, examine the getHydrologicCause() method of MetaData_FF_W_NonConvective.py.) Also note that while interdependency scripts can result in changing the state of other elements, invoking the refresh logic is the only way for one choice to result in adding, removing, or changing the label of some other element.  Also, one does not have access to the hazard event inside an interdependency script.

Note that the method applyInterdependencies() is a static method of metadata modules. This particular static module method is overridable, but only because the logic that executes the metadata python is hardwired to do so. Therefore, be advised that it is not possible to introduce any additional static module methods in any of the metadata modules through override. Also be aware that many instances of applyInterdependencies() start with something similar to the following:

def applyInterdependencies(triggerIdentifiers, mutableProperties):

    propertyChanges = CommonMetaData.applyInterdependencies(triggerIdentifiers, mutableProperties)

The exact name of the module from CommonMetaData may be different than applyInterdependencies.  The thing to keep in mind is if you have a similar construct in an override MetaData module, then at times one must put import CommonMetaData at the top of the override file.

Under the next two headings are examples of using an interdependency script. There is more information about interdependency scripts in the megawidget document.

3.2.7.1 Workaround for problems with floating point spinners.  contents    ^  ─  + v   

One common feature of the metadata for areal flood hazards is the use of floating point spinners to enter observed or expected rainfall amounts. Ideally these spinners would also allow the user to type in a number, but that feature has never worked well.  This customization example leverages an interdependency script to pair floating point spinners with a text entry field to at least obtain the desired functionality. Here we apply this to the Rain so far: entries for the convective FFW; it is left as an exercise for the reader to apply this to other situations if desired. Note that the extra 30 spaces added to the "inches of rain have fallen" label is to avoid annoying extra whitespace after the Text widgets.


MetaData_FF_W_Convective.py:

class MetaData(CommonMetaData.MetaData):

   def enterAmount(self):
       return  {"identifier":"rainKnown", "displayString":"Between",
                "detailFields": [
                   {
                       "fieldType": "FractionSpinner",
                       "fieldName": "rainSoFarLowerBound",
                       "sendEveryChange": False,
                       "minValue": 0,
                       "maxValue": 99,
                       "values": 0,
                       "incrementDelta": 1,
                       "precision": 1
                   },
                    {
                       "fieldType": "Text",
                       "fieldName": "rainSoFarLowerBoundTextMirror",
                       "maxChars": 5,
                       "label": "<>",
                       "values": "0"
                   },
                    {
                       "fieldType": "FractionSpinner",
                       "fieldName": "rainSoFarUpperBound",
                       "label": " and",
                       "sendEveryChange": False,
                       "minValue": 0,
                       "maxValue": 99,
                       "values": 0,
                       "incrementDelta": 1,
                       "precision": 1
                   },
                    {
                       "fieldType": "Text",
                       "fieldName": "rainSoFarUpperBoundTextMirror",
                       "maxChars": 5,
                       "label": "<>",
                       "values": "0"
                   },
                    {
                       "fieldType": "Label",
                       "fieldName": "rainAmtSuffix",
                       "values": "inches of rain have fallen"
+(" "*30)
                   }
                ]
               }

def applyInterdependencies(triggerIdentifiers, mutableProperties):
   propertyChanges = CommonMetaData.applyInterdependencies(triggerIdentifiers, mutableProperties)

    # Only invocations with a single trigger indicate HID edits.
   if not triggerIdentifiers :
       return propertyChanges
   if len(triggerIdentifiers)!=1:
       return propertyChanges
   triggerId = str(triggerIdentifiers[0])

   if not triggerId in ["rainSoFarLowerBound", "rainSoFarLowerBoundTextMirror",
                        "rainSoFarUpperBound", "rainSoFarUpperBoundTextMirror", ] :
       return propertyChanges
   propertyDict = mutableProperties.get(triggerId)
   if not propertyDict :
       return propertyChanges
   dictValue = propertyDict.get("values")
   if not dictValue :
       return propertyChanges
   dictValue = str(dictValue)

   if triggerId.find("TextMirror")>0 :
       try :
           floatval = float(dictValue)
       except :
           return propertyChanges
       propertyChanges = {}
       propertyChanges[triggerId.replace("TextMirror","")] = { "values" : floatval }
   else :
       propertyChanges = {}
       propertyChanges[triggerId+"TextMirror"] = { "values" : dictValue }
    return propertyChanges


3.2.7.2 Changing other default state based on the Source selection.
           
contents    ^    + v   

This example focuses on the state of a checkbox labeled Flash Flooding occurring that appears in the Hazard Information Dialog for convective Flash Flood warnings. Here the goal is to change its default value depending on which value is selected for the set of radio buttons under Source:. If the user actually changes the state of the Flash Flooding occurring checkbox from the Hazard Information Dialog, thereafter the user’s choice for the checkbox will be honored and this defaulting logic will be disabled.

The override that follows for MetaData_FF_W_Convective.py contains an interdependency script that invokes this behavior:

import copy

def applyInterdependencies(triggerIdentifiers, mutableProperties):
   propertyChanges = None

   # Make sure we have access to the mutable info for source and flashFlood.
   # source is our basis reporting source, flashFlood is for whether flash
   # flooding is occurring immediately.
   try:
       flashFloodMutable = mutableProperties['flashFlood']
       sourceMutable = mutableProperties['source']
   except:
       return propertyChanges

   # If the flashFlood selector has already been changed by user, leave it alone.
   try :
       flashFloodSelected = flashFloodMutable['extraData']['chosen']
   except :
       flashFloodSelected = False
   if flashFloodSelected :
       return propertyChanges

   # If the flashFlood selector was just changed by the user, mark it as such.
   if triggerIdentifiers :
       if 'flashFlood' in triggerIdentifiers and len(triggerIdentifiers)==1 :
           flashFloodChanges = copy.deepcopy(flashFloodMutable)
           flashFloodChanges['extraData'] = { 'chosen' : True }
           propertyChanges = { 'flashFlood' : flashFloodChanges }
           return propertyChanges

   # If basis reporting source selector was NOT just changed by user, then
   # nothing to do.
   if not triggerIdentifiers :
       return propertyChanges
   if len(triggerIdentifiers)!=1 or not 'source' in triggerIdentifiers :
       return propertyChanges

   # Gather up current state of these selectors.
   try :
       sourceValue = sourceMutable['values']
       flashFloodValue = flashFloodMutable['values']
   except :
       return propertyChanges

   # If these states are already in sync, then nothing to do.
   desiredValue = sourceValue in [ "trainedSpottersSource", "publicSource", \
                                   "localLawEnforcementSource", "emergencyManagementSource" ]
   if desiredValue == flashFloodValue :
       return propertyChanges

   # Forcibly update state of flash flood occuring selector based on state of
   # the basis reporting source
   flashFloodChanges = copy.deepcopy(flashFloodMutable)
   flashFloodChanges['values'] = desiredValue
   propertyChanges = { 'flashFlood' : flashFloodChanges }
   return propertyChanges

3.2.7.3 Sharing methods between MetaData class and interdependency script.
           
contents    ^    + v   

There may be times when, in order to avoid duplicating code, it would be desirable to share methods between the code in a MetaData class and the code in an interdependency script. This is a bit tricky, because many of the typical classes used for broadly shared methods (such as TextProductCommon.py) are not importable by the MetaData modules. There is one medium weight class with no constructor overhead that is importable by MetaData modules, and that is CallsToActionAndImpacts.py.  Therefore, for now, this is probably as good a place as any to put modules shared between MetaData class code and interdependency script code.  At some point in the future we may try to formalize this.

Here will be one of the rare instances where we will demonstrate a point with an override that does nothing that is actually useful; this is just an example of how to structure the code such that a MetaData class and an interdependency script can share a method.  This override example will function, but will only output the file /tmp/testSharedMethod.txt to demonstrate the method sharing; it changes no actual behavior for the user.

CallsToActionAndImpacts.py:

import traceback

import time

import datetime

class CallsToActionAndImpacts(object):

    def testSharedMethod(self):

        stamp = str(datetime.datetime.fromtimestamp(time.time())).split(' ')[1][:11]

        ffff = open("/tmp/testSharedMethod.txt", "a")

        ffff.write(stamp+"\n")

        tbData = traceback.format_stack()

        for onetb in tbData[:-1] :

            ffff.write(onetb)

        ffff.write("\n")

        ffff.close()

        return None

MetaData_FA_Y.py:

import CommonMetaData

import HazardConstants

from CallsToActionAndImpacts import CallsToActionAndImpacts

class MetaData(CommonMetaData.MetaData):

    def execute(self, hazardEvent=None, metaDict=None):

        self.initialize(hazardEvent, metaDict)

        if self.hazardStatus in ["ending", "ended", "elapsing", "elapsed"]:

            metaData = [

                        self.getEndingOption(),

                        ]

        else: # issued/pending

            source = self.getSource()

            eventType = self.getEventType()

            self.ctaImpact = CallsToActionAndImpacts()

            self.ctaImpact.testSharedMethod()

            metaData = [

                    self.getImmediateCause(),

                    source,

                    eventType,

                    self.getAdvisoryType(),

                    self.getOptionalSpecificType(),

                    self.getMinorFloodOccurring(),

                    self.getRainAmt(),

                    self.getLocationsAffected(),

                    self.lidImpactsList(),

                    self.getAdditionalInfo(notation="comments"),

                    self.getCTAs(self.hazardEvent.get("cta")),

                ]

            if hazardEvent:

                immediateCause = hazardEvent.get('immediateCause')

                # Immediate causes that should use a default value of 'None'

                # for event type and 'Public reported' for source

                icsWithDifferentDefaults = [self.immediateCauseIJ()['identifier'],

                                      self.immediateCauseGO()['identifier']]

                if immediateCause == self.immediateCauseDR()['identifier']:

                    damOrLeveeName = hazardEvent.get('damOrLeveeName')

                    metaData.insert(2, self.getDamOrLevee(damOrLeveeName))

                elif immediateCause in icsWithDifferentDefaults:

                    source['values'] = self.publicSource()['identifier']

                    eventType['values'] = self.noEventType()['identifier']

                # Force event types to default value when switching between

                # immediate causes with different default values

                prevMetadata = hazardEvent.get('prevMetadata', {})

                prevImmediateCause = prevMetadata.get('immediateCause')

                icHasDifferentDefaults = immediateCause in icsWithDifferentDefaults

                prevICHasDifferentDefaults = prevImmediateCause in icsWithDifferentDefaults

                if (icHasDifferentDefaults != prevICHasDifferentDefaults):

                    source['useNewValueOnRefresh'] = True

                    eventType['useNewValueOnRefresh'] = True

                prevMetadata['immediateCause'] = immediateCause

                hazardEvent.addHazardAttribute('prevMetadata', prevMetadata)

        metaData += self.getDoesNotContributeToLockingBolding(False)

        return {

                HazardConstants.METADATA_KEY: metaData

                }

def applyInterdependencies(triggerIdentifiers, mutableProperties):

    ctaImpactObj = CallsToActionAndImpacts()

    ctaImpactObj.testSharedMethod()

    propertyChanges = CommonMetaData.applyInterdependencies(triggerIdentifiers, mutableProperties)

    return propertyChanges


3.2.8 Disclaimers that clarify what is editable.    contents    ^    + v   

A Hazard Services ticket (#45995) has been written to address some confusing aspects of the appearance of the Impact Statement tab in the Hazard Information Dialog for River hazards. The problem is that the individual impact statements appear to be editable in the Hazard Information Dialog but are not. It may be some time before this ticket is completed, but in the mean time we present this customization that shows disclaimers that make it clear what is editable and what is not. The file being overridden is CommonMetaData.py. This customization is large because it overrides some methods with alot of code, but the changes themselves (in the customary green) are very simple.

class MetaData(object):

   def getRiseCrestFall(self):
       return [
               {
                "fieldName": "riseAbove",
                "fieldType": "HiddenField"
                },
               {
                "fieldName": "crest",
                "fieldType": "HiddenField"
                },
               {
                "fieldName": "fallBelow",
                "fieldType": "HiddenField"
                },
                {
                "fieldType": "Label",
                "fieldName": "userInfo",
                "values": "Note: Parameters shown are for user reference and are uneditable.",
                "italic": True,
                "bold": True
               },
 
             {
                "fieldName": "riseAboveDescription",
                "fieldType": "Text",
                "label": "Rise Above Time:",
                "visibleChars": 18,
                "spacing": 5,
                "editable": False,
                "interdependencyOnly": True,
                "readOnly": True
                },
               {
                "fieldName": "crestDescription",
                "fieldType": "Text",
                "label": "Crest Time:",
                "visibleChars": 18,
                "spacing": 2,
                "editable": False,
                "interdependencyOnly": True,
                "readOnly": True
                },
               {
                "fieldName": "fallBelowDescription",
                "fieldType": "Text",
                "label": "Fall Below Time:",
                "visibleChars": 18,
                "spacing": 2,
                "editable": False,
                "interdependencyOnly": True,
                "readOnly": True
                },
               {
                "fieldName": "editRiseCrestFallButtonComp",
                "fieldType": "Composite",
                "expandHorizontally": False,
                "fields": [
                           {
                            "fieldType": "Button",
                            "fieldName": "riseCrestFallButton",
                            "label": " Graphical Time Editor... ",
                            "editRiseCrestFall": True
                            }
                           ]
                }
               ]

   def getForecastPointsSection(self,parm):
       pointID = self.hazardEvent.get("pointID")

       if self._riverForecastUtils is None:
           self._riverForecastUtils = RiverForecastUtils()

       self.getRiverForecastPoint(pointID, True)
       PE = self._riverForecastPoint.getPhysicalElement()

       curObs = self._riverForecastUtils.getObservedLevel(self._riverForecastPoint)
       maxFcst = self._riverForecastUtils.getMaximumForecastLevel(self._riverForecastPoint, PE)

       if PE[0] == PE_H :
           # get flood stage
           fldStg = self._riverForecastPoint.getFloodStage()
           fldFlow = HazardConstants.MISSING_VALUE
       else :
           # get flood flow
           fldStg = HazardConstants.MISSING_VALUE
           fldFlow = self._riverForecastPoint.getFloodFlow()

       curObs = '{:<15.2f}'.format(curObs)
       maxFcst = '{:<15.2f}'.format(maxFcst)
       fldStg = '{:<15.2f}'.format(fldStg)
       fldFlow = '{:<15.2f}'.format(fldFlow)
       lookupPE = '{:15s}'.format(PE)
       basedOnLookupPE = self._basedOnLookupPE
       riverLabel = self.getRiverLabel(parm)

       
userInfo = {
                     "fieldType": "Label",
                     "fieldName": parm+"UserInfo",
                     "values": "Note: Parameters shown are for user reference and are uneditable.",
                     "italic": True,
                     "bold": True
                     }
        curObsField = {
                     "fieldName": parm + "CurObsField",
                     "fieldType": "Text",
                     "label": "CurObs",
                     "values": curObs,
                     "readOnly":True,
                 }
       maxFcstField = {
                     "fieldName":  parm + "MaxFcstField",
                     "fieldType": "Text",
                     "label": "MaxFcst",
                     "values": maxFcst,
                     "readOnly":True,
                 }
       fldStgField = {
                     "fieldName":  parm + "FldStgField",
                     "fieldType": "Text",
                     "label": "FldStg",
                     "values": fldStg,
                     "readOnly":True,
                 }
       fldFlowField = {
                     "fieldName":  parm + "FldFlowField",
                     "fieldType": "Text",
                     "label": "FldFlow",
                     "values": fldFlow,
                     "readOnly":True,
                 }
       lookupPEField = {
                     "fieldName":  parm + "LookupPEField",
                     "fieldType": "Text",
                     "label": "Lookup PE",
                     "values": lookupPE,
                     "readOnly":True,
                 }
       basedOnLookupPEField = {
                     "fieldName":  parm + "BasedOnLookupPEField",
                     "fieldType": "Text",
                     "label": "Based On Lookup PE",
                     "values": basedOnLookupPE,
                     "readOnly":True,
                 }

       group = {
                "fieldType": "Group",
                "fieldName": parm + "ForecastPointsGroup",
                "expandHorizontally": False,
                "expandVertically": True,
                "fields" : [riverLabel,
userInfo, curObsField, maxFcstField, fldStgField, fldFlowField, lookupPEField, basedOnLookupPEField]
                }
       return group

   def getSelectedForecastPoints(self,parm):
       filters = self._setupSearchParameterFilters(parm)

       if self.hazardEvent.get(parm+"ReferenceStageFlow"):
           self._updateSearchParmsWithHazardEvent(self.hazardEvent, parm, filters)

       filterValues = {k:filters[k]['values'] for k in filters}
       pointID = self.hazardEvent.get("pointID")
       if self._riverForecastUtils is None:
           self._riverForecastUtils = RiverForecastUtils()

       self.getRiverForecastPoint(pointID, True)
       primaryPE = self._riverForecastPoint.getPhysicalElement()
       impactsTextField = None
       if parm == "impacts":
           headerLabel = "Impacts to Use"
           selectionLabel = "ImpactStg/Flow - Start - End - Tendency"
           
           simTimeMils = SimulatedTime.getSystemTime().getMillis()
           currentTime = datetime.datetime.utcfromtimestamp(simTimeMils / 1000)
           
           impactDataList = self._riverForecastUtils.getImpactsDataList(pointID, currentTime.month, currentTime.day)
           plist = JUtilHandler.javaCollectionToPyCollection(impactDataList)
           
           characterizations, physicalElements, descriptions = self._riverForecastUtils.getImpacts(plist, primaryPE, self._riverForecastPoint, filterValues)
           impactChoices, values = self._makeImpactsChoices(characterizations, physicalElements, descriptions)

           checkedValues = []
           searchType = filters.get('Search Type')
           searchTypeValue = searchType.get('values')

           if searchTypeValue:
               referenceType = filters['Reference Type']
               referenceTypeValue = referenceType.get('values')
               referenceValue = None
               if referenceTypeValue == 'Max Forecast' :
                   referenceValue = self.hazardEvent.get("forecastCrestStage")
               elif referenceTypeValue == 'Current Observed' :
                   referenceValue = self.hazardEvent.get("obsCrestStage")
               elif referenceTypeValue == 'Current Obs/Max Fcst':
                   fcstLevel = self.hazardEvent.get("forecastCrestStage")
                   obsLevel = self.hazardEvent.get("obsCrestStage")
                   referenceValue = max(fcstLevel,obsLevel)

               stageWindowLower = filters['Stage Window Lower']['values']
               stageWindowUpper = filters['Stage Window Upper']['values']

               tupleList, tupleMap = self.createImpactsData(values)

               # Sort the list of tuples
               tupleList.sort()
               if referenceValue == None or referenceValue == HazardConstants.MISSING_VALUE:
                   # No obs or maxfcst value to reference. Default to checking
                   # the first impact only.
                   if len(tupleList) > 0 :
                       defaultChoice = tupleMap.get(tupleList[0], None)
                       if defaultChoice:
                           checkedValues.append(defaultChoice)
               else:
                   if searchTypeValue == 'All Below Upper Stage/Flow':
                       for impactTuple in tupleList:
                           value, trend = impactTuple
                           if value <= referenceValue + stageWindowUpper:
                               checkedValues.append(tupleMap.get(impactTuple))
                   else:
                       windowTuples = []
                       if searchTypeValue == 'Closest in Stage/Flow Window':
                           for impactTuple in tupleList:
                               value, trend = impactTuple
                               if value > referenceValue + stageWindowLower and value < referenceValue + stageWindowUpper:
                                   windowTuples.append(impactTuple)

                           closest = (-9999, "Rising")
                           delta = 9999
                           # select closest to the reference value
                           for impactTuple in windowTuples:
                               value, trend = impactTuple
                               if abs(value - referenceValue) < delta:
                                   closest = impactTuple
                                   delta = abs(value - referenceValue)
                           if (closest[0] != -9999):
                               checkedValues.append(tupleMap.get(closest))

                       elif searchTypeValue == 'Highest in Stage/Flow Window':
                           for impactTuple in tupleList:
                               value, trend = impactTuple
                               if value > referenceValue + stageWindowLower and value < referenceValue + stageWindowUpper:
                                   windowTuples.append(impactTuple)

                           highest = (-9999, "Rising")
                           # select the highest in the list
                           for impactTuple in windowTuples:
                               value, trend = impactTuple
                               if value > highest[0]:
                                   highest = impactTuple;
                           if highest[0] != -9999:
                               checkedValues.append(tupleMap.get(highest))

           selectedForecastPoints = {
                                     "fieldType":"CheckBoxes",
                                     "fieldName": "impactCheckBoxes",
                                     "label": "Impacts",
                                     "choices": self._sortImpactsChoices(impactChoices, values),
                                     "values" : checkedValues,
                                     "extraData" : { "origList" : checkedValues},
                                     "useNewValueOnRefresh": True,
                                     }
           
userInfo2 = {
                     "fieldType": "Label",
                     "fieldName": parm+"UserInfo2",
                     "values": \
           "Note: All following text is for user reference; edits must be made in Product Editor.",
                     "italic": True,
                     "bold": True
                     }
        else:
           
userInfo2 = None
           headerLabel = "Crest to Use"
           selectionLabel = "CrestStg/Flow - CrestDate"
           defCrest, crestList = self._riverForecastUtils.getHistoricalCrest(self._riverForecastPoint, primaryPE, filterValues)

           if defCrest.startswith(HazardConstants.MISSING_VALUE_STR):
               defCrest=""
               crestList.append("")
           choices = crestList
           value = defCrest

           selectedForecastPoints = {
                   "fieldType": "ComboBox",
                   "fieldName": parm + "SelectedForecastPointsComboBox",
                   "choices": choices,
                   "values": value,
                   "expandHorizontally": False,
                   "expandVertically": True,
                   "useNewValueOnRefresh": True
           }

       groupHeaderLabel  = {
                     
                      "fieldType": "Label",
                      "fieldName": parm+"GroupForecastPointsLabel",
                      "leftMargin": 40,
                      "rightMargin": 10,
                      "values": headerLabel,
                     
                      }

       selectionHeaderLabel = {
                     
                      "fieldType": "Label",
                      "fieldName": parm+"SelectedForecastPointsLabel",
                      "values": selectionLabel,
                     
                      }

       
if userInfo2 :
           fields = [ groupHeaderLabel,selectionHeaderLabel,userInfo2,selectedForecastPoints ]
       else :
            fields = [ groupHeaderLabel,selectionHeaderLabel,selectedForecastPoints ]

       grp = {
           "fieldName": parm+"PointsAndTextFieldGroup",
           "fieldType":"Group",
           "label": "",
           "expandHorizontally": False,
           "expandVertically": True,
           "fields": fields
           }
       return grp

   def _makeImpactsChoices(self, characterizations, physicalElements, descriptions):
       choices = []
       values = []

       zipped = zip(characterizations, physicalElements, descriptions)
       zipped.sort()
       for char, pe, desc in zipped :
           
dispStr = str(char).replace("01/01-12/31","All year")
           id = "impactCheckBox_" + pe + '_'+ char
           entry = {
                    "identifier": id,
                    "displayString":
dispStr,
                    "physicalElement": pe,
                    "detailFields": [
                                    {
                                    "fieldType": "Text",
                                    "fieldName": "impactTextField_"+str(char),
                                    "expandHorizontally": False,
                                    "visibleChars": 35,
                                    "lines":2,
                                    "values": desc,
                                    "readOnly":True,
                                    }
                                  ]
                    }
           choices.append(entry)
           values.append(id)
       return choices, values

3.2.9 Showing Type Source on the Hazard Information Dialog.    contents    ^  v   

This only applies to river flood hazards. Issuing a river flood hazard depends on having observed and forecast stage or flow data for a gauge. Most gauges have multiple potential sources of both observed and forecast stage and/or flow data. These are referred to as a “Type Source”, and by default these are always shown in the Graphical Time Editor.  This override allows this information to also be shown directly on the Hazard Information Dialog. This involves very simple overrides to four modules; CommonMetaData.py, MetaData_FL_A.py,   MetaData_FL_W.py, and  MetaData_FL_Y.py.  Here we only show the overrides to CommonMetaData.py and MetaData_FL_A.py, with changes from base in the customary green. The overrides for MetaData_FL_W.py and  MetaData_FL_Y.py are so completely analogous to the override for MetaData_FL_A.py that we leave those as an exercise for the reader.

CommonMetaData.py:

class MetaData(object):

    def getTypeSource(self):
       typeSourceObs = self.hazardEvent.get("recommendationTypeSourceObs")
       typeSourceFcst = self.hazardEvent.get("recommendationTypeSourceFcst")
       if typeSourceObs or typeSourceFcst :
           if not typeSourceObs :
               typeSourceObs = 'na'
           if not typeSourceFcst :
               typeSourceFcst = 'na'
           labelStr = "Observed|Forecast type source:  "+typeSourceObs+" | "+typeSourceFcst
       else :
           labelStr = "No type source information."
       return {
           "fieldName": "TypeSource",
           "fieldType": "Label",
           "values": labelStr,
           }

MetaData_FL_A.py:

class MetaData(CommonMetaData.MetaData):
   
   def execute(self, hazardEvent=None, metaDict=None):
       self.initialize(hazardEvent, metaDict)
       self._basedOnLookupPE = '{:15s}'.format('YES')

       if self.hazardStatus in ["ending", "ended", "elapsing"]:
           metaData = [
                       self.getInclude(),
           ]
       else:
           pointDetails = [
                           self.getRiverLabel(),
                            self.getTypeSource(),
                            self.getImmediateCause(),
                           self.getTypeSource(),
                           # A Watch has no observed flooding.
                           # self.getFloodCategoryObserved(),
                           self.getFloodCategoryForecast(),
                           self.getFloodRecord(),
                           self.getInclude(),
                           ]
           pointDetails.extend(self.getRiseCrestFall())
           impacts = [self.getCrestsOrImpacts("impacts")]
           metaData = [
                          {
                   "fieldType": "TabbedComposite",
                   "fieldName": "FLWTabbedComposite",
                   "leftMargin": 10,
                   "rightMargin": 10,
                   "topMargin": 10,
                   "bottomMargin": 10,
                   "expandHorizontally": True,
                   "expandVertically": True,
                   "pages": [
                                 {
                                   "pageName": "Point Details",
                                   "pageFields": pointDetails
                                  },
                                 {
                                   "pageName": "Impact Statement",
                                   "pageFields": impacts
                                  }
                           ]
                   },
                       
                   # Megawidget state flag must be defined in order to add the
                   # applySideEffects flag to applyInterdependencies' returnDict.
                   {
                       "fieldName": "applySideEffects",
                       "fieldType": "HiddenField",
                   }
              ]
       return {
               HazardConstants.METADATA_KEY: metaData
               }

3.2.10 Including gauge data in areal flood hazards.    contents    ^  v   

Hazard Services has functionality that allows one to include river gauge data in areal flood hazards.  Typically, river gauge data is only shown in river hazard products, so this functionality is deactivated by default.  However, it is very simple to activate through the following override of CommonMetaData.py:

class MetaData(object):

        def riverInfoInFA(self):

            return True

This results in radio buttons labeled River Information and With Impacts being added in the Locations Affected section of the Hazard Information Dialog for areal flood hazards.  Activating the River Information radio button populates the list of Gauges to Include with any for which either the gauge itself or its reach polygon overlaps the area of the hazard. Activating the With Impacts radio button creates a hierarchical choice list below the Locations Affected section; this hierarchical choice list allows one to select gauges and impacts to mention for the gauge.

There are two other methods one can override that are binary switches controlling other aspects of this capability. These are hideMissingDataGauges() in CommonMetaData.py and mentionMissingDataGauges() in SectionLevelMethods.py.  It is left to the reader to read the comments on the Base level instances of these methods to learn what they do.