modules/shared/protocols.mjs

export { Protocol, Protocols };

import { OccupantBodyPart } from "./occupant.mjs";
import { WorkflowOccupant } from "./workflow_occupant.mjs";
import { Structure } from "./structure.mjs";
import { Regulation } from "./regulations.mjs";
import { CrashTest } from "./crash_tests.mjs";
import { AssessmentType } from "./assessment_types.mjs";
import { AssessmentDatums } from "../post/assessment_datums.mjs";
import { find_all_json_files } from "./file_helper.mjs";
import { JSPath } from "./path.mjs";
import { ProtocolVehicle } from "./vehicle.mjs";

class Protocol {
    /**
     *
     * @param {string} regulation Regulation, e.g. Regulation.CNCAP
     * @param {string} crash_test Crash test, e.g. CrashTest.ODB
     * @param {string} version Version, e.g. "7.1.2"
     * @param {ProtocolVehicle} vehicle class contining info about valid occupant types
     * @param {AssessmentDatums[]} assessment_datums Array of AssessmentDatums instances
     * @example
     * let protocol = new ProtocolAssessment(Regulation.EuroNCAP, CrashTest.ODB, "7.1.2", []);
     */
    constructor(regulation, crash_test, version, vehicle, assessment_datums) {
        this.regulation = regulation;
        this.crash_test = crash_test;
        this.version = version;
        this.vehicle = vehicle;
        this.assessment_datums = assessment_datums;
    }

    /* Instance property getter and setters */

    /**
     * Protocol regulation
     * @type {string}
     */
    get regulation() {
        return this._regulation;
    }
    set regulation(new_regulation) {
        if (Regulation.GetAll().indexOf(new_regulation) == -1) {
            throw new Error(`Invalid version: ${new_regulation}`);
        }

        this._regulation = new_regulation;
    }

    /**
     * Protocol crash test
     * @type {string}
     */
    get crash_test() {
        return this._crash_test;
    }
    set crash_test(new_crash_test) {
        if (CrashTest.GetAll().indexOf(new_crash_test) == -1) {
            throw new Error(`Invalid crash_test: ${new_crash_test}`);
        }

        this._crash_test = new_crash_test;
    }

    /**
     * Protocol version
     * @type {string|number}
     */
    get version() {
        return this._version;
    }
    set version(new_version) {
        this._version = new_version.toString();
    }

    /**
     * Array of AssessmentDatums instances
     * @type {AssessmentDatums[]}
     */
    get assessment_datums() {
        return this._assessment_datums;
    }
    set assessment_datums(new_assessment_datums) {
        if (!(new_assessment_datums instanceof Array)) {
            throw new Error("assessment_datums must be an Array");
        }

        for (let new_assessment_datum of new_assessment_datums) {
            if (!(new_assessment_datum instanceof AssessmentDatums)) {
                throw new Error("assessment_datums must be an array of AssessmentDatums instances");
            }
        }

        this._assessment_datums = new_assessment_datums;
    }

    /**
     * Create a Protocol instance with the default AssessmentDatums for
     * the given regulation, test and year
     * @param {string} regulation Protocol regulation, e.g. Regulastion.CNCAP
     * @param {string} crash_test Protocol crash test, e.g. CrashTest.ODB
     * @param {string} version Version, e.g. "7.1.2"
     * @returns {Protocol}
     * @example
     * let p = Protocol.CreateDefaultProtocol(Regulation.CNCAP, CrashTest.ODB, "7.1.2");
     */
    static CreateDefaultProtocol(regulation, crash_test, version) {
        let vehicle = Protocols.GetProtocolVehicle(regulation, crash_test, version);
        let assessment_datums = Protocol.CreateDefaultAssessmentDatums(regulation, crash_test, version);

        return new Protocol(regulation, crash_test, version, vehicle, assessment_datums);
    }

    /**
     * Create AssessmentDatums for the given regulation, test and year
     * These are read from the default.json file in the datums/[regulation]/[crash_test]/[assessment] directory
     * @param {string} regulation Protocol regulation, e.g. Regulation.CNCAP
     * @param {string} crash_test Protocol crash test, e.g. CrashTest.ODB
     * @param {string} version Protocol version, e.g. "7.1.2"
     * @returns {AssessmentDatums[]}
     * @example
     * let a = Protocol.CreateDefaultAssessmentDatums(Regulation.CNCAP, CrashTest.ODB, "7.1.2");
     */
    static CreateDefaultAssessmentDatums(regulation, crash_test, version) {
        /* Create the AssessmentDatums based on the protocol/test/version */

        /** @type {AssessmentDatums[]} */
        let assessment_datums = [];

        for (let assessment_type of AssessmentType.GetAll()) {
            let assessment_datum = AssessmentDatums.ReadFromFile(
                regulation,
                crash_test,
                version,
                assessment_type,
                "default"
            );

            if (assessment_datum) {
                assessment_datums.push(assessment_datum);
            }
        }

        return assessment_datums;
    }

    /**
     * this constructs a protocol object from a filepath
     * @param {string} filepath to a protocol json file
     * @returns {?Protocol}
     *
     */
    static FromJSONFile(filepath) {
        let f = new File(filepath, File.READ);
        let text = f.ReadAll();
        f.Close();

        let o = JSON.parse(text);

        //extract base filename from path
        let match = filepath.match(/([^\\/]*)\.json$/i);
        if (match != null) {
            o.name = match[1]; //first capture group
        } else {
            Message(`failed to extract protocol from ${filepath}`);
            return null;
        }

        // Message(`read ${filepath}`);

        return Protocol.FromJSON(o);
    }

    /**
     * construct a Protocol from JSON
     * @param {Object} json JSON of Protocol
     * @returns {?Protocol}
     */
    static FromJSON(json) {
        //first check the input object contains valid keys

        let expected_keys = ["regulation", "crash_test", "version", "driver"];

        for (let key of expected_keys) {
            if (!(key in json)) {
                throw new Error(`${key} is not a key in the json object passed to Protocol.FromJSON(json)`);
            }
        }

        //set undefined optional keys to empty array

        let optional_keys = ["front", "behind_driver", "middle", "behind_front"];

        for (let key of optional_keys) {
            if (!(key in json)) {
                json[key] = [];
            }
        }

        //then construct Occupant object. Note setters check they are valid

        try {
            let vehicle = ProtocolVehicle.FromJSON(json);

            let assessment_datums = Protocol.CreateDefaultAssessmentDatums(
                json.regulation,
                json.crash_test,
                json.version
            );

            return new Protocol(json.regulation, json.crash_test, json.version, vehicle, assessment_datums);
        } catch (error) {
            Message(`${error} so skipping parsing JSON for this protocol.`);
            return null;
        }
    }

    /**
     * Returns the versions we support for a regulation and crash test
     * (Most recent version first)
     * @param {string} regulation Regulation, e.g. Regulation.CNCAP
     * @param {string} crash_test Crash test, e.g. CrashTest.ODB
     * @returns {string[]}
     * @example
     * let versions = Protocol.Versions(Regulation.CNCAP, CrashTest.ODB);
     */
    static Versions(regulation, crash_test) {
        let protocols = Protocols.GetOnly(regulation, crash_test, "ALL");

        let versions = [];
        for (let protocol of protocols) {
            versions.push(protocol.version.toString());
        }

        /* If it gets here we support the crash test, but we haven't added it to the switch
         * statements in this function so write an error message to flag that it needs to be added. */

        if (versions.length == 0) {
            ErrorMessage(`No protocol versions found for ${regulation} ${crash_test}.`);
            return [];
        }

        return versions.sort();
    }

    /**
     * Returns the structure types that need to be assessed for a regulation and crash test
     * @param {string} regulation Regulation, e.g. Regulation.CNCAP
     * @param {string} crash_test Crash test, e.g. CrashTest.ODB
     * @returns {string[]}
     * @example
     * let structure_types = Protocol.StructureTypes(Regulation.CNCAP, CrashTest.ODB);
     */
    static StructureTypes(regulation, crash_test) {
        let crash_tests = Regulation.CrashTests(regulation);

        for (let ct of crash_tests) {
            if (ct != crash_test) continue;

            switch (regulation) {
                case Regulation.CNCAP:
                    switch (crash_test) {
                        case CrashTest.ODB:
                            return [
                                Structure.A_PILLAR,
                                Structure.ACCELERATOR_PEDAL,
                                Structure.BRAKE_PEDAL,
                                Structure.CLUTCH_PEDAL,
                                Structure.STEERING_COLUMN
                            ];
                        case CrashTest.MPDB:
                            return [];
                    }

                case Regulation.EuroNCAP:
                    switch (crash_test) {
                        case CrashTest.ODB:
                            return [
                                Structure.A_PILLAR,
                                Structure.ACCELERATOR_PEDAL,
                                Structure.BRAKE_PEDAL,
                                Structure.CLUTCH_PEDAL,
                                Structure.STEERING_COLUMN
                            ];
                        case CrashTest.FFB:
                            return [Structure.STEERING_COLUMN];
                        case CrashTest.MPDB:
                            return [];
                        case CrashTest.FAR_SIDE:
                            return [];
                        case CrashTest.MDB:
                            return [];
                        case CrashTest.SIDE_POLE:
                            return [];
                    }
                    break;

                case Regulation.IIHS:
                    switch (crash_test) {
                        case CrashTest.ODB:
                            return [
                                Structure.BRAKE_PEDAL,
                                Structure.CLUTCH_PEDAL,
                                Structure.DRIVER_LEFT_TOEPAN,
                                Structure.DRIVER_CENTRE_TOEPAN,
                                Structure.DRIVER_RIGHT_TOEPAN,
                                Structure.DOOR,
                                Structure.DRIVER_FOOTREST,
                                Structure.LEFT_INSTRUMENT_PANEL,
                                Structure.RIGHT_INSTRUMENT_PANEL
                            ];
                        case CrashTest.MDB:
                            return [Structure.B_PILLAR];
                        case CrashTest.SOB:
                            return [
                                Structure.BRAKE_PEDAL,
                                Structure.CLUTCH_PEDAL,
                                Structure.PARKING_BRAKE,
                                Structure.DRIVER_LEFT_TOEPAN,
                                Structure.DRIVER_CENTRE_TOEPAN,
                                Structure.DRIVER_RIGHT_TOEPAN,
                                Structure.PASSENGER_LEFT_TOEPAN,
                                Structure.PASSENGER_CENTRE_TOEPAN,
                                Structure.PASSENGER_RIGHT_TOEPAN,
                                Structure.DOOR,
                                Structure.DRIVER_FOOTREST,
                                Structure.PASSENGER_FOOTREST,
                                Structure.LEFT_INSTRUMENT_PANEL,
                                Structure.RIGHT_INSTRUMENT_PANEL,
                                Structure.STEERING_COLUMN,
                                Structure.DRIVER_LOWER_HINGE_1,
                                Structure.DRIVER_LOWER_HINGE_2,
                                Structure.DRIVER_LOWER_HINGE_3,
                                Structure.DRIVER_UPPER_HINGE_1,
                                Structure.DRIVER_UPPER_HINGE_2,
                                Structure.DRIVER_UPPER_HINGE_3,
                                Structure.PASSENGER_LOWER_HINGE_1,
                                Structure.PASSENGER_LOWER_HINGE_2,
                                Structure.PASSENGER_LOWER_HINGE_3,
                                Structure.PASSENGER_UPPER_HINGE_1,
                                Structure.PASSENGER_UPPER_HINGE_2,
                                Structure.PASSENGER_UPPER_HINGE_3,
                                Structure.DRIVER_ROCKER_PANEL_1,
                                Structure.DRIVER_ROCKER_PANEL_2,
                                Structure.DRIVER_ROCKER_PANEL_3,
                                Structure.PASSENGER_ROCKER_PANEL_1,
                                Structure.PASSENGER_ROCKER_PANEL_2,
                                Structure.PASSENGER_ROCKER_PANEL_3,
                                Structure.PASSENGER_LEFT_LOWER_DASH,
                                Structure.PASSENGER_RIGHT_LOWER_DASH,
                                Structure.PASSENGER_CENTRE_DASH,
                                Structure.DRIVER_UPPER_DASH,
                                Structure.PASSENGER_UPPER_DASH
                            ];
                    }
                    break;

                case Regulation.USNCAP:
                    switch (crash_test) {
                        case CrashTest.FFB:
                            return [];
                        case CrashTest.MDB:
                            return [];
                        case CrashTest.SIDE_POLE:
                            return [];
                    }
                    break;
            }

            /* If it gets here we support the crash test, but we haven't added it to the switch
             * statements in this function so write an error message to flag that it needs to be added. */

            ErrorMessage(`Crash test ${crash_test} needs to be added to <protocol.StructureTypes> for ${regulation}`);
        }

        return [];
    }

    /* Instance methods */

    /**
     * Returns the assessment types we support for the protocol
     * and the given occupant and body part type
     * @param {WorkflowOccupant} occupant Occupant
     * @param {string} body_part_type Occupant body part type
     * @returns {string[]}
     * @example
     * let assessment_types = p.OccupantAssessmentTypes(o, OccupantBodyPart.NECK);
     */
    OccupantAssessmentTypes(occupant, body_part_type) {
        /* Return assessment types based on regulation, crash test and version */

        let crash_tests = Regulation.CrashTests(this.regulation);

        for (let crash_test of crash_tests) {
            if (this.crash_test != crash_test) continue;

            switch (this.regulation) {
                case Regulation.CNCAP:
                    switch (crash_test) {
                        case CrashTest.ODB:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [
                                        AssessmentType.HEAD_HIC,
                                        AssessmentType.HEAD_THREE_MS_EXCEEDENCE,
                                        AssessmentType.HEAD_PEAK_ACCELERATION
                                    ];
                                case OccupantBodyPart.NECK:
                                    /* Assessments are different for the front and rear occupants */
                                    if (occupant.front_rear == WorkflowOccupant.FRONT) {
                                        return [
                                            AssessmentType.NECK_SHEAR_EXCEEDENCE,
                                            AssessmentType.NECK_TENSION_EXCEEDENCE,
                                            AssessmentType.NECK_EXTENSION
                                        ];
                                    } else if (occupant.front_rear == WorkflowOccupant.REAR) {
                                        return [
                                            AssessmentType.NECK_SHEAR,
                                            AssessmentType.NECK_AXIAL,
                                            AssessmentType.NECK_EXTENSION_REAR_PASSENGER
                                        ];
                                    } else {
                                        return [];
                                    }
                                case OccupantBodyPart.CHEST:
                                    /* Assessments are different for the front and rear occupants */
                                    if (occupant.front_rear == WorkflowOccupant.FRONT) {
                                        return [
                                            AssessmentType.CHEST_COMPRESSION,
                                            AssessmentType.CHEST_THREE_MS_EXCEEDENCE
                                        ];
                                    } else if (occupant.front_rear == WorkflowOccupant.REAR) {
                                        return [
                                            AssessmentType.CHEST_COMPRESSION_REAR_PASSENGER,
                                            AssessmentType.CHEST_THREE_MS_EXCEEDENCE
                                        ];
                                    } else {
                                        return [];
                                    }
                                case OccupantBodyPart.FEMUR:
                                    if (occupant.front_rear == WorkflowOccupant.FRONT) {
                                        return [
                                            AssessmentType.LEFT_FEMUR_COMPRESSION_EXCEEDENCE,
                                            AssessmentType.RIGHT_FEMUR_COMPRESSION_EXCEEDENCE
                                        ];
                                    } else {
                                        return [];
                                    }
                                case OccupantBodyPart.KNEE:
                                    if (occupant.front_rear == WorkflowOccupant.FRONT) {
                                        return [
                                            AssessmentType.LEFT_KNEE_DISPLACEMENT,
                                            AssessmentType.RIGHT_KNEE_DISPLACEMENT
                                        ];
                                    } else {
                                        return [];
                                    }
                                case OccupantBodyPart.TIBIA:
                                    if (occupant.front_rear == WorkflowOccupant.FRONT) {
                                        return [
                                            AssessmentType.LEFT_TIBIA_COMPRESSION,
                                            AssessmentType.RIGHT_TIBIA_COMPRESSION,
                                            AssessmentType.LEFT_TIBIA_INDEX,
                                            AssessmentType.RIGHT_TIBIA_INDEX
                                        ];
                                    } else {
                                        return [];
                                    }
                                default:
                                    return [];
                            }

                        case CrashTest.MPDB:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [
                                        AssessmentType.HEAD_HIC,
                                        AssessmentType.HEAD_THREE_MS_EXCEEDENCE,
                                        AssessmentType.HEAD_PEAK_ACCELERATION
                                    ];
                                case OccupantBodyPart.NECK:
                                    if (occupant.position == WorkflowOccupant.DRIVER) {
                                        return [
                                            AssessmentType.NECK_SHEAR,
                                            AssessmentType.NECK_AXIAL,
                                            AssessmentType.NECK_EXTENSION
                                        ];
                                    } else {
                                        return [
                                            AssessmentType.NECK_SHEAR_PASSENGER,
                                            AssessmentType.NECK_AXIAL_PASSENGER,
                                            AssessmentType.NECK_EXTENSION_PASSENGER
                                        ];
                                    }
                                case OccupantBodyPart.ABDOMEN:
                                    if (occupant.position == WorkflowOccupant.DRIVER) {
                                        return [
                                            AssessmentType.ABDOMEN_COMPRESSION_LEFT,
                                            AssessmentType.ABDOMEN_COMPRESSION_RIGHT
                                        ];
                                    } else {
                                        return [];
                                    }
                                case OccupantBodyPart.CHEST:
                                    if (occupant.position == WorkflowOccupant.DRIVER) {
                                        return [
                                            AssessmentType.CHEST_COMPRESSION_LEFT,
                                            AssessmentType.CHEST_COMPRESSION_RIGHT
                                        ];
                                    } else {
                                        return [
                                            AssessmentType.CHEST_COMPRESSION_PASSENGER,
                                            AssessmentType.CHEST_VISCOUS_CRITERION
                                        ];
                                    }
                                case OccupantBodyPart.PELVIS:
                                    if (occupant.position == WorkflowOccupant.DRIVER) {
                                        return [
                                            AssessmentType.ACETABULAR_FORCE_LEFT,
                                            AssessmentType.ACETABULAR_FORCE_RIGHT
                                        ];
                                    } else {
                                        return [];
                                    }
                                case OccupantBodyPart.FEMUR:
                                    if (occupant.position == WorkflowOccupant.DRIVER) {
                                        return [
                                            AssessmentType.LEFT_FEMUR_COMPRESSION_EXCEEDENCE,
                                            AssessmentType.RIGHT_FEMUR_COMPRESSION_EXCEEDENCE
                                        ];
                                    } else {
                                        return [AssessmentType.LEFT_FEMUR_AXIAL, AssessmentType.RIGHT_FEMUR_AXIAL];
                                    }
                                case OccupantBodyPart.KNEE:
                                    if (occupant.position == WorkflowOccupant.DRIVER) {
                                        return [
                                            AssessmentType.LEFT_KNEE_DISPLACEMENT,
                                            AssessmentType.RIGHT_KNEE_DISPLACEMENT
                                        ];
                                    } else {
                                        return [];
                                    }
                                case OccupantBodyPart.TIBIA:
                                    if (occupant.position == WorkflowOccupant.DRIVER) {
                                        return [
                                            AssessmentType.LEFT_TIBIA_COMPRESSION,
                                            AssessmentType.RIGHT_TIBIA_COMPRESSION,
                                            AssessmentType.LEFT_TIBIA_INDEX,
                                            AssessmentType.RIGHT_TIBIA_INDEX
                                        ];
                                    } else {
                                        return [];
                                    }
                                default:
                                    return [];
                            }
                    }
                    break;

                case Regulation.EuroNCAP:
                    switch (crash_test) {
                        case CrashTest.ODB:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [
                                        AssessmentType.HEAD_HIC,
                                        AssessmentType.HEAD_THREE_MS_EXCEEDENCE,
                                        AssessmentType.HEAD_PEAK_ACCELERATION
                                    ];
                                case OccupantBodyPart.NECK:
                                    return [
                                        AssessmentType.NECK_SHEAR_EXCEEDENCE,
                                        AssessmentType.NECK_TENSION_EXCEEDENCE,
                                        AssessmentType.NECK_EXTENSION
                                    ];
                                case OccupantBodyPart.CHEST:
                                    return [AssessmentType.CHEST_COMPRESSION, AssessmentType.CHEST_VISCOUS_CRITERION];
                                case OccupantBodyPart.FEMUR:
                                    return [
                                        AssessmentType.LEFT_FEMUR_COMPRESSION_EXCEEDENCE,
                                        AssessmentType.RIGHT_FEMUR_COMPRESSION_EXCEEDENCE
                                    ];
                                case OccupantBodyPart.KNEE:
                                    return [
                                        AssessmentType.LEFT_KNEE_DISPLACEMENT,
                                        AssessmentType.RIGHT_KNEE_DISPLACEMENT
                                    ];
                                case OccupantBodyPart.TIBIA:
                                    return [
                                        AssessmentType.LEFT_TIBIA_COMPRESSION,
                                        AssessmentType.RIGHT_TIBIA_COMPRESSION,
                                        AssessmentType.LEFT_TIBIA_INDEX,
                                        AssessmentType.RIGHT_TIBIA_INDEX
                                    ];
                                default:
                                    return [];
                            }
                        case CrashTest.FFB:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [
                                        AssessmentType.HEAD_HIC,
                                        AssessmentType.HEAD_THREE_MS_EXCEEDENCE,
                                        AssessmentType.HEAD_PEAK_ACCELERATION
                                    ];
                                case OccupantBodyPart.NECK:
                                    return [
                                        AssessmentType.NECK_SHEAR,
                                        AssessmentType.NECK_AXIAL,
                                        AssessmentType.NECK_EXTENSION
                                    ];
                                case OccupantBodyPart.CHEST:
                                    return [AssessmentType.CHEST_COMPRESSION, AssessmentType.CHEST_VISCOUS_CRITERION];
                                case OccupantBodyPart.FEMUR:
                                    return [AssessmentType.LEFT_FEMUR_AXIAL, AssessmentType.RIGHT_FEMUR_AXIAL];
                                default:
                                    return [];
                            }
                        case CrashTest.MPDB:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [
                                        AssessmentType.HEAD_HIC,
                                        AssessmentType.HEAD_THREE_MS_EXCEEDENCE,
                                        AssessmentType.HEAD_PEAK_ACCELERATION
                                    ];
                                case OccupantBodyPart.NECK:
                                    if (occupant.position == WorkflowOccupant.DRIVER) {
                                        return [
                                            AssessmentType.NECK_SHEAR,
                                            AssessmentType.NECK_AXIAL,
                                            AssessmentType.NECK_EXTENSION
                                        ];
                                    } else {
                                        return [
                                            AssessmentType.NECK_SHEAR_EXCEEDENCE,
                                            AssessmentType.NECK_TENSION_EXCEEDENCE,
                                            AssessmentType.NECK_EXTENSION
                                        ];
                                    }
                                case OccupantBodyPart.ABDOMEN:
                                    if (occupant.position == WorkflowOccupant.DRIVER) {
                                        return [
                                            AssessmentType.ABDOMEN_COMPRESSION_LEFT,
                                            AssessmentType.ABDOMEN_COMPRESSION_RIGHT
                                        ];
                                    } else {
                                        return [];
                                    }
                                case OccupantBodyPart.CHEST:
                                    if (occupant.position == WorkflowOccupant.DRIVER) {
                                        return [
                                            AssessmentType.CHEST_COMPRESSION_LEFT,
                                            AssessmentType.CHEST_COMPRESSION_RIGHT
                                        ];
                                    } else {
                                        return [
                                            AssessmentType.CHEST_COMPRESSION,
                                            AssessmentType.CHEST_VISCOUS_CRITERION
                                        ];
                                    }
                                case OccupantBodyPart.PELVIS:
                                    if (occupant.position == WorkflowOccupant.DRIVER) {
                                        return [
                                            AssessmentType.ACETABULAR_FORCE_LEFT,
                                            AssessmentType.ACETABULAR_FORCE_RIGHT
                                        ];
                                    } else {
                                        return [];
                                    }
                                case OccupantBodyPart.FEMUR:
                                    return [
                                        AssessmentType.LEFT_FEMUR_COMPRESSION_EXCEEDENCE,
                                        AssessmentType.RIGHT_FEMUR_COMPRESSION_EXCEEDENCE
                                    ];
                                case OccupantBodyPart.KNEE:
                                    return [
                                        AssessmentType.LEFT_KNEE_DISPLACEMENT,
                                        AssessmentType.RIGHT_KNEE_DISPLACEMENT
                                    ];
                                case OccupantBodyPart.TIBIA:
                                    return [
                                        AssessmentType.LEFT_TIBIA_COMPRESSION,
                                        AssessmentType.RIGHT_TIBIA_COMPRESSION,
                                        AssessmentType.LEFT_TIBIA_INDEX,
                                        AssessmentType.RIGHT_TIBIA_INDEX
                                    ];
                                default:
                                    return [];
                            }
                        case CrashTest.FAR_SIDE:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [AssessmentType.HEAD_HIC, AssessmentType.HEAD_THREE_MS_EXCEEDENCE];
                                case OccupantBodyPart.NECK:
                                    return [
                                        AssessmentType.NECK_UPPER_EXTENSION,
                                        AssessmentType.NECK_LOWER_EXTENSION,
                                        AssessmentType.NECK_UPPER_FLEXION,
                                        AssessmentType.NECK_LOWER_FLEXION,
                                        AssessmentType.NECK_UPPER_AXIAL,
                                        AssessmentType.NECK_LOWER_AXIAL
                                    ];
                                case OccupantBodyPart.CHEST:
                                    return [AssessmentType.CHEST_COMPRESSION];
                                case OccupantBodyPart.ABDOMEN:
                                    return [AssessmentType.ABDOMEN_COMPRESSION];
                                case OccupantBodyPart.LUMBAR:
                                    return [
                                        AssessmentType.LUMBAR_SHEAR,
                                        AssessmentType.LUMBAR_AXIAL,
                                        AssessmentType.LUMBAR_TORSION
                                    ];
                                case OccupantBodyPart.PELVIS:
                                    return [AssessmentType.PELVIS_FORCE];
                                default:
                                    return [];
                            }

                        case CrashTest.MDB:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [AssessmentType.HEAD_HIC, AssessmentType.HEAD_THREE_MS_EXCEEDENCE];
                                case OccupantBodyPart.SHOULDER:
                                    return [AssessmentType.SHOULDER_LATERAL_FORCES];
                                case OccupantBodyPart.CHEST:
                                    return [AssessmentType.CHEST_COMPRESSION, AssessmentType.CHEST_VISCOUS_CRITERION];
                                case OccupantBodyPart.ABDOMEN:
                                    return [
                                        AssessmentType.ABDOMEN_COMPRESSION,
                                        AssessmentType.ABDOMEN_VISCOUS_CRITERION
                                    ];
                                case OccupantBodyPart.PELVIS:
                                    return [AssessmentType.PELVIS_FORCE];
                                default:
                                    return [];
                            }

                        case CrashTest.SIDE_POLE:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [AssessmentType.HEAD_HIC, AssessmentType.HEAD_PEAK_ACCELERATION];
                                case OccupantBodyPart.SHOULDER:
                                    return [AssessmentType.SHOULDER_LATERAL_FORCES];
                                case OccupantBodyPart.CHEST:
                                    return [AssessmentType.CHEST_COMPRESSION, AssessmentType.CHEST_VISCOUS_CRITERION];
                                case OccupantBodyPart.ABDOMEN:
                                    return [
                                        AssessmentType.ABDOMEN_COMPRESSION,
                                        AssessmentType.ABDOMEN_VISCOUS_CRITERION
                                    ];
                                case OccupantBodyPart.PELVIS:
                                    return [AssessmentType.PELVIS_FORCE];
                                default:
                                    return [];
                            }
                    }
                    break;

                case Regulation.IIHS:
                    switch (crash_test) {
                        case CrashTest.ODB:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [AssessmentType.HEAD_HIC];
                                case OccupantBodyPart.NECK:
                                    return [
                                        AssessmentType.NECK_SHEAR_EXCEEDENCE,
                                        AssessmentType.NECK_TENSION_EXCEEDENCE,
                                        AssessmentType.NECK_COMPRESSION_EXCEEDENCE,
                                        AssessmentType.NECK_AXIAL,
                                        AssessmentType.NECK_NIJ
                                    ];
                                case OccupantBodyPart.CHEST:
                                    return [
                                        AssessmentType.CHEST_COMPRESSION,
                                        AssessmentType.CHEST_COMPRESSION_RATE,
                                        AssessmentType.CHEST_THREE_MS_EXCEEDENCE,
                                        AssessmentType.CHEST_VISCOUS_CRITERION
                                    ];
                                case OccupantBodyPart.FEMUR:
                                    return [
                                        AssessmentType.LEFT_FEMUR_AXIAL,
                                        AssessmentType.RIGHT_FEMUR_AXIAL,
                                        AssessmentType.LEFT_FEMUR_COMPRESSION_EXCEEDENCE,
                                        AssessmentType.RIGHT_FEMUR_COMPRESSION_EXCEEDENCE
                                    ];
                                case OccupantBodyPart.KNEE:
                                    return [
                                        AssessmentType.LEFT_KNEE_DISPLACEMENT,
                                        AssessmentType.RIGHT_KNEE_DISPLACEMENT
                                    ];
                                case OccupantBodyPart.TIBIA:
                                    return [
                                        AssessmentType.LEFT_TIBIA_COMPRESSION,
                                        AssessmentType.RIGHT_TIBIA_COMPRESSION,
                                        AssessmentType.LEFT_TIBIA_INDEX,
                                        AssessmentType.RIGHT_TIBIA_INDEX
                                    ];
                                case OccupantBodyPart.FOOT:
                                    return [
                                        AssessmentType.LEFT_FOOT_ACCELERATION,
                                        AssessmentType.RIGHT_FOOT_ACCELERATION
                                    ];
                                default:
                                    return [];
                            }
                        case CrashTest.MDB:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [AssessmentType.HEAD_HIC];
                                case OccupantBodyPart.NECK:
                                    switch (this.version) {
                                        case "2021":
                                            return [AssessmentType.NECK_AXIAL];
                                        case "2016":
                                            return [
                                                AssessmentType.NECK_AXIAL,
                                                AssessmentType.NECK_TENSION_EXCEEDENCE,
                                                AssessmentType.NECK_COMPRESSION_EXCEEDENCE,
                                                AssessmentType.NECK_SHEAR_EXCEEDENCE
                                            ];
                                        default:
                                            return [];
                                    }
                                case OccupantBodyPart.SHOULDER:
                                    return [
                                        AssessmentType.SHOULDER_DEFLECTION,
                                        AssessmentType.SHOULDER_DEFLECTION_RATE,
                                        AssessmentType.SHOULDER_VISCOUS_CRITERION
                                    ];
                                case OccupantBodyPart.CHEST:
                                    return [
                                        AssessmentType.CHEST_COMPRESSION,
                                        AssessmentType.CHEST_COMPRESSION_RATE,
                                        AssessmentType.CHEST_VISCOUS_CRITERION
                                    ];
                                case OccupantBodyPart.PELVIS:
                                    switch (this.version) {
                                        case "2021":
                                            return [AssessmentType.PELVIS_FORCE];
                                        case "2016":
                                            return [
                                                AssessmentType.ACETABULAR_FORCE,
                                                AssessmentType.ILIUM_FORCE,
                                                AssessmentType.PELVIS_FORCE
                                            ];
                                        default:
                                            return [];
                                    }
                                default:
                                    return [];
                            }
                        case CrashTest.SOB:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [AssessmentType.HEAD_HIC];
                                case OccupantBodyPart.NECK:
                                    return [
                                        AssessmentType.NECK_SHEAR_EXCEEDENCE,
                                        AssessmentType.NECK_TENSION_EXCEEDENCE,
                                        AssessmentType.NECK_COMPRESSION_EXCEEDENCE,
                                        AssessmentType.NECK_AXIAL,
                                        AssessmentType.NECK_NIJ
                                    ];
                                case OccupantBodyPart.CHEST:
                                    return [
                                        AssessmentType.CHEST_COMPRESSION,
                                        AssessmentType.CHEST_COMPRESSION_RATE,
                                        AssessmentType.CHEST_THREE_MS_EXCEEDENCE,
                                        AssessmentType.CHEST_VISCOUS_CRITERION
                                    ];
                                case OccupantBodyPart.FEMUR:
                                    return [
                                        AssessmentType.LEFT_FEMUR_AXIAL,
                                        AssessmentType.RIGHT_FEMUR_AXIAL,
                                        AssessmentType.LEFT_FEMUR_COMPRESSION_VS_IMPULSE,
                                        AssessmentType.RIGHT_FEMUR_COMPRESSION_VS_IMPULSE
                                    ];
                                case OccupantBodyPart.KNEE:
                                    return [
                                        AssessmentType.LEFT_KNEE_DISPLACEMENT,
                                        AssessmentType.RIGHT_KNEE_DISPLACEMENT
                                    ];
                                case OccupantBodyPart.TIBIA:
                                    return [
                                        AssessmentType.LEFT_TIBIA_COMPRESSION,
                                        AssessmentType.RIGHT_TIBIA_COMPRESSION,
                                        AssessmentType.LEFT_TIBIA_INDEX,
                                        AssessmentType.RIGHT_TIBIA_INDEX
                                    ];
                                case OccupantBodyPart.FOOT:
                                    return [
                                        AssessmentType.LEFT_FOOT_ACCELERATION,
                                        AssessmentType.RIGHT_FOOT_ACCELERATION
                                    ];
                                default:
                                    return [];
                            }
                    }
                    break;

                case Regulation.USNCAP:
                    switch (crash_test) {
                        case CrashTest.FFB:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [AssessmentType.HEAD_HIC];
                                case OccupantBodyPart.NECK:
                                    return [
                                        AssessmentType.NECK_COMPRESSION_EXCEEDENCE,
                                        AssessmentType.NECK_TENSION_EXCEEDENCE,
                                        AssessmentType.NECK_NIJ
                                    ];
                                case OccupantBodyPart.CHEST:
                                    return [AssessmentType.CHEST_COMPRESSION];
                                case OccupantBodyPart.FEMUR:
                                    return [AssessmentType.LEFT_FEMUR_FORCE, AssessmentType.RIGHT_FEMUR_FORCE];
                                default:
                                    return [];
                            }
                        case CrashTest.MDB:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [AssessmentType.HEAD_HIC];
                                case OccupantBodyPart.CHEST:
                                    return [AssessmentType.CHEST_COMPRESSION];
                                case OccupantBodyPart.ABDOMEN:
                                    return [AssessmentType.ABDOMEN_FORCE];
                                case OccupantBodyPart.PELVIS:
                                    return [AssessmentType.PELVIS_FORCE];
                                default:
                                    return [];
                            }
                        case CrashTest.SIDE_POLE:
                            switch (body_part_type) {
                                case OccupantBodyPart.HEAD:
                                    return [AssessmentType.HEAD_HIC];
                                case OccupantBodyPart.PELVIS:
                                    return [AssessmentType.PELVIS_FORCE];
                                default:
                                    return [];
                            }
                    }
                    break;
            }

            /* If it gets here we support the crash test, but we haven't added it to the switch
             * statements in this function so write an error message to flag that it needs to be added. */

            ErrorMessage(
                `Crash test ${crash_test} needs to be added to <protocol.OccupantAssessmentTypes> for ${this.regulation}`
            );
        }

        return [];
    }

    /**
     * Returns the assessment types we support for the protocol
     * and the given structure
     * @param {Structure} structure Structure
     * @returns {string[]}
     * @example
     * let assessment_types = p.StructureAssessmentTypes(s);
     */
    StructureAssessmentTypes(structure) {
        /* Return assessment types based on regulation, crash test and version */

        let crash_tests = Regulation.CrashTests(this.regulation);

        let structure_type = structure.component_type;

        for (let crash_test of crash_tests) {
            if (this.crash_test != crash_test) continue;

            switch (this.regulation) {
                case Regulation.CNCAP:
                    switch (crash_test) {
                        case CrashTest.ODB:
                            switch (structure_type) {
                                case Structure.A_PILLAR:
                                    return [AssessmentType.A_PILLAR_FORE_AFT_INTRUSION];
                                case Structure.ACCELERATOR_PEDAL:
                                    return [
                                        AssessmentType.PEDAL_FORE_AFT_INTRUSION,
                                        AssessmentType.PEDAL_VERTICAL_INTRUSION
                                    ];
                                case Structure.BRAKE_PEDAL:
                                    return [
                                        AssessmentType.PEDAL_FORE_AFT_INTRUSION,
                                        AssessmentType.PEDAL_VERTICAL_INTRUSION
                                    ];
                                case Structure.CLUTCH_PEDAL:
                                    return [
                                        AssessmentType.PEDAL_FORE_AFT_INTRUSION,
                                        AssessmentType.PEDAL_VERTICAL_INTRUSION
                                    ];
                                case Structure.STEERING_COLUMN:
                                    return [
                                        AssessmentType.STEERING_COLUMN_FORE_AFT_INTRUSION,
                                        AssessmentType.STEERING_COLUMN_VERTICAL_INTRUSION
                                    ];
                                default:
                                    return [];
                            }

                        case CrashTest.MPDB:
                            return [];
                    }
                    break;

                case Regulation.EuroNCAP:
                    switch (crash_test) {
                        case CrashTest.ODB:
                            switch (structure_type) {
                                case Structure.A_PILLAR:
                                    return [AssessmentType.A_PILLAR_FORE_AFT_INTRUSION];
                                case Structure.ACCELERATOR_PEDAL:
                                    return [
                                        AssessmentType.PEDAL_FORE_AFT_INTRUSION,
                                        AssessmentType.PEDAL_VERTICAL_INTRUSION
                                    ];
                                case Structure.BRAKE_PEDAL:
                                    return [
                                        AssessmentType.PEDAL_FORE_AFT_INTRUSION,
                                        AssessmentType.PEDAL_VERTICAL_INTRUSION
                                    ];
                                case Structure.CLUTCH_PEDAL:
                                    return [
                                        AssessmentType.PEDAL_FORE_AFT_INTRUSION,
                                        AssessmentType.PEDAL_VERTICAL_INTRUSION
                                    ];
                                case Structure.STEERING_COLUMN:
                                    return [AssessmentType.STEERING_COLUMN_ALL_INTRUSION];
                                default:
                                    return [];
                            }
                        case CrashTest.FFB:
                            switch (structure_type) {
                                case Structure.STEERING_COLUMN:
                                    return [AssessmentType.STEERING_COLUMN_ALL_INTRUSION];
                                default:
                                    return [];
                            }
                        case CrashTest.MPDB:
                            return [];
                        case CrashTest.FAR_SIDE:
                            return [];
                        case CrashTest.MDB:
                            return [];
                        case CrashTest.SIDE_POLE:
                            return [];
                    }
                    break;

                case Regulation.IIHS:
                    switch (crash_test) {
                        case CrashTest.ODB:
                            switch (structure_type) {
                                case Structure.BRAKE_PEDAL:
                                    return [
                                        AssessmentType.PEDAL_FORE_AFT_INTRUSION,
                                        AssessmentType.PEDAL_LATERAL_INTRUSION,
                                        AssessmentType.PEDAL_VERTICAL_INTRUSION
                                    ];
                                case Structure.CLUTCH_PEDAL:
                                    return [
                                        AssessmentType.PEDAL_FORE_AFT_INTRUSION,
                                        AssessmentType.PEDAL_LATERAL_INTRUSION,
                                        AssessmentType.PEDAL_VERTICAL_INTRUSION
                                    ];
                                case Structure.DRIVER_LEFT_TOEPAN:
                                    return [AssessmentType.DRIVER_LEFT_TOEPAN_ALL_INTRUSION];
                                case Structure.DRIVER_CENTRE_TOEPAN:
                                    return [AssessmentType.DRIVER_CENTRE_TOEPAN_ALL_INTRUSION];
                                case Structure.DRIVER_RIGHT_TOEPAN:
                                    return [AssessmentType.DRIVER_RIGHT_TOEPAN_ALL_INTRUSION];
                                case Structure.DOOR:
                                    return [AssessmentType.DOOR_FORE_AFT_INTRUSION];
                                case Structure.DRIVER_FOOTREST:
                                    return [AssessmentType.DRIVER_FOOTREST_ALL_INTRUSION];
                                case Structure.LEFT_INSTRUMENT_PANEL:
                                    return [AssessmentType.LEFT_INSTRUMENT_PANEL_FORE_AFT_INTRUSION];
                                case Structure.RIGHT_INSTRUMENT_PANEL:
                                    return [AssessmentType.RIGHT_INSTRUMENT_PANEL_FORE_AFT_INTRUSION];
                                default:
                                    return [];
                            }
                        case CrashTest.MDB:
                            switch (structure_type) {
                                case Structure.B_PILLAR:
                                    return [AssessmentType.B_PILLAR_INTRUSION];
                                default:
                                    return [];
                            }
                        case CrashTest.SOB:
                            switch (structure_type) {
                                case Structure.BRAKE_PEDAL:
                                    return [
                                        AssessmentType.PEDAL_FORE_AFT_INTRUSION,
                                        AssessmentType.PEDAL_LATERAL_INTRUSION,
                                        AssessmentType.PEDAL_VERTICAL_INTRUSION
                                    ];
                                case Structure.CLUTCH_PEDAL:
                                    return [
                                        AssessmentType.PEDAL_FORE_AFT_INTRUSION,
                                        AssessmentType.PEDAL_LATERAL_INTRUSION,
                                        AssessmentType.PEDAL_VERTICAL_INTRUSION
                                    ];
                                case Structure.PARKING_BRAKE:
                                    return [
                                        AssessmentType.PEDAL_FORE_AFT_INTRUSION,
                                        AssessmentType.PEDAL_LATERAL_INTRUSION,
                                        AssessmentType.PEDAL_VERTICAL_INTRUSION
                                    ];
                                case Structure.DRIVER_LEFT_TOEPAN:
                                    return [AssessmentType.DRIVER_LEFT_TOEPAN_ALL_INTRUSION];
                                case Structure.DRIVER_CENTRE_TOEPAN:
                                    return [AssessmentType.DRIVER_CENTRE_TOEPAN_ALL_INTRUSION];
                                case Structure.DRIVER_RIGHT_TOEPAN:
                                    return [AssessmentType.DRIVER_RIGHT_TOEPAN_ALL_INTRUSION];
                                case Structure.PASSENGER_LEFT_TOEPAN:
                                    return [AssessmentType.PASSENGER_LEFT_TOEPAN_ALL_INTRUSION];
                                case Structure.PASSENGER_CENTRE_TOEPAN:
                                    return [AssessmentType.PASSENGER_CENTRE_TOEPAN_ALL_INTRUSION];
                                case Structure.PASSENGER_RIGHT_TOEPAN:
                                    return [AssessmentType.PASSENGER_RIGHT_TOEPAN_ALL_INTRUSION];
                                case Structure.DOOR:
                                    return [AssessmentType.DOOR_FORE_AFT_INTRUSION];
                                case Structure.DRIVER_FOOTREST:
                                    return [AssessmentType.DRIVER_FOOTREST_ALL_INTRUSION];
                                case Structure.PASSENGER_FOOTREST:
                                    return [AssessmentType.PASSENGER_FOOTREST_ALL_INTRUSION];
                                case Structure.LEFT_INSTRUMENT_PANEL:
                                    return [AssessmentType.LEFT_INSTRUMENT_PANEL_FORE_AFT_INTRUSION];
                                case Structure.RIGHT_INSTRUMENT_PANEL:
                                    return [AssessmentType.RIGHT_INSTRUMENT_PANEL_FORE_AFT_INTRUSION];
                                case Structure.STEERING_COLUMN:
                                    return [AssessmentType.STEERING_COLUMN_FORE_AFT_INTRUSION];
                                case Structure.DRIVER_UPPER_DASH:
                                    return [AssessmentType.DRIVER_UPPER_DASH_ALL_INTRUSION];
                                case Structure.PASSENGER_UPPER_DASH:
                                    return [AssessmentType.PASSENGER_UPPER_DASH_ALL_INTRUSION];
                                case Structure.PASSENGER_LEFT_LOWER_DASH:
                                    return [AssessmentType.PASSENGER_LEFT_LOWER_DASH_ALL_INTRUSION];
                                case Structure.PASSENGER_RIGHT_LOWER_DASH:
                                    return [AssessmentType.PASSENGER_RIGHT_LOWER_DASH_ALL_INTRUSION];
                                case Structure.PASSENGER_CENTRE_DASH:
                                    return [AssessmentType.PASSENGER_CENTRE_DASH_ALL_INTRUSION];
                                case Structure.DRIVER_UPPER_HINGE_1:
                                    return [AssessmentType.DRIVER_UPPER_HINGE_1_ALL_INTRUSION];
                                case Structure.DRIVER_UPPER_HINGE_2:
                                    return [AssessmentType.DRIVER_UPPER_HINGE_2_ALL_INTRUSION];
                                case Structure.DRIVER_UPPER_HINGE_3:
                                    return [AssessmentType.DRIVER_UPPER_HINGE_3_ALL_INTRUSION];
                                case Structure.DRIVER_LOWER_HINGE_1:
                                    return [AssessmentType.DRIVER_LOWER_HINGE_1_ALL_INTRUSION];
                                case Structure.DRIVER_LOWER_HINGE_2:
                                    return [AssessmentType.DRIVER_LOWER_HINGE_2_ALL_INTRUSION];
                                case Structure.DRIVER_LOWER_HINGE_3:
                                    return [AssessmentType.DRIVER_LOWER_HINGE_3_ALL_INTRUSION];
                                case Structure.PASSENGER_LOWER_HINGE_1:
                                    return [AssessmentType.PASSENGER_LOWER_HINGE_1_ALL_INTRUSION];
                                case Structure.PASSENGER_LOWER_HINGE_2:
                                    return [AssessmentType.PASSENGER_LOWER_HINGE_2_ALL_INTRUSION];
                                case Structure.PASSENGER_LOWER_HINGE_3:
                                    return [AssessmentType.PASSENGER_LOWER_HINGE_3_ALL_INTRUSION];
                                case Structure.PASSENGER_UPPER_HINGE_1:
                                    return [AssessmentType.PASSENGER_UPPER_HINGE_1_ALL_INTRUSION];
                                case Structure.PASSENGER_UPPER_HINGE_2:
                                    return [AssessmentType.PASSENGER_UPPER_HINGE_2_ALL_INTRUSION];
                                case Structure.PASSENGER_UPPER_HINGE_3:
                                    return [AssessmentType.PASSENGER_UPPER_HINGE_3_ALL_INTRUSION];
                                case Structure.DRIVER_ROCKER_PANEL_1:
                                    return [AssessmentType.DRIVER_ROCKER_PANEL_1_LATERAL_INTRUSION];
                                case Structure.DRIVER_ROCKER_PANEL_2:
                                    return [AssessmentType.DRIVER_ROCKER_PANEL_2_LATERAL_INTRUSION];
                                case Structure.DRIVER_ROCKER_PANEL_3:
                                    return [AssessmentType.DRIVER_ROCKER_PANEL_3_LATERAL_INTRUSION];
                                case Structure.PASSENGER_ROCKER_PANEL_1:
                                    return [AssessmentType.PASSENGER_ROCKER_PANEL_1_LATERAL_INTRUSION];
                                case Structure.PASSENGER_ROCKER_PANEL_2:
                                    return [AssessmentType.PASSENGER_ROCKER_PANEL_2_LATERAL_INTRUSION];
                                case Structure.PASSENGER_ROCKER_PANEL_3:
                                    return [AssessmentType.PASSENGER_ROCKER_PANEL_3_LATERAL_INTRUSION];
                                default:
                                    return [];
                            }
                    }
                    break;

                case Regulation.USNCAP:
                    switch (crash_test) {
                        case CrashTest.FFB:
                            return [];
                        case CrashTest.MDB:
                            return [];
                        case CrashTest.SIDE_POLE:
                            return [];
                    }
                    break;
            }

            /* If it gets here we support the crash test, but we haven't added it to the switch
             * statements in this function so write an error message to flag that it needs to be added. */

            ErrorMessage(
                `Crash test ${crash_test} needs to be added to <protocol.StructureAssessmentTypes> for ${this.regulation}`
            );
        }

        return [];
    }

    /**
     * @typedef {Object} ViscousCriterionConstants
     * @property {number} A Viscous Criterion Constant A (for m/s)
     * @property {number} B Viscous Criterion Constant B (for m/s)
     */

    /**
     * Returns the Viscous Criterion Constants
     * @returns {?ViscousCriterionConstants}
     */
    GetViscousCriterionConstants() {
        /* These values are taken from the T/HIS manual Appendix E.3
         *
         * A = 1.3 for frontal, 1.0 side impacts
         * B = 0.229 for frontal, 0.14 side impacts
         */

        switch (this.crash_test) {
            case CrashTest.ODB:
            case CrashTest.FFB:
            case CrashTest.MPDB:
                return {
                    A: 1.3,
                    B: 0.229
                };

            case CrashTest.MDB:
            case CrashTest.SIDE_POLE:
            case CrashTest.SOB:
            case CrashTest.FAR_SIDE:
                return {
                    A: 1.0,
                    B: 0.14
                };

            default:
                ErrorMessage(
                    `Unknown crash test ${this.crash_test} in <Protocol>.GetViscousCriterion(). Returning null.`
                );
                return null;
        }
    }

    /**
     * Returns the datums for the given assessment
     * @param {string} assessment Assessment type, e.g. AssessmentType.NECK_AXIAL
     * @returns {?AssessmentDatums}
     * @example
     * let assessment_datums = p.GetDatumsByAssessment(AssessmentType.NECK_EXTENSION);
     * for (let datum of assessment_datums.datums)
     * {
     *     datum.Plot();
     * }
     */
    GetDatumsByAssessment(assessment) {
        for (let assessment_datums of this.assessment_datums) {
            if (assessment_datums.assessment == assessment) {
                return assessment_datums;
            }
        }

        return null;
    }

    /**
     * Returns the HIC window (in seconds) for the protocol and the given occupant
     * @param {WorkflowOccupant} occupant Workflow occupant
     * @returns {number}
     * @example
     * // Get the HIC window to use for Protocol p and occupant o
     * let hic_window = p.HICWindow(o);
     */
    HICWindow(occupant) {
        if (!occupant) {
            throw new Error("No occupant specified in <protocol.HICWindow>");
        }

        if (this.regulation == Regulation.CNCAP) {
            if (this.crash_test == CrashTest.ODB) {
                if (occupant.position == WorkflowOccupant.DRIVER) {
                    return 0.036;
                } else if (occupant.position == WorkflowOccupant.PASSENGER) {
                    if (occupant.front_rear == WorkflowOccupant.FRONT) {
                        return 0.036;
                    } else if (occupant.front_rear == WorkflowOccupant.REAR) {
                        return 0.015;
                    }
                }
            } else if (this.crash_test == CrashTest.MPDB) {
                return 0.015;
            }
        } else if (this.regulation == Regulation.EuroNCAP) {
            if (
                this.crash_test == CrashTest.FAR_SIDE ||
                this.crash_test == CrashTest.FFB ||
                this.crash_test == CrashTest.MPDB ||
                this.crash_test == CrashTest.MDB ||
                this.crash_test == CrashTest.ODB ||
                this.crash_test == CrashTest.SIDE_POLE
            ) {
                return 0.015;
            }
        } else if (this.regulation == Regulation.IIHS) {
            if (
                this.crash_test == CrashTest.MDB ||
                this.crash_test == CrashTest.ODB ||
                this.crash_test == CrashTest.SOB
            ) {
                return 0.015;
            }
        } else if (this.regulation == Regulation.USNCAP) {
            if (this.crash_test == CrashTest.FFB) {
                return 0.036;
            } else if (this.crash_test == CrashTest.MDB || this.crash_test == CrashTest.SIDE_POLE) {
                return 0.015;
            }
        }

        /* Get here and we don't know what to do, so return a default of 0.015*/

        ErrorMessage(
            `Unknown regulation ${this.regulation} and crash test ${this.crash_test} in <protocol.HICWindow>. Returning 0.015 seconds.`
        );

        return 0.015;
    }

    /**
     * String representation
     * @returns {string}
     */
    toString() {
        return `${this.regulation} ${this.crash_test} ${this.version}`;
    }
}

class Protocols {
    /**
     * Class to store supported protocols with getters for
     * protocols based on crash test, regulation and occupant position/side. It is not instantiable. It behaves like a singleton
     * where the files should only be imported once.
     *
     * if we want to reimport the json files we simply need to call Protocols.importAllOccupantJSONFiles()
     * again and it will overwrite this._protocols property returned by OccupantVersion.protocols
     */

    /**
     * Initialises the Protocols class for the specified Workflows definitions directory
     * @example
     * Protocols.Initialise();
     */
    static Initialise() {
        if (!this._protocols) Protocols._importAllProtocolJSONFiles();
    }

    /**
     * Returns an array of all the Protocol classes.
     * @returns {Protocol[]}
     * @example
     * let protocols = Protocols.GetAll();
     */
    static GetAll() {
        return this._protocols;
    }

    /**
     * TODO this will supercede the versions and structure types once fully implemented
     * this class method imports all the protocol json files in the protocols directory
     * and returns an array of protocol objects.
     * TODO implement FromJSONFile
     *
     * IMPORTANT we ideally want to ensure that this is only called once
     * as it is relatively expensive finding and reading lots of JSON files
     */
    static _importAllProtocolJSONFiles() {
        this._protocols = [];

        var protocol_directory = JSPath.GetProtocolsDirectory();

        Message("protocol_directory = " + protocol_directory);

        var file_list = find_all_json_files(protocol_directory);

        //Message(file_list.join("\n"));

        if (file_list.length == 0) {
            throw new Error(`Failed to parse protocol data from ${protocol_directory}`);
        }

        for (let filepath of file_list) {
            let protocol;

            try {
                if (/\.json$/i.test(filepath)) {
                    protocol = Protocol.FromJSONFile(filepath);

                    if (protocol) this._protocols.push(protocol);
                    else throw new Error(`Failed to parse protocol data from ${filepath}`);

                    //TODO write check for protocols (or add check to FromJSONFile and throw errors)
                }
            } catch (error) {
                ErrorMessage(`${error} so skipping file ${filepath}`);
            }
        }
    }

    // /* Static methods */

    /**
     * Returns an array of all the protocol regulations
     * @returns {string[]}
     * @example
     * let regulations = Protocols.GetAllRegulations();
     */
    static GetAllRegulations() {
        let protocols = Protocols.GetAll();
        let regulations = [];

        for (let protocol of protocols) {
            if (regulations.indexOf(protocol.regulation) == -1) {
                regulations.push(protocol.regulation);
            }
        }
        //TODO: SORT alphabetically?

        return regulations.sort();
    }

    /**
     * Returns an array of all the supported crash test types
     * @returns {string[]}
     * @example
     * let crash_tests = Protocols.GetAllCrashTests();
     */
    static GetAllCrashTests() {
        let protocols = Protocols.GetAll();
        let crash_tests = [];

        for (let protocol of protocols) {
            if (crash_tests.indexOf(protocol.crash_test) == -1) {
                crash_tests.push(protocol.crash_test);
            }
        }
        //TODO: SORT alphabetically?

        return crash_tests.sort();
    }

    /**
     * Returns an array of only the protocols matching the crash test type, regulation body and verion
     * Note that passing 'ALL' (or invalid value) for any parameter means that the filter will not be applied for that parameter
     * @param {string} [regulation = 'ALL'] regulation body e.g. EuroNCAP, IIHS...
     * @param {string} [crash_test = 'ALL'] type e.g. ODB, Side Pole...
     * @param {string} [version = 'ALL'] regulation vesion
     * @returns {Protocol[]}
     * @example
     * let protocols = Protocols.GetOnly();
     */
    static GetOnly(regulation = "ALL", crash_test = "ALL", version = "ALL") {
        let protocols = Protocols.GetAll();
        let temp_protocols = [];

        if (!/ALL/i.test(crash_test)) {
            for (let protocol of protocols) {
                //Message(`protocol.crash_test ${protocol.crash_test}`);
                if (protocol.crash_test == crash_test) {
                    temp_protocols.push(protocol);
                }
            }
            if (temp_protocols.length == 0) {
                Message(`Crash test ${crash_test} was not found in any protocols so ignoring this filter`);
                crash_test = "ALL";
                return [];
            } else {
                protocols = temp_protocols;
            }

            temp_protocols = [];
        }

        if (!/ALL/i.test(regulation)) {
            for (let protocol of protocols) {
                if (protocol.regulation == regulation) {
                    temp_protocols.push(protocol);
                }
            }
            if (temp_protocols.length == 0) {
                Message(`Regulation ${regulation} was not found in any protocols so ignoring this filter`);
                regulation = "ALL";
                return [];
            } else {
                protocols = temp_protocols;
            }
            temp_protocols = [];
        }

        if (!/ALL/i.test(version)) {
            for (let protocol of protocols) {
                if (protocol.version == version) {
                    temp_protocols.push(protocol);
                }
            }
            if (temp_protocols.length == 0) {
                Message(`Version ${version} was not found in any protocols so ignoring this filter`);
                return [];
            } else {
                protocols = temp_protocols;
            }
            temp_protocols = [];
        }

        return protocols;
    }

    /**
     * Returns a single ProtocolVehicle for the specified crash test type, regulation body and version.
     * Returns null if none or more than one matching ProtocolVehicle found.
     * @param {string} regulation regulation body e.g. EuroNCAP, IIHS, ...
     * @param {string} crash_test Crash test e.g. ODB, Side Pole, ...
     * @param {string} version Protocol version e.g. 2022, XVIII, ...
     * @returns {?ProtocolVehicle}
     * @example
     * let vehicle = Protocols.GetProtocolVehicle("EuroNCAP", "ODB", "2017");
     */
    static GetProtocolVehicle(regulation, crash_test, version) {
        let protocols = Protocols.GetOnly(regulation, crash_test, version);

        if (protocols.length == 0) {
            ErrorMessage(
                `Tried to get protocol vehicle for ${regulation} ${crash_test} ${version} which is unsupported.`
            );
            return null;
        } else if (protocols.length > 1) {
            WarningMessage(
                `Multiple (${protocols.length}) protocols found for ${regulation} ${crash_test} ${version}. The first one found will be used, but please ensure the protocol jsons are unique. `
            );
        }

        return protocols[0].vehicle;
    }
}