Report endpoints
Routes
GET /report(requires roleInstructor): Returns grouped session results withdata,users, andcolumnOrder(ordered fromreport.Columns*configuration when present).GET /report/download?format=csv|xlsx(requires roleInstructor): Streams a binary file containing only the rows from each session’sdataarray, using the configured column order when available.
Configuration
- Column ordering is pulled from platform configuration keys that start with
report.Columns(viagetPlatformConfigurationByPrefix). The returned configuration defines the desired column order; that order is returned in the/reportresponse (columnOrder) and applied to/report/downloadexports. - If no configuration is present, column order falls back to the order produced by the underlying report attributes.
- In stub mode (
STUB_MODE=true), both endpoints return stubbed report data.
Example configuration entries
Each column is a key report.Columns.<Name> with JSON value describing the column. Example rows:
report.Columns.Time {"name":"Time","expression":"from","type":"datetime","format":"HH:mm:ss","source":"scenarioInstance","order":2}
report.Columns.Date {"name":"Date","expression":"from","type":"datetime","format":"HH:mm:ss","source":"scenarioInstance","order":1}
report.Columns.Account {"name":"Account Name","expression":"username","type":"string","source":"user","order":3}
report.Columns.Surname {"name":"Surname","expression":"lastName","type":"string","source":"user","order":7}
report.Columns.Firstname {"name":"FirstName","expression":"firstName","type":"string","source":"user","order":6}
report.Columns.Scenario {"name":"Scenario","expression":"scenario_name","regex":"Scenario\\s+(\\d+)","regexGroup":1,"type":"numeric","source":"metadata","order":10}
report.Columns.Rank {"name":"Rank","expression":"","order":5}
report.Columns.NumberCorrect {"name":"Number Correct","expression":"","order":11}
report.Columns.ArmyID {"name":"Army_ID","expression":"","order":4}
report.Columns.Level {"name":"Level","expression":"scenario_name","regex":"Level\\s+(\\d+)","regexGroup":1,"type":"numeric","source":"metadata","order":9}
report.Columns.AFVRTest {"name":"AFVR Test","expression":"scenario_name","regex":"\\b(Practice|Qualification)\\b","regexGroup":1,"type":"string","source":"metadata","order":8}
report.Columns.Result {"name":"Result","expression":"scoring.dataset[].data[?title==`Score`][].checkResult","type":"string","source":"dimension","order":12}
report.Filename "AFV_Report"Fields:
name: column header returned to clients.expression: path used to extract data from the chosensource.source:scenarioInstance,user,metadata, ordimension.regex/regexGroup(optional): post-process extracted value.type(optional): hints formatting (e.g.,datetime,numeric,string);formatcan specify date/time format.order: numeric ordering applied when buildingcolumnOrderand exports.Filename(optional): plain string used as the prefix for downloaded CSV/XLSX filenames. If omitted or empty, the legacy defaultSessionListis used. Filenames followPREFIX_ddMMyyyy_HH.mm(e.g.,AFV_Report_05012026_16.59.csv).
How source and expression work
sourcechooses which object to read from when evaluating the column/filter:dimension(default) → data from the node output (nodeOutput.raw)metadata→ session metadata (session.metadata)scenarioInstance→ scenario instance infouser→ user object matched byexternalHeatId
expressionis a JMESPath query applied to that source object to extract the value. Examples:expression: "score"withsource: "dimension"reads{ score: 10 }→10expression: "scenario_type"withsource: "metadata"reads{ scenario_type: "AFV" }→"AFV"expression: "scoring.dataset[].data[?title==\Score`][].checkResult”` digs into nested arrays for the first matching value.
Tests
- Unit tests use Jest: run
npm test. - Existing suites target middleware and report behavior; ensure new changes keep
columnOrderand download outputs consistent with configuration.