modules/shared/occupant.mjs

// module: TRUE

/**
 * The occupant module provides classes for storing the entities
 * in each body part of an occupant that can be used to extract
 * data from LS-DYNA results.<br><br>
 */

import { BaseEntity, BaseComponent, BaseEntityWidgets, BaseComponentWidgets } from "./base.mjs";
import { Measurement } from "./measurement.mjs";
import { OccupantVersion } from "./occupant_version.mjs";
import { WorkflowOccupant } from "./workflow_occupant.mjs";
export {
    Occupant,
    OccupantProduct,
    OccupantSupplier,
    OccupantPhysiology,
    OccupantEntity,
    OccupantBodyPart,
    OccupantEntityWidgets,
    BodyPartWidgets,
    OccupantWidgets,
    OccupantChestRotationFactors
};

/** Class representing an entity to extract data from an occupant<br><br>
 * It extends the BaseEntity class
 * @extends BaseEntity
 * @param {string} entity_type Entity type constant
 * @param {number|string} id Entity id (label or database history name)
 * @param {string} name Entity name (this is used as the label in the GUI)
 * @param {string} tag Entity tag (name in the default entity id csv file)
 * @param {Measurement[]} measurements List of raw measurements that can be read from the entity
 * @param {string} [history_title=""] The entity database history title
 * @param {string} [iso=""] The entity iso name
 * @example
 * let entity = new OccupantEntity(
 *                          OccupantEntity.NODE,
 *                          1,
 *                          "Node X Acceleration",
 *                          OccupantEntity.HEAD_NODE,
 *                          [new Measurement(Measurement.X_ACCELERATION, "AX")]);
 */
class OccupantEntity extends BaseEntity {
    constructor(entity_type, id, name, tag, measurements, history_title = "", iso = "") {
        super(entity_type, id, name, tag, measurements, history_title, iso);
    }

    /**
     * This is used to create the entities (with name and measurements) when reading in the json occupants
     * as the json strips the name and measurement data out to keep it clean and easier to read
     * @param {string} tag the entity tag e.g. HEAD_NODE_Y
     * @param {string} entity_type the entity type e.g. node, beam, spring tr, section...
     * @param {string|number} id the id of the entity
     * @param {string} [history_title=""] The entity database history title
     * @param {string} [iso=""] The entity iso name
     */
    static FromTagAndType(tag, entity_type, id, history_title = "", iso = "") {
        let name_and_measurements = OccupantEntity.GetNameAndMeasurements(tag, entity_type);
        let name = name_and_measurements.name;
        let measurements = name_and_measurements.measurements;

        return new OccupantEntity(entity_type, id, name, tag, measurements, history_title, iso);
    }

    /**
     * construct an OccupantEntity from JSON
     * @param {Object} json
     * @returns {?OccupantEntity}
     */
    static FromJSON(json) {
        /**
         * check that expected keys exist. Note name and measurements are optional
         * as they can be generated using OccupantEntity.FromTagAndType()
         */
        let expected_keys = ["entity_type", "id", "tag", "iso"];

        for (let key of expected_keys) {
            if (!(key in json)) {
                throw new Error(`${key} is not a key in the body part entity`);
            }
        }

        //optional properties in the entity
        if (!json.history_title) json.history_title = "";
        if (!json.iso) json.iso = "";

        if (json.measurements && json.name) {
            return new OccupantEntity(
                json.entity_type,
                json.id,
                json.name,
                json.tag,
                json.measurements,
                json.history_title,
                json.iso
            );
        }

        //construct OccupantEntity from tag and type if measurement&/name preoperties missing
        return OccupantEntity.FromTagAndType(json.tag, json.entity_type, json.id, json.history_title, json.iso);
    }

    /* Class constant getters (read-only) */

    // #region Tags

    /**
     * HEAD_NODE entity tag
     * @type {string} */
    static get HEAD_NODE() {
        return "HEAD_NODE";
    }
    /**
     * HEAD_NODE_Y entity tag
     * @type {string} */
    static get HEAD_NODE_Y() {
        return "HEAD_NODE_Y";
    }
    /**
     * HEAD_NODE_Z entity tag
     * @type {string} */
    static get HEAD_NODE_Z() {
        return "HEAD_NODE_Z";
    }
    /**
     * NECK_LOADCELL entity tag
     * @type {string} */
    static get NECK_LOADCELL() {
        return "NECK_LOADCELL";
    }
    /**
     * NECK_UPPER_BEAM entity tag
     * @type {string} */
    static get NECK_UPPER_BEAM() {
        return "NECK_UPPER_BEAM";
    }
    /**
     * NECK_LOWER_BEAM entity tag
     * @type {string}
     */
    static get NECK_LOWER_BEAM() {
        return "NECK_LOWER_BEAM";
    }
    /**
     * CHEST_SPRING entity tag
     */
    static get CHEST_SPRING() {
        return "CHEST_SPRING";
    }
    /**
     * CHEST_SPRING_UPP entity tag
     * @type {string}
     */
    static get CHEST_SPRING_UPP() {
        return "CHEST_SPRING_UPP";
    }
    /**
     * CHEST_SPRING_LOW entity tag
     * @type {string}
     */
    static get CHEST_SPRING_LOW() {
        return "CHEST_SPRING_LOW";
    }
    /**
     * CHEST_SPRING_MID entity tag
     * @type {string}
     */
    static get CHEST_SPRING_MID() {
        return "CHEST_SPRING_MID";
    }
    /**
     * CHEST_NODE_X entity tag
     */
    static get CHEST_NODE_X() {
        return "CHEST_NODE_X";
    }
    /**
     * CHEST_NODE_Y entity tag
     * @type {string}
     */
    static get CHEST_NODE_Y() {
        return "CHEST_NODE_Y";
    }
    /**
     * CHEST_NODE_Z entity tag
     * @type {string}
     */
    static get CHEST_NODE_Z() {
        return "CHEST_NODE_Z";
    }
    /**
     * CHEST_COMPRESSION_LOWER_LEFT_NODE_1 entity tag
     * @type {string}
     */
    static get CHEST_COMPRESSION_LOWER_LEFT_NODE_1() {
        return "CHEST_COMPRESSION_LOWER_LEFT_NODE_1";
    }
    /**
     * CHEST_COMPRESSION_LOWER_LEFT_NODE_2 entity tag
     * @type {string}
     */
    static get CHEST_COMPRESSION_LOWER_LEFT_NODE_2() {
        return "CHEST_COMPRESSION_LOWER_LEFT_NODE_2";
    }
    /**
     * CHEST_COMPRESSION_UPPER_LEFT_NODE_1 entity tag
     * @type {string}
     */
    static get CHEST_COMPRESSION_UPPER_LEFT_NODE_1() {
        return "CHEST_COMPRESSION_UPPER_LEFT_NODE_1";
    }
    /**
     * CHEST_COMPRESSION_UPPER_LEFT_NODE_2 entity tag
     * @type {string}
     */
    static get CHEST_COMPRESSION_UPPER_LEFT_NODE_2() {
        return "CHEST_COMPRESSION_UPPER_LEFT_NODE_2";
    }
    /**
     * CHEST_COMPRESSION_LOWER_RIGHT_NODE_1 entity tag
     * @type {string}
     */
    static get CHEST_COMPRESSION_LOWER_RIGHT_NODE_1() {
        return "CHEST_COMPRESSION_LOWER_RIGHT_NODE_1";
    }
    /**
     * CHEST_COMPRESSION_LOWER_RIGHT_NODE_2 entity tag
     * @type {string}
     */
    static get CHEST_COMPRESSION_LOWER_RIGHT_NODE_2() {
        return "CHEST_COMPRESSION_LOWER_RIGHT_NODE_2";
    }
    /**
     * CHEST_COMPRESSION_UPPER_RIGHT_NODE_1 entity tag
     * @type {string}
     */
    static get CHEST_COMPRESSION_UPPER_RIGHT_NODE_1() {
        return "CHEST_COMPRESSION_UPPER_RIGHT_NODE_1";
    }
    /**
     * CHEST_COMPRESSION_UPPER_RIGHT_NODE_2 entity tag
     * @type {string}
     */
    static get CHEST_COMPRESSION_UPPER_RIGHT_NODE_2() {
        return "CHEST_COMPRESSION_UPPER_RIGHT_NODE_2";
    }
    /**
     * CHEST_UPPER_RIB_SPRING_ROT entity tag
     * @type {string}
     */
    static get CHEST_UPPER_RIB_SPRING_ROT() {
        return "CHEST_UPPER_RIB_SPRING_ROT";
    }
    /**
     * CHEST_MIDDLE_RIB_SPRING_ROT entity tag
     * @type {string}
     */
    static get CHEST_MIDDLE_RIB_SPRING_ROT() {
        return "CHEST_MIDDLE_RIB_SPRING_ROT";
    }
    /**
     * CHEST_BOTTOM_RIB_SPRING_ROT entity tag
     * @type {string}
     */
    static get CHEST_BOTTOM_RIB_SPRING_ROT() {
        return "CHEST_BOTTOM_RIB_SPRING_ROT";
    }
    /**
     * CHEST_UPPER_RIB_SPRING_TRANS entity tag
     * @type {string}
     */
    static get CHEST_UPPER_RIB_SPRING_TRANS() {
        return "CHEST_UPPER_RIB_SPRING_TRANS";
    }
    /**
     * CHEST_MIDDLE_RIB_SPRING_TRANS entity tag
     * @type {string}
     */
    static get CHEST_MIDDLE_RIB_SPRING_TRANS() {
        return "CHEST_MIDDLE_RIB_SPRING_TRANS";
    }
    /**
     * CHEST_BOTTOM_RIB_SPRING_TRANS entity tag
     * @type {string}
     */
    static get CHEST_BOTTOM_RIB_SPRING_TRANS() {
        return "CHEST_BOTTOM_RIB_SPRING_TRANS";
    }
    /**
     * CHEST_TOP_THORAX_TRANSDUCER entity tag
     * @type {string}
     */
    static get CHEST_TOP_THORAX_TRANSDUCER() {
        return "TOP_THORAX_TRANSDUCER";
    }
    /**
     * CHEST_MIDDLE_THORAX_TRANSDUCER entity tag
     * @type {string}
     */
    static get CHEST_MIDDLE_THORAX_TRANSDUCER() {
        return "MIDDLE_THORAX_TRANSDUCER";
    }
    /**
     * CHEST_BOTTOM_THORAX_TRANSDUCER entity tag
     * @type {string}
     */
    static get CHEST_BOTTOM_THORAX_TRANSDUCER() {
        return "BOTTOM_THORAX_TRANSDUCER";
    }
    /**
     * SHOULDER_TRANSDUCER entity tag
     * @type {string}
     */
    static get SHOULDER_TRANSDUCER() {
        return "SHOULDER_TRANSDUCER";
    }
    /**
     * SHOULDER_LEFT_BEAM entity tag
     * @type {string}
     */
    static get SHOULDER_LEFT_BEAM() {
        return "SHOULDER_LEFT_BEAM";
    }
    /**
     * SHOULDER_RIGHT_BEAM entity tag
     * @type {string}
     */
    static get SHOULDER_RIGHT_BEAM() {
        return "SHOULDER_RIGHT_BEAM";
    }
    /**
     * ABDOMEN_COMPRESSION_LEFT_NODE_1 entity tag
     * @type {string}
     */
    static get ABDOMEN_COMPRESSION_LEFT_NODE_1() {
        return "ABDOMEN_COMPRESSION_LEFT_NODE_1";
    }
    /**
     * ABDOMEN_COMPRESSION_LEFT_NODE_2 entity tag
     * @type {string}
     */
    static get ABDOMEN_COMPRESSION_LEFT_NODE_2() {
        return "ABDOMEN_COMPRESSION_LEFT_NODE_2";
    }
    /**
     * ABDOMEN_COMPRESSION_RIGHT_NODE_1 entity tag
     * @type {string}
     */
    static get ABDOMEN_COMPRESSION_RIGHT_NODE_1() {
        return "ABDOMEN_COMPRESSION_RIGHT_NODE_1";
    }
    /**
     * ABDOMEN_COMPRESSION_RIGHT_NODE_2 entity tag
     * @type {string}
     */
    static get ABDOMEN_COMPRESSION_RIGHT_NODE_2() {
        return "ABDOMEN_COMPRESSION_RIGHT_NODE_2";
    }
    /**
     * ABDOMEN_BEAM_FRNT entity tag
     * @type {string}
     */
    static get ABDOMEN_BEAM_FRNT() {
        return "ABDOMEN_BEAM_FRNT";
    }
    /**
     * ABDOMEN_BEAM_MID entity tag
     * @type {string}
     */
    static get ABDOMEN_BEAM_MID() {
        return "ABDOMEN_BEAM_MID";
    }
    /**
     * ABDOMEN_BEAM_BACK entity tag
     * @type {string}
     */
    static get ABDOMEN_BEAM_BACK() {
        return "ABDOMEN_BEAM_BACK";
    }
    /**
     * TOP_ABDOMEN_TRANSDUCER entity tag
     * @type {string}
     */
    static get TOP_ABDOMEN_TRANSDUCER() {
        return "TOP_ABDOMEN_TRANSDUCER";
    }
    /**
     * BOTTOM_ABDOMEN_TRANSDUCER entity tag
     * @type {string}
     */
    static get BOTTOM_ABDOMEN_TRANSDUCER() {
        return "BOTTOM_ABDOMEN_TRANSDUCER";
    }
    /**
     * ABDOMEN_UPPER_SPRING_ROT entity tag
     * @type {string}
     */
    static get ABDOMEN_UPPER_SPRING_ROT() {
        return "ABDOMEN_UPPER_SPRING_ROT";
    }
    /**
     * ABDOMEN_LOWER_SPRING_ROT entity tag
     * @type {string}
     */
    static get ABDOMEN_LOWER_SPRING_ROT() {
        return "ABDOMEN_LOWER_SPRING_ROT";
    }
    /**
     * ABDOMEN_UPPER_SPRING_TRANS entity tag
     * @type {string}
     */
    static get ABDOMEN_UPPER_SPRING_TRANS() {
        return "ABDOMEN_UPPER_SPRING_TRANS";
    }
    /**
     * ABDOMEN_LOWER_SPRING_TRANS entity tag
     * @type {string}
     */
    static get ABDOMEN_LOWER_SPRING_TRANS() {
        return "ABDOMEN_LOWER_SPRING_TRANS";
    }
    /**
     * LUMBAR_BEAM entity tag
     * @type {string}
     */
    static get LUMBAR_BEAM() {
        return "LUMBAR_BEAM";
    }
    /**
     * PELVIS_BEAM entity tag
     * @type {string}
     */
    static get PELVIS_BEAM() {
        return "PELVIS_BEAM";
    }
    /**
     * PUBIC_SYMPHYSIS_BEAM entity tag
     * @type {string}
     */
    static get PUBIC_SYMPHYSIS_BEAM() {
        return "PUBIC_SYMPHYSIS_BEAM";
    }
    /**
     * LEFT_KNEE_TRANSDUCER entity tag
     * @type {string}
     */
    static get LEFT_KNEE_TRANSDUCER() {
        return "LEFT_KNEE_TRANSDUCER";
    }
    /**
     * RIGHT_KNEE_TRANSDUCER entity tag
     * @type {string}
     */
    static get RIGHT_KNEE_TRANSDUCER() {
        return "RIGHT_KNEE_TRANSDUCER";
    }
    /**
     * LEFT_FEMUR_LOADCELL entity tag
     * @type {string}
     */
    static get LEFT_FEMUR_LOADCELL() {
        return "LEFT_FEMUR_LOADCELL";
    }
    /**
     * RIGHT_FEMUR_LOADCELL entity tag
     * @type {string}
     */
    static get RIGHT_FEMUR_LOADCELL() {
        return "RIGHT_FEMUR_LOADCELL";
    }
    /**
     * BOTTOM_FEMUR_BEAM entity tag
     * @type {string}
     */
    static get BOTTOM_FEMUR_BEAM() {
        return "BOTTOM_FEMUR_BEAM";
    }
    /**
     * UPPER_FEMUR_BEAM entity tag
     * @type {string}
     */
    static get UPPER_FEMUR_BEAM() {
        return "UPPER_FEMUR_BEAM";
    }
    /**
     * LEFT_UPPER_TIBIA_LOADCELL entity tag
     * @type {string}
     */
    static get LEFT_UPPER_TIBIA_LOADCELL() {
        return "LEFT_UPPER_TIBIA_LOADCELL";
    }
    /**
     * RIGHT_UPPER_TIBIA_LOADCELL entity tag
     * @type {string}
     */
    static get RIGHT_UPPER_TIBIA_LOADCELL() {
        return "RIGHT_UPPER_TIBIA_LOADCELL";
    }
    /**
     * LEFT_LOWER_TIBIA_LOADCELL entity tag
     * @type {string}
     */
    static get LEFT_LOWER_TIBIA_LOADCELL() {
        return "LEFT_LOWER_TIBIA_LOADCELL";
    }
    /**
     * RIGHT_LOWER_TIBIA_LOADCELL entity tag
     * @type {string}
     */
    static get RIGHT_LOWER_TIBIA_LOADCELL() {
        return "RIGHT_LOWER_TIBIA_LOADCELL";
    }
    /**
     * LEFT_FOOT_NODE entity tag
     * @type {string}
     */
    static get LEFT_FOOT_NODE() {
        return "LEFT_FOOT_NODE";
    }
    /**
     * RIGHT_FOOT_NODE entity tag
     * @type {string}
     */
    static get RIGHT_FOOT_NODE() {
        return "RIGHT_FOOT_NODE";
    }
    /**
     * XSECTION_ACETABULAR_ID entity tag
     * @type {string}
     */
    static get XSECTION_ACETABULAR_ID() {
        return "XSECTION_ACETABULAR_ID";
    }
    /**
     * XSECTION_ILIAC_ID entity tag
     * @type {string}
     */
    static get XSECTION_ILIAC_ID() {
        return "XSECTION_ILIAC_ID";
    }
    /**
     * LEFT_ACETABULUM_LOADCELL entity tag
     * @type {string}
     */
    static get LEFT_ACETABULUM_LOADCELL() {
        return "LEFT_ACETABULUM_LOADCELL";
    }
    /**
     * RIGHT_ACETABULUM_LOADCELL entity tag
     * @type {string}
     */
    static get RIGHT_ACETABULUM_LOADCELL() {
        return "RIGHT_ACETABULUM_LOADCELL";
    }

    // #endregion

    /**
     * The NameMeasurement object returned by GetNameAndMeasurements and used in OccupantEntity.FromTagAndType
     * @typedef {Object} NameMeasurement
     * @property {string} name measurement name (appears in gui label)
     * @property {Measurement[]} measurements measurements
     */
    /**
     * Returns array of measurments based on the tag and the entity_type
     * @param {string} tag e.g. "HEAD_NODE"
     * @param {string} entity_type
     * @returns {NameMeasurement} //TODO GOOGLE THIS or ASK Chris if no luck
     */
    static GetNameAndMeasurements(tag, entity_type) {
        switch (tag) {
            case OccupantEntity.HEAD_NODE:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Head Node (X-accelerometer)",
                            measurements: [new Measurement(Measurement.X_ACCELERATION, "AX")]
                        };
                        break;
                }
                break;
            case OccupantEntity.HEAD_NODE_Y:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Head Node (Y-accelerometer)",
                            measurements: [new Measurement(Measurement.Y_ACCELERATION, "AY")]
                        };
                        break;
                }
                break;
            case OccupantEntity.HEAD_NODE_Z:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Head Node (Z-accelerometer)",
                            measurements: [new Measurement(Measurement.Z_ACCELERATION, "AZ")]
                        };
                        break;
                }
                break;
            case OccupantEntity.NECK_LOADCELL:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Neck Loadcell (Beam)",
                            measurements: [
                                new Measurement(Measurement.SHEAR, "NX"),
                                new Measurement(Measurement.AXIAL, "NZ"),
                                new Measurement(Measurement.BENDING, "MY")
                            ]
                        };
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Neck Loadcell (X-Section)",
                            measurements: [
                                new Measurement(Measurement.SHEAR, "FX"),
                                new Measurement(Measurement.AXIAL, "FZ"),
                                new Measurement(Measurement.BENDING, "MY")
                            ]
                        };
                    case OccupantEntity.JOINT:
                        return {
                            name: "Neck Loadcell (Joint)",
                            measurements: []
                        };
                        break; /* TODO - need to work out what the data components are for a joint */
                }
                break;
            case OccupantEntity.NECK_UPPER_BEAM:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Upper Neck (Beam)",
                            measurements: [
                                new Measurement(Measurement.UPPER_AXIAL, "NZ"),
                                new Measurement(Measurement.UPPER_FLEXION, "MX"),
                                new Measurement(Measurement.UPPER_EXTENSION, "MY")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.NECK_LOWER_BEAM:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Lower Neck (Beam)",
                            measurements: [
                                new Measurement(Measurement.LOWER_AXIAL, "NZ"),
                                new Measurement(Measurement.LOWER_FLEXION, "MX"),
                                new Measurement(Measurement.LOWER_EXTENSION, "MY")
                            ]
                        };
                        break;
                }
                break;

            case OccupantEntity.CHEST_SPRING_UPP:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Upper Rib Deflection (Spring)",
                            measurements: [new Measurement(Measurement.UPPER_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_SPRING_MID:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Middle Rib Deflection (Spring)",
                            measurements: [new Measurement(Measurement.MID_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_SPRING_LOW:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Lower Rib Deflection (Spring)",
                            measurements: [new Measurement(Measurement.LOWER_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;

            case OccupantEntity.CHEST_SPRING:
                switch (entity_type) {
                    case OccupantEntity.SPRING_ROTATIONAL:
                        return {
                            name: "Chest Transducer (Spring)",
                            measurements: [new Measurement(Measurement.ROTATION, "RT")]
                        };
                        break;
                }
                break;

            case OccupantEntity.CHEST_NODE_X:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Chest Node (X Accn)",
                            measurements: [new Measurement(Measurement.X_ACCELERATION, "AX")]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_NODE_Y:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Chest Node (Y Accn)",
                            measurements: [new Measurement(Measurement.Y_ACCELERATION, "AY")]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_NODE_Z:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Chest Node (Z Accn)",
                            measurements: [new Measurement(Measurement.Z_ACCELERATION, "AZ")]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_COMPRESSION_LOWER_LEFT_NODE_1:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Chest Left Lower IR-TRACC (node1)",
                            measurements: [
                                new Measurement(Measurement.LOWER_LEFT_COMPRESSION_X_1, "DX"),
                                new Measurement(Measurement.LOWER_LEFT_COMPRESSION_Y_1, "DY"),
                                new Measurement(Measurement.LOWER_LEFT_COMPRESSION_Z_1, "DZ")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_COMPRESSION_LOWER_LEFT_NODE_2:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Chest Left Lower IR-TRACC (node2)",
                            measurements: [
                                new Measurement(Measurement.LOWER_LEFT_COMPRESSION_X_2, "DX"),
                                new Measurement(Measurement.LOWER_LEFT_COMPRESSION_Y_2, "DY"),
                                new Measurement(Measurement.LOWER_LEFT_COMPRESSION_Z_2, "DZ")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_COMPRESSION_UPPER_LEFT_NODE_1:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Chest Left Upper IR-TRACC (node1)",
                            measurements: [
                                new Measurement(Measurement.UPPER_LEFT_COMPRESSION_X_1, "DX"),
                                new Measurement(Measurement.UPPER_LEFT_COMPRESSION_Y_1, "DY"),
                                new Measurement(Measurement.UPPER_LEFT_COMPRESSION_Z_1, "DZ")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_COMPRESSION_UPPER_LEFT_NODE_2:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Chest Left Upper IR-TRACC (node2)",
                            measurements: [
                                new Measurement(Measurement.UPPER_LEFT_COMPRESSION_X_2, "DX"),
                                new Measurement(Measurement.UPPER_LEFT_COMPRESSION_Y_2, "DY"),
                                new Measurement(Measurement.UPPER_LEFT_COMPRESSION_Z_2, "DZ")
                            ]
                        };
                        break;
                }
                break;

            case OccupantEntity.CHEST_COMPRESSION_LOWER_RIGHT_NODE_1:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Chest Right Lower IR-TRACC (node1)",
                            measurements: [
                                new Measurement(Measurement.LOWER_RIGHT_COMPRESSION_X_1, "DX"),
                                new Measurement(Measurement.LOWER_RIGHT_COMPRESSION_Y_1, "DY"),
                                new Measurement(Measurement.LOWER_RIGHT_COMPRESSION_Z_1, "DZ")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_COMPRESSION_LOWER_RIGHT_NODE_2:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Chest Right Lower IR-TRACC (node2)",
                            measurements: [
                                new Measurement(Measurement.LOWER_RIGHT_COMPRESSION_X_2, "DX"),
                                new Measurement(Measurement.LOWER_RIGHT_COMPRESSION_Y_2, "DY"),
                                new Measurement(Measurement.LOWER_RIGHT_COMPRESSION_Z_2, "DZ")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_COMPRESSION_UPPER_RIGHT_NODE_1:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Chest Right Upper IR-TRACC (node1)",
                            measurements: [
                                new Measurement(Measurement.UPPER_RIGHT_COMPRESSION_X_1, "DX"),
                                new Measurement(Measurement.UPPER_RIGHT_COMPRESSION_Y_1, "DY"),
                                new Measurement(Measurement.UPPER_RIGHT_COMPRESSION_Z_1, "DZ")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_COMPRESSION_UPPER_RIGHT_NODE_2:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Chest Right Upper IR-TRACC (node2)",
                            measurements: [
                                new Measurement(Measurement.UPPER_RIGHT_COMPRESSION_X_2, "DX"),
                                new Measurement(Measurement.UPPER_RIGHT_COMPRESSION_Y_2, "DY"),
                                new Measurement(Measurement.UPPER_RIGHT_COMPRESSION_Z_2, "DZ")
                            ]
                        };
                        break;
                }
                break;

            case OccupantEntity.CHEST_UPPER_RIB_SPRING_ROT:
                switch (entity_type) {
                    case OccupantEntity.SPRING_ROTATIONAL:
                        return {
                            name: "Chest Upper Rib Rotation Transducer (Spring)",
                            measurements: [new Measurement(Measurement.UPPER_ROTATION, "RT")]
                        };
                        break;
                }
                break;

            case OccupantEntity.CHEST_MIDDLE_RIB_SPRING_ROT:
                switch (entity_type) {
                    case OccupantEntity.SPRING_ROTATIONAL:
                        return {
                            name: "Chest Middle Rib Rotation Transducer (Spring)",
                            measurements: [new Measurement(Measurement.MID_ROTATION, "RT")]
                        };
                        break;
                }
                break;

            case OccupantEntity.CHEST_BOTTOM_RIB_SPRING_ROT:
                switch (entity_type) {
                    case OccupantEntity.SPRING_ROTATIONAL:
                        return {
                            name: "Chest Lower Rib Rotation Transducer (Spring)",
                            measurements: [new Measurement(Measurement.LOWER_ROTATION, "RT")]
                        };
                        break;
                }
                break;

            case OccupantEntity.CHEST_UPPER_RIB_SPRING_TRANS:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Chest Upper Rib Deflection Transducer (Spring)",
                            measurements: [new Measurement(Measurement.UPPER_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;

            case OccupantEntity.CHEST_MIDDLE_RIB_SPRING_TRANS:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Chest Middle Rib Deflection Transducer (Spring)",
                            measurements: [new Measurement(Measurement.MID_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;

            case OccupantEntity.CHEST_BOTTOM_RIB_SPRING_TRANS:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Chest Lower Rib Deflection Transducer (Spring)",
                            measurements: [new Measurement(Measurement.LOWER_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;

            case OccupantEntity.CHEST_TOP_THORAX_TRANSDUCER:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Top Thorax Rib Deflection (Spring)",
                            measurements: [new Measurement(Measurement.UPPER_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_MIDDLE_THORAX_TRANSDUCER:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Middle Thorax Rib Deflection (Spring)",
                            measurements: [new Measurement(Measurement.MID_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;
            case OccupantEntity.CHEST_BOTTOM_THORAX_TRANSDUCER:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Bottom Thorax Rib Deflection (Spring)",
                            measurements: [new Measurement(Measurement.LOWER_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;

            case OccupantEntity.SHOULDER_LEFT_BEAM:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Left Shoulder Loadcell (Beam)",
                            measurements: [new Measurement(Measurement.LEFT_FORCE, "NY")]
                        };
                        break;
                }
                break;
            case OccupantEntity.SHOULDER_RIGHT_BEAM:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Right Shoulder Loadcell (Beam)",
                            measurements: [new Measurement(Measurement.RIGHT_FORCE, "NY")]
                        };
                        break;
                }
                break;
            case OccupantEntity.SHOULDER_TRANSDUCER:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Shoulder Deflection (Spring)",
                            measurements: [new Measurement(Measurement.DEFLECTION, "ET")]
                        };
                        break;
                }
                break;

            case OccupantEntity.LUMBAR_BEAM:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Lumbar loadcell (Beam)",
                            measurements: [
                                new Measurement(Measurement.SHEAR, "NY"),
                                new Measurement(Measurement.AXIAL, "NZ"),
                                new Measurement(Measurement.TORSION, "MX")
                            ]
                        };
                        break;
                }
                break;

            case OccupantEntity.ABDOMEN_COMPRESSION_LEFT_NODE_1:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Abdomen Left IR-TRACC (node1)",
                            measurements: [
                                new Measurement(Measurement.LEFT_COMPRESSION_X_1, "DX"),
                                new Measurement(Measurement.LEFT_COMPRESSION_Y_1, "DY"),
                                new Measurement(Measurement.LEFT_COMPRESSION_Z_1, "DZ")
                            ]
                        };
                        break;
                }
                break;

            case OccupantEntity.ABDOMEN_COMPRESSION_LEFT_NODE_2:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Abdomen Left IR-TRACC (node2)",
                            measurements: [
                                new Measurement(Measurement.LEFT_COMPRESSION_X_2, "DX"),
                                new Measurement(Measurement.LEFT_COMPRESSION_Y_2, "DY"),
                                new Measurement(Measurement.LEFT_COMPRESSION_Z_2, "DZ")
                            ]
                        };
                        break;
                }
                break;

            case OccupantEntity.ABDOMEN_COMPRESSION_RIGHT_NODE_1:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Abdomen Right IR-TRACC (node1)",
                            measurements: [
                                new Measurement(Measurement.RIGHT_COMPRESSION_X_1, "DX"),
                                new Measurement(Measurement.RIGHT_COMPRESSION_Y_1, "DY"),
                                new Measurement(Measurement.RIGHT_COMPRESSION_Z_1, "DZ")
                            ]
                        };
                        break;
                }
                break;

            case OccupantEntity.ABDOMEN_COMPRESSION_RIGHT_NODE_2:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Abdomen Right IR-TRACC (node2)",
                            measurements: [
                                new Measurement(Measurement.RIGHT_COMPRESSION_X_2, "DX"),
                                new Measurement(Measurement.RIGHT_COMPRESSION_Y_2, "DY"),
                                new Measurement(Measurement.RIGHT_COMPRESSION_Z_2, "DZ")
                            ]
                        };
                        break;
                }
                break;

            case OccupantEntity.ABDOMEN_UPPER_SPRING_ROT:
                switch (entity_type) {
                    case OccupantEntity.SPRING_ROTATIONAL:
                        return {
                            name: "Upper Abdomen Rotation Transducer (Spring)",
                            measurements: [new Measurement(Measurement.UPPER_ROTATION, "RT")]
                        };
                        break;
                }
                break;
            case OccupantEntity.ABDOMEN_LOWER_SPRING_ROT:
                switch (entity_type) {
                    case OccupantEntity.SPRING_ROTATIONAL:
                        return {
                            name: "Lower Abdomen Rotation Transducer (Spring)",
                            measurements: [new Measurement(Measurement.LOWER_ROTATION, "RT")]
                        };
                        break;
                }
                break;
            case OccupantEntity.ABDOMEN_UPPER_SPRING_TRANS:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Upper Abdomen Deflection Transducer (Spring)",
                            measurements: [new Measurement(Measurement.UPPER_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;
            case OccupantEntity.ABDOMEN_LOWER_SPRING_TRANS:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Lower Abdomen Deflection Transducer (Spring)",
                            measurements: [new Measurement(Measurement.LOWER_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;

            case OccupantEntity.ABDOMEN_BEAM_FRNT:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Front Abdomen Force (Beam)",
                            measurements: [new Measurement(Measurement.FRONT_FORCE, "NY")]
                        };
                        break;
                }
                break;
            case OccupantEntity.ABDOMEN_BEAM_MID:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Middle Abdomen Force (Beam)",
                            measurements: [new Measurement(Measurement.MID_FORCE, "NY")]
                        };
                        break;
                }
                break;
            case OccupantEntity.ABDOMEN_BEAM_BACK:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Back Abdomen Force (Beam)",
                            measurements: [new Measurement(Measurement.BACK_FORCE, "NY")]
                        };
                        break;
                }
                break;

            case OccupantEntity.TOP_ABDOMEN_TRANSDUCER:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Upper Abdomen Deflection (Spring)",
                            measurements: [new Measurement(Measurement.UPPER_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;
            case OccupantEntity.BOTTOM_ABDOMEN_TRANSDUCER:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Lower Abdomen Deflection (Spring)",
                            measurements: [new Measurement(Measurement.LOWER_TRANSLATION, "ET")]
                        };
                        break;
                }
                break;

            case OccupantEntity.PUBIC_SYMPHYSIS_BEAM:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Pubic Symphysis Loadcell (Beam)",
                            measurements: [new Measurement(Measurement.PUBIC_SYMPHYSIS_FORCE, "NY")]
                        };
                        break;
                }
                break;
            case OccupantEntity.PELVIS_BEAM:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Pubic Symphysis Loadcell (Beam)",
                            measurements: [new Measurement(Measurement.PUBIC_SYMPHYSIS_FORCE, "NY")]
                        };
                        break;
                }
                break;
            case OccupantEntity.XSECTION_ILIAC_ID:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Iliac Loadcell (Beam)",
                            measurements: [new Measurement(Measurement.ILIAC_FORCE, "NY")]
                        };
                        break;
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Iliac Loadcell (X-Section)",
                            measurements: [new Measurement(Measurement.ILIAC_FORCE, "FY")]
                        };
                        break;
                }
                break;
            case OccupantEntity.XSECTION_ACETABULAR_ID:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Acetabular Loadcell (Beam)",
                            measurements: [new Measurement(Measurement.ACETABULUM_FORCE, "NY")]
                        };
                        break;
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Acetabular Loadcell (X-Section)",
                            measurements: [new Measurement(Measurement.ACETABULUM_FORCE, "FY")]
                        };
                        break;
                }
                break;
            case OccupantEntity.LEFT_ACETABULUM_LOADCELL:
                switch (entity_type) {
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Left Pelvis Force (X-Section)",
                            measurements: [
                                new Measurement(Measurement.LEFT_FORCE_X, "FX"),
                                new Measurement(Measurement.LEFT_FORCE_Y, "FY"),
                                new Measurement(Measurement.LEFT_FORCE_Z, "FZ")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.RIGHT_ACETABULUM_LOADCELL:
                switch (entity_type) {
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Right Pelvis Force (X-Section)",
                            measurements: [
                                new Measurement(Measurement.RIGHT_FORCE_X, "FX"),
                                new Measurement(Measurement.RIGHT_FORCE_Y, "FY"),
                                new Measurement(Measurement.RIGHT_FORCE_Z, "FZ")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.LEFT_FEMUR_LOADCELL:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Left Femur Loadcell (Beam)",
                            measurements: [
                                new Measurement(Measurement.LEFT_AXIAL_FORCE, "NZ"),
                                new Measurement(Measurement.LEFT_FORCE_X, "NX"),
                                new Measurement(Measurement.LEFT_FORCE_Y, "NY"),
                                new Measurement(Measurement.LEFT_FORCE_Z, "NZ")
                            ]
                        };
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Left Femur Loadcell (X-Section)",
                            measurements: [
                                new Measurement(Measurement.LEFT_AXIAL_FORCE, "FZ"),
                                new Measurement(Measurement.LEFT_FORCE_X, "FX"),
                                new Measurement(Measurement.LEFT_FORCE_Y, "FY"),
                                new Measurement(Measurement.LEFT_FORCE_Z, "FZ")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.RIGHT_FEMUR_LOADCELL:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Right Femur Loadcell (Beam)",
                            measurements: [
                                new Measurement(Measurement.RIGHT_AXIAL_FORCE, "NZ"),
                                new Measurement(Measurement.RIGHT_FORCE_X, "NX"),
                                new Measurement(Measurement.RIGHT_FORCE_Y, "NY"),
                                new Measurement(Measurement.RIGHT_FORCE_Z, "NZ")
                            ]
                        };
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Right Femur Loadcell (X-Section)",
                            measurements: [
                                new Measurement(Measurement.RIGHT_AXIAL_FORCE, "FZ"),
                                new Measurement(Measurement.RIGHT_FORCE_X, "FX"),
                                new Measurement(Measurement.RIGHT_FORCE_Y, "FY"),
                                new Measurement(Measurement.RIGHT_FORCE_Z, "FZ")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.UPPER_FEMUR_BEAM:
                switch (entity_type) {
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Upper Femur Loadcell (X-Section)",
                            measurements: [
                                new Measurement(Measurement.UPPER_FORCE_X, "FX"),
                                new Measurement(Measurement.UPPER_FORCE_Y, "FY"),
                                new Measurement(Measurement.UPPER_FORCE_Z, "FZ"),
                                new Measurement(Measurement.UPPER_MOMENT_X, "MX"),
                                new Measurement(Measurement.UPPER_MOMENT_Y, "MY"),
                                new Measurement(Measurement.UPPER_MOMENT_Z, "MZ")
                            ]
                        };
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Upper Femur Loadcell (Beam)",
                            measurements: [
                                new Measurement(Measurement.UPPER_FORCE_X, "NX"),
                                new Measurement(Measurement.UPPER_FORCE_Y, "NY"),
                                new Measurement(Measurement.UPPER_FORCE_Z, "NZ"),
                                new Measurement(Measurement.UPPER_MOMENT_X, "MX"),
                                new Measurement(Measurement.UPPER_MOMENT_Y, "MY"),
                                new Measurement(Measurement.UPPER_MOMENT_Z, "MZ")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.BOTTOM_FEMUR_BEAM:
                switch (entity_type) {
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Bottom Femur Loadcell (X-Section)",
                            measurements: [
                                new Measurement(Measurement.LOWER_FORCE_X, "FX"),
                                new Measurement(Measurement.LOWER_FORCE_Y, "FY"),
                                new Measurement(Measurement.LOWER_FORCE_Z, "FZ"),
                                new Measurement(Measurement.LOWER_MOMENT_X, "MX"),
                                new Measurement(Measurement.LOWER_MOMENT_Y, "MY"),
                                new Measurement(Measurement.LOWER_MOMENT_Z, "MZ")
                            ]
                        };
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Bottom Femur Loadcell (Beam)",
                            measurements: [
                                new Measurement(Measurement.LOWER_FORCE_X, "NX"),
                                new Measurement(Measurement.LOWER_FORCE_Y, "NY"),
                                new Measurement(Measurement.LOWER_FORCE_Z, "NZ"),
                                new Measurement(Measurement.LOWER_MOMENT_X, "MX"),
                                new Measurement(Measurement.LOWER_MOMENT_Y, "MY"),
                                new Measurement(Measurement.LOWER_MOMENT_Z, "MZ")
                            ]
                        };
                        break;
                }
                break;

            case OccupantEntity.LEFT_KNEE_TRANSDUCER:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Left Knee Transducer (Spring)",
                            measurements: [new Measurement(Measurement.LEFT_TRANSLATION, "ET")]
                        };
                        break;
                    case OccupantEntity.JOINT:
                        return {
                            name: "Left Knee Transducer (Joint)",
                            measurements: [new Measurement(Measurement.LEFT_TRANSLATION, "XD")]
                        };
                        break;
                }
                break;
            case OccupantEntity.RIGHT_KNEE_TRANSDUCER:
                switch (entity_type) {
                    case OccupantEntity.SPRING_TRANSLATIONAL:
                        return {
                            name: "Right Knee Transducer (Spring)",
                            measurements: [new Measurement(Measurement.RIGHT_TRANSLATION, "ET")]
                        };
                        break;
                    case OccupantEntity.JOINT:
                        return {
                            name: "Right Knee Transducer (Joint)",
                            measurements: [new Measurement(Measurement.RIGHT_TRANSLATION, "XD")]
                        };
                        break;
                }
                break;
            case OccupantEntity.LEFT_UPPER_TIBIA_LOADCELL:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Left Upper Tibia Loadcell (Beam)",
                            measurements: [
                                new Measurement(Measurement.LEFT_UPPER_AXIAL_FORCE, "NZ"),
                                new Measurement(Measurement.LEFT_UPPER_BENDING_X, "MX"),
                                new Measurement(Measurement.LEFT_UPPER_BENDING_Y, "MY")
                            ]
                        };
                        break;
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Left Upper Tibia Loadcell (X-Section)",
                            measurements: [
                                new Measurement(Measurement.LEFT_UPPER_AXIAL_FORCE, "FZ"),
                                new Measurement(Measurement.LEFT_UPPER_BENDING_X, "MX"),
                                new Measurement(Measurement.LEFT_UPPER_BENDING_Y, "MY")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.RIGHT_UPPER_TIBIA_LOADCELL:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Right Upper Tibia Loadcell (Beam)",
                            measurements: [
                                new Measurement(Measurement.RIGHT_UPPER_AXIAL_FORCE, "NZ"),
                                new Measurement(Measurement.RIGHT_UPPER_BENDING_X, "MX"),
                                new Measurement(Measurement.RIGHT_UPPER_BENDING_Y, "MY")
                            ]
                        };
                        break;
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Right Upper Tibia Loadcell (X-Section)",
                            measurements: [
                                new Measurement(Measurement.RIGHT_UPPER_AXIAL_FORCE, "FZ"),
                                new Measurement(Measurement.RIGHT_UPPER_BENDING_X, "MX"),
                                new Measurement(Measurement.RIGHT_UPPER_BENDING_Y, "MY")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.LEFT_LOWER_TIBIA_LOADCELL:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Left Lower Tibia Loadcell (Beam)",
                            measurements: [
                                new Measurement(Measurement.LEFT_LOWER_AXIAL_FORCE, "NZ"),
                                new Measurement(Measurement.LEFT_LOWER_BENDING_X, "MX"),
                                new Measurement(Measurement.LEFT_LOWER_BENDING_Y, "MY")
                            ]
                        };
                        break;
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Left Lower Tibia Loadcell (X-Section)",
                            measurements: [
                                new Measurement(Measurement.LEFT_LOWER_AXIAL_FORCE, "FZ"),
                                new Measurement(Measurement.LEFT_LOWER_BENDING_X, "MX"),
                                new Measurement(Measurement.LEFT_LOWER_BENDING_Y, "MY")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.RIGHT_LOWER_TIBIA_LOADCELL:
                switch (entity_type) {
                    case OccupantEntity.BEAM_BASIC:
                        return {
                            name: "Right Lower Tibia Loadcell (Beam)",
                            measurements: [
                                new Measurement(Measurement.RIGHT_LOWER_AXIAL_FORCE, "NZ"),
                                new Measurement(Measurement.RIGHT_LOWER_BENDING_X, "MX"),
                                new Measurement(Measurement.RIGHT_LOWER_BENDING_Y, "MY")
                            ]
                        };
                        break;
                    case OccupantEntity.XSECTION:
                        return {
                            name: "Right Lower Tibia Loadcell (X-Section)",
                            measurements: [
                                new Measurement(Measurement.RIGHT_LOWER_AXIAL_FORCE, "FZ"),
                                new Measurement(Measurement.RIGHT_LOWER_BENDING_X, "MX"),
                                new Measurement(Measurement.RIGHT_LOWER_BENDING_Y, "MY")
                            ]
                        };
                        break;
                }
                break;
            case OccupantEntity.LEFT_FOOT_NODE:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Left Foot Node",
                            measurements: [
                                new Measurement(Measurement.LEFT_ACCELERATION_X, "AX"),
                                new Measurement(Measurement.LEFT_ACCELERATION_Y, "AY"),
                                new Measurement(Measurement.LEFT_ACCELERATION_Z, "AZ")
                            ]
                        };
                        break;
                }
                break;

            case OccupantEntity.RIGHT_FOOT_NODE:
                switch (entity_type) {
                    case OccupantEntity.NODE:
                        return {
                            name: "Right Foot Node",
                            measurements: [
                                new Measurement(Measurement.RIGHT_ACCELERATION_X, "AX"),
                                new Measurement(Measurement.RIGHT_ACCELERATION_Y, "AY"),
                                new Measurement(Measurement.RIGHT_ACCELERATION_Z, "AZ")
                            ]
                        };
                        break;
                }
                break;
        }

        throw new Error(`Entity tag ${tag} and type ${entity_type} combination is not valid`);
    }

    /**
     * Returns an array of all the available entity tag strings
     * @returns {string[]}
     * @param {string} [body_part_type=null] Body part type, if given only tags for that body part type will be returned, otherwise all entity tags are returned
     * @example
     * // Get all entity tags
     * let all_entity_tags = OccupantEntity.EntityTags();
     *
     * // Get entity tags for the neck
     * let neck_entity_tags = OccupantEntityTags(OccupantBodyPart.NECK);
     */
    static EntityTags(body_part_type = null) {
        if (body_part_type) {
            if (!OccupantBodyPart.Valid(body_part_type)) {
                throw new Error(`Invalid body_part_type ${body_part_type} in OccupantEntity.EntityTags()`);
            }
        }

        /* Get the body parts to loop over */

        let body_parts = [];

        if (body_part_type) {
            body_parts.push(body_part_type);
        } else {
            for (let body_part of OccupantBodyPart.Types()) {
                body_parts.push(body_part);
            }
        }

        /* Get the tags for each body part */

        let tags = [];

        for (let body_part of body_parts) {
            switch (body_part) {
                case OccupantBodyPart.HEAD:
                    tags.push(OccupantEntity.HEAD_NODE);
                    tags.push(OccupantEntity.HEAD_NODE_Y);
                    tags.push(OccupantEntity.HEAD_NODE_Z);
                    break;

                case OccupantBodyPart.NECK:
                    tags.push(OccupantEntity.NECK_LOADCELL);
                    tags.push(OccupantEntity.NECK_LOWER_BEAM);
                    tags.push(OccupantEntity.NECK_UPPER_BEAM);
                    break;

                case OccupantBodyPart.CHEST:
                    tags.push(OccupantEntity.CHEST_SPRING);
                    tags.push(OccupantEntity.CHEST_SPRING_UPP);
                    tags.push(OccupantEntity.CHEST_SPRING_LOW);
                    tags.push(OccupantEntity.CHEST_SPRING_MID);
                    tags.push(OccupantEntity.CHEST_NODE_X);
                    tags.push(OccupantEntity.CHEST_NODE_Y);
                    tags.push(OccupantEntity.CHEST_NODE_Z);
                    tags.push(OccupantEntity.CHEST_COMPRESSION_LOWER_LEFT_NODE_1);
                    tags.push(OccupantEntity.CHEST_COMPRESSION_LOWER_LEFT_NODE_2);
                    tags.push(OccupantEntity.CHEST_COMPRESSION_UPPER_LEFT_NODE_1);
                    tags.push(OccupantEntity.CHEST_COMPRESSION_UPPER_LEFT_NODE_2);
                    tags.push(OccupantEntity.CHEST_COMPRESSION_LOWER_RIGHT_NODE_1);
                    tags.push(OccupantEntity.CHEST_COMPRESSION_LOWER_RIGHT_NODE_2);
                    tags.push(OccupantEntity.CHEST_COMPRESSION_UPPER_RIGHT_NODE_1);
                    tags.push(OccupantEntity.CHEST_COMPRESSION_UPPER_RIGHT_NODE_2);
                    tags.push(OccupantEntity.CHEST_UPPER_RIB_SPRING_ROT);
                    tags.push(OccupantEntity.CHEST_MIDDLE_RIB_SPRING_ROT);
                    tags.push(OccupantEntity.CHEST_BOTTOM_RIB_SPRING_ROT);
                    tags.push(OccupantEntity.CHEST_UPPER_RIB_SPRING_TRANS);
                    tags.push(OccupantEntity.CHEST_MIDDLE_RIB_SPRING_TRANS);
                    tags.push(OccupantEntity.CHEST_BOTTOM_RIB_SPRING_TRANS);
                    tags.push(OccupantEntity.CHEST_TOP_THORAX_TRANSDUCER);
                    tags.push(OccupantEntity.CHEST_MIDDLE_THORAX_TRANSDUCER);
                    tags.push(OccupantEntity.CHEST_BOTTOM_THORAX_TRANSDUCER);
                    break;

                case OccupantBodyPart.SHOULDER:
                    tags.push(OccupantEntity.SHOULDER_TRANSDUCER);
                    tags.push(OccupantEntity.SHOULDER_LEFT_BEAM);
                    tags.push(OccupantEntity.SHOULDER_RIGHT_BEAM);
                    break;

                case OccupantBodyPart.ABDOMEN:
                    tags.push(OccupantEntity.ABDOMEN_COMPRESSION_LEFT_NODE_1);
                    tags.push(OccupantEntity.ABDOMEN_COMPRESSION_LEFT_NODE_2);
                    tags.push(OccupantEntity.ABDOMEN_COMPRESSION_RIGHT_NODE_1);
                    tags.push(OccupantEntity.ABDOMEN_COMPRESSION_RIGHT_NODE_2);
                    tags.push(OccupantEntity.ABDOMEN_BEAM_FRNT);
                    tags.push(OccupantEntity.ABDOMEN_BEAM_MID);
                    tags.push(OccupantEntity.ABDOMEN_BEAM_BACK);
                    tags.push(OccupantEntity.TOP_ABDOMEN_TRANSDUCER);
                    tags.push(OccupantEntity.BOTTOM_ABDOMEN_TRANSDUCER);
                    tags.push(OccupantEntity.ABDOMEN_UPPER_SPRING_ROT);
                    tags.push(OccupantEntity.ABDOMEN_LOWER_SPRING_ROT);
                    tags.push(OccupantEntity.ABDOMEN_UPPER_SPRING_TRANS);
                    tags.push(OccupantEntity.ABDOMEN_LOWER_SPRING_TRANS);
                    break;

                case OccupantBodyPart.LUMBAR:
                    tags.push(OccupantEntity.LUMBAR_BEAM);
                    break;

                case OccupantBodyPart.PELVIS:
                    tags.push(OccupantEntity.PELVIS_BEAM);
                    tags.push(OccupantEntity.PUBIC_SYMPHYSIS_BEAM);
                    tags.push(OccupantEntity.XSECTION_ACETABULAR_ID);
                    tags.push(OccupantEntity.XSECTION_ILIAC_ID);
                    tags.push(OccupantEntity.LEFT_ACETABULUM_LOADCELL);
                    tags.push(OccupantEntity.RIGHT_ACETABULUM_LOADCELL);
                    break;

                case OccupantBodyPart.FEMUR:
                    tags.push(OccupantEntity.LEFT_FEMUR_LOADCELL);
                    tags.push(OccupantEntity.RIGHT_FEMUR_LOADCELL);
                    tags.push(OccupantEntity.BOTTOM_FEMUR_BEAM);
                    tags.push(OccupantEntity.UPPER_FEMUR_BEAM);
                    break;

                case OccupantBodyPart.KNEE:
                    tags.push(OccupantEntity.LEFT_KNEE_TRANSDUCER);
                    tags.push(OccupantEntity.RIGHT_KNEE_TRANSDUCER);
                    break;

                case OccupantBodyPart.TIBIA:
                    tags.push(OccupantEntity.LEFT_UPPER_TIBIA_LOADCELL);
                    tags.push(OccupantEntity.RIGHT_UPPER_TIBIA_LOADCELL);
                    tags.push(OccupantEntity.LEFT_LOWER_TIBIA_LOADCELL);
                    tags.push(OccupantEntity.RIGHT_LOWER_TIBIA_LOADCELL);
                    break;

                case OccupantBodyPart.FOOT:
                    tags.push(OccupantEntity.LEFT_FOOT_NODE);
                    tags.push(OccupantEntity.RIGHT_FOOT_NODE);
                    break;

                default:
                    ErrorMessage(`Unknown body part type ${body_part} in OccupantEntity.EntityTags()`);
            }
        }

        return tags;
    }
}

/** Class representing a body part of an occupant<br><br>
 * It extends the BaseComponent class
 * @extends BaseComponent
 * @param {string} body_part_type Body part constant
 * @param {OccupantEntity[]} entities Array of OccupantEntity instances
 * @example
 * let body_part = new OccupantBodyPart(OccupantBodyPart.HEAD, [entity1, entity2]);
 */
class OccupantBodyPart extends BaseComponent {
    constructor(body_part_type, entities) {
        super(body_part_type, entities);

        this.occupant = null; /* Parent occupant */
    }

    /* Instance property getter and setters */

    /**
     * Parent occupant
     * @type {WorkflowOccupant} */
    get occupant() {
        return this._occupant;
    }
    set occupant(new_occupant) {
        // if (new_occupant && !(new_occupant instanceof WorkflowOccupant)) {
        //     throw new Error("occupant must be a WorkflowOccupant instance");
        // }

        this._occupant = new_occupant;
    }

    /**
     * check if body_part_type is valid
     * @param {string} body_part_type
     * @returns {boolean} true if valid
     */
    static Valid(body_part_type) {
        return OccupantBodyPart.Types().indexOf(body_part_type) != -1;
    }

    /**
     * construct an OccupantBodyPart from JSON
     * @param {Object} json
     * @returns {?OccupantBodyPart}
     */
    static FromJSON(json) {
        let entities = [];

        for (let e of json.entities) {
            entities.push(OccupantEntity.FromJSON(e));
        }

        return new OccupantBodyPart(json.component_type, entities);
    }

    /* Class constant getters (read-only) */

    // #region Types

    /**
     * Abdomen body part
     * @type {string} */
    static get ABDOMEN() {
        return "abdomen";
    }
    /**
     * Chest body part
     * @type {string} */
    static get CHEST() {
        return "chest";
    }
    /**
     * Foot body part
     * @type {string} */
    static get FOOT() {
        return "foot";
    }
    /**
     * Head body part
     * @type {string} */
    static get HEAD() {
        return "head";
    }
    /**
     * Knee body part
     * @type {string} */
    static get KNEE() {
        return "knee";
    }
    /**
     * Tibia body part
     * @type {string} */
    static get TIBIA() {
        return "tibia";
    }
    /**
     * Lumbar body part
     * @type {string} */
    static get LUMBAR() {
        return "lumbar";
    }
    /**
     * Neck body part
     * @type {string} */
    static get NECK() {
        return "neck";
    }
    /**
     * Pelvis body part
     * @type {string} */
    static get PELVIS() {
        return "pelvis";
    }
    /**
     * Shoulder body part
     * @type {string} */
    static get SHOULDER() {
        return "shoulder";
    }
    /**
     * Femur body part
     * @type {string} */
    static get FEMUR() {
        return "femur";
    }

    // #endregion

    /* Class methods */

    /**
     * Return an array of all the available body part type strings
     * @returns {string[]}
     * @example
     * let body_part_types = OccupantBodyPart.Types();
     */
    static Types() {
        return [
            OccupantBodyPart.HEAD,
            OccupantBodyPart.NECK,
            OccupantBodyPart.SHOULDER,
            OccupantBodyPart.CHEST,
            OccupantBodyPart.ABDOMEN,
            OccupantBodyPart.LUMBAR,
            OccupantBodyPart.PELVIS,
            OccupantBodyPart.FEMUR,
            OccupantBodyPart.KNEE,
            OccupantBodyPart.TIBIA,
            OccupantBodyPart.FOOT
        ];
    }
}

/** Class representing widgets for selecting Entity ids in an Occupant
 * It extends the BaseEntityWidgets class
 * @extends BaseEntityWidgets
 * @param {string} entity_type Entity type constant
 * @param {Widget} label The entity label widget
 * @param {Widget} textbox The entity textbox widget
 * @param {string} tag The entity tag
 * @param {string} [hover=""] The entity label hover text
 * @example
 * let entity_widget = new OccupantEntityWidgets(occupant_entity.entity_type, label, textbox, occupant_entity.tag, occupant_entity.hover);
 * let head_node_entity_widget = new OccupantEntityWidgets(OccupantEntity.NODE, label, textbox, OccupantEntity.HEAD_NODE, "??HEAD0000H3ACX");
 *
 */
class OccupantEntityWidgets extends BaseEntityWidgets {
    constructor(entity_type, label, textbox, tag, hover = "") {
        super(entity_type, label, textbox, tag, OccupantEntity.EntityTags(), hover);
    }
}

/** Class representing the entity widgets for a body part
 * It extends the BaseComponentWidgets class
 * @param {Widget} label The body part label widget
 * @param {OccupantEntityWidgets[]} entity_widgets Array of entity widgets
 * @example
 * let body_part_widgets = new BodyPartWidgets(label, entity_widgets);
 */
class BodyPartWidgets extends BaseComponentWidgets {
    constructor(label, entity_widgets) {
        super(label, entity_widgets);
    }
}

/** Class representing the body part widgets for an occupant */
class OccupantWidgets {
    /**
     *
     * @param {string} name Occupant name
     * @param {BodyPartWidgets[]} body_part_widgets Array of body part widgets
     * @example
     * let occupant_widgets = new OccupantWidgets(
     *                            WorkflowOccupant.HUMANETICS_HIII_50M_V1_5,
     *                            body_part_widgets);
     */
    constructor(name, body_part_widgets) {
        this.name = name;
        this.body_part_widgets = body_part_widgets;
    }

    /* Instance property getter and setters */

    /** Occupant name
     * @type {string} */
    get name() {
        return this._name;
    }
    set name(new_name) {
        if (typeof new_name != "string") {
            throw new Error("name must be a string");
        }

        // if (WorkflowOccupant.Versions().indexOf(new_name) == -1) {
        //     throw new Error(`Invalid name: ${new_name}`);
        // }

        this._name = new_name;
    }

    /** Array of body part widgets
     * @type {BodyPartWidgets[]} */
    get body_part_widgets() {
        return this._body_part_widgets;
    }
    set body_part_widgets(new_body_part_widgets) {
        if (!(new_body_part_widgets instanceof Array)) {
            throw new Error("body_part_widgets must be an Array");
        }

        for (let new_body_part_widget of new_body_part_widgets) {
            if (!(new_body_part_widget instanceof BodyPartWidgets)) {
                throw new Error("body_part_widgets must be an array of BodyPartWidgets instances");
            }
        }

        this._body_part_widgets = new_body_part_widgets;
    }

    /* Instance methods */

    /**
     * Show all the body part widgets for this occupant
     * @example
     * occupant_widgets.Show();
     */
    Show() {
        for (let body_part_widgets of this.body_part_widgets) {
            body_part_widgets.Show();
        }
    }

    /**
     * Hide all the body part widgets for this occupant
     * @example
     * occupant_widgets.Hide();
     */
    Hide() {
        for (let body_part_widgets of this.body_part_widgets) {
            body_part_widgets.Hide();
        }
    }
}

/***
 *
 * MOVED FROM occupant version
 *
 */

class OccupantSupplier {
    /**
     * Class to define occupant suppliers
     */

    // #region OccupantSupplier Getters

    /**
     * check if the supplier name is valid
     * @param {string} supplier name of supplier
     * @returns {boolean}
     */
    static Valid(supplier) {
        return OccupantSupplier.GetAll().indexOf(supplier) != -1;
    }

    /**
     * check if the supplier name is valid (ignoring case)
     * and if it is then return the supplier name with the correct case
     * otherwise return null
     * @param {string} supplier name of supplier
     * @returns {?string}
     */
    static GetValid(supplier) {
        for (let os of OccupantSupplier.GetAll()) {
            if (os.toLowerCase() == supplier.toLowerCase()) return os;
        }
        return null;
    }

    /**
     * ATD-MODELS
     * @type {string}
     */
    static get ATD() {
        return "ATD-MODELS";
    }

    /**
     * DYNAMORE
     * @type {string}
     */
    static get DYNAMORE() {
        return "DYNAmore";
    }

    /**
     * HUMANETICS
     * @type {string}
     */
    static get HUMANETICS() {
        return "Humanetics";
    }

    /**
     * LSTC
     * @type {string}
     */
    static get LSTC() {
        return "LSTC";
    }

    /**
     * PDB
     * @type {string}
     */
    static get PDB() {
        return "PDB";
    }

    /**
     * Returns an array of all the supplier names
     * @returns {string[]}
     * @example
     * let suppliers = OccupantSupplier.GetAll();
     */
    static GetAll() {
        return [
            OccupantSupplier.ATD,
            OccupantSupplier.DYNAMORE,
            OccupantSupplier.HUMANETICS,
            OccupantSupplier.LSTC,
            OccupantSupplier.PDB
        ];
    }
}

class OccupantProduct {
    /**
     * Class to define occupant products e.g. HIII, THOR, WSID...
     */

    /**
     * check if the product name is valid
     * @param {string} product name of product
     * @returns {boolean}
     */
    static Valid(product) {
        return OccupantProduct.GetAll().indexOf(product) != -1;
    }

    /**
     * check if the product name is valid (ignoring case)
     * and if it is then return the product name with the correct case
     * otherwise return null
     * @param {string} product name of product
     * @returns {?string}
     */
    static GetValid(product) {
        for (let op of OccupantProduct.GetAll()) {
            if (op.toLowerCase() == product.toLowerCase()) return op;
        }
        return null;
    }

    // #region OccupantProduct Getters

    /**
     * Hybrid 3
     * @type {string}
     */
    static get HIII() {
        return "HIII";
    }

    /**
     * THOR
     * @type {string}
     */
    static get THOR() {
        return "THOR";
    }

    /**
     * World SID
     * @type {string}
     */
    static get WSID() {
        return "WSID";
    }

    /**
     * SBLD
     * @type {string}
     */
    static get SID2() {
        return "SID2-SBLD";
    }

    /**
     * ES-2re product produced by Dynamore
     * @type {string}
     */
    static get ES2RE() {
        return "ES-2re";
    }

    /**
     * ES-2 product produced by Dynamore
     * @type {string}
     */
    static get ES2() {
        return "ES-2";
    }

    /**
     * Q dummies (child occupants)
     * @type {string}
     */
    static get Q() {
        return "Q";
    }
    /**
     * Returns an array of all the occupant product type names
     * @returns {string[]}
     * @example
     * let occupant_products = OccupantProduct.GetAll();
     */
    static GetAll() {
        return [
            OccupantProduct.HIII,
            OccupantProduct.SID2,
            OccupantProduct.THOR,
            OccupantProduct.WSID,
            OccupantProduct.ES2RE,
            OccupantProduct.ES2,
            OccupantProduct.Q
        ];
    }
}

class OccupantPhysiology {
    /**
     * Class to define occupant physiologies
     */

    /**
     * check if the physiology name is valid
     * @param {string} physiology name of physiology
     * @returns {boolean}
     */
    static Valid(physiology) {
        return OccupantPhysiology.GetAll().indexOf(physiology) != -1;
    }

    /**
     * check if the physiology name is valid (ignoring case)
     * and if it is then return the physiology name with the correct case
     * otherwise return null
     * @param {string} physiology name of physiology
     * @returns {?string}
     */
    static GetValid(physiology) {
        for (let op of OccupantPhysiology.GetAll()) {
            if (op.toLowerCase() == physiology.toLowerCase()) return op;
        }
        return null;
    }

    // #region OccupantPhysiology Getters

    /** 95th percentile Male
     * @type {string} */
    static get M95() {
        return "95M";
    }

    /** 50th percentile Male
     * @type {string} */
    static get M50() {
        return "50M";
    }

    /** 5th percentile Female
     * @type {string} */
    static get F5() {
        return "5F";
    }

    /** 10 year old child
     * @type {string} */
    static get C10YO() {
        return "10";
    }

    /** 6 year old child
     * @type {string} */
    static get C6YO() {
        return "6";
    }

    /** 3 year old child
     * @type {string} */
    static get C3YO() {
        return "3";
    }

    /** 1 and a half year old child
     * @type {string} */
    static get C1_5YO() {
        return "1.5";
    }

    /**
     * Returns an array of all the occupant physiology names
     * @returns {string[]}
     * @example
     * let occupant_physiologies = OccupantPhysiology.GetAll();
     */
    static GetAll() {
        return [
            OccupantPhysiology.M95,
            OccupantPhysiology.M50,
            OccupantPhysiology.F5,
            OccupantPhysiology.C10YO,
            OccupantPhysiology.C6YO,
            OccupantPhysiology.C3YO,
            OccupantPhysiology.C1_5YO
        ];
    }
}

class Occupant {
    /**
     * Class to define a OccupantVersion with a name supplier product and version number
     * to extract the data for the measurement
     * @param {string} supplier Supplier of dummy product
     * @param {string} product Product type (e.g. HIII, WSID, SBD2)
     * @param {string} physiology Product physiology (e.g. 50th percentile male, 6 year old child)
     * @param {string} version Product version number/name
     * @param {OccupantBodyPart[]} body_parts Array of body parts [default] empty array []
     * @param {?OccupantChestRotationFactors} [chest_rotation_factors = null] used for hybrid 3 occupants
     * @example
     * let m = new Occupant(OccupantSupplier.ATD,
     *                      OccupantProduct.HIII,
     *                      OccupantPhysiology.M50,
     *                      "1.0.7"});
     */
    constructor(supplier, product, physiology, version, body_parts, chest_rotation_factors = null) {
        this.supplier = supplier;
        this.product = product;
        this.physiology = physiology;
        this.version = version;
        this.body_parts = body_parts;
        this.chest_rotation_factors = chest_rotation_factors;
    }

    /**
     * construct an Occupant from a JSON file
     * @param {string} occupant_filename JSON file of occupant
     * @returns {?Occupant}
     */
    static FromJSONFile(occupant_filename) {
        let f = new File(occupant_filename, File.READ);
        let text = f.ReadAll();
        f.Close();

        let o = JSON.parse(text);

        Message(`read ${occupant_filename}`);

        return Occupant.FromJSON(o);
    }

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

        let expected_keys = ["supplier", "product", "physiology", "body_parts", "version"];

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

        let optional_keys = ["chest_rotation_factors"];

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

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

        try {
            let body_parts = [];
            for (let body_part of json.body_parts) {
                body_parts.push(OccupantBodyPart.FromJSON(body_part));
            }

            let chest_rotation_factors = OccupantChestRotationFactors.FromJSON(json.chest_rotation_factors);

            return new Occupant(
                json.supplier,
                json.product,
                json.physiology,
                json.version,
                body_parts,
                chest_rotation_factors
            );
        } catch (error) {
            Message(`${error}\nso skipping parsing JSON for this occupant.`);
            return null;
        }
    }

    /** Occupant name (constructed from supplier, product, physiology and version)
     * @type {string} */
    get name() {
        return `${this.supplier} ${this.product} ${this.physiology} ${this.version}`;
    }

    /** Occupant supplier
     * @type {string} */
    get supplier() {
        return this._supplier;
    }
    set supplier(new_supplier) {
        let new_supplier_correct_case;
        if (!(new_supplier_correct_case = OccupantSupplier.GetValid(new_supplier))) {
            throw new Error(`Invalid supplier ${new_supplier} in Occupant constructor`);
        }
        this._supplier = new_supplier_correct_case;
    }

    /** Occupant product
     * @type {string} */
    get product() {
        return this._product;
    }
    set product(new_product) {
        let new_product_correct_case;
        if (!(new_product_correct_case = OccupantProduct.GetValid(new_product))) {
            throw new Error(`Invalid product ${new_product} in Occupant constructor`);
        }
        this._product = new_product_correct_case;
    }

    /** Occupant physiology
     * @type {string} */
    get physiology() {
        return this._physiology;
    }
    set physiology(new_physiology) {
        let new_physiology_correct_case;
        if (!(new_physiology_correct_case = OccupantPhysiology.GetValid(new_physiology))) {
            throw new Error(`Invalid physiology ${new_physiology} in Occupant constructor`);
        }
        this._physiology = new_physiology_correct_case;
    }

    /** Occupant version
     * @type {string} */
    get version() {
        return this._version;
    }
    set version(new_version) {
        if (typeof new_version != "string") {
            throw new Error(`version must be a string in Occupant constructor`);
        }
        this._version = new_version;
    }

    /** Occupant chest rotation factors
     * @type {?OccupantChestRotationFactors} */
    get chest_rotation_factors() {
        return this._chest_rotation_factors;
    }
    set chest_rotation_factors(new_chest_rotation_factors) {
        if (!new_chest_rotation_factors) this._chest_rotation_factors = null;
        else if (!(new_chest_rotation_factors instanceof OccupantChestRotationFactors)) {
            throw new Error("chest_rotation_factors must be an instance of OccupantChestRotationFactors or null");
        }
        this._chest_rotation_factors = new_chest_rotation_factors;
    }

    /** Array of OccupantBodyPart instances
     * @type {OccupantBodyPart[]} */
    get body_parts() {
        return this._body_parts;
    }
    set body_parts(new_body_parts) {
        if (!(new_body_parts instanceof Array)) {
            throw new Error("body_parts must be an array");
        }

        for (let new_body_part of new_body_parts) {
            if (!(new_body_part instanceof OccupantBodyPart)) {
                throw new Error("body_parts must be an array of OccupantBodyPart instances");
            }
        }

        this._body_parts = new_body_parts;
    }

    /**
     * convert occupant to JSON representation
     */
    toJSON() {
        //reorder this to change the order that it is written out
        let o = {};
        //o.name = this.name; //don't repeat name as it is defined by supplier, product, version and physiology
        o.supplier = this.supplier;
        o.product = this.product;
        o.physiology = this.physiology;
        o.version = this.version;
        if (this.chest_rotation_factors) o.chest_rotation_factors = this.chest_rotation_factors;
        o.body_parts = this.body_parts;

        return o;
    }

    /**
     * Get an OccupantEntity by tag
     * @param {string} tag Entity tag
     * @returns {?OccupantEntity}
     * @example
     * let entity = occupant.GetEntityByTag(OccupantEntity.HEAD_NODE);
     */
    GetEntityByTag(tag) {
        for (let body_part of this.body_parts) {
            let entity = body_part.GetEntityByTag(tag);
            if (entity) return entity;
        }

        return null;
    }
}

class OccupantChestRotationFactors {
    /**
     * Class to hold the chest rotation factors
     * @param {string} type Type of factors "linear" [a single factor] or "third_order" [three factors]
     * @param {number[]} values Factors
     * @example
     * let m = new Measurement(Measurement.X_ACCELERATION, "AX");
     */
    constructor(type, values) {
        this.type = type;
        this.values = values;
    }

    /**
     * check if type is valid
     * @param {string} type
     */
    static Valid(type) {
        return OccupantChestRotationFactors.GetAllOrders().indexOf(type) != -1;
    }

    /**
     * OccupantChestRotationFactors type
     * @type {string}
     */
    get type() {
        return this._type;
    }
    set type(new_type) {
        if (!OccupantChestRotationFactors.Valid(new_type)) {
            throw new Error(`Invalid type ${new_type} in OccupantChestRotationFactors constructor`);
        }

        this._type = new_type;
    }

    /** OccupantChestRotationFactors type
     * @type {number[]} */
    get values() {
        return this._values;
    }
    set values(new_values) {
        //ensure that values are valid when set, otherwise throw error
        if (!Array.isArray(new_values)) {
            throw new Error(`chest_rotation_factors values must be an array in Occupant constructor`);
        } else {
            if (this.type == OccupantChestRotationFactors.THIRD_ORDER) {
                if (new_values.length != 3) {
                    throw new Error(
                        `chest_rotation_factors is third_order so values must be an array of length 3 in Occupant constructor`
                    );
                }
            } else if (this.type == OccupantChestRotationFactors.LINEAR) {
                if (new_values.length != 1) {
                    throw new Error(
                        `chest_rotation_factors is linear so values must be an array of length 1 in Occupant constructor`
                    );
                }
            } else {
                throw new Error(`chest_rotation_factors type ${this._type} is not supported`);
            }

            for (let i = 0; i < new_values.length; i++) {
                //let new_values[i] = parseFloat(new_values[i]);
                if (isNaN(new_values[i])) {
                    throw new Error(
                        `chest_rotation_factors value ${new_values[i]} in array index ${i} must be an number in Occupant constructor`
                    );
                }
            }

            this._values = new_values;
        }
    }

    /** linear
     * @type {string} */
    static get LINEAR() {
        return "linear";
    }
    /** third order polynomial
     * @type {string} */
    static get THIRD_ORDER() {
        return "third_order";
    }

    /**
     * construct OccupantChestRotationFactors from json
     * note that chest_rotation_factors is an optional property so json may be passed in as null or undefined
     * @param {?Object} json
     * @returns {?OccupantChestRotationFactors}
     */
    static FromJSON(json) {
        try {
            if (!json) {
                //not all occupants need chest rotation factors defined so return null for them
                return null;
            } else {
                //but if json is present for chest_rotation_factors then try and construct OccupantChestRotationFactors
                return new OccupantChestRotationFactors(json.type, json.values);
            }
        } catch (error) {
            WarningMessage(
                `${error}\nChest rotation factors will not be set and will be determined based on occupant supplier and physiology`
            );

            return null;
        }
    }

    /**
     * Returns an array of all the supported type orders
     * @returns {string[]}
     * @example
     * let types = OccupantChestRotationFactors.GetAllOrders();
     */
    static GetAllOrders() {
        return [OccupantChestRotationFactors.LINEAR, OccupantChestRotationFactors.THIRD_ORDER];
    }
}