Results & Interpretation
+
diff --git a/script.js b/script.js
index 677e69e..2108697 100644
--- a/script.js
+++ b/script.js
@@ -1,4 +1,4 @@
-const questions = [
+const baseAdultQuestions = [
{ id: 1, text: "Considerate of other people's feelings.", subscale: 'Prosocial' },
{ id: 2, text: 'Restless, overactive, cannot stay still for long.', subscale: 'Hyperactivity' },
{ id: 3, text: 'Often complains of headaches, stomach-aches or sickness.', subscale: 'Emotional Symptoms' },
@@ -26,49 +26,73 @@ const questions = [
{ id: 25, text: 'Good attention span, sees chores or homework through to the end.', subscale: 'Hyperactivity', reverse: true }
];
-const scoringBands = {
- 'Emotional Symptoms': {
- normal: [0, 5],
- borderline: [6, 6],
- abnormal: [7, 10]
- },
- 'Conduct Problems': {
- normal: [0, 3],
- borderline: [4, 4],
- abnormal: [5, 10]
- },
- Hyperactivity: {
- normal: [0, 5],
- borderline: [6, 6],
- abnormal: [7, 10]
- },
- 'Peer Problems': {
- normal: [0, 3],
- borderline: [4, 5],
- abnormal: [6, 10]
+const selfReportQuestions = [
+ { id: 1, text: 'I try to be nice to other people. I care about their feelings.', subscale: 'Prosocial' },
+ { id: 2, text: 'I am restless, I cannot stay still for long.', subscale: 'Hyperactivity' },
+ { id: 3, text: 'I get a lot of headaches, stomach-aches or sickness.', subscale: 'Emotional Symptoms' },
+ { id: 4, text: 'I usually share with others, for example CDs, games, food.', subscale: 'Prosocial' },
+ { id: 5, text: 'I get very angry and often lose my temper.', subscale: 'Conduct Problems' },
+ { id: 6, text: 'I would rather be alone than with people of my age.', subscale: 'Peer Problems' },
+ { id: 7, text: 'I usually do as I am told.', subscale: 'Conduct Problems', reverse: true },
+ { id: 8, text: 'I worry a lot.', subscale: 'Emotional Symptoms' },
+ { id: 9, text: 'I am helpful if someone is hurt, upset or feeling ill.', subscale: 'Prosocial' },
+ { id: 10, text: 'I am constantly fidgeting or squirming.', subscale: 'Hyperactivity' },
+ { id: 11, text: 'I have one good friend or more.', subscale: 'Peer Problems', reverse: true },
+ { id: 12, text: 'I fight a lot. I can make other people do what I want.', subscale: 'Conduct Problems' },
+ { id: 13, text: 'I am often unhappy, down-hearted or tearful.', subscale: 'Emotional Symptoms' },
+ { id: 14, text: 'Other people my age generally like me.', subscale: 'Peer Problems', reverse: true },
+ { id: 15, text: 'I am easily distracted, I find it difficult to concentrate.', subscale: 'Hyperactivity' },
+ { id: 16, text: 'I am nervous in new situations. I easily lose confidence.', subscale: 'Emotional Symptoms' },
+ { id: 17, text: 'I am kind to younger children.', subscale: 'Prosocial' },
+ { id: 18, text: 'I am often accused of lying or cheating.', subscale: 'Conduct Problems' },
+ { id: 19, text: 'Other children or young people pick on me or bully me.', subscale: 'Peer Problems' },
+ { id: 20, text: 'I often volunteer to help others (parents, teachers, children).', subscale: 'Prosocial' },
+ { id: 21, text: 'I think before I do things.', subscale: 'Hyperactivity', reverse: true },
+ { id: 22, text: 'I take things that are not mine from home, school or elsewhere.', subscale: 'Conduct Problems' },
+ { id: 23, text: 'I get along better with adults than with people my own age.', subscale: 'Peer Problems' },
+ { id: 24, text: 'I have many fears, I am easily scared.', subscale: 'Emotional Symptoms' },
+ { id: 25, text: "I finish the work I'm doing. My attention is good.", subscale: 'Hyperactivity', reverse: true }
+];
+
+const scoringBandsByVersion = {
+ parent: {
+ 'Emotional Symptoms': { normal: [0, 5], borderline: [6, 6], abnormal: [7, 10] },
+ 'Conduct Problems': { normal: [0, 3], borderline: [4, 4], abnormal: [5, 10] },
+ Hyperactivity: { normal: [0, 5], borderline: [6, 6], abnormal: [7, 10] },
+ 'Peer Problems': { normal: [0, 3], borderline: [4, 5], abnormal: [6, 10] },
+ Prosocial: { normal: [6, 10], borderline: [5, 5], abnormal: [0, 4] },
+ 'Total Difficulties': { normal: [0, 13], borderline: [14, 16], abnormal: [17, 40] }
},
- Prosocial: {
- normal: [6, 10],
- borderline: [5, 5],
- abnormal: [0, 4]
+ teacher: {
+ 'Emotional Symptoms': { normal: [0, 4], borderline: [5, 5], abnormal: [6, 10] },
+ 'Conduct Problems': { normal: [0, 2], borderline: [3, 3], abnormal: [4, 10] },
+ Hyperactivity: { normal: [0, 5], borderline: [6, 6], abnormal: [7, 10] },
+ 'Peer Problems': { normal: [0, 3], borderline: [4, 4], abnormal: [5, 10] },
+ Prosocial: { normal: [6, 10], borderline: [5, 5], abnormal: [0, 4] },
+ 'Total Difficulties': { normal: [0, 11], borderline: [12, 15], abnormal: [16, 40] }
},
- 'Total Difficulties': {
- normal: [0, 13],
- borderline: [14, 16],
- abnormal: [17, 40]
+ self: {
+ 'Emotional Symptoms': { normal: [0, 5], borderline: [6, 6], abnormal: [7, 10] },
+ 'Conduct Problems': { normal: [0, 3], borderline: [4, 4], abnormal: [5, 10] },
+ Hyperactivity: { normal: [0, 5], borderline: [6, 6], abnormal: [7, 10] },
+ 'Peer Problems': { normal: [0, 3], borderline: [4, 5], abnormal: [6, 10] },
+ Prosocial: { normal: [6, 10], borderline: [5, 5], abnormal: [0, 4] },
+ 'Total Difficulties': { normal: [0, 15], borderline: [16, 19], abnormal: [20, 40] }
}
};
+const questionnaires = {
+ parent: { label: 'Parent / Carer Report', questions: baseAdultQuestions },
+ teacher: { label: 'Teacher Report', questions: baseAdultQuestions },
+ self: { label: 'Self-Report (ages 11–17)', questions: selfReportQuestions }
+};
+
const responseLabels = [
{ value: 0, label: 'Not true' },
{ value: 1, label: 'Somewhat true' },
{ value: 2, label: 'Certainly true' }
];
-const responses = Array(questions.length).fill(null);
-let currentQuestionIndex = 0;
-let latestResults = null;
-
const questionCard = document.getElementById('questionCard');
const questionText = document.getElementById('questionText');
const responseForm = document.getElementById('responseForm');
@@ -81,12 +105,23 @@ const resultsGrid = document.getElementById('resultsGrid');
const resultsSummary = document.getElementById('resultsSummary');
const exportPdfButton = document.getElementById('exportPdf');
const retakeButton = document.getElementById('retake');
+const questionnaireTypeSelect = document.getElementById('questionnaireType');
+const identifierInput = document.getElementById('identifier');
+const dateInput = document.getElementById('date');
+const resultsDetails = document.getElementById('resultsDetails');
+
+let currentQuestionnaireKey = questionnaireTypeSelect?.value || 'parent';
+let currentQuestions = questionnaires[currentQuestionnaireKey].questions;
+let currentScoringBands = scoringBandsByVersion[currentQuestionnaireKey];
+let responses = Array(currentQuestions.length).fill(null);
+let currentQuestionIndex = 0;
+let latestResults = null;
function renderQuestion() {
- const question = questions[currentQuestionIndex];
+ const question = currentQuestions[currentQuestionIndex];
questionText.textContent = question.text;
- progressIndicator.textContent = `Question ${currentQuestionIndex + 1} of ${questions.length}`;
- const progressPercent = ((currentQuestionIndex + 1) / questions.length) * 100;
+ progressIndicator.textContent = `Question ${currentQuestionIndex + 1} of ${currentQuestions.length}`;
+ const progressPercent = ((currentQuestionIndex + 1) / currentQuestions.length) * 100;
progressFill.style.width = `${progressPercent}%`;
responseForm.innerHTML = '';
@@ -123,7 +158,7 @@ function updateNavigationState() {
const hasResponse = responses[currentQuestionIndex] !== null;
nextButton.disabled = !hasResponse;
nextButton.textContent =
- currentQuestionIndex === questions.length - 1 ? 'Generate Results' : 'Next';
+ currentQuestionIndex === currentQuestions.length - 1 ? 'Generate Results' : 'Next';
}
function goToQuestion(index) {
@@ -141,7 +176,7 @@ function calculateScores() {
Prosocial: 0
};
- questions.forEach((question, index) => {
+ currentQuestions.forEach((question, index) => {
const responseValue = responses[index];
if (responseValue === null || responseValue === undefined) {
throw new Error(`Missing response for question ${index + 1}`);
@@ -160,7 +195,7 @@ function calculateScores() {
}
function classifyScore(subscale, value) {
- const bands = scoringBands[subscale];
+ const bands = currentScoringBands[subscale];
if (!bands) return { label: 'N/A', key: 'normal' };
if (value >= bands.normal[0] && value <= bands.normal[1]) {
@@ -212,8 +247,9 @@ function buildSummary(scores) {
const totalClassification = classifyScore('Total Difficulties', scores['Total Difficulties']);
const prosocialClassification = classifyScore('Prosocial', scores['Prosocial']);
+ const versionLabel = questionnaires[currentQuestionnaireKey].label;
- let summary = `The Total Difficulties score is ${scores['Total Difficulties']}, which falls within the ${totalClassification.label.toLowerCase()} band.`;
+ let summary = `${versionLabel}: The Total Difficulties score is ${scores['Total Difficulties']}, which falls within the ${totalClassification.label.toLowerCase()} band.`;
if (alerts.length > 0) {
summary += ` Key areas for follow up: ${alerts.join(' ')}`;
@@ -232,13 +268,44 @@ function buildSummary(scores) {
return summary;
}
+function renderResultsDetails({ versionLabel, identifier, date }) {
+ resultsDetails.innerHTML = '';
+ const details = [
+ { term: 'Questionnaire', value: versionLabel },
+ { term: 'Identifier / Name', value: identifier || 'Not provided' },
+ { term: 'Date', value: date || 'Not provided' }
+ ];
+
+ details.forEach(({ term, value }) => {
+ const dt = document.createElement('dt');
+ dt.textContent = term;
+ const dd = document.createElement('dd');
+ dd.textContent = value;
+ resultsDetails.appendChild(dt);
+ resultsDetails.appendChild(dd);
+ });
+}
+
function generateResults() {
try {
const scores = calculateScores();
buildResultsCards(scores);
const summary = buildSummary(scores);
+ const identifier = identifierInput.value.trim();
+ const date = dateInput.value;
+ const versionLabel = questionnaires[currentQuestionnaireKey].label;
+
+ renderResultsDetails({ versionLabel, identifier, date });
resultsSummary.textContent = summary;
- latestResults = { scores, summary };
+
+ latestResults = {
+ scores,
+ summary,
+ questionnaireKey: currentQuestionnaireKey,
+ identifier,
+ date
+ };
+
questionCard.hidden = true;
resultsSection.hidden = false;
resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
@@ -248,11 +315,13 @@ function generateResults() {
}
function resetAssessment() {
- responses.fill(null);
+ responses = Array(currentQuestions.length).fill(null);
currentQuestionIndex = 0;
latestResults = null;
resultsSection.hidden = true;
questionCard.hidden = false;
+ resultsSummary.textContent = '';
+ resultsDetails.innerHTML = '';
renderQuestion();
window.scrollTo({ top: 0, behavior: 'smooth' });
}
@@ -268,6 +337,8 @@ function exportResultsAsPdf() {
const margin = 48;
let cursorY = margin;
+ const versionLabel = questionnaires[latestResults.questionnaireKey].label;
+
doc.setFont('helvetica', 'bold');
doc.setFontSize(18);
doc.text('Strengths and Difficulties Questionnaire (SDQ) Results', margin, cursorY);
@@ -275,7 +346,23 @@ function exportResultsAsPdf() {
cursorY += 32;
doc.setFontSize(12);
doc.setFont('helvetica', 'normal');
- const wrapSummary = doc.splitTextToSize(latestResults.summary, doc.internal.pageSize.getWidth() - margin * 2);
+ doc.text(`Questionnaire: ${versionLabel}`, margin, cursorY);
+
+ if (latestResults.identifier) {
+ cursorY += 18;
+ doc.text(`Identifier / Name: ${latestResults.identifier}`, margin, cursorY);
+ }
+
+ if (latestResults.date) {
+ cursorY += 18;
+ doc.text(`Date: ${latestResults.date}`, margin, cursorY);
+ }
+
+ cursorY += 24;
+ const wrapSummary = doc.splitTextToSize(
+ latestResults.summary,
+ doc.internal.pageSize.getWidth() - margin * 2
+ );
doc.text(wrapSummary, margin, cursorY);
cursorY += wrapSummary.length * 16 + 12;
@@ -301,6 +388,20 @@ function exportResultsAsPdf() {
doc.save('sdq-results.pdf');
}
+function loadQuestionnaire(key) {
+ currentQuestionnaireKey = key;
+ currentQuestions = questionnaires[key].questions;
+ currentScoringBands = scoringBandsByVersion[key];
+ responses = Array(currentQuestions.length).fill(null);
+ currentQuestionIndex = 0;
+ latestResults = null;
+ resultsSection.hidden = true;
+ questionCard.hidden = false;
+ resultsSummary.textContent = '';
+ resultsDetails.innerHTML = '';
+ renderQuestion();
+}
+
prevButton.addEventListener('click', () => {
if (currentQuestionIndex > 0) {
goToQuestion(currentQuestionIndex - 1);
@@ -312,7 +413,7 @@ nextButton.addEventListener('click', () => {
return;
}
- if (currentQuestionIndex === questions.length - 1) {
+ if (currentQuestionIndex === currentQuestions.length - 1) {
if (responses.some((response) => response === null)) {
alert('Please answer all questions before generating results.');
return;
@@ -326,13 +427,27 @@ nextButton.addEventListener('click', () => {
retakeButton.addEventListener('click', resetAssessment);
exportPdfButton.addEventListener('click', exportResultsAsPdf);
-renderQuestion();
+if (questionnaireTypeSelect) {
+ questionnaireTypeSelect.addEventListener('change', (event) => {
+ const newKey = event.target.value;
+ if (newKey === currentQuestionnaireKey) return;
-// Auto-fill date field with today's date
-document.addEventListener("DOMContentLoaded", () => {
- const dateField = document.getElementById("date");
- if (dateField && !dateField.value) {
- const today = new Date().toISOString().split("T")[0];
- dateField.value = today;
- }
-});
+ const hasProgress = responses.some((response) => response !== null);
+ const hasResults = !resultsSection.hidden;
+
+ if ((hasProgress || hasResults) && !confirm('Switching the questionnaire version will clear current responses and results. Continue?')) {
+ event.target.value = currentQuestionnaireKey;
+ return;
+ }
+
+ loadQuestionnaire(newKey);
+ });
+}
+
+loadQuestionnaire(currentQuestionnaireKey);
+
+// Auto-fill date field with today's date if empty
+if (dateInput && !dateInput.value) {
+ const today = new Date().toISOString().split('T')[0];
+ dateInput.value = today;
+}
diff --git a/styles.css b/styles.css
index 6ffe5fa..7c94c0e 100644
--- a/styles.css
+++ b/styles.css
@@ -32,6 +32,48 @@ body {
width: min(850px, 100%);
}
+.metadata {
+ background: var(--surface);
+ border-radius: 1.25rem;
+ padding: clamp(1.25rem, 3vw, 2rem);
+ box-shadow: 0 18px 35px rgba(15, 23, 42, 0.08);
+ margin-bottom: 2rem;
+ display: grid;
+ gap: 1.25rem;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+}
+
+.metadata__field {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.metadata__field--full {
+ grid-column: 1 / -1;
+}
+
+.metadata label {
+ font-weight: 600;
+ color: var(--primary-dark);
+}
+
+.metadata input,
+.metadata select {
+ border: 1px solid var(--border);
+ border-radius: 0.75rem;
+ padding: 0.75rem 1rem;
+ font-size: 1rem;
+ transition: border-color 200ms ease, box-shadow 200ms ease;
+}
+
+.metadata input:focus,
+.metadata select:focus {
+ outline: none;
+ border-color: var(--primary);
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
+}
+
.app__header {
text-align: center;
margin-bottom: 2rem;
@@ -224,6 +266,24 @@ body {
line-height: 1.6;
}
+.results__details {
+ display: grid;
+ gap: 0.5rem 1.5rem;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ margin-bottom: 1.5rem;
+}
+
+.results__details dt {
+ font-weight: 600;
+ color: var(--muted);
+}
+
+.results__details dd {
+ margin: 0;
+ font-weight: 600;
+ color: var(--text);
+}
+
.results__note {
font-size: 0.95rem;
color: var(--muted);
@@ -242,6 +302,10 @@ body {
padding: 1.5rem 0.75rem;
}
+ .metadata {
+ padding: 1.25rem;
+ }
+
.question-card__actions {
flex-direction: column;
}