- {{ formPreviewData.submitted ? 'Submitted' : 'Not submitted' }}
-
- ·
- {{
- new Date(
- formPreviewData.completedAt ?? formPreviewData.submittedAt ?? ''
- ).toLocaleString('en-US')
- }}
-
-
-
-
{{ q.label }}
-
-
- {{ q.answer || '—' }}
-
+
+
+
-
-
-
-
+
+
+
+
+ Score: {{ formPreviewData.score }}
+
+
+ {{ formPreviewData.severity }}
+
+
+
+
+
+
+
+
+
+
+ {{ label }}
+
+
+
+
+
+
{{ q.label }}
+
+ {{ selectedFormKey === 'application' ? formatAppAnswer(q.answer) : (q.answer || '—') }}
+
+
+
+
No answers yet.
@@ -1855,7 +1959,7 @@
-
+
\ No newline at end of file
diff --git a/app/composables/useApplicationOptions.ts b/app/composables/useApplicationOptions.ts
new file mode 100644
index 0000000..c85f18d
--- /dev/null
+++ b/app/composables/useApplicationOptions.ts
@@ -0,0 +1,57 @@
+// Shared options (gender, yes/no, custody, etc.)
+export function useApplicationOptions() {
+ const genderOptions = [
+ { label: 'Male', value: 'Male' },
+ { label: 'Female', value: 'Female' },
+ { label: 'Non-binary', value: 'Non-binary' },
+ { label: 'Prefer not to say', value: 'Prefer not to say' },
+ { label: 'Other', value: 'Other' },
+ ]
+ const yesNoOptions = [
+ { label: 'Yes', value: 'yes' },
+ { label: 'No', value: 'no' },
+ ]
+ const custodyOptions = [
+ { label: 'Mother', value: 'mother' },
+ { label: 'Father', value: 'father' },
+ { label: 'Joint', value: 'joint' },
+ { label: 'Other', value: 'other' },
+ ]
+ const caregiverOptions = [
+ { label: 'Mom', value: 'Mom' },
+ { label: 'Dad', value: 'Dad' },
+ { label: 'Both', value: 'Both' },
+ { label: 'Other', value: 'Other' },
+ ]
+ const siblingOptions = [
+ { label: 'Yes', value: 'yes' },
+ { label: 'No', value: 'no' },
+ { label: 'N/A', value: 'na' },
+ ]
+ const supportGroupOptions = [
+ { label: 'Adolescent child diagnosed with cancer', value: 'adolescent_child_diagnosed_with_cancer' },
+ { label: 'Adolescent sibling', value: 'adolescent_sibling' },
+ { label: 'Parent', value: 'parent' },
+ { label: 'No', value: 'no' },
+ ]
+ const referralOptions = [
+ { label: 'I have a therapist', value: 'have_therapist' },
+ { label: 'I need a referral', value: 'need_referral' },
+ ]
+ const insuranceOptions = [
+ { label: 'Yes, with mental health benefits', value: 'yes_with_mental_health_benefits' },
+ { label: 'Yes, without mental health benefits', value: 'yes_without_mental_health_benefits' },
+ { label: 'No insurance', value: 'no_insurance' },
+ ]
+
+ return {
+ genderOptions,
+ yesNoOptions,
+ custodyOptions,
+ caregiverOptions,
+ siblingOptions,
+ supportGroupOptions,
+ referralOptions,
+ insuranceOptions,
+ }
+}
\ No newline at end of file
diff --git a/app/pages/forms/pcl.vue b/app/pages/forms/pcl.vue
index 2eb82ee..943cfed 100644
--- a/app/pages/forms/pcl.vue
+++ b/app/pages/forms/pcl.vue
@@ -58,11 +58,29 @@
}
function buildPayload() {
- const body: Record
= { worstEvent: worstEvent.value }
- responses.value.forEach((val, i) => {
- body[`q${i + 1}`] = val === -1 ? null : val
- })
- return body
+ return {
+ worstEvent: worstEvent.value,
+ q01: responses.value[0],
+ q02: responses.value[1],
+ q03: responses.value[2],
+ q04: responses.value[3],
+ q05: responses.value[4],
+ q06: responses.value[5],
+ q07: responses.value[6],
+ q08: responses.value[7],
+ q09: responses.value[8],
+ q10: responses.value[9],
+ q11: responses.value[10],
+ q12: responses.value[11],
+ q13: responses.value[12],
+ q14: responses.value[13],
+ q15: responses.value[14],
+ q16: responses.value[15],
+ q17: responses.value[16],
+ q18: responses.value[17],
+ q19: responses.value[18],
+ q20: responses.value[19],
+ }
}
async function submitForm() {
diff --git a/prisma/seed.ts b/prisma/seed.ts
index df323a9..8a706c4 100644
--- a/prisma/seed.ts
+++ b/prisma/seed.ts
@@ -14,12 +14,8 @@ async function seedForms(userId: string) {
// 1. AppForm (Application)
const appForm = await prisma.appForm.upsert({
where: { userId },
- update: {},
- create: {
- userId,
- status: 'COMPLETE',
- submittedAt: new Date(),
- },
+ update: { status: 'COMPLETE', submittedAt: new Date() },
+ create: { userId, status: 'COMPLETE', submittedAt: new Date() },
})
await prisma.appQuestion.upsert({
@@ -31,33 +27,15 @@ async function seedForms(userId: string) {
q01: 'Bob',
q02: 'Builder',
q05: '1234567890',
- },
- })
+ },
+})
// 2. GAD-7
const gadForm = await prisma.gadForm.create({
- data: {
- userId,
- status: 'COMPLETE',
- totalScore: 12,
- severity: 'Moderate',
- submittedAt: new Date(),
- },
+ data: { userId, status: 'COMPLETE', totalScore: 12, severity: 'Moderate', submittedAt: new Date() },
})
-
await prisma.gadQuestion.create({
- data: {
- formId: gadForm.id,
- userId,
- g01: 2,
- g02: 1,
- g03: 2,
- g04: 1,
- g05: 2,
- g06: 2,
- g07: 2,
- g08: 1,
- },
+ data: { formId: gadForm.id, userId, g01: 2, g02: 1, g03: 2, g04: 1, g05: 2, g06: 2, g07: 2, g08: 1 },
})
// 3. PHQ-9
@@ -70,61 +48,19 @@ async function seedForms(userId: string) {
submittedAt: new Date(),
},
})
-
- await prisma.phqQuestion.upsert({
- where: { formId: phqForm.id },
- update: {},
- create: {
- formId: phqForm.id,
- userId,
- q1: 2,
- q2: 1,
- q3: 2,
- q4: 1,
- q5: 2,
- q6: 2,
- q7: 2,
- q8: 1,
- q9: 1,
- q10: 1,
- },
+ await prisma.phqQuestion.create({
+ data: { formId: phqForm.id, userId, q1: 2, q2: 1, q3: 2, q4: 1, q5: 2, q6: 2, q7: 2, q8: 1, q9: 1, q10: 1 },
})
// 4. PCL-5
const pclForm = await prisma.pclForm.create({
- data: {
- userId,
- status: 'COMPLETE',
- totalScore: 45,
- severity: 'Moderate',
- submittedAt: new Date(),
- },
+ data: { userId, status: 'COMPLETE', totalScore: 45, severity: 'Moderate', submittedAt: new Date() },
})
-
await prisma.pclQuestion.create({
data: {
- formId: pclForm.id,
- userId,
- q01: 3,
- q02: 2,
- q03: 3,
- q04: 2,
- q05: 2,
- q06: 3,
- q07: 2,
- q08: 2,
- q09: 2,
- q10: 2,
- q11: 2,
- q12: 2,
- q13: 2,
- q14: 2,
- q15: 2,
- q16: 2,
- q17: 2,
- q18: 2,
- q19: 2,
- q20: 2,
+ formId: pclForm.id, userId,
+ q01: 3, q02: 2, q03: 3, q04: 2, q05: 2, q06: 3, q07: 2, q08: 2, q09: 2, q10: 2,
+ q11: 2, q12: 2, q13: 2, q14: 2, q15: 2, q16: 2, q17: 2, q18: 2, q19: 2, q20: 2,
},
})
@@ -138,31 +74,19 @@ async function seedForms(userId: string) {
submittedAt: new Date(),
},
})
-
- await prisma.aceQuestion.upsert({
- where: { formId: aceForm.id },
- update: {},
- create: {
- formId: aceForm.id,
- userId,
- a01: 'Yes',
- a02: 'Yes',
- a03: 'No',
- a04: 'No',
- a05: 'No',
- a06: 'No',
- a07: 'No',
- a08: 'No',
- a09: 'No',
- a10: 'No',
+ await prisma.aceQuestion.create({
+ data: {
+ formId: aceForm.id, userId,
+ a01: 'Yes', a02: 'No', a03: 'No', a04: 'Yes', a05: 'Yes',
+ a06: 'Yes', a07: 'No', a08: 'Yes', a09: 'No', a10: 'No',
},
})
}
-async function ensureBobBuilderSessionNotes(bobUserId: string) {
+async function ensureBobBuilderSessionNotes(bobUserId: string, clinicianUserId: string) {
const client = await prisma.client.upsert({
where: { userId: bobUserId },
- update: { status: 'ACTIVE' },
+ update: { status: 'ACTIVE', clinician: { connect: { id: clinicianUserId } } },
create: { userId: bobUserId, status: 'ACTIVE' },
})
@@ -170,27 +94,27 @@ async function ensureBobBuilderSessionNotes(bobUserId: string) {
where: { clientId: client.id },
})
- if (existingSessionNotes === 0) {
- await prisma.sessionNote.createMany({
- data: [
- {
- clientId: client.id,
- sessionName: 'Intake / Week 1',
- sessionNumber: 1,
- content:
- 'Intake / Week 1 — Rapport established. Bob reviewed clinic policies and confidentiality. Reported primary stressors related to work deadlines and sleep disruption. PHQ-9 and GAD-7 administered; safety screen negative. Plan: sleep hygiene handout, begin weekly CBT skills.',
- },
- {
- clientId: client.id,
- sessionName: 'Session 2',
- sessionNumber: 2,
- content:
- 'Session 2 — Focus on thought challenging around catastrophic predictions at work. Homework: thought record for 3 situations. Bob engaged well; identified one automatic thought pattern to monitor between sessions.',
- },
- ],
- })
- console.log('Created sample SessionNote rows for Bob Builder.')
- }
+ // if (existingSessionNotes === 0) {
+ // await prisma.sessionNote.createMany({
+ // data: [
+ // {
+ // clientId: client.id,
+ // sessionName: 'Intake / Week 1',
+ // sessionNumber: 1,
+ // content:
+ // 'Intake / Week 1 — Rapport established. Bob reviewed clinic policies and confidentiality. Reported primary stressors related to work deadlines and sleep disruption. PHQ-9 and GAD-7 administered; safety screen negative. Plan: sleep hygiene handout, begin weekly CBT skills.',
+ // },
+ // {
+ // clientId: client.id,
+ // sessionName: 'Session 2',
+ // sessionNumber: 2,
+ // content:
+ // 'Session 2 — Focus on thought challenging around catastrophic predictions at work. Homework: thought record for 3 situations. Bob engaged well; identified one automatic thought pattern to monitor between sessions.',
+ // },
+ // ],
+ // })
+ // console.log('Created sample SessionNote rows for Bob Builder.')
+ // }
// Populate form dummy data if it doesn't exist
const existingApp = await prisma.appForm.count({ where: { userId: bobUserId } })
@@ -198,6 +122,89 @@ async function ensureBobBuilderSessionNotes(bobUserId: string) {
await seedForms(bobUserId)
console.log('Seeded clinical forms for Bob Builder.')
}
+
+ return client
+}
+
+async function seedApprovalWorkflowNotes(
+ clientId: string,
+ clinicianUserId: string,
+ adminUserId: string
+) {
+ const existing = await prisma.sessionNote.count({ where: { clientId } })
+ if (existing > 0) {
+ console.log('Session notes already exist; skipping approval-workflow seed.')
+ return
+ }
+
+ const PLACEHOLDER_SIG =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
+
+ const now = new Date()
+
+ await prisma.sessionNote.create({
+ data: {
+ clientId, sessionName: 'Intake – initial session', sessionNumber: 1,
+ kind: 'PROGRESS', status: 'DRAFT',
+ content: 'Client arrived on time. Presenting concerns include sleep disruption and work stress. Draft — still gathering history.',
+ attendanceStatus: 'show',
+ },
+ })
+
+ const pendingNote = await prisma.sessionNote.create({
+ data: {
+ clientId, sessionName: 'Session 2 – CBT intro', sessionNumber: 2,
+ kind: 'PROGRESS', status: 'CLINICIAN_SIGNED',
+ content: 'Reviewed sleep-hygiene worksheet. Client reports mild improvement. Introduced cognitive-restructuring framework.',
+ attendanceStatus: 'late',
+ clinicianSignedAt: now, clinicianSignedById: clinicianUserId, clinicianSignatureData: PLACEHOLDER_SIG,
+ },
+ })
+
+ await prisma.notification.create({
+ data: {
+ userId: adminUserId,
+ type: 'NOTE_READY_FOR_APPROVAL',
+ title: 'Note awaiting approval',
+ message: 'Carl Karl signed a progress note for Bob Builder. Please review and countersign.',
+ sessionNoteId: pendingNote.id,
+ },
+ })
+
+ await prisma.sessionNote.create({
+ data: {
+ clientId, sessionName: 'Session 3 – thought records', sessionNumber: 3,
+ kind: 'PROGRESS', status: 'FULLY_APPROVED',
+ content: 'Completed two thought-record examples in session. Client identified core belief patterns and committed to daily practice.',
+ attendanceStatus: 'no-show',
+ clinicianSignedAt: now, clinicianSignedById: clinicianUserId, clinicianSignatureData: PLACEHOLDER_SIG,
+ adminSignedAt: now, adminSignedById: adminUserId, adminSignatureData: PLACEHOLDER_SIG,
+ adminApprovalNote: 'Reviewed — documentation meets clinic standard.',
+ },
+ })
+
+ await prisma.sessionNote.create({
+ data: {
+ clientId, sessionName: 'Session 3 – clinician process notes', sessionNumber: 3,
+ kind: 'PSYCHOTHERAPY', status: 'DRAFT',
+ content: 'Clinician-only process notes: countertransference observations, working hypotheses, and next-session targets.',
+ attendanceStatus: 'show',
+ },
+ })
+
+ await prisma.sessionNote.create({
+ data: {
+ clientId, sessionName: 'Session 2 – clinician process notes', sessionNumber: 2,
+ kind: 'PSYCHOTHERAPY', status: 'FULLY_APPROVED',
+ content: 'Process notes: explored defense patterns around perfectionism. Plan to revisit in session 4.',
+ attendanceStatus: 'show',
+ clinicianSignedAt: now, clinicianSignedById: clinicianUserId, clinicianSignatureData: PLACEHOLDER_SIG,
+ adminSignedAt: now, adminSignedById: adminUserId, adminSignatureData: PLACEHOLDER_SIG,
+ adminApprovalNote: 'Approved — psychotherapy note retained separately.',
+ },
+ })
+
+ console.log('Seeded 5 session notes across DRAFT / CLINICIAN_SIGNED / FULLY_APPROVED.')
}
async function main() {
@@ -207,16 +214,14 @@ async function main() {
await backfillSessionNotesRequestTemplates(prisma)
// Create / Upsert Alice (Admin)
- await prisma.user.upsert({
+ const alice = await prisma.user.upsert({
where: { email: 'alice@a.com' },
update: { role: 'ADMIN', name: 'Alice Wonderland' },
- create: {
- id: 'alice_id',
- email: 'alice@a.com',
- name: 'Alice Wonderland',
- emailVerified: true,
- role: 'ADMIN',
- },
+ create: { id: 'alice_id',
+ email: 'alice@a.com',
+ name: 'Alice Wonderland',
+ emailVerified: true,
+ role: 'ADMIN' },
})
console.log('Seeded Admin: alice@a.com')
@@ -245,34 +250,22 @@ async function main() {
console.log(`Seeded Admin: ${admin.email}`)
}
- await prisma.user.upsert({
+ const carl = await prisma.user.upsert({
where: { email: 'carl@c.com' },
update: { role: 'CLINICIAN', name: 'Carl Karl' },
- create: {
- id: 'carl_id',
- email: 'carl@c.com',
- name: 'Carl Karl',
- emailVerified: true,
- role: 'CLINICIAN',
- },
+ create: { id: 'carl_id', email: 'carl@c.com', name: 'Carl Karl', emailVerified: true, role: 'CLINICIAN' },
})
console.log('Seeded Clinician: carl@c.com')
- // Create / Upsert Bob (Client)
const bob = await prisma.user.upsert({
where: { email: 'bob@b.com' },
update: { role: 'CLIENT', name: 'Bob Builder' },
- create: {
- id: 'bob_id',
- email: 'bob@b.com',
- name: 'Bob Builder',
- emailVerified: true,
- role: 'CLIENT',
- },
+ create: { id: 'bob_id', email: 'bob@b.com', name: 'Bob Builder', emailVerified: true, role: 'CLIENT' },
})
console.log('Seeded Client: bob@b.com')
- await ensureBobBuilderSessionNotes(bob.id)
+ const bobClient = await ensureBobBuilderSessionNotes(bob.id, carl.id)
+ await seedApprovalWorkflowNotes(bobClient.id, carl.id, alice.id)
console.log('Seeding finished.')
}
diff --git a/server/api/admin/backfill-absences.post.ts b/server/api/admin/backfill-absences.post.ts
index 4a39ae6..23b4777 100644
--- a/server/api/admin/backfill-absences.post.ts
+++ b/server/api/admin/backfill-absences.post.ts
@@ -22,7 +22,7 @@ export default defineEventHandler(async (event) => {
const calendarAbsences = await prisma.sessionNote.count({
where: {
clientId: client.id,
- attended: false,
+ attendanceStatus: 'no-show',
},
})
diff --git a/server/api/appointments/index.post.ts b/server/api/appointments/index.post.ts
index 4a1b91d..64df377 100644
--- a/server/api/appointments/index.post.ts
+++ b/server/api/appointments/index.post.ts
@@ -130,7 +130,7 @@ export default defineEventHandler(async (event) => {
sessionName,
sessionNumber,
content: '',
- attended: true,
+ attendanceStatus: 'show',
},
})
}
diff --git a/server/api/clients/[id]/forms/[formKey].get.ts b/server/api/clients/[id]/forms/[formKey].get.ts
index 9c2bb50..ad5af96 100644
--- a/server/api/clients/[id]/forms/[formKey].get.ts
+++ b/server/api/clients/[id]/forms/[formKey].get.ts
@@ -115,7 +115,18 @@ export default defineEventHandler(async (event) => {
if (!trimmed) return ''
try {
const parsed = JSON.parse(trimmed) as unknown
- if (Array.isArray(parsed)) return parsed.join(', ')
+ if (Array.isArray(parsed)) {
+ return parsed.map((item: unknown) => {
+ if (item && typeof item === 'object') {
+ const r = item as Record
+ if (typeof r.firstName === 'string') {
+ return [r.firstName, r.middleInitial, r.lastName, r.age ? `(age ${r.age})` : '', r.relationship].filter(Boolean).join(' ')
+ }
+ return Object.values(r).filter(Boolean).join(' ')
+ }
+ return String(item)
+ }).join(', ')
+ }
if (parsed && typeof parsed === 'object') {
const r = parsed as Record
if (Array.isArray(r.values)) {
@@ -176,6 +187,7 @@ export default defineEventHandler(async (event) => {
formName: 'GAD-7',
questions,
submitted: gadForm?.status === 'COMPLETE',
+ submittedAt: gadForm?.submittedAt,
score: gadForm?.totalScore,
severity: gadForm?.severity,
}
@@ -193,10 +205,13 @@ export default defineEventHandler(async (event) => {
formName: 'PHQ-9',
questions,
submitted: phqForm?.status === 'COMPLETE',
+ submittedAt: phqForm?.submittedAt,
score: phqForm?.totalScore,
+ severity: phqForm?.severity,
}
}
+ // ── PCL-5 ─────────────────────────────────────────────────
if (formKey === 'pcl') {
const pclForm = await prisma.pclForm.findFirst({
where: { userId: clientUserId },
@@ -209,7 +224,46 @@ export default defineEventHandler(async (event) => {
(await prisma.pclQuestion.findFirst({ where: { formId: pclForm.id } })) ??
(await prisma.pclQuestion.findFirst({ where: { userId: clientUserId } }))
}
- const questions = await loadClinicalFormQuestions(prisma, clientUserId, 'pcl')
+ let questions = await loadClinicalFormQuestions(prisma, clientUserId, 'pcl')
+
+ const OPTIONS = ['Not at all', 'A little bit', 'Moderately', 'Quite a bit', 'Extremely']
+
+ if (questions.length < 20 && pclForm) { // ← was `< 0`, which is never true
+ const PCL5_QUESTIONS = [
+ 'Repeated, disturbing, and unwanted memories of the stressful experience?',
+ 'Repeated, disturbing dreams of the stressful experience?',
+ 'Suddenly feeling or acting as if the stressful experience were actually happening again?',
+ 'Feeling very upset when something reminded you of the stressful experience?',
+ 'Having strong physical reactions when something reminded you of the stressful experience?',
+ 'Avoiding memories, thoughts, or feelings related to the stressful experience?',
+ 'Avoiding external reminders of the stressful experience?',
+ 'Trouble remembering important parts of the stressful experience?',
+ 'Having strong negative beliefs about yourself, other people, or the world?',
+ 'Blaming yourself or someone else for the stressful experience or what happened after it?',
+ 'Having strong negative feelings such as fear, horror, anger, guilt, or shame?',
+ 'Loss of interest in activities that you used to enjoy?',
+ 'Feeling distant or cut off from other people?',
+ 'Trouble experiencing positive feelings?',
+ 'Irritable behavior, angry outbursts, or acting aggressively?',
+ 'Taking too many risks or doing things that could cause you harm?',
+ 'Being "superalert" or watchful or on guard?',
+ 'Feeling jumpy or easily startled?',
+ 'Having difficulty concentrating?',
+ 'Trouble falling or staying asleep?',
+ ]
+
+ questions = [
+ // worstEvent as the first question so it appears at the top
+ { label: 'Worst event', answer: q?.worstEvent ?? '' },
+ ...PCL5_QUESTIONS.map((label, i) => {
+ const key = `q${String(i + 1).padStart(2, '0')}` as keyof typeof q
+ const raw = q?.[key]
+ const answer = typeof raw === 'number' && raw >= 0 && raw <= 4 ? (OPTIONS[raw] ?? '') : ''
+ return { label, answer }
+ }),
+ ]
+ }
+
let totalScore = pclForm?.totalScore ?? null
if (q && totalScore == null) {
totalScore = 0
@@ -236,6 +290,5 @@ export default defineEventHandler(async (event) => {
severity,
}
}
-
throw createError({ statusCode: 400, statusMessage: 'Invalid form key' })
})
diff --git a/server/api/clients/[id]/forms/[formKey].patch.ts b/server/api/clients/[id]/forms/[formKey].patch.ts
index 0b77895..392944c 100644
--- a/server/api/clients/[id]/forms/[formKey].patch.ts
+++ b/server/api/clients/[id]/forms/[formKey].patch.ts
@@ -11,6 +11,13 @@ const PHQ_OPTIONS: Record = {
'Nearly every day': 3,
}
+const PHQ_DIFFICULTY_OPTIONS: Record = {
+ 'Not difficult at all': 0,
+ 'Somewhat difficult': 1,
+ 'Very difficult': 2,
+ 'Extremely difficult': 3,
+}
+
const GAD_OPTIONS: Record = {
'Not at all': 0,
'Several days': 1,
@@ -18,6 +25,14 @@ const GAD_OPTIONS: Record = {
'Nearly every day': 3,
}
+const PCL_OPTIONS: Record = {
+ 'Not at all': 0,
+ 'A little bit': 1,
+ 'Moderately': 2,
+ 'Quite a bit': 3,
+ 'Extremely': 4,
+}
+
function toInt(val: string, map: Record): number | null {
if (val in map) return map[val]!
const n = parseInt(val)
@@ -56,12 +71,17 @@ export default defineEventHandler(async (event) => {
// ── ACE ──────────────────────────────────────────────────
if (formKey === 'ace') {
- const form = await prisma.aceForm.findFirst({ where: { userId: clientUserId } })
+ const form = await prisma.aceForm.findFirst({ where: { userId: clientUserId }, orderBy: { id: 'desc' } })
if (!form) throw createError({ statusCode: 404, statusMessage: 'Form not found' })
const keys = ['a01','a02','a03','a04','a05','a06','a07','a08','a09','a10']
const data: Record = {}
answers.forEach((a, i) => { if (keys[i]) data[keys[i]!] = a.answer })
await prisma.aceQuestion.update({ where: { formId: form.id }, data })
+
+ // Recalculate score
+ const total = Object.values(data).filter(v => v === 'Yes').length
+ const severity = total === 0 ? 'No reported ACEs' : total <= 3 ? 'Low' : total <= 6 ? 'Moderate' : 'High'
+ await prisma.aceForm.update({ where: { id: form.id }, data: { totalScore: total, severity } })
return { ok: true }
}
@@ -71,8 +91,17 @@ export default defineEventHandler(async (event) => {
if (!form) throw createError({ statusCode: 404, statusMessage: 'Form not found' })
const keys = ['g01','g02','g03','g04','g05','g06','g07','g08']
const data: Record = {}
- answers.forEach((a, i) => { if (keys[i]) data[keys[i]!] = toInt(a.answer, GAD_OPTIONS) })
+ answers.forEach((a, i) => {
+ if (!keys[i]) return
+ const map = keys[i] === 'g08' ? PHQ_DIFFICULTY_OPTIONS : GAD_OPTIONS
+ data[keys[i]!] = toInt(a.answer, map)
+ })
await prisma.gadQuestion.update({ where: { formId: form.id }, data })
+
+ // Recalculate score (g01-g07 only, g08 is difficulty)
+ const total = ['g01','g02','g03','g04','g05','g06','g07'].reduce((sum, k) => sum + (data[k] ?? 0), 0)
+ const severity = total <= 4 ? 'Minimal' : total <= 9 ? 'Mild' : total <= 14 ? 'Moderate' : 'Severe'
+ await prisma.gadForm.update({ where: { id: form.id }, data: { totalScore: total, severity } })
return { ok: true }
}
@@ -82,23 +111,56 @@ export default defineEventHandler(async (event) => {
if (!form) throw createError({ statusCode: 404, statusMessage: 'Form not found' })
const keys = ['q1','q2','q3','q4','q5','q6','q7','q8','q9','q10']
const data: Record = {}
- answers.forEach((a, i) => { if (keys[i]) data[keys[i]!] = toInt(a.answer, PHQ_OPTIONS) })
- await prisma.phqQuestion.update({ where: { formId: form.id }, data })
- return { ok: true }
- }
-
- // ── PCL-5 ─────────────────────────────────────────────────
- if (formKey === 'pcl') {
- const form = await prisma.pclForm.findFirst({ where: { userId: clientUserId }, orderBy: { id: 'desc' } })
- if (!form) throw createError({ statusCode: 404, statusMessage: 'Form not found' })
- const data: Record = {}
answers.forEach((a, i) => {
- const key = `q${String(i + 1).padStart(2, '0')}`
- data[key] = toInt(a.answer, {})
+ if (!keys[i]) return
+ // q10 uses different options than q1-q9
+ const map = keys[i] === 'q10' ? PHQ_DIFFICULTY_OPTIONS : PHQ_OPTIONS
+ data[keys[i]!] = toInt(a.answer, map)
})
- await prisma.pclQuestion.update({ where: { formId: form.id }, data })
+ await prisma.phqQuestion.update({ where: { formId: form.id }, data })
+
+ // Recalculate score (q1-q9 only, q10 is difficulty)
+ const total = ['q1','q2','q3','q4','q5','q6','q7','q8','q9'].reduce((sum, k) => sum + (data[k] ?? 0), 0)
+ const severity = total <= 4 ? 'Minimal' : total <= 9 ? 'Mild' : total <= 14 ? 'Moderate depression' : total <= 19 ? 'Moderately severe depression' : 'Severe depression'
+ await prisma.phqForm.update({ where: { id: form.id }, data: { totalScore: total, severity } })
return { ok: true }
}
+// ── PCL-5 ─────────────────────────────────────────────────
+if (formKey === 'pcl') {
+ const form = await prisma.pclForm.findFirst({ where: { userId: clientUserId }, orderBy: { id: 'desc' } })
+ if (!form) throw createError({ statusCode: 404, statusMessage: 'Form not found' })
+
+ const worstEventAnswer = answers.find(a => a.label === 'Worst event')
+ const questionAnswers = answers.filter(a => a.label !== 'Worst event')
+
+ const data: Record = {}
+
+ if (worstEventAnswer !== undefined) {
+ data.worstEvent = worstEventAnswer.answer
+ }
+
+ questionAnswers.forEach((a, i) => {
+ const key = `q${String(i + 1).padStart(2, '0')}`
+ data[key] = toInt(a.answer, PCL_OPTIONS)
+ })
+
+ await prisma.pclQuestion.update({ where: { formId: form.id }, data })
+
+ // Recalculate score (only scored questions, not worstEvent)
+ const total = questionAnswers.reduce((sum, a) => sum + (PCL_OPTIONS[a.answer] ?? 0), 0)
+ const severity = total <= 20 ? 'Minimal' : total <= 40 ? 'Mild' : total <= 60 ? 'Moderate' : 'Severe'
+ await prisma.pclForm.update({
+ where: { id: form.id },
+ data: {
+ totalScore: total,
+ severity,
+ status: 'COMPLETE', // ← add
+ submittedAt: new Date(), // ← add
+ }
+ })
+ return { ok: true }
+}
+
throw createError({ statusCode: 400, statusMessage: 'Invalid form key' })
})
\ No newline at end of file
diff --git a/server/api/clients/[id]/forms/[formKey]/raw.get.ts b/server/api/clients/[id]/forms/[formKey]/raw.get.ts
new file mode 100644
index 0000000..5abb70e
--- /dev/null
+++ b/server/api/clients/[id]/forms/[formKey]/raw.get.ts
@@ -0,0 +1,41 @@
+// Returns raw db values for the application form
+import { requireUser } from '../../../../../utils/guard'
+import { assertStaffCanAccessClient } from '../../../../../utils/clinician-access'
+import { createError, defineEventHandler, getRouterParam } from 'h3'
+import { prisma } from '../../../../../utils/prisma'
+
+export default defineEventHandler(async (event) => {
+ requireUser(event)
+ const clientUserId = getRouterParam(event, 'id')
+ const formKey = getRouterParam(event, 'formKey')
+
+ if (!clientUserId || !formKey) {
+ throw createError({ statusCode: 400, statusMessage: 'Missing params' })
+ }
+ if (!event.context.isStaff) {
+ throw createError({ statusCode: 403, statusMessage: 'Staff only' })
+ }
+ await assertStaffCanAccessClient(event, clientUserId)
+
+ if (formKey !== 'application') {
+ throw createError({ statusCode: 400, statusMessage: 'Raw endpoint only supports application' })
+ }
+
+ const appForm = await prisma.appForm.findFirst({
+ where: { userId: clientUserId },
+ orderBy: { id: 'desc' },
+ include: { questions: true },
+ })
+
+ if (!appForm?.questions) return { answers: {} }
+
+ const q = appForm.questions
+ const answers: Record = {}
+ for (let i = 1; i <= 50; i++) {
+ const key = `q${String(i).padStart(2, '0')}` as keyof typeof q
+ const val = q[key]
+ answers[key] = typeof val === 'string' ? val : ''
+ }
+
+ return { answers }
+})
\ No newline at end of file
diff --git a/server/api/forms/pcl/save.post.ts b/server/api/forms/pcl/save.post.ts
index 319e1d8..1def41a 100644
--- a/server/api/forms/pcl/save.post.ts
+++ b/server/api/forms/pcl/save.post.ts
@@ -42,7 +42,7 @@ export default defineEventHandler(async (event) => {
let form = await prisma.pclForm.findFirst({
where: { userId },
- orderBy: { id: 'asc' },
+ orderBy: { id: 'desc' },
})
if (!form) {
@@ -102,8 +102,8 @@ export default defineEventHandler(async (event) => {
await prisma.pclForm.update({
where: { id: form.id },
data: {
- status: 'IN_PROGRESS',
- submittedAt: null,
+ status: 'COMPLETE',
+ submittedAt: new Date(),
totalScore,
severity,
},
diff --git a/server/api/forms/phq/save.post.ts b/server/api/forms/phq/save.post.ts
index 9c2f9a4..94b484e 100644
--- a/server/api/forms/phq/save.post.ts
+++ b/server/api/forms/phq/save.post.ts
@@ -31,7 +31,7 @@ export default defineEventHandler(async (event) => {
let form = await prisma.phqForm.findFirst({
where: { userId },
- orderBy: { id: 'asc' },
+ orderBy: { id: 'desc' },
})
if (!form) {
@@ -86,11 +86,12 @@ export default defineEventHandler(async (event) => {
data,
})
+ // save score
await prisma.phqForm.update({
where: { id: form.id },
data: {
- status: 'IN_PROGRESS',
- submittedAt: null,
+ status: 'COMPLETE', // ← was 'IN_PROGRESS'
+ submittedAt: new Date(), // ← was null
totalScore,
severity,
},
diff --git a/server/api/session-notes/pending-approvals.get.ts b/server/api/session-notes/pending-approvals.get.ts
index 7409519..20e2198 100644
--- a/server/api/session-notes/pending-approvals.get.ts
+++ b/server/api/session-notes/pending-approvals.get.ts
@@ -15,7 +15,7 @@ export default defineEventHandler(async (event) => {
typeof query.kind === 'string' ? query.kind.toUpperCase() : ''
const kindFilter =
kindParam === 'PROGRESS' || kindParam === 'PSYCHOTHERAPY'
- ? { kind: kindParam }
+ ? { kind: kindParam as 'PROGRESS' | 'PSYCHOTHERAPY' }
: {}
const rows = await prisma.sessionNote.findMany({
@@ -55,7 +55,7 @@ export default defineEventHandler(async (event) => {
kind: r.kind,
status: r.status,
content: r.content,
- attended: r.attended,
+ attendanceStatus: r.attendanceStatus,
appointmentId: r.appointmentId,
appointmentStartTime: r.appointment?.startTime?.toISOString() ?? null,
clinicianSignedAt: r.clinicianSignedAt?.toISOString() ?? null,
diff --git a/server/utils/clinical-form-display.ts b/server/utils/clinical-form-display.ts
index 47e868a..4d68ce4 100644
--- a/server/utils/clinical-form-display.ts
+++ b/server/utils/clinical-form-display.ts
@@ -27,6 +27,29 @@ export const PHQ_LABELS = [
'If you checked any problems, how difficult have they made it?',
]
+export const PCL_LABELS = [
+ 'Repeated, disturbing, and unwanted memories of the stressful experience?',
+ 'Repeated, disturbing dreams of the stressful experience?',
+ 'Suddenly feeling or acting as if the stressful experience were actually happening again?',
+ 'Feeling very upset when something reminded you of the stressful experience?',
+ 'Having strong physical reactions when something reminded you of the stressful experience?',
+ 'Avoiding memories, thoughts, or feelings related to the stressful experience?',
+ 'Avoiding external reminders of the stressful experience?',
+ 'Trouble remembering important parts of the stressful experience?',
+ 'Having strong negative beliefs about yourself, other people, or the world?',
+ 'Blaming yourself or someone else for the stressful experience or what happened after it?',
+ 'Having strong negative feelings such as fear, horror, anger, guilt, or shame?',
+ 'Loss of interest in activities that you used to enjoy?',
+ 'Feeling distant or cut off from other people?',
+ 'Trouble experiencing positive feelings?',
+ 'Irritable behavior, angry outbursts, or acting aggressively?',
+ 'Taking too many risks or doing things that could cause you harm?',
+ 'Being "superalert" or watchful or on guard?',
+ 'Feeling jumpy or easily startled?',
+ 'Having difficulty concentrating?',
+ 'Trouble falling or staying asleep?',
+]
+
export const PHQ_OPTIONS: Record = {
0: 'Not at all',
1: 'Several days',
@@ -41,6 +64,21 @@ export const GAD_OPTIONS: Record = {
3: 'Nearly every day',
}
+export const DIFFICULTY_OPTIONS: Record = {
+ 0: 'Not difficult at all',
+ 1: 'Somewhat difficult',
+ 2: 'Very difficult',
+ 3: 'Extremely difficult',
+}
+
+export const PCL_OPTIONS: Record = {
+ 0: 'Not at all',
+ 1: 'A little bit',
+ 2: 'Moderately',
+ 3: 'Quite a bit',
+ 4: 'Extremely',
+}
+
const ACE_QUESTIONS_TEXT = [
'Did a parent or other adult in the household often swear at you, insult you, put you down, or humiliate you?',
'Did a parent or other adult in the household often push, grab, slap, or throw something at you?',
@@ -96,7 +134,9 @@ export async function loadClinicalFormQuestions(
const answers = [q.g01, q.g02, q.g03, q.g04, q.g05, q.g06, q.g07, q.g08]
return GAD_LABELS.slice(0, answers.length).map((label, i) => ({
label,
- answer: answers[i] != null ? (GAD_OPTIONS[answers[i] as number] ?? String(answers[i])) : '',
+ answer: answers[i] != null
+ ? ((i === 7 ? DIFFICULTY_OPTIONS : GAD_OPTIONS)[answers[i] as number] ?? String(answers[i]))
+ : '',
}))
}
@@ -114,7 +154,9 @@ export async function loadClinicalFormQuestions(
const answers = [q.q1, q.q2, q.q3, q.q4, q.q5, q.q6, q.q7, q.q8, q.q9, q.q10]
return PHQ_LABELS.slice(0, answers.length).map((label, i) => ({
label,
- answer: answers[i] != null ? (PHQ_OPTIONS[answers[i] as number] ?? String(answers[i])) : '',
+ answer: answers[i] != null
+ ? ((i === 9 ? DIFFICULTY_OPTIONS : PHQ_OPTIONS)[answers[i] as number] ?? String(answers[i]))
+ : '',
}))
}
@@ -130,14 +172,14 @@ export async function loadClinicalFormQuestions(
(await prisma.pclQuestion.findFirst({ where: { userId } }))
}
if (!q) return []
- const questions: ClinicalFormQuestionRow[] = []
+ const questions: ClinicalFormQuestionRow[] = [
+ { label: 'Worst event', answer: q.worstEvent ?? '' },
+ ]
for (let i = 1; i <= 20; i++) {
const key = `q${String(i).padStart(2, '0')}` as keyof typeof q
const val = q[key]
const numVal = typeof val === 'number' ? val : null
- if (numVal != null && numVal >= 0) {
- questions.push({ label: `Item ${i}`, answer: String(numVal) })
- }
+ questions.push({ label: PCL_LABELS[i - 1] ?? `Item ${i}`, answer: numVal != null ? (PCL_OPTIONS[numVal] ?? String(numVal)) : '' })
}
return questions
}