// 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}`
);
}
}
}