|
|
import { SectionKey, SECTIONS as SECTION_DEFS } from "@/lib/sections"; |
|
|
|
|
|
export type QuestionType = "likert" | "mcq" | "text"; |
|
|
|
|
|
export type QuestionOption = { id: string; label: string }; |
|
|
|
|
|
export type BaseQuestion = { |
|
|
baseId: string; |
|
|
section: SectionKey; |
|
|
type: QuestionType; |
|
|
domain: string; |
|
|
variants: [string, string, string]; |
|
|
options?: QuestionOption[]; |
|
|
mcqScores?: Record<string, number>; |
|
|
reverse?: boolean; |
|
|
minChars?: number; |
|
|
}; |
|
|
|
|
|
export type ExamQuestion = { |
|
|
baseId: string; |
|
|
section: SectionKey; |
|
|
type: QuestionType; |
|
|
domain: string; |
|
|
prompt: string; |
|
|
options?: QuestionOption[]; |
|
|
mcqScores?: Record<string, number>; |
|
|
reverse?: boolean; |
|
|
minChars?: number; |
|
|
}; |
|
|
|
|
|
|
|
|
export const SECTIONS = SECTION_DEFS; |
|
|
|
|
|
const LIKERT_LABELS = [ |
|
|
"Strongly disagree", |
|
|
"Disagree", |
|
|
"Neutral", |
|
|
"Agree", |
|
|
"Strongly agree" |
|
|
] as const; |
|
|
|
|
|
|
|
|
function likertVariants(a: string, b: string, c: string, reverse = false): [string, string, string] { |
|
|
return [a, b, c]; |
|
|
} |
|
|
function makeLikert(id: string, section: SectionKey, domain: string, v: [string, string, string], reverse = false): BaseQuestion { |
|
|
return { baseId: id, section, type: "likert", domain, variants: v, reverse }; |
|
|
} |
|
|
function makeText(id: string, section: SectionKey, domain: string, v: [string, string, string], minChars = 60): BaseQuestion { |
|
|
return { baseId: id, section, type: "text", domain, variants: v, minChars }; |
|
|
} |
|
|
function makeMCQ( |
|
|
id: string, |
|
|
section: SectionKey, |
|
|
domain: string, |
|
|
v: [string, string, string], |
|
|
options: QuestionOption[], |
|
|
scores: Record<string, number> |
|
|
): BaseQuestion { |
|
|
return { baseId: id, section, type: "mcq", domain, variants: v, options, mcqScores: scores }; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const QUESTION_BANK: BaseQuestion[] = (() => { |
|
|
const q: BaseQuestion[] = []; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const A: Array<{ domain: string; reverse?: boolean; v: [string, string, string] }> = [ |
|
|
{ domain: "EmotionalStability", v: likertVariants( |
|
|
"In tense situations, I stay emotionally steady.", |
|
|
"When situations get tense, I maintain emotional control.", |
|
|
"I remain emotionally steady when tension increases." |
|
|
)}, |
|
|
{ domain: "Conscientiousness", v: likertVariants( |
|
|
"I follow through on commitments even when no one is checking.", |
|
|
"Even without supervision, I complete what I promise.", |
|
|
"I reliably finish tasks I commit to, even unobserved." |
|
|
)}, |
|
|
{ domain: "StressTolerance", v: likertVariants( |
|
|
"I handle time pressure without making careless mistakes.", |
|
|
"Even when rushed, I keep my work accurate.", |
|
|
"Under time pressure, I remain careful and accurate." |
|
|
)}, |
|
|
{ domain: "ImpulseControl", reverse: true, v: likertVariants( |
|
|
"When pressured, I can react impulsively.", |
|
|
"Under stress, I may respond before fully thinking.", |
|
|
"I sometimes act first and think later when stressed." |
|
|
)}, |
|
|
{ domain: "TeamOrientation", v: likertVariants( |
|
|
"I actively support teammates when workload increases.", |
|
|
"When others are overloaded, I help keep operations running.", |
|
|
"I step in to assist colleagues to ensure continuity." |
|
|
)}, |
|
|
{ domain: "Adaptability", v: likertVariants( |
|
|
"I adjust quickly when priorities or plans change.", |
|
|
"When schedules change, I adapt without losing quality.", |
|
|
"I shift my approach quickly when circumstances change." |
|
|
)}, |
|
|
{ domain: "Integrity", reverse: true, v: likertVariants( |
|
|
"Small shortcuts are acceptable if they save time.", |
|
|
"Bending minor rules is fine if it speeds things up.", |
|
|
"If it helps finish faster, small rule-bending is acceptable." |
|
|
)}, |
|
|
{ domain: "Reliability", v: likertVariants( |
|
|
"I plan my work so deadlines are met consistently.", |
|
|
"I organize tasks to finish on time.", |
|
|
"I schedule my work to meet deadlines reliably." |
|
|
)}, |
|
|
{ domain: "DetailCare", v: likertVariants( |
|
|
"I double-check important details before finalizing work.", |
|
|
"I verify key details before completing a task.", |
|
|
"I re-check critical details to prevent mistakes." |
|
|
)}, |
|
|
{ domain: "FeedbackUse", v: likertVariants( |
|
|
"I handle feedback without taking it personally.", |
|
|
"I accept feedback calmly and use it to improve.", |
|
|
"I receive feedback professionally and apply it." |
|
|
)}, |
|
|
{ domain: "RespectUnderTension", v: likertVariants( |
|
|
"I remain respectful even when others are frustrated.", |
|
|
"When others are upset, I keep a respectful tone.", |
|
|
"Even under tension, I stay respectful." |
|
|
)}, |
|
|
{ domain: "FocusSwitching", v: likertVariants( |
|
|
"I can switch between tasks without losing focus.", |
|
|
"I transition between tasks without losing concentration.", |
|
|
"I change tasks smoothly while staying focused." |
|
|
)}, |
|
|
{ domain: "Ownership", v: likertVariants( |
|
|
"I take ownership when something goes wrong.", |
|
|
"If an error happens, I take responsibility.", |
|
|
"I own mistakes and focus on correcting them." |
|
|
)}, |
|
|
{ domain: "SolutionOrientation", v: likertVariants( |
|
|
"I stay focused on solutions instead of blame.", |
|
|
"I prioritize fixing issues rather than blaming others.", |
|
|
"I focus on resolving problems, not assigning blame." |
|
|
)}, |
|
|
{ domain: "DistractionControl", v: likertVariants( |
|
|
"I avoid distractions when completing important work.", |
|
|
"I keep distractions low while doing important tasks.", |
|
|
"I stay away from distractions during critical work." |
|
|
)}, |
|
|
{ domain: "Patience", v: likertVariants( |
|
|
"I stay patient when procedures take time.", |
|
|
"When processes take longer than expected, I remain patient.", |
|
|
"I keep my patience when steps take time to complete." |
|
|
)}, |
|
|
{ domain: "Consistency", v: likertVariants( |
|
|
"I behave consistently across different teams.", |
|
|
"I keep a consistent work style across groups.", |
|
|
"My work behavior stays consistent across teams." |
|
|
)}, |
|
|
{ domain: "CalmChange", v: likertVariants( |
|
|
"I keep calm when plans change at the last minute.", |
|
|
"Last-minute changes do not shake my calmness.", |
|
|
"I remain calm during sudden last-minute changes." |
|
|
)}, |
|
|
{ domain: "RoutineEndurance", v: likertVariants( |
|
|
"I keep a positive attitude even when tasks are repetitive.", |
|
|
"Even with repetitive tasks, I maintain a positive attitude.", |
|
|
"Routine work does not reduce my positive attitude." |
|
|
)}, |
|
|
{ domain: "DependabilityBoring", v: likertVariants( |
|
|
"I remain reliable even when work becomes boring.", |
|
|
"When work feels boring, I still stay dependable.", |
|
|
"Even if a task is boring, I remain dependable." |
|
|
)}, |
|
|
|
|
|
{ domain: "AttentionCheck", v: likertVariants( |
|
|
"Attention check: please select 'Agree' for this statement.", |
|
|
"To confirm attention, choose 'Agree' here.", |
|
|
"For quality control, select 'Agree' for this item." |
|
|
)}, |
|
|
|
|
|
{ domain: "ProfessionalConflict", v: likertVariants( |
|
|
"I remain professional when dealing with conflict.", |
|
|
"In conflict situations, I stay professional.", |
|
|
"I handle conflict professionally and calmly." |
|
|
)}, |
|
|
{ domain: "TimeManagement", v: likertVariants( |
|
|
"I manage my time effectively during busy shifts.", |
|
|
"During busy shifts, I manage time well.", |
|
|
"I use my time efficiently when workload is high." |
|
|
)}, |
|
|
{ domain: "QualityAtSpeed", v: likertVariants( |
|
|
"I maintain quality even when working quickly.", |
|
|
"When I work fast, I still keep quality high.", |
|
|
"Even at speed, I protect the quality of my work." |
|
|
)}, |
|
|
{ domain: "ClearRoleUpdates", v: likertVariants( |
|
|
"I communicate clearly when responsibilities change.", |
|
|
"When roles shift, I communicate clearly.", |
|
|
"I explain updates clearly when responsibilities change." |
|
|
)}, |
|
|
{ domain: "SteadyPace", v: likertVariants( |
|
|
"I keep a steady pace even during long busy periods.", |
|
|
"During long busy periods, I work steadily.", |
|
|
"I maintain consistent effort during extended busy times." |
|
|
)} |
|
|
]; |
|
|
|
|
|
A.forEach((it, i) => q.push(makeLikert(`A-${String(i + 1).padStart(2, "0")}`, "A", it.domain, it.v, Boolean(it.reverse)))); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const mcqOptions: QuestionOption[] = [ |
|
|
{ id: "stop_procedure", label: "Stop and follow the documented procedure immediately." }, |
|
|
{ id: "continue_later", label: "Continue to avoid delay and fix it later." }, |
|
|
{ id: "copy_colleague", label: "Ask a colleague what they usually do and copy that." }, |
|
|
{ id: "report_end", label: "Proceed and report after the shift ends." } |
|
|
]; |
|
|
const mcqScores = { stop_procedure: 5, copy_colleague: 2, report_end: 1, continue_later: 0 }; |
|
|
|
|
|
const mcqPrompts: [string, string, string] = [ |
|
|
"A required checklist step seems to be missing. What should you do first?", |
|
|
"You notice the procedure is not being followed fully. What is your first action?", |
|
|
"A compliance step appears skipped. What do you do first?" |
|
|
]; |
|
|
for (let i = 1; i <= 10; i++) { |
|
|
q.push(makeMCQ(`B-MCQ-${String(i).padStart(2, "0")}`, "B", "SafetyJudgment", mcqPrompts, mcqOptions, mcqScores)); |
|
|
} |
|
|
|
|
|
const B_domains = [ |
|
|
{ d: "RuleAdherence", v: likertVariants( |
|
|
"I follow procedures even when it slows the work down.", |
|
|
"Even if it takes longer, I stick to the correct procedure.", |
|
|
"I do tasks the right way even when it costs time." |
|
|
)}, |
|
|
{ d: "DecisionPressure", v: likertVariants( |
|
|
"Under pressure, I decide using rules and evidence.", |
|
|
"When urgency is high, I prioritize safety and accuracy in decisions.", |
|
|
"Even in urgency, I base decisions on facts and procedure." |
|
|
)}, |
|
|
{ d: "AttentionToDetail", v: likertVariants( |
|
|
"I notice small issues early and correct them.", |
|
|
"I detect small errors before they become bigger problems.", |
|
|
"I catch details that could cause later errors." |
|
|
)}, |
|
|
{ d: "SafetyMindset", v: likertVariants( |
|
|
"If I notice a risk, I stop and reassess before continuing.", |
|
|
"I pause work if I believe safety could be compromised.", |
|
|
"When I see a hazard, I address it before proceeding." |
|
|
)}, |
|
|
{ d: "ClearCommunication", v: likertVariants( |
|
|
"When priorities shift, I clarify roles and expectations.", |
|
|
"I communicate clearly when tasks change suddenly.", |
|
|
"I keep communication clear during rapid changes." |
|
|
)}, |
|
|
{ d: "ProcedureDiscipline", v: likertVariants( |
|
|
"I keep accurate records when documentation is required.", |
|
|
"When documentation is needed, I complete it correctly.", |
|
|
"I ensure required documentation is accurate and complete." |
|
|
)}, |
|
|
{ d: "SituationalAwareness", v: likertVariants( |
|
|
"I notice changes around me that could affect safety.", |
|
|
"I stay aware of surroundings that could create risks.", |
|
|
"I remain alert to conditions that might affect safety." |
|
|
)}, |
|
|
{ d: "PrioritySetting", v: likertVariants( |
|
|
"I prioritize tasks based on safety and urgency.", |
|
|
"When multiple tasks compete, I prioritize correctly.", |
|
|
"I choose priorities using safety, urgency, and procedure." |
|
|
)}, |
|
|
{ d: "ShortcutRisk", rev: true, v: likertVariants( |
|
|
"If I’m confident, I may skip steps to move faster.", |
|
|
"I’m willing to bypass steps when the outcome seems obvious.", |
|
|
"Taking shortcuts is worth it if it speeds things up." |
|
|
)}, |
|
|
{ d: "Composure", v: likertVariants( |
|
|
"When a situation escalates, I remain calm and professional.", |
|
|
"I keep composure when problems happen unexpectedly.", |
|
|
"I stay calm when operations become chaotic." |
|
|
)} |
|
|
]; |
|
|
|
|
|
|
|
|
for (let i = 1; i <= 29; i++) { |
|
|
const t = B_domains[(i - 1) % B_domains.length]; |
|
|
q.push(makeLikert(`B-${String(i).padStart(2, "0")}`, "B", t.d, t.v, Boolean((t as any).rev))); |
|
|
} |
|
|
|
|
|
q.push(makeText( |
|
|
"B-TEXT", |
|
|
"B", |
|
|
"OperationalScenario", |
|
|
[ |
|
|
"Scenario: A flight is delayed, passengers are frustrated, and the team is short-staffed. Describe your actions to keep operations safe and compliant.", |
|
|
"Scenario: During disruption and heavy workload, explain how you would prioritize tasks and communicate while maintaining safety and procedure.", |
|
|
"Scenario: Under operational pressure, explain step-by-step how you keep safety, rules, and service quality." |
|
|
], |
|
|
90 |
|
|
)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i <= 20; i++) { |
|
|
const rev = i % 5 === 0; |
|
|
q.push(makeLikert( |
|
|
`C-${String(i).padStart(2, "0")}`, |
|
|
"C", |
|
|
rev ? "RecoveryCapacity" : "WorkloadTolerance", |
|
|
[ |
|
|
rev ? "After demanding workdays, I struggle to recover before the next shift." : "I can handle sustained workload without feeling overwhelmed.", |
|
|
rev ? "I often start shifts without feeling fully recovered." : "I remain effective when workload stays high for several days.", |
|
|
rev ? "I frequently begin work still tired from prior days." : "I manage time pressure without building excessive stress." |
|
|
], |
|
|
rev |
|
|
)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i <= 20; i++) { |
|
|
const rev = i % 4 === 0; |
|
|
q.push(makeLikert( |
|
|
`D-${String(i).padStart(2, "0")}`, |
|
|
"D", |
|
|
rev ? "ErrorRiskUnderLoad" : "SustainedPerformance", |
|
|
[ |
|
|
rev ? "When workload stays high, my accuracy drops noticeably." : "I maintain consistent performance across long busy periods.", |
|
|
rev ? "During extended busy periods, I make more avoidable mistakes." : "Even with heavy demand, I keep my output stable.", |
|
|
rev ? "Under prolonged pressure, my attention slips." : "I manage energy so I can perform reliably long-term." |
|
|
], |
|
|
rev |
|
|
)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i <= 13; i++) { |
|
|
const rev = i % 6 === 0; |
|
|
q.push(makeLikert( |
|
|
`E-${String(i).padStart(2, "0")}`, |
|
|
"E", |
|
|
rev ? "SpeakUpBarrier" : "SpeakUpCulture", |
|
|
[ |
|
|
rev ? "If a supervisor is wrong, it is better to stay quiet to avoid conflict." : "I would speak up respectfully if I notice a safety or compliance risk.", |
|
|
rev ? "I avoid raising concerns when senior staff are involved." : "I raise issues early when I believe safety could be affected.", |
|
|
rev ? "It is not my place to question decisions, even if risks exist." : "I am comfortable reporting risks through the proper channels." |
|
|
], |
|
|
rev |
|
|
)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i <= 15; i++) { |
|
|
const rev = i % 5 === 0; |
|
|
q.push(makeLikert( |
|
|
`F-${String(i).padStart(2, "0")}`, |
|
|
"F", |
|
|
rev ? "HelpSeekingBarrier" : "FunctionalReadiness", |
|
|
[ |
|
|
rev ? "If I were struggling, I would avoid seeking support at work." : "I manage stress in a way that keeps me functional and reliable at work.", |
|
|
rev ? "I would hide difficulties rather than ask for help or guidance." : "I recognize early signs of fatigue and take appropriate steps to stay safe.", |
|
|
rev ? "I prefer to handle serious workload strain alone without involving anyone." : "I remain ready and effective across different shifts and demands." |
|
|
], |
|
|
rev |
|
|
)); |
|
|
} |
|
|
|
|
|
return q; |
|
|
})(); |
|
|
|
|
|
type PRNG = { rand: () => number; pick3: () => 0 | 1 | 2 }; |
|
|
|
|
|
function seeded(seed: string): PRNG { |
|
|
let h = 2166136261; |
|
|
for (let i = 0; i < seed.length; i++) { |
|
|
h ^= seed.charCodeAt(i); |
|
|
h = Math.imul(h, 16777619); |
|
|
} |
|
|
function rand() { |
|
|
h += 0x6d2b79f5; |
|
|
let t = Math.imul(h ^ (h >>> 15), 1 | h); |
|
|
t ^= t + Math.imul(t ^ (t >>> 7), 61 | t); |
|
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296; |
|
|
} |
|
|
return { rand, pick3: () => Math.floor(rand() * 3) as 0 | 1 | 2 }; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function buildExam(seed: string) { |
|
|
const { rand, pick3 } = seeded(seed); |
|
|
|
|
|
const bySection: Record<SectionKey, BaseQuestion[]> = { A: [], B: [], C: [], D: [], E: [], F: [] }; |
|
|
for (const b of QUESTION_BANK) bySection[b.section].push(b); |
|
|
|
|
|
const examBySection: Record<SectionKey, ExamQuestion[]> = { A: [], B: [], C: [], D: [], E: [], F: [] }; |
|
|
const flat: ExamQuestion[] = []; |
|
|
|
|
|
for (const s of ["A", "B", "C", "D", "E", "F"] as SectionKey[]) { |
|
|
const used = new Set<string>(); |
|
|
const pool = bySection[s].slice(); |
|
|
|
|
|
|
|
|
for (let i = pool.length - 1; i > 0; i--) { |
|
|
const j = Math.floor(rand() * (i + 1)); |
|
|
[pool[i], pool[j]] = [pool[j], pool[i]]; |
|
|
} |
|
|
|
|
|
for (const b of pool) { |
|
|
let vi = pick3(); |
|
|
let prompt = b.variants[vi]; |
|
|
|
|
|
if (used.has(prompt)) { |
|
|
const alts: (0 | 1 | 2)[] = [0, 1, 2].filter((x) => x !== vi) as any; |
|
|
for (const a of alts) { |
|
|
const p2 = b.variants[a]; |
|
|
if (!used.has(p2)) { |
|
|
vi = a; |
|
|
prompt = p2; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (used.has(prompt)) { |
|
|
prompt = prompt + "\u200B"; |
|
|
} |
|
|
|
|
|
used.add(prompt); |
|
|
|
|
|
const eq: ExamQuestion = { |
|
|
baseId: b.baseId, |
|
|
section: b.section, |
|
|
type: b.type, |
|
|
domain: b.domain, |
|
|
prompt, |
|
|
options: b.options ?? (b.type === "likert" ? LIKERT_LABELS.map((l) => ({ id: l, label: l })) : undefined), |
|
|
mcqScores: b.mcqScores, |
|
|
reverse: b.reverse, |
|
|
minChars: b.minChars |
|
|
}; |
|
|
examBySection[s].push(eq); |
|
|
flat.push(eq); |
|
|
} |
|
|
} |
|
|
|
|
|
return { examBySection, flat }; |
|
|
} |
|
|
|