136 lines
4.5 KiB
JavaScript
136 lines
4.5 KiB
JavaScript
const STABLE_KEY_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
const RESULTS_EXCLUDED_KEYS = new Set(['data_final_warning']);
|
|
const RESULTS_EXCLUDED_TYPES = new Set(['last_page']);
|
|
|
|
/** Questions hidden from results tables and CSV exports (e.g. last-page warnings). */
|
|
export function isResultsExcludedQuestion(q) {
|
|
if (!q) return false;
|
|
if (RESULTS_EXCLUDED_TYPES.has(q.type)) return true;
|
|
return RESULTS_EXCLUDED_KEYS.has(questionDisplayKey(q));
|
|
}
|
|
|
|
/** Local question id from full questionID (hash__q5 → q5). */
|
|
export function questionLocalKey(questionID, fallback = '') {
|
|
const id = String(questionID ?? '');
|
|
const sep = id.lastIndexOf('__');
|
|
if (sep >= 0) return id.slice(sep + 2);
|
|
return fallback || id;
|
|
}
|
|
|
|
function parseQuestionConfig(q) {
|
|
if (!q || typeof q !== 'object') return {};
|
|
let cfg = q.configJson;
|
|
if (typeof cfg === 'string') {
|
|
try {
|
|
cfg = JSON.parse(cfg);
|
|
} catch {
|
|
cfg = {};
|
|
}
|
|
}
|
|
return cfg && typeof cfg === 'object' ? cfg : {};
|
|
}
|
|
|
|
/** Stable question key for table headers / CSV (matches server qdb_question_column_label). */
|
|
export function questionDisplayKey(q) {
|
|
if (!q) return '';
|
|
if (q.questionKey) return String(q.questionKey);
|
|
const cfg = parseQuestionConfig(q);
|
|
const fromCfg = String(cfg.questionKey || '').trim();
|
|
if (fromCfg && STABLE_KEY_RE.test(fromCfg)) return fromCfg;
|
|
const dt = String(q.defaultText || '').trim();
|
|
if (dt && STABLE_KEY_RE.test(dt)) return dt;
|
|
return questionLocalKey(q.questionID, dt);
|
|
}
|
|
|
|
/** German question text for header tooltips. */
|
|
export function questionGermanLabel(q) {
|
|
const dt = String(q?.defaultText || '').trim();
|
|
const key = questionDisplayKey(q);
|
|
if (dt && dt !== key) return dt;
|
|
return '';
|
|
}
|
|
|
|
/** One symptom value from parent glass-scale JSON answer. */
|
|
export function glassSymptomAnswerValue(raw, symptomKey) {
|
|
if (!raw || !symptomKey) return '';
|
|
try {
|
|
const o = JSON.parse(raw);
|
|
if (o && typeof o === 'object' && !Array.isArray(o)) {
|
|
const v = o[symptomKey];
|
|
return v != null ? String(v).trim() : '';
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Flat result/export columns: glass_scale questions → one column per symptom.
|
|
*/
|
|
export function buildResultColumns(questions) {
|
|
const cols = [];
|
|
for (const q of questions || []) {
|
|
if (isResultsExcludedQuestion(q)) continue;
|
|
const cfg = parseQuestionConfig(q);
|
|
const symptoms = cfg.symptoms;
|
|
if (q.type === 'glass_scale_question' && Array.isArray(symptoms) && symptoms.length > 0) {
|
|
const parentKey = questionDisplayKey(q);
|
|
for (const sk of symptoms) {
|
|
const symptomKey = String(sk).trim();
|
|
if (!symptomKey) continue;
|
|
cols.push({
|
|
key: `glass_${q.questionID}_${symptomKey}`,
|
|
questionID: q.questionID,
|
|
symptomKey,
|
|
label: symptomKey,
|
|
title: parentKey && parentKey !== symptomKey
|
|
? `${parentKey} · ${symptomKey}`
|
|
: symptomKey,
|
|
isGlassSymptom: true,
|
|
question: q,
|
|
});
|
|
}
|
|
continue;
|
|
}
|
|
const label = questionDisplayKey(q);
|
|
cols.push({
|
|
key: `q_${q.questionID}`,
|
|
questionID: q.questionID,
|
|
symptomKey: null,
|
|
label,
|
|
title: questionGermanLabel(q) || label,
|
|
isGlassSymptom: false,
|
|
question: q,
|
|
});
|
|
}
|
|
return cols;
|
|
}
|
|
|
|
/** Display text for one results table cell. */
|
|
export function resultColumnCellValue(col, answer, optionMap = {}) {
|
|
if (!answer) return null;
|
|
if (col.isGlassSymptom) {
|
|
const v = glassSymptomAnswerValue(answer.freeTextValue, col.symptomKey);
|
|
return v || null;
|
|
}
|
|
const q = col.question;
|
|
if (answer.answerOptionID && optionMap[answer.answerOptionID]) {
|
|
return optionMap[answer.answerOptionID];
|
|
}
|
|
if (answer.freeTextValue) return answer.freeTextValue;
|
|
if (answer.numericValue !== null && answer.numericValue !== undefined) {
|
|
return answer.numericValue;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** header_order.json column id → question key (questionnaire_x-y → y). */
|
|
export function headerColumnQuestionKey(headerId) {
|
|
const id = String(headerId ?? '');
|
|
if (id === 'client_code') return 'client_code';
|
|
const dash = id.indexOf('-');
|
|
if (dash < 0) return id;
|
|
return id.slice(dash + 1);
|
|
}
|