Source code for emod_api.config.dtk_pre_process_adhocevents

#!/usr/bin/python
import json
import os
import pathlib
import time

"""
This is the pre-processing script to use ad-hoc events in your campaign files. It will 'scrape' your campaign.json for events
that aren't available in the bulit-in list and map them to GP_EVENT_xxx events. It will currently handle up to 100, though 
that can be changed if someone really truly needs more.
Note that this works in conjunction with a post-proc script that maps all the GP_EVENT's back to the user-specified names.
Several things to note about design choices and possible areas of improvement: 
    1) It would obviously be great to get the list of built-in events from the code but currently that would mean parsing a C 
    header file (which relies on a macro). There are various kinds of 'meh' associated with that approach. For now I've 
    copy-pasted the list of actual campaign-related events used in our regression tests from the built-in list here. Looking 
    for a great idea for how to do this.
    2) There is no perfect way of finding events in the campaign file so I've relied on parsing and our current conventions:
    - _Events, Choice_Names, Event_Trigger, Event_To_Broadcast. Ideally there'd be a hard-and-fast rule.
    3) This code is super-simple except for the recursive march through the campaign file, but that's similar to how we do
    that same thing elsewhere and have been for some time.
"""
adhoc_events = []
builtin_events = [ "StartedART", "StoppedART", "Births", "TBTestPositive", "TBTestNegative", "TBTestDefault", "Blackout" ]
def _recursive_json( ref_json, flat_input_json ):
    for val in ref_json:
        try:
            #if not leaf, call recursive_json_leaf_reader
            if isinstance( ref_json[val], dict ): 
                #print( "recursing on nested object: val = " + val )
                _recursive_json( ref_json[val], flat_input_json ) 
            elif isinstance( ref_json[val], list ):
                #print( "Iterating over list under val: " + val )
                for obj in ref_json[val]: 
                    # Ignore lists of numbers and strings
                    if isinstance( obj, dict ) or isinstance( obj, list ): 
                        _recursive_json( obj, flat_input_json ) 
                    elif "_Events" in val or val == "Choice_Names":
                        for event in ref_json[val]:
                            print( event )
                            if event != "NoTrigger" and event not in adhoc_events and event not in builtin_events and len(event)>0:
                                adhoc_events.append( event )
            else:
                #print( "Considering " + str(val ))
                if( "_Event" in val or "Event_Trigger" in val or val == "Event" or val == "Event_To_Broadcast" ) and "Event_Type" not in val:
                    broadcast_event = ref_json[val]
                    print( broadcast_event  )
                    if broadcast_event not in adhoc_events and broadcast_event not in builtin_events and len(broadcast_event)>0:
                        adhoc_events.append( broadcast_event )
    
                if val not in flat_input_json:
                    flat_input_json[val] = ref_json[val]
                #else:
                    #print( "Ignoring {0} because already present.".format( val ) )
        except Exception as ex:
            print( "Exception processing {0} in {1}. Usually only happens in NChooser.".format( val, ref_json ) )

[docs]def do_mapping_from_events( config, adhoc_events ): """ Given a config file, a campaign file, and a list of adhoc_events, do the mappings. The adhoc_event list originally came from scraping an existing campaign file but now comes from emod_api.campaign. """ camp_filename = json.loads( open( config ).read() )["parameters"]["Campaign_Filename"] camp_json = json.loads( open( camp_filename ).read() ) event_map = {} for event in adhoc_events: counter = len( event_map ) builtin = "GP_EVENT_{:03d}".format(counter) event_map[ event ] = builtin camp_json_str = json.dumps( camp_json ) for event in event_map: camp_json_str = camp_json_str.replace( '"' + event + '"', '"' + event_map[event] + '"') camp_json = json.loads( camp_json_str ) with open( "campaign_xform.json", "w" ) as camp_json_handle: camp_json_handle.write( json.dumps( camp_json, sort_keys=True, indent=4 ) ) # Do event mapping in config.json # 1) Load config_json = json.loads( open( config ).read() ) # Could do a nifty for loop but obsessing about copy-pasting can sometimes lead to unnecessarily opaque code crf = "" if "Custom_Reports_Filename" in config_json["parameters"]: crf = config_json["parameters"]["Custom_Reports_Filename"] if os.path.exists(crf): report_json = None with open( crf ) as crf_handle: # could just read string, do replace, and write string but seems nasty to skip json parsing.... report_json_str = json.dumps( json.load( crf_handle ) ) for event in event_map: report_json_str = report_json_str.replace( '"' + event + '"', '"' + event_map[event] + '"') report_json = json.loads( report_json_str ) with open( "custom_reports_xform.json", "w" ) as report_json_handle: report_json_handle.write( json.dumps( report_json, sort_keys=True, indent=4 ) ) # 2) Make changes config_json["parameters"]["Campaign_Filename"] = "campaign_xform.json" if os.path.exists(crf): config_json["parameters"]["Custom_Reports_Filename"] = "custom_reports_xform.json" config_json_str = json.dumps( config_json ) reverse_map = {} for user_name, builtin_name in event_map.items(): config_json_str = config_json_str.replace( '"' + str(user_name) + '"', '"' + str(builtin_name) + '"' ) reverse_map[ builtin_name ] = user_name # 3) Save config_json = json.loads( config_json_str ) config_json["parameters"]["Event_Map"] = reverse_map with open( "config_xform.json", "w" ) as conf_json_handle: conf_json_handle.write( json.dumps( config_json, sort_keys=True, indent=4 ) )
[docs]def application( config ): """ This is the public interface function to the submodule. """ if pathlib.Path( "config_xform.json" ).is_file(): # This means (in the multcore context) that another 'thread' (core) got here first and is doing the work for us. # We know what the filename is going to be. # TBD: We should really add code that waits until config_xform.json does not equal "HOLD". while pathlib.Path( "config_xform.json" ).stat().st_size <= 5: time.sleep( 0.01 ) return "config_xform.json" with open( "config_xform.json", "w" ) as f: f.write( "HOLD" ) print( "DTK PRE PROC SCRIPT: Scrape all ad-hoc events and map to GPIO_X." ) camp_filename = json.loads( open( "config.json" ).read() )["parameters"]["Campaign_Filename"] camp_json = json.loads( open( camp_filename ).read() ) for camp_event in camp_json["Events"]: output_json = {} _recursive_json( camp_event, output_json ) do_mapping_from_events( config, adhoc_events ) return "config_xform.json"