modules/post/this/reporter_automotive_assessment.mjs

// module: TRUE
import {
    ProtocolAssessment,
    DoOccupantAssessmentOccupantData,
    DoStructureAssessmentStructureData,
    DoAssessmentOptions
} from "./this_automotive_assessments.mjs";
import { WorkflowOccupant } from "../../shared/workflow_occupant.mjs";
import { Structure } from "../../shared/structure.mjs";
import { check_report_contents } from "./reporter_check_user_data.mjs";
import { JobControl } from "../../pre/reporter_job_control.mjs";
import { ReporterVariablesFile } from "../../shared/reporter_variables_transfer.mjs";

// T/HIS script to run REPORTER version of automotive assessments workflow

/* TODO TODO TODO
 *
 * Questions for Chris:
 *
 * 1.   Why do we need all the if statements with upper_rib_irtracc_length etc?
 *      Could they be included in the WorkflowOccupant constructor? In fact,
 *      why don't they belong to the Chest or Abdomen OccupantBodyParts?
 * 2.   How do we get the Workflow class to behave when T/HIS launched from
 *      REPORTER? At the moment, it doesn't seem to check for the workflow
 *      stuff at all. I have commented it out and replaced it with hardwired
 *      JSON parsing below, for now.
 * 3.   How do you think we should trigger image generation?
 *
 */

/**
 * The user data object in the workflow file
 * @typedef {Object} UserData
 * @property {WorkflowOccupant[]} occupants Array of WorkflowOccupants
 */

/**
 * Carries out an assessment on the types specified by the
 * REPORTER template, according to the specified regulation and
 * occupant/structure, plotting the results in T/HIS and capturing images
 * that can be loaded by the REPORTER template.
 * @param {string} job_control Overall job control
 * @param {string} reporter_temp REPORTER_TEMP directory
 * @param {string} output_dir REPORTER output directory for images etc.
 * @param {string} drive_side VehicleOccupant.LHD or VehicleOccupant.RHD
 */
export function reporter_assessment(job_control, reporter_temp, output_dir, drive_side) {
    /* Handle skip and abort. If we are running this the first time, we will do the check, and
     * depending on the outcome, launch the PRIMER GUI or continue with the T/HIS run.
     * If we are running this the second time, we won't do the check (because status should never be Check for the second time)
     * and will proceed straight to the T/HIS run. */

    while (JobControl.GetAll().includes(job_control)) {
        if (job_control == JobControl.ABORT) {
            Message(`Generation aborted (job control).`);
            return;
        }
        if (job_control == JobControl.SKIP) {
            Message(`Skipping remaining T/HIS check and assessment.`);
            return;
        }
        if (job_control == JobControl.CHECK) {
            /* Run check of report contents against available user data. Expected return values
             * are JobControl.CHECK, in which case PRIMER GUI item will need to be run, or
             * JobControl.RUN, in which case we can proceed directly to the T/HIS assessment. */
            job_control = check_report_contents(output_dir, drive_side);

            /*if job_control is still 'Check' after comparing required data (reporter_user_data) 
            to user_data (either json file in same directory as keyword or post *END) then we need to run PRIMER
            in special REPORTER mode to request the data */
            if (job_control == JobControl.CHECK) {
                Message(`User data GUI required.`);
                return;
            }
        }
        if (job_control == JobControl.RUN) {
            /**
             * set JOB_CONTROL status to Abort - this should be overwritten with SKIP if completed T-HIS successfully,
             * this prevents it being re-run
             * NOTE that writing the JOB_CONTROL status does not actually change the REPORTER JOB_CONTROL variable value
             * until '#AAW REPORTER read variables from PRIMER job control' is generated ( this happens in reporter_run_primer_rerun_this.js ).
             * NOTE it also does not change the value of job_control in this function*/

            let file = new ReporterVariablesFile(reporter_temp, ReporterVariablesFile.JOB_CONTROL);
            file.WriteVariable(
                `JOB_CONTROL`,
                JobControl.ABORT,
                `"Check"/"Run"/"Skip"/"Abort" for #AAW items`,
                `String`
            );
            file.Close();

            Message(`Doing occupant assessment...`);
            try {
                do_reporter_assessment(output_dir);
            } catch (error) {
                ErrorMessage(`Something went wrong in T-HIS when running do_reporter_assessment\n${error}`);
            }

            Message(`Completed occupant assessment.`);
            /* Once we have finished the assessment, indicate that the T/HIS rerun can be skipped */
            file = new ReporterVariablesFile(reporter_temp, ReporterVariablesFile.JOB_CONTROL);
            file.WriteVariable(`JOB_CONTROL`, JobControl.SKIP, `"Check"/"Run"/"Skip"/"Abort" for #AAW items`, `String`);
            file.Close();
            return;
        }
    }
    ErrorMessage(`Unexpected value of <job_control = "${job_control}"> in function reporter_occupant_assessment.`);
    return;
}

function do_reporter_assessment(output_dir) {
    /* Parse the REPORTER jobs JSON file */
    let f_job_name = `${output_dir}/report_contents.json`;
    let f_job = new File(f_job_name, File.READ);
    let job_str = "";
    let line;
    while ((line = f_job.ReadLongLine()) != undefined) {
        job_str = job_str + line;
    }
    f_job.Close();
    let jobs = JSON.parse(job_str).jobs;

    let results = [];

    /* For each REPORTER job, we need to do an occupant assessment for each
     * occupant variant i.e. to run through the process of plotting graphs for
     * e.g. Driver only, or a comparison of Driver and Passenger.
     *
     * We also need to do a structure assessment for each structure assessment type
     * listed.
     */
    for (let job of jobs) {
        /* Do the occupant or structures assessment for each variant */
        for (let variant_index = 0; variant_index < job.variants.length; variant_index++) {
            do_occupant_or_structure_assessment(output_dir, results, job, variant_index);
        }
    }

    /* Send the results to REPORTER */

    let f_res_name = `${output_dir}/automotive_assessment_results.csv`;
    let f_res = new File(f_res_name, File.WRITE);
    for (let output of results) {
        for (let value of output.values) {
            let variable_name = `M${output.model_id}_${output.location}_${output.component}_${value.parameter}`
                .replace(/[-\s]/g, "_")
                .toUpperCase();
            f_res.Writeln(
                `${variable_name},${value.value.toPrecision(6)},Result from Automotive Assessments Workflow,General`
            );
        }
    }
    f_res.Close();
}

function do_occupant_or_structure_assessment(output_dir, overall_results, job, variant_index) {
    /* Check function arguments */

    if (
        variant_index == undefined ||
        (variant_index != undefined && (variant_index < 0 || variant_index >= job.variants.length))
    ) {
        ErrorMessage(`Invalid <variant_index = ${variant_index}> in function do_occupant_or_structure_assessment.`);
        Exit();
    }

    let variant = job.variants[variant_index];
    let what = variant.type;

    if (what != `occupants` && what != `structures`) {
        ErrorMessage(`Invalid variant type "${what}" in function do_occupant_or_structure_assessment.`);
        Exit();
    }

    let reporter_data = [];
    let reporter_assessment_types;
    // TODO only support one model for now
    let num_selected_models = 1;

    for (let i = 0; i < num_selected_models; i++) {
        /** @type {UserData} */

        let user_data;
        let model_id;
        let unit_system;

        try {
            if (Workflow.NumberOfSelectedModels() == 0) throw Error("No user data present");
            /* Retrieve user data for this model */
            user_data = Workflow.ModelUserDataFromIndex(i, `Automotive Assessments`);
            model_id = Workflow.ModelIdFromIndex(i);
            unit_system = Workflow.ModelUnitSystemFromIndex(i, `Automotive Assessments`);
        } catch (error) {
            //warn user if no user data exists or can be found
            ErrorMessage(error);
            //TODO set JOB_CONTROL status to Abort? - it should already be set to this in calling function so may not be required
            break;
        }

        if (what == `occupants`) {
            /* Add user occupant data to the list if it matches the occupant variant requested in the reporter job */
            for (let o of user_data.occupants) {
                let user_occ = WorkflowOccupant.CreateFromUserData(o);
                for (let job_occ of variant.data) {
                    if (job_occ == user_occ.position) {
                        reporter_data.push(new DoOccupantAssessmentOccupantData(user_occ, model_id, unit_system));
                    }
                }
            }
        } else if (what == `structures`) {
            /* Add structure data to the list */
            for (let s of user_data.structures) {
                let user_struct = Structure.CreateFromUserData(s);
                reporter_data.push(new DoStructureAssessmentStructureData(user_struct, model_id, unit_system));
            }
        }
    }

    reporter_assessment_types = variant.assessment_types;

    /* There might not be any data for this job if it contains either only occupant data or only
     * structures data. If so, just return. */
    if (reporter_data.length == 0) {
        Message("No data requested");
        return;
    }

    if (reporter_assessment_types.length == 0) {
        ErrorMessage("No assessment types selected");
        return;
    }

    /* Create a protocol instance with the required datums */

    let protocol = ProtocolAssessment.CreateDefaultProtocol(job.regulation, job.crash_test, job.version);

    /* Options to pass to DoOccupantAssessment and DoStructureAssessment*/

    let first_graph_id = 1;
    let first_page_id = 1;
    let blank_all = true;
    let remove_existing_graphs = true;

    let options = new DoAssessmentOptions(
        DoAssessmentOptions.GRAPH_LAYOUT_REPORTER,
        first_graph_id,
        first_page_id,
        blank_all,
        remove_existing_graphs,
        output_dir
    );

    /* Do the assessment */

    let results; // Results from this assessment
    if (what == `occupants`) {
        results = protocol.DoOccupantAssessment(reporter_data, reporter_assessment_types, options);
    } else if (what == `structures`) {
        results = protocol.DoStructureAssessment(reporter_data, reporter_assessment_types, options);
    }

    /* Do the scoring */

    //let scores = scoring(job.regulation, job.crash_test, job.version, job_occ_var_results);

    Message("Results:");
    /* Add results for this assessment to the overall list of results */
    for (let output of results.outputs) {
        overall_results.push(output);
        /* Print a copy of the results */
        for (let value of output.values) {
            Message(
                `M${output.model_id} ${output.location.replace("-", " ")} ${output.component.toUpperCase()} ${
                    value.parameter
                }: ${value.value.toPrecision(6)} ${value.units}`
            );
        }
    }
}