python3.8 -m venv validator
Note: You may have to install venv tools on your system. For linux, the command is
sudo apt install python3.8-venv
To activate the virtual environment:
Windows:
validator\Scripts\activate
MacOS/Linux:
source validator/bin/activate
You are now in a virtual environment where you can install the requirements and run the main script.
To deactivate the environment, run
deactivate
pip install -r requirements.txt
To run the validator, execute the following command:
python3 validator.py -f [path_to_file]
Ensure that the path leads to a yaml file.
See full usage options below:
usage: validator.py [-h] [-f PATH] [-u] [-t]
options:
-h, --help Show this help message and exit.
-f PATH, --filepath PATH The path to the yaml file. Required if -u is not specified.
-u, --update Switch to update the api files or not. Required if -f is not specified.
-t, --train Validate a training scenario yaml.
- When the Swagger API changes, make sure you upload the newest version as
api.yamlto theapi_filesdirectory. - Once the api file is up-to-date, run
python3 validator.py --update
To change the log level, edit the value in the .env file.
The dependencies json lists specific rules for the validator to follow. When listing a field, use '.' between each level. For levels that contain arrays, ensure you put '[]' at the end of the level name. For example, scenes[].id, or state.characters[].demographics.skills[].level.
| key | description | value |
|---|---|---|
simpleRequired |
"If [field1] is provided, then [field2] is required." | A dictionary where each key is [field1] and the value is a list of [field2] names |
conditionalRequired |
"If [field1] is provided and [conditions] apply, then [field2] is required." | A dictionary where each key is [field1] and the value is a list of objects that define conditions and [field2] names. Optional field "logLevel" exists, with options for "warn" and "error". Defaults to "error". |
conditionalForbid |
"If [field1] has a value of [value1], then [field2] should not be provided" | A dictionary where each key is [field1] and the value is a list of objects that define conditions and [field2] names. If [field2] is in the yaml to validate when the conditions are true, a warning will be given. Optional field "logLevel" exists, with options for "warn" and "error". Defaults to "error". |
simpleAllowedValues |
"If [field1] has a value of [value1], then [field2] values must be [...]" | A dictionary where each key is a [field1] and the value is an object where each key is a possible value for field 1. Those keys are mapped to objects whose keys are [field2] names with a matching value of an array of possible allowed values for field2 |
deepLinks |
"If [field1] has a value matching one of [a, b, ...] and [field2] has a value matching one of [c, d, ...], then [field3] value must match one of [e, f, ...]" | An object where each key is a shared parent of all fields throughout the rest of the object. For example, fa.fb[].fc is the parent of field1, field2, and field3. Each value contains "sharedParent", "condition" and "requirement". "condition" is an object where each key-value pair refers to a field (key) and the list of its possible values it must match (value) in order for "requirement" to be required. "requirement" is an object where each key-value pair refers to a field (key) and the list of allowed values (value) for it given the conditions |
valueMatch |
"[field1] must match one of the values from [field2]" | An object in the form [field1]: [field2]. Each value of the object is a field name whose values form the complete list of valid values for the corresponding key, [field1]. The value of [field1] must match one of these values. |
characterMatching |
Character ids found at the locations in this list must match state.characters[].id if the scene is the first scene, and scenes[ind].state.characters[].id otherwise | A list of locations that must follow this rule |
unique |
"all values of [field1] must be unique within the scope of [field2]" | An object in the form [field1]: [field2]. Both must be a complete path. field1 refers to the path where unique values must live. field2 refers to the path that begins the uniqueness. For example, scenes[] as field2 means that field1 cannot have a repeated value in each individual scene. To get uniqueness for an id throughout the entire yaml, field2 should be "" |
conditions |
An object containing specific conditions that must apply before the appropriate action is taken | An object containing keys such as length, exists, or value, where the value of that key is the length or value that must hold true for the key to be required or ignored, or to require/forbid keys based on the existence of another key |
In order for a yaml file to be considered "valid", the following conditions must be met:
- The yaml must not contain duplicate keys at the same level
- The yaml must be in valid yaml format. To check your yaml format, use yamllint
- All value types should follow the
api.yamlfile - All keys defined as required by
api.yamlare required - Exceptions to the two rules above include the following:
scenario.scenesis requiredtagis not allowed for Characters- All vitals properties are required
avpumental_statusbreathingheart_ratespo2ambulatory
restricted_actionscannot includeend_scenesession_completeis a prohibited key inscenarioscenario_completeis a prohibited key instateelapsed_timeis a prohibited key instateaction_typeinaction_mappingcannot be one ofrestricted_actions- In
scenes.state:- Only
charactersis required (ifpersist_charactersis false) - Only one
unstructuredproperty is required in the whole object Mission,Environment,DecisionEnvironment, andSimEnvironmentonly require theunstructuredpropertytypeis a prohibted key inSimEnvironmentAidonly requiresid
- Only
- If
scenes[n].action_mapping[m].probe_conditionshas a length of 2 or more,scenes[n].action_mapping[m].probe_condition_semanticsis required - If
scenes[n].action_mapping[m].action_conditionshas a length of 2 or more,scenes[n].action_mapping[m].action_condition_semanticsis required - If
scenes[n].transitionshas a length of 2 or more,scenes[n].transition_semanticsis required - If
state.characters[n].demographics.military_dispositionis "Allied US",state.characters[n].demographics.military_branchis required - If
state.characters[n].injuries[m].nameis "Burn",state.characters[n].injuries[m].severityis required - If
scenes[n].action_mapping[m].action_typeis "APPLY_TREATMENT",scenes[n].action_mapping[m].character_idis recommended - If
scenes[n].action_mapping[m].action_typeis "CHECK_ALL_VITALS",scenes[n].action_mapping[m].character_idis recommended - If
scenes[n].action_mapping[m].action_typeis "CHECK_PULSE",scenes[n].action_mapping[m].character_idis recommended - If
scenes[n].action_mapping[m].action_typeis "CHECK_RESPIRATION",scenes[n].action_mapping[m].character_idis recommended - If
scenes[n].action_mapping[m].action_typeis "CHECK_BLOOD_OXYGEN",scenes[n].action_mapping[m].character_idis recommended - If
scenes[n].action_mapping[m].action_typeis "MOVE_TO",scenes[n].action_mapping[m].character_idis recommended - If
scenes[n].action_mapping[m].action_typeis "MOVE_TO_EVAC",scenes[n].action_mapping[m].character_idis recommended - If
scenes[n].action_mapping[m].action_typeis "MOVE_TO_EVAC",scenes[n].action_mapping[m].parameters.aid_idis recommended - If
scenes[n].action_mapping[m].action_typeis "TAG_CHARACTER",scenes[n].action_mapping[m].character_idis recommended - If
scenes[n].action_mapping[m].action_typeis "TAG_CHARACTER",scenes[n].action_mapping[m].parameters.categoryis recommended - If
scenes[n].action_mapping[m].action_typeis "MESSAGE",scenes[n].action_mapping[m].parameters.typeis required - If
scenes[n].state.events[m].typeis "change", "emphasize", or "inform",scenes[n].state.events[m].relevant_stateis required - If
scenes[n].state.events[m].typeis "order" or "recommend",scenes[n].state.events[m].action_idis required - If
scenes[n].action_mapping[m].parameters.typeis "ask", "allow", "delegate", or "recommend",scenes[n].action_mapping[m].parameters.action_typeis required - If
scenes[n].action_mapping[m].parameters.typeis "justify",scenes[n].action_mapping[m].parameters.relevant_stateis required
- If
state.characters[n].demographics.military_branchdoes not exist,state.characters[n].demographics.rankandstate.characters[n].demographics.rank_titleshould not be provided - If `scenes[n].action_mapping[m].action_type' is "CHECK_BLOOD_OXYGEN" and there is no pulse oximeter correctly configured in the supplies for the scene, a warning will be given.
- If
scenes[n].persist_charactersis false or does not exist,scenes[n].removed_charactersmust not exist - If
scenes[n].action_mapping[m].parameters.character_idexists,scenes[n].action_mapping[m].parameters.recipientmust not exist (it will be ignored)
state.characters[n].injuries[m].source_charactermust be one of thestate.characters.character_id'sscenes[n].tagging.referencemust be one of thescenes[n].id'sscenes[n].action_mapping[m].next_scenemust be one of thescenes[n].id'sscenes[n].action_mapping[m].parameters.aid_idmust be one of thescenes[n].state.environment.decision_environment.aid[p].id'sscenes[n].transitions.actions[m]must be one of thescenes[n].action_mapping[x].action_id'sscenes[n].state.events[m].action_idmust be one of thescenes[n].action_mapping[x].action_id's
If persist_characters is false:
scenes[0].action_mapping[].character_id:state.characters[].id,scenes[0].tagging.probe_responses[].character_id:state.characters[].id,scenes[0].transitions.character_vitals[].character_id:state.characters[].id,scenes[0].action_mapping[].action_conditions.character_vitals[].character_id:state.characters[].idscenes[0].action_mapping[].probe_conditions.character_vitals[].character_id:state.characters[].id- For scenes[n] where (n>0):
scenes[n].action_mapping[].character_id:scenes[n].state.characters[].id,scenes[n].tagging.probe_responses[].character_id:scenes[n].state.characters[].id,scenes[n].transitions.character_vitals[].character_id:scenes[n].state.characters[].id,scenes[n].action_mapping[].probe_conditions.character_vitals[].character_id:scenes[n].state.characters[].idscenes[n].action_mapping[].action_conditions.character_vitals[].character_id:scenes[n].state.characters[].id
Otherwise, complete the same checks, but match it up against all characters defined throughout the scenario file. Note that this may give some false validity, as a character may end up being used before it is defined. Please be cautious when defining characters and using persist_characters. In addition, if a character is removed anywhere throughout the scenario, a warning will be issued. Please make sure that your branching scenes do not cause a situation where a character has been removed and then used.
scenes[].state.environment.decision_environment.aid[].idmust not have any repeated values within eachscenescenes[].state.characters[].idmust not have any repeated values within eachscenescenes[].action_mapping[].action_idmust not have any repeated values within eachscenestate.characters[].idmust not have any repeated valuesscenes[].idmust not have any repeated valuesstate.environment.decision_environment.aid[].idmust not have any repeated valuesscenes[].action_mapping[].unstructuredmust not have any repeated values
Any supply name placed in this array will be excluded from the allowed supplies if eval mode is true.
- At least one scene must have
final_scene=true scenes[n].action_mapping[m].parameters.treatmentmust come fromSupplyTypeEnumscenes[n].action_mapping[m].parameters.locationmust come fromInjuryLocationEnumscenes[n].action_mapping[m].parameters.categorymust come fromCharacterTagEnum- If there are
Nscenario.scenes, then all scenes except first must contain state scenario.state.characters.demographics.mission_importancemust be consistent withscenario.state.mission.character_importance- Every character with
mission_importanceshould be an entry incharacter_importance, and vice-versa - This does not include "normal", which is the default level of importance. For example, a character may not specify
mission_importanceandcharacter_importancemay explicitly specify the character with importance "normal", or a character may specifymission_importancewith "normal" andcharacter_importancemay not list that character
- Every character with
- If the scene is the first scene,
scenes[].stateshould not be provided - No blanket can appear on the character at the start.
- Quantized injuries (where
treatments_required> 1) aren't supported for injuries that aren't successfully treated by hemostatic gauze or pressure bandage. - No more than one injury can appear on the same limb or same side of the face
- APPLY_TREATMENT actions must not contain locations that do not match the location of an injury on the specified character
sourceis recommended for all eventssourceandobjectmust be either a valid character id or anEntityTypeEnumfor all eventsaction_typemust be a valid action type (ActionTypeEnum) for all messagesobjectmust be either a valid character id or anEntityTypeEnumfor all messageswhencannot be 0
- An injury may not be partially treated
- An injury's treatments_applied property must either be 0 or equal to treatments_required
- An injury's status must be 'treated' iff treatments_applied == treatments_required
- An injury's status must be 'treated' if a relateed injury's status is treated (i.e. if R Bicep Puncture is treated, R Forearm Puncture must be treated)
- If a character's
unseenproperty istrue, none of thevitalsare required - If a specified
character_idis unseen, and it's not an "intent action", then the correspondingaction_typemust beMOVE_TOorMOVE_TO_EVAC - If a specified
character_idis NOT unseen, then the correspondingaction_typecannot beMOVE_TO
When not running in training mode (-t), additional checks are implemented:
- No supplies or treatments are allowed that are not in the simulator. Put the names of these treatments in the trainingOnlySupplies array in dependencies.json
- No more than 12 injuries of types (abrasion, puncture, burn, or laceration) may be given to a character at a time
- Injuries are only allowed to have specific locations. Please follow the table to create valid matches:
| Injury name | Allowed Locations |
|---|---|
Traumatic Brain Injury |
head |
Open Abdominal Wound |
stomach |
Ear Bleed |
left face |
Asthmatic |
internal |
Abrasion |
left face, right face, left thigh, right thigh, left calf, right calf, left bicep, right bicep, left forearm, right forearm |
Laceration |
left face, left forearm, right forearm, left stomach, left thigh, right thigh, left calf, right calf, left hand, right hand |
Puncture |
left neck, right neck, left bicep, right bicep, left forearm, right forearm, left shoulder, right shoulder, left stomach, right stomach, left side, right side, left thigh, right thigh, left chest, right chest, center chest, left calf, right calf |
Shrapnel |
right face, left calf, right calf |
Chest Collapse |
left chest, right chest |
Amputation |
left wrist, right wrist, left calf, right calf, left thigh, right thigh |
Burn |
right forearm, left forearm, right calf, left calf, right thigh, left thigh, right side, left side, right chest, left chest, neck, right bicep, left bicep |
Broken Bone |
right leg, left leg, right shoulder, left shoulder, right wrist, left wrist |
Internal |
internal, unspecified |
Also, in some cases, treatments are not allowed at specific locations:
Nasopharyngeal airwaymust be placed in left/right face;internalandunspecifiedare only valid for treatments that do not actually treat injuries, e.g.Epi Pen,Blanket,Blood,Pain Medications,IV Bag, andFentanyl Lollipop. Other treatments/locations might not be successful, but are not flagged by the validator as errors.
military_branchis only allowed ifmilitary_dispositionis "Allied US"rankandrank_titleare not allowed ifmilitary_branchis not providedmilitary_branch,rank, andrank_titlemust match the information found here
To convert a file from sim JSON format to YAML, run python3 freeform_json_to_yaml.py -i [input-path] -o [output-path].
Currently, this cannot handle multiple scenes. This will work best for freeform scenarios that have one narrative element with an additionalInfo property that explains the scene and no other interaction from the sim to the participant.
The script will take data from the JSON file inputted and output it in a valid YAML format.