is there a research paper that has compiled good and bad prognostic factor of major adult psychiatric disorders?
prognostic factors psychiatric disorders systematic review
outcome predictors schizophrenia bipolar depression prognosis review
systematic review prognostic factors major adult psychiatric disorders compiled good bad prognosis
prognostic factors schizophrenia bipolar depression anxiety disorders review
PMID: 36417817
PMID: 20444581
PMID: 37957292
PMID: 29843662
"An umbrella review of candidate predictors of response, remission, recovery, and relapse across mental disorders" Solmi M, Cortese S, Vita G, De Prisco M, Radua J, Dragioti E, et al. Molecular Psychiatry, 2023 PMID: 37957292 | DOI: 10.1038/s41380-023-02298-3 | Free via PMC: PMC10730397
| Disorder | Good prognostic MCPs | Bad prognostic MCPs |
|---|---|---|
| Depression | Female sex, less depressive symptoms, better functioning, fewer negative life events | Recurrent episodes, residual symptoms, childhood maltreatment |
| Schizophrenia | Fewer positive symptoms | Higher depressive symptoms, more positive symptoms at baseline |
| Substance use disorder | Marital status, higher education | - |
any similar studies for OCD and other anxiety related disorders?
prognostic factors OCD obsessive compulsive disorder outcome predictors review
prognosis predictors anxiety disorders PTSD panic GAD outcome review
systematic review prognostic factors OCD anxiety disorders PTSD good bad outcome predictors compiled
PMID: 30745676
PMID: 25295427
prognostic factors PTSD panic disorder social anxiety GAD long-term outcome predictors
OCD course outcome predictors treatment response adult review
PMID: 27282021
prognostic factors PTSD outcome predictors review
long-term prognosis anxiety disorders panic social phobia GAD predictors outcome
PMID: 38884956
anxiety disorders course prognosis outcome natural history review
"Long-term outcome of obsessive-compulsive disorder in adults: a meta-analysis" Sharma E, Thennarasu K, Reddy YCJ. J Clin Psychiatry, 2014 PMID: 25295427
| Factor | Effect |
|---|---|
| Earlier age of onset | Significant negative association with remission |
| Longer illness duration | Worse remission |
| Higher baseline Y-BOCS score (greater severity) | Worse remission |
| Male gender | Worse remission |
"Course and outcome of obsessive-compulsive disorder" Sharma E, Math SB. Indian J Psychiatry, 2019 PMID: 30745676 | Free PMC full text: PMC6343417
"Predictors of response to serotonin reuptake inhibitors in OCD" Hazari N, Narayanaswamy JC, Arumugham SS. Expert Rev Neurother, 2016 PMID: 27282021
"A systematic review and meta-analysis of predictors of response to trauma-focused psychotherapy for PTSD" Keyan D, Garland N, et al. Psychological Bulletin, 2024 PMID: 38884956
| Disorder | Best Review Paper | PMID |
|---|---|---|
| OCD - long-term prognosis | Sharma et al. 2014 meta-analysis | 25295427 |
| OCD - course/outcome narrative | Sharma & Math 2019 | 30745676 |
| OCD - SRI response predictors | Hazari et al. 2016 | 27282021 |
| PTSD - psychotherapy predictors | Keyan et al. 2024 | 38884956 |
| All psychiatric disorders (transdiagnostic) | Solmi et al. 2023 | 37957292 |
can you compile these good and bad prognostic factors into a checklist that i can use in clinical setting
What format would you like the checklist in? · Which disorders should the checklist cover? · How should the checklist be structured?
mkdir -p /tmp/workspace/psych-checklist && ls /tmp/workspace/psych-checklist/
~/psych-checklist/build_checklist.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus import Flowable
from reportlab.pdfgen import canvas as pdfcanvas
OUTPUT = "/tmp/workspace/psych-checklist/Psychiatric_Prognostic_Checklist.pdf"
# ── Colours ──────────────────────────────────────────────────────────────────
DARK_NAVY = colors.HexColor("#1a2b4a")
TEAL = colors.HexColor("#007b7f")
GOOD_GREEN = colors.HexColor("#d4edda")
BAD_RED = colors.HexColor("#f8d7da")
GOOD_DARK = colors.HexColor("#155724")
BAD_DARK = colors.HexColor("#721c24")
HEADER_BG = colors.HexColor("#2c3e6b")
SUBHDR_BG = colors.HexColor("#e8f4f8")
WHITE = colors.white
LIGHT_GREY = colors.HexColor("#f5f5f5")
MID_GREY = colors.HexColor("#cccccc")
TICK_GREEN = colors.HexColor("#28a745")
CROSS_RED = colors.HexColor("#dc3545")
# ── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
title_style = ParagraphStyle(
"TitleStyle", parent=styles["Title"],
fontSize=20, textColor=WHITE, alignment=TA_CENTER,
spaceAfter=4, fontName="Helvetica-Bold"
)
subtitle_style = ParagraphStyle(
"SubtitleStyle", parent=styles["Normal"],
fontSize=10, textColor=colors.HexColor("#c8d8f0"),
alignment=TA_CENTER, spaceAfter=2, fontName="Helvetica-Oblique"
)
section_style = ParagraphStyle(
"SectionStyle", parent=styles["Normal"],
fontSize=13, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_LEFT, spaceAfter=0, leftIndent=6
)
col_header_good = ParagraphStyle(
"ColGood", parent=styles["Normal"],
fontSize=10, textColor=GOOD_DARK, fontName="Helvetica-Bold",
alignment=TA_CENTER
)
col_header_bad = ParagraphStyle(
"ColBad", parent=styles["Normal"],
fontSize=10, textColor=BAD_DARK, fontName="Helvetica-Bold",
alignment=TA_CENTER
)
item_style = ParagraphStyle(
"ItemStyle", parent=styles["Normal"],
fontSize=8.5, textColor=colors.HexColor("#222222"),
fontName="Helvetica", leading=12, leftIndent=4
)
source_style = ParagraphStyle(
"SourceStyle", parent=styles["Normal"],
fontSize=7, textColor=colors.HexColor("#555555"),
fontName="Helvetica-Oblique", alignment=TA_LEFT, leftIndent=4
)
footer_style = ParagraphStyle(
"FooterStyle", parent=styles["Normal"],
fontSize=7.5, textColor=colors.HexColor("#555555"),
fontName="Helvetica-Oblique", alignment=TA_CENTER
)
transdiag_label = ParagraphStyle(
"TransdiagLabel", parent=styles["Normal"],
fontSize=8, textColor=colors.HexColor("#6c3483"),
fontName="Helvetica-Bold", alignment=TA_LEFT
)
note_style = ParagraphStyle(
"NoteStyle", parent=styles["Normal"],
fontSize=8, textColor=colors.HexColor("#333333"),
fontName="Helvetica", leading=11, leftIndent=6
)
def checkbox(text, style):
return Paragraph(f"☐ {text}", style)
def make_factor_row(good_items, bad_items, row_idx):
"""Returns a list of two-column table rows, one item per row."""
max_len = max(len(good_items), len(bad_items))
rows = []
for i in range(max_len):
g = checkbox(good_items[i], item_style) if i < len(good_items) else Paragraph("", item_style)
b = checkbox(bad_items[i], item_style) if i < len(bad_items) else Paragraph("", item_style)
rows.append([g, b])
return rows
def build_disorder_section(disorder_name, good_factors, bad_factors, source_text, emoji=""):
elements = []
# Section header bar
header_data = [[Paragraph(f" {emoji} {disorder_name}", section_style)]]
header_table = Table(header_data, colWidths=[18.5*cm])
header_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), HEADER_BG),
("ROWBACKGROUNDS", (0,0), (-1,-1), [HEADER_BG]),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 6),
("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#1a2b4a")),
]))
elements.append(header_table)
# Column sub-headers
col_headers = [
[Paragraph("✅ GOOD PROGNOSTIC FACTORS", col_header_good),
Paragraph("⚠️ POOR PROGNOSTIC FACTORS", col_header_bad)]
]
col_hdr_table = Table(col_headers, colWidths=[9.25*cm, 9.25*cm])
col_hdr_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), GOOD_GREEN),
("BACKGROUND", (1,0), (1,0), BAD_RED),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("LINEBELOW", (0,0), (-1,-1), 0.5, MID_GREY),
]))
elements.append(col_hdr_table)
# Factor rows
max_len = max(len(good_factors), len(bad_factors))
data = []
for i in range(max_len):
g = checkbox(good_factors[i], item_style) if i < len(good_factors) else Paragraph("", item_style)
b = checkbox(bad_factors[i], item_style) if i < len(bad_factors) else Paragraph("", item_style)
data.append([g, b])
factors_table = Table(data, colWidths=[9.25*cm, 9.25*cm])
row_colors = []
for i in range(max_len):
bg = colors.HexColor("#f0faf0") if i % 2 == 0 else colors.HexColor("#fdf5f5")
row_colors.append(("ROWBACKGROUNDS", (0,i), (-1,i), [bg]))
ts = TableStyle([
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "TOP"),
("LINEAFTER", (0,0), (0,-1), 0.5, MID_GREY),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
])
# Alternating row bg
for i in range(max_len):
bg = colors.HexColor("#f2fbf2") if i % 2 == 0 else colors.HexColor("#fef5f5")
ts.add("BACKGROUND", (0,i), (0,i), bg)
ts.add("BACKGROUND", (1,i), (1,i), colors.HexColor("#fef5f5") if i % 2 == 0 else colors.HexColor("#fff8f8"))
factors_table.setStyle(ts)
elements.append(factors_table)
# Source citation
src_data = [[Paragraph(f"Source: {source_text}", source_style)]]
src_table = Table(src_data, colWidths=[18.5*cm])
src_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#f0f0f0")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
("BOX", (0,0), (-1,-1), 0.3, MID_GREY),
]))
elements.append(src_table)
elements.append(Spacer(1, 0.35*cm))
return elements
# ── DATA ─────────────────────────────────────────────────────────────────────
disorders = [
{
"name": "SCHIZOPHRENIA",
"emoji": "🧠",
"good": [
"Female sex",
"Later / acute onset of illness",
"Predominantly positive symptoms",
"Good premorbid functioning",
"Short duration of untreated psychosis (DUP)",
"Good insight into illness",
"Strong social support network",
"Employment / higher education",
"Early and intensive treatment initiation",
"Absence of negative symptoms at baseline",
"No comorbid substance use disorder",
"Married / stable intimate relationship",
],
"bad": [
"Male sex",
"Insidious / early onset",
"Predominantly negative symptoms",
"Poor premorbid functioning",
"Long duration of untreated psychosis",
"Poor insight / anosognosia",
"Social isolation / lack of support",
"Unemployment / low education",
"Delayed treatment initiation",
"Prominent cognitive deficits",
"Comorbid substance use disorder",
"Family history of schizophrenia",
],
"source": "Solmi et al. 2023 (Mol Psychiatry, PMID 37957292); Molstrom et al. 2022 (Schizophr Res, PMID 36417817)"
},
{
"name": "BIPOLAR DISORDER",
"emoji": "🔄",
"good": [
"Later age of onset",
"Fewer prior mood episodes",
"Good psychosocial functioning at baseline",
"Long-term symptomatic remission achieved",
"Good treatment adherence",
"Strong social / family support",
"Absence of psychotic features",
"No comorbid anxiety or substance use",
"Higher education / employment",
"Female sex (for certain outcomes)",
],
"bad": [
"Early age of onset",
"High number of prior episodes",
"Poor interepisode functioning",
"Rapid cycling course",
"Psychotic features present",
"Comorbid ADHD",
"Comorbid anxiety disorder",
"Comorbid substance use disorder",
"Comorbid personality disorder",
"Younger age at first treatment",
"Cognitive impairment",
"Poor medication adherence",
],
"source": "Treuer & Tohen 2010 (Eur Psychiatry, PMID 20444581); Solmi et al. 2023 (PMID 37957292)"
},
{
"name": "MAJOR DEPRESSIVE DISORDER (MDD)",
"emoji": "🌧️",
"good": [
"Female sex",
"Single depressive episode (not recurrent)",
"Acute onset with identifiable trigger",
"Mild-to-moderate baseline severity",
"Early improvement within first 2 weeks of Rx",
"Good social support",
"Higher education / employment",
"Absence of residual symptoms at remission",
"Early and adequate treatment",
"No childhood maltreatment history",
"Better quality of life / functioning at baseline",
],
"bad": [
"Recurrent episode history",
"Residual depressive symptoms after treatment",
"Childhood maltreatment / adverse events",
"Comorbid anxiety disorder",
"Comorbid physical illness (chronic pain, etc.)",
"Comorbid personality disorder",
"High baseline symptom severity",
"Long duration of untreated depression",
"Poor social support / social isolation",
"Negative life events",
"Suicidality at presentation",
"Low socioeconomic status",
],
"source": "Solmi et al. 2023 (PMID 37957292); Prognosis & improved outcomes in MDD (Transl Psychiatry 2019)"
},
{
"name": "OBSESSIVE-COMPULSIVE DISORDER (OCD)",
"emoji": "🔁",
"good": [
"Short duration of illness at presentation",
"Low symptom severity (low Y-BOCS score)",
"Good insight into obsessions",
"Later age of onset",
"Female sex",
"Early and intensive treatment",
"Long-term maintenance treatment",
"Good social support / functional family",
"Absence of comorbid personality disorder",
"Absence of severe comorbid depression",
"Employment / married status",
"Pure obsessional or checking subtype",
],
"bad": [
"Early age of onset",
"Long illness duration at presentation",
"High baseline Y-BOCS score",
"Poor insight / overvalued ideation",
"Male sex",
"Hoarding symptom subtype",
"Symmetry / ordering subtype",
"Contamination-washing subtype (for SRI response)",
"Comorbid schizotypal or borderline PD",
"Comorbid severe depression",
"Unemployment / single / unmarried",
"Family dysfunction / high expressed emotion",
"Long duration of untreated OCD",
],
"source": "Sharma et al. 2014 meta-analysis (J Clin Psychiatry, PMID 25295427); Sharma & Math 2019 (Indian J Psychiatry, PMID 30745676); Hazari et al. 2016 (PMID 27282021)"
},
{
"name": "PTSD",
"emoji": "⚡",
"good": [
"Single, adult-onset trauma (no childhood trauma)",
"Female sex (for treatment response)",
"Good social support",
"Higher education",
"Strong executive control / cognitive function",
"High fear psychophysiology at baseline (extinction learning capacity)",
"Early initiation of trauma-focused therapy",
"Absence of comorbid depression / alcohol use",
"Single trauma type (not complex/multiple traumas)",
"Caucasian ethnicity (study-specific finding)",
],
"bad": [
"Male sex",
"Childhood / early-onset trauma (complex PTSD)",
"Greater cumulative trauma exposure",
"Combat trauma history",
"Poor / absent social support",
"Comorbid depression",
"Comorbid alcohol / substance use disorder",
"Comorbid chronic pain",
"Comorbid sleep disorder",
"Older age",
"Non-Caucasian ethnicity (study-specific finding)",
"Lower pre-treatment fear activation",
"Higher anger dysregulation",
"Lower quality of life at baseline",
],
"source": "Keyan et al. 2024 systematic review & meta-analysis (Psychol Bull, PMID 38884956); Solmi et al. 2023 (PMID 37957292)"
},
{
"name": "ANXIETY DISORDERS (Panic, Social Anxiety, GAD)",
"emoji": "😰",
"good": [
"Later / adult onset (not childhood-onset)",
"Milder baseline symptom severity",
"Good social support",
"Absence of comorbid mood disorder",
"Early treatment initiation",
"Higher education / employment",
"Limited avoidance behaviour at presentation",
"Single anxiety disorder (no comorbid anxiety)",
"Good treatment adherence",
"Absence of neuroticism trait",
"Better baseline quality of life / functioning",
],
"bad": [
"Childhood / early onset",
"Severe baseline symptoms",
"Comorbid major depressive disorder",
"Comorbid personality disorder",
"Comorbid substance use disorder",
"Multiple comorbid anxiety disorders",
"Entrenched avoidance behaviours",
"High neuroticism trait",
"Poor social support",
"Low socioeconomic status",
"Negative life events",
"Long duration of untreated disorder",
],
"source": "Solmi et al. 2023 (PMID 37957292); Wang et al. 2018 (BMC Psychiatry, PMID 29843662); General anxiety literature"
},
]
# ── TRANSDIAGNOSTIC SUMMARY DATA ──────────────────────────────────────────────
transdiag_good = [
"Female sex",
"Higher education",
"Social contacts and employment",
"Early access to and initiation of treatment",
"Absence of negative life events",
"Lower disease-specific symptom burden at baseline",
"Better quality of life / functioning at baseline",
"Absence of comorbid mental disorders",
"Absence of comorbid physical illness",
"Good treatment adherence",
]
transdiag_bad = [
"Male sex",
"Younger age at onset",
"Higher disease-specific symptom burden",
"Higher depressive symptoms (even as comorbidity)",
"Higher anxiety symptoms",
"Greater global illness severity",
"More prior episodes / relapses",
"Negative life events",
"Comorbid mental disorders",
"Comorbid physical disorders",
"Social isolation",
"Unemployment / low education",
]
# ── PAGE TEMPLATE ─────────────────────────────────────────────────────────────
def add_page_background(canvas, doc):
canvas.saveState()
w, h = A4
# Top header band
canvas.setFillColor(DARK_NAVY)
canvas.rect(0, h - 2.8*cm, w, 2.8*cm, fill=1, stroke=0)
# Teal accent strip
canvas.setFillColor(TEAL)
canvas.rect(0, h - 3.0*cm, w, 0.22*cm, fill=1, stroke=0)
# Bottom footer band
canvas.setFillColor(colors.HexColor("#f0f0f0"))
canvas.rect(0, 0, w, 1.2*cm, fill=1, stroke=0)
canvas.setFillColor(MID_GREY)
canvas.rect(0, 1.2*cm, w, 0.05*cm, fill=1, stroke=0)
# Footer text
canvas.setFont("Helvetica-Oblique", 7.5)
canvas.setFillColor(colors.HexColor("#555555"))
canvas.drawCentredString(w/2, 0.45*cm,
"Clinical Psychiatric Prognostic Checklist | For clinical use only - not a diagnostic tool | Sources: Solmi et al. 2023 · Sharma et al. 2014 · Keyan et al. 2024 · Treuer & Tohen 2010")
canvas.drawRightString(w - 1.5*cm, 0.45*cm, f"Page {doc.page}")
canvas.restoreState()
# ── BUILD ─────────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
topMargin=3.2*cm,
bottomMargin=1.6*cm,
leftMargin=1.5*cm,
rightMargin=1.5*cm,
title="Psychiatric Prognostic Checklist",
author="Orris AI",
subject="Clinical Prognostic Factors in Adult Psychiatric Disorders",
)
story = []
# ── TITLE BLOCK ──
title_data = [
[Paragraph("Clinical Prognostic Checklist", title_style)],
[Paragraph("Adult Psychiatric Disorders · Good & Poor Prognostic Factors", subtitle_style)],
[Paragraph("Based on systematic reviews and meta-analyses (2010–2024)", subtitle_style)],
]
title_table = Table(title_data, colWidths=[18.5*cm])
title_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_NAVY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 8),
]))
story.append(title_table)
story.append(Spacer(1, 0.3*cm))
# ── HOW TO USE ──
how_data = [[
Paragraph(
"<b>HOW TO USE:</b> For each patient, tick applicable factors in both columns. "
"A cluster of poor prognostic factors should prompt more intensive monitoring, "
"early escalation of treatment, and active social support planning. "
"This checklist is a clinical aide - it does not replace clinical judgement.",
note_style
)
]]
how_table = Table(how_data, colWidths=[18.5*cm])
how_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#fffbe6")),
("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#e0c050")),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
]))
story.append(how_table)
story.append(Spacer(1, 0.4*cm))
# ── TRANSDIAGNOSTIC SECTION ──
td_header_data = [[Paragraph(" 🌐 TRANSDIAGNOSTIC FACTORS (apply across ALL disorders)", section_style)]]
td_header_table = Table(td_header_data, colWidths=[18.5*cm])
td_header_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#4a235a")),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 6),
("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#2d1440")),
]))
story.append(td_header_table)
td_col_hdr = [[
Paragraph("✅ GOOD (applies across disorders)", col_header_good),
Paragraph("⚠️ POOR (applies across disorders)", col_header_bad)
]]
td_col_hdr_table = Table(td_col_hdr, colWidths=[9.25*cm, 9.25*cm])
td_col_hdr_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), GOOD_GREEN),
("BACKGROUND", (1,0), (1,0), BAD_RED),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 4),
("LINEBELOW", (0,0), (-1,-1), 0.5, MID_GREY),
]))
story.append(td_col_hdr_table)
max_td = max(len(transdiag_good), len(transdiag_bad))
td_data = []
for i in range(max_td):
g = checkbox(transdiag_good[i], item_style) if i < len(transdiag_good) else Paragraph("", item_style)
b = checkbox(transdiag_bad[i], item_style) if i < len(transdiag_bad) else Paragraph("", item_style)
td_data.append([g, b])
td_table = Table(td_data, colWidths=[9.25*cm, 9.25*cm])
ts_td = TableStyle([
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "TOP"),
("LINEAFTER", (0,0), (0,-1), 0.5, MID_GREY),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
])
for i in range(max_td):
ts_td.add("BACKGROUND", (0,i), (0,i), colors.HexColor("#f2fbf2") if i%2==0 else colors.HexColor("#e8f8e8"))
ts_td.add("BACKGROUND", (1,i), (1,i), colors.HexColor("#fef5f5") if i%2==0 else colors.HexColor("#fdeaea"))
td_table.setStyle(ts_td)
story.append(td_table)
td_src_data = [[Paragraph("Source: Solmi et al. 2023 – Umbrella review, 117 SRs, 299,888 patients (Mol Psychiatry, PMID 37957292)", source_style)]]
td_src_table = Table(td_src_data, colWidths=[18.5*cm])
td_src_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#f0f0f0")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
("BOX", (0,0), (-1,-1), 0.3, MID_GREY),
]))
story.append(td_src_table)
story.append(Spacer(1, 0.4*cm))
# ── DISORDER SECTIONS ──
for d in disorders:
section_elements = build_disorder_section(
d["name"], d["good"], d["bad"], d["source"], d["emoji"]
)
story.extend(section_elements)
# ── NOTES PAGE ──
story.append(PageBreak())
notes_header = [[Paragraph(" 📋 CLINICAL NOTES", section_style)]]
notes_hdr_table = Table(notes_header, colWidths=[18.5*cm])
notes_hdr_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#2c3e6b")),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
story.append(notes_hdr_table)
story.append(Spacer(1, 0.3*cm))
line_data = []
for _ in range(22):
line_data.append([Paragraph("", item_style), HRFlowable(width="100%", thickness=0.4, color=MID_GREY)])
notes_lines = []
for i in range(22):
notes_lines.append([Paragraph("", item_style)])
notes_lines.append([HRFlowable(width="100%", thickness=0.4, color=MID_GREY)])
notes_lines.append([Spacer(1, 0.45*cm)])
for item in notes_lines:
story.append(item[0])
# ── REFERENCES ──
story.append(Spacer(1, 0.5*cm))
ref_header = [[Paragraph(" 📚 KEY REFERENCES", section_style)]]
ref_hdr_table = Table(ref_header, colWidths=[18.5*cm])
ref_hdr_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#2c3e6b")),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
story.append(ref_hdr_table)
story.append(Spacer(1, 0.2*cm))
refs = [
"1. Solmi M, et al. (2023). An umbrella review of candidate predictors of response, remission, recovery, and relapse across mental disorders. Molecular Psychiatry. PMID: 37957292",
"2. Molstrom IM, et al. (2022). The prognosis of schizophrenia: A systematic review and meta-analysis with meta-regression of 20-year follow-up studies. Schizophrenia Research. PMID: 36417817",
"3. Treuer T & Tohen M. (2010). Predicting the course and outcome of bipolar disorder: a review. European Psychiatry. PMID: 20444581",
"4. Sharma E, Thennarasu K, Reddy YCJ. (2014). Long-term outcome of OCD in adults: a meta-analysis. Journal of Clinical Psychiatry. PMID: 25295427",
"5. Sharma E & Math SB. (2019). Course and outcome of obsessive-compulsive disorder. Indian Journal of Psychiatry. PMID: 30745676",
"6. Hazari N, et al. (2016). Predictors of response to serotonin reuptake inhibitors in OCD. Expert Review of Neurotherapeutics. PMID: 27282021",
"7. Keyan D, et al. (2024). A systematic review and meta-analysis of predictors of response to trauma-focused psychotherapy for PTSD. Psychological Bulletin. PMID: 38884956",
"8. Wang J, et al. (2018). Associations between loneliness/social support and outcomes of mental health problems: a systematic review. BMC Psychiatry. PMID: 29843662",
]
for ref in refs:
story.append(Paragraph(ref, source_style))
story.append(Spacer(1, 0.15*cm))
# ── BUILD PDF ──
doc.build(story, onFirstPage=add_page_background, onLaterPages=add_page_background)
print("PDF created successfully:", OUTPUT)
cd /tmp/workspace/psych-checklist && python build_checklist.py 2>&1
ls -lh /tmp/workspace/psych-checklist/Psychiatric_Prognostic_Checklist.pdf
Psychiatric Prognostic Checklist
PDF Document · PDF
are there any biomarkers that can be added to the list of each disorder
biomarkers prognosis schizophrenia bipolar depression outcome predictors review
biomarkers OCD PTSD anxiety disorder treatment outcome predictors review
prognostic biomarkers psychiatric disorders schizophrenia bipolar depression OCD PTSD CRP BDNF cortisol neuroimaging review 2022 2023 2024
PMID: 39456983
PMID: 39954922
https://pmc.ncbi.nlm.nih.gov/articles/PMC10168176
inflammatory biomarkers CRP IL-6 depression schizophrenia prognosis treatment outcome
biological markers OCD PTSD anxiety disorders consensus neurochemistry neuroimaging
inflammatory biomarkers CRP interleukin depression prognosis treatment response meta-analysis
https://link.springer.com/article/10.1186/s12888-025-07429-4
are there any biomarkers that can be added to the list of each disorder
are there any biomarkers that can be added to the list of each disorder
biomarkers prognosis schizophrenia bipolar major depression outcome treatment response review
biological markers OCD PTSD anxiety disorders consensus neuroimaging neurochemistry
prognostic biomarkers schizophrenia bipolar depression OCD PTSD anxiety BDNF CRP cortisol neuroimaging systematic review 2023 2024
PMID: 27419272
PMID: 27403679
https://pmc.ncbi.nlm.nih.gov/articles/PMC10168176
~/psych-checklist/build_checklist_v2.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER
OUTPUT = "/tmp/workspace/psych-checklist/Psychiatric_Prognostic_Checklist_v2.pdf"
# ── Colours ──────────────────────────────────────────────────────────────────
DARK_NAVY = colors.HexColor("#1a2b4a")
TEAL = colors.HexColor("#007b7f")
GOOD_GREEN = colors.HexColor("#d4edda")
BAD_RED = colors.HexColor("#f8d7da")
GOOD_DARK = colors.HexColor("#155724")
BAD_DARK = colors.HexColor("#721c24")
HEADER_BG = colors.HexColor("#2c3e6b")
WHITE = colors.white
MID_GREY = colors.HexColor("#cccccc")
BIOMARKER_BG = colors.HexColor("#eaf4fb")
BIOMARKER_HDR = colors.HexColor("#1a6b8a")
BIO_GOOD = colors.HexColor("#1a6b8a")
BIO_BAD = colors.HexColor("#7b3a10")
BIO_GOOD_BG = colors.HexColor("#d6eef7")
BIO_BAD_BG = colors.HexColor("#fde8d0")
# ── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
title_style = ParagraphStyle("TitleStyle", parent=styles["Title"],
fontSize=20, textColor=WHITE, alignment=TA_CENTER,
spaceAfter=4, fontName="Helvetica-Bold")
subtitle_style = ParagraphStyle("SubtitleStyle", parent=styles["Normal"],
fontSize=10, textColor=colors.HexColor("#c8d8f0"),
alignment=TA_CENTER, spaceAfter=2, fontName="Helvetica-Oblique")
section_style = ParagraphStyle("SectionStyle", parent=styles["Normal"],
fontSize=13, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_LEFT, spaceAfter=0, leftIndent=6)
col_header_good = ParagraphStyle("ColGood", parent=styles["Normal"],
fontSize=10, textColor=GOOD_DARK, fontName="Helvetica-Bold", alignment=TA_CENTER)
col_header_bad = ParagraphStyle("ColBad", parent=styles["Normal"],
fontSize=10, textColor=BAD_DARK, fontName="Helvetica-Bold", alignment=TA_CENTER)
bio_col_good = ParagraphStyle("BioColGood", parent=styles["Normal"],
fontSize=9, textColor=BIO_GOOD, fontName="Helvetica-Bold", alignment=TA_CENTER)
bio_col_bad = ParagraphStyle("BioColBad", parent=styles["Normal"],
fontSize=9, textColor=BIO_BAD, fontName="Helvetica-Bold", alignment=TA_CENTER)
item_style = ParagraphStyle("ItemStyle", parent=styles["Normal"],
fontSize=8.5, textColor=colors.HexColor("#222222"),
fontName="Helvetica", leading=12, leftIndent=4)
bio_item_style = ParagraphStyle("BioItemStyle", parent=styles["Normal"],
fontSize=8, textColor=colors.HexColor("#1a3a4a"),
fontName="Helvetica", leading=11, leftIndent=4)
source_style = ParagraphStyle("SourceStyle", parent=styles["Normal"],
fontSize=7, textColor=colors.HexColor("#555555"),
fontName="Helvetica-Oblique", alignment=TA_LEFT, leftIndent=4)
note_style = ParagraphStyle("NoteStyle", parent=styles["Normal"],
fontSize=8, textColor=colors.HexColor("#333333"),
fontName="Helvetica", leading=11, leftIndent=6)
warning_style = ParagraphStyle("WarnStyle", parent=styles["Normal"],
fontSize=7.5, textColor=colors.HexColor("#7b3a10"),
fontName="Helvetica-Oblique", leading=10, leftIndent=4)
def checkbox(text, style):
return Paragraph(f"☐ {text}", style)
def diamond(text, style):
return Paragraph(f"◇ {text}", style)
def build_disorder_section(disorder_name, good_factors, bad_factors,
bio_good, bio_bad, source_text, bio_source, emoji=""):
elements = []
# Section header
header_data = [[Paragraph(f" {emoji} {disorder_name}", section_style)]]
header_table = Table(header_data, colWidths=[18.5*cm])
header_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), HEADER_BG),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 6),
("BOX", (0,0), (-1,-1), 0.5, DARK_NAVY),
]))
elements.append(header_table)
# ── CLINICAL FACTORS columns ──
col_hdr = [[Paragraph("✅ GOOD PROGNOSTIC FACTORS", col_header_good),
Paragraph("⚠️ POOR PROGNOSTIC FACTORS", col_header_bad)]]
col_hdr_table = Table(col_hdr, colWidths=[9.25*cm, 9.25*cm])
col_hdr_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), GOOD_GREEN),
("BACKGROUND", (1,0), (1,0), BAD_RED),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 4),
("LINEBELOW", (0,0), (-1,-1), 0.5, MID_GREY),
]))
elements.append(col_hdr_table)
max_len = max(len(good_factors), len(bad_factors))
data = []
for i in range(max_len):
g = checkbox(good_factors[i], item_style) if i < len(good_factors) else Paragraph("", item_style)
b = checkbox(bad_factors[i], item_style) if i < len(bad_factors) else Paragraph("", item_style)
data.append([g, b])
factors_table = Table(data, colWidths=[9.25*cm, 9.25*cm])
ts = TableStyle([
("LEFTPADDING", (0,0), (-1,-1), 5), ("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3), ("BOTTOMPADDING", (0,0), (-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "TOP"),
("LINEAFTER", (0,0), (0,-1), 0.5, MID_GREY),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
])
for i in range(max_len):
ts.add("BACKGROUND", (0,i), (0,i), colors.HexColor("#f2fbf2") if i%2==0 else colors.HexColor("#e8f8e8"))
ts.add("BACKGROUND", (1,i), (1,i), colors.HexColor("#fef5f5") if i%2==0 else colors.HexColor("#fdeaea"))
factors_table.setStyle(ts)
elements.append(factors_table)
# ── BIOMARKER SUBSECTION ──
bio_hdr_data = [[Paragraph(" 🔬 BIOMARKER INDICATORS (research-grade; use to inform, not replace, clinical judgement)",
ParagraphStyle("BioHdr", parent=styles["Normal"],
fontSize=9, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_LEFT))]]
bio_hdr_table = Table(bio_hdr_data, colWidths=[18.5*cm])
bio_hdr_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), BIOMARKER_HDR),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
elements.append(bio_hdr_table)
bio_col_hdr = [[Paragraph("🔵 FAVOURABLE BIOMARKER PROFILE", bio_col_good),
Paragraph("🔴 UNFAVOURABLE BIOMARKER PROFILE", bio_col_bad)]]
bio_col_hdr_table = Table(bio_col_hdr, colWidths=[9.25*cm, 9.25*cm])
bio_col_hdr_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), BIO_GOOD_BG),
("BACKGROUND", (1,0), (1,0), BIO_BAD_BG),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 4),
("LINEBELOW", (0,0), (-1,-1), 0.5, MID_GREY),
]))
elements.append(bio_col_hdr_table)
bio_max = max(len(bio_good), len(bio_bad))
bio_data = []
for i in range(bio_max):
g = diamond(bio_good[i], bio_item_style) if i < len(bio_good) else Paragraph("", bio_item_style)
b = diamond(bio_bad[i], bio_item_style) if i < len(bio_bad) else Paragraph("", bio_item_style)
bio_data.append([g, b])
bio_table = Table(bio_data, colWidths=[9.25*cm, 9.25*cm])
bio_ts = TableStyle([
("LEFTPADDING", (0,0), (-1,-1), 5), ("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3), ("BOTTOMPADDING", (0,0), (-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "TOP"),
("LINEAFTER", (0,0), (0,-1), 0.5, MID_GREY),
("BOX", (0,0), (-1,-1), 0.5, MID_GREY),
])
for i in range(bio_max):
bio_ts.add("BACKGROUND", (0,i), (0,i), colors.HexColor("#eaf4fb") if i%2==0 else colors.HexColor("#dceef8"))
bio_ts.add("BACKGROUND", (1,i), (1,i), colors.HexColor("#fef0e0") if i%2==0 else colors.HexColor("#fde5c8"))
bio_table.setStyle(bio_ts)
elements.append(bio_table)
# Sources strip
src_text = f"Clinical sources: {source_text} | Biomarker sources: {bio_source}"
src_data = [[Paragraph(src_text, source_style)]]
src_table = Table(src_data, colWidths=[18.5*cm])
src_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#f0f0f0")),
("TOPPADDING", (0,0), (-1,-1), 3), ("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
("BOX", (0,0), (-1,-1), 0.3, MID_GREY),
]))
elements.append(src_table)
elements.append(Spacer(1, 0.35*cm))
return elements
# ── DATA ─────────────────────────────────────────────────────────────────────
disorders = [
{
"name": "SCHIZOPHRENIA",
"emoji": "🧠",
"good": [
"Female sex",
"Later / acute onset of illness",
"Predominantly positive symptoms",
"Good premorbid functioning",
"Short duration of untreated psychosis (DUP)",
"Good insight into illness",
"Strong social support network",
"Employment / higher education",
"Early and intensive treatment initiation",
"Absence of negative symptoms at baseline",
"No comorbid substance use disorder",
"Married / stable intimate relationship",
],
"bad": [
"Male sex",
"Insidious / early onset",
"Predominantly negative symptoms",
"Poor premorbid functioning",
"Long duration of untreated psychosis",
"Poor insight / anosognosia",
"Social isolation / lack of support",
"Unemployment / low education",
"Delayed treatment initiation",
"Prominent cognitive deficits",
"Comorbid substance use disorder",
"Family history of schizophrenia",
],
"bio_good": [
"Higher baseline BDNF levels (→ better antipsychotic response)",
"BDNF Val/Val genotype (vs. Met carrier)",
"Striatal connectivity index (SCI) - intact connectivity",
"Normal caudate / putamen volume on MRI",
"Low inflammatory markers (CRP, IL-6) at baseline",
"Normal anterior cingulate cortex (ACC) volume",
"Normal prefrontal cortex activation on fMRI",
"Low polygenic risk score for schizophrenia",
],
"bio_bad": [
"Low baseline serum BDNF",
"BDNF Met/Met or Val/Met genotype",
"Elevated striatal functional abnormalities (FSA index)",
"Reduced caudate volume on structural MRI",
"Elevated CRP / IL-6 (high inflammatory state)",
"Reduced ACC and orbitofrontal cortex volume",
"Hypofrontality on fMRI (reduced PFC activation)",
"High polygenic risk score for schizophrenia",
"Elevated homocysteine levels",
"Low serum folate / B12",
],
"source": "Solmi et al. 2023 (PMID 37957292); Molstrom et al. 2022 (PMID 36417817)",
"bio_source": "Liberona et al. 2024 (PMID 39456983); Candidate biomarkers review (PMC10168176)"
},
{
"name": "BIPOLAR DISORDER",
"emoji": "🔄",
"good": [
"Later age of onset",
"Fewer prior mood episodes",
"Good psychosocial functioning at baseline",
"Long-term symptomatic remission achieved",
"Good treatment adherence",
"Strong social / family support",
"Absence of psychotic features",
"No comorbid anxiety or substance use",
"Higher education / employment",
"Female sex (for certain outcomes)",
],
"bad": [
"Early age of onset",
"High number of prior episodes",
"Poor interepisode functioning",
"Rapid cycling course",
"Psychotic features present",
"Comorbid ADHD",
"Comorbid anxiety disorder",
"Comorbid substance use disorder",
"Comorbid personality disorder",
"Cognitive impairment",
"Poor medication adherence",
],
"bio_good": [
"Higher baseline BDNF (→ better lithium response)",
"BDNF Val/Val genotype",
"Low polygenic risk score for MDD / schizophrenia",
"Episodic clinical course on HPA axis testing",
"Normal cortisol awakening response",
"Normal lithium erythrocyte / plasma ratio",
"Low CRP / IL-6 inflammatory markers",
"Preserved hippocampal volume on MRI",
],
"bio_bad": [
"Low serum BDNF",
"High polygenic risk score for schizophrenia or MDD",
"Elevated cortisol (HPA dysregulation)",
"Elevated CRP / IL-6 / TNF-alpha",
"Reduced hippocampal volume on MRI",
"Reduced subgenual ACC volume",
"Elevated homocysteine",
"Telomere shortening (surrogate for biological ageing)",
"Abnormal lithium erythrocyte:plasma ratio",
],
"source": "Treuer & Tohen 2010 (PMID 20444581); Solmi et al. 2023 (PMID 37957292)",
"bio_source": "Scott et al. 2025 (PMID 39954922); Liberona et al. 2024 (PMID 39456983)"
},
{
"name": "MAJOR DEPRESSIVE DISORDER (MDD)",
"emoji": "🌧️",
"good": [
"Female sex",
"Single depressive episode (not recurrent)",
"Acute onset with identifiable trigger",
"Mild-to-moderate baseline severity",
"Early improvement within first 2 weeks of Rx",
"Good social support",
"Higher education / employment",
"Absence of residual symptoms at remission",
"Early and adequate treatment",
"No childhood maltreatment history",
"Better quality of life / functioning at baseline",
],
"bad": [
"Recurrent episode history",
"Residual depressive symptoms after treatment",
"Childhood maltreatment / adverse events",
"Comorbid anxiety disorder",
"Comorbid physical illness (chronic pain, etc.)",
"Comorbid personality disorder",
"High baseline symptom severity",
"Long duration of untreated depression",
"Poor social support / social isolation",
"Negative life events",
"Suicidality at presentation",
],
"bio_good": [
"Higher serum BDNF at baseline (→ antidepressant response)",
"BDNF Val/Val genotype",
"Low baseline CRP / IL-6 (better SSRI response)",
"Normal hippocampal volume on MRI",
"Higher subgenual ACC activity on PET/fMRI",
"Normal HPA axis cortisol pattern",
"Rising BDNF levels after 2 weeks of treatment",
"Normal thyroid function (TSH within range)",
"Normal folate / B12 levels",
],
"bio_bad": [
"Low serum BDNF at baseline",
"High CRP / IL-6 / TNF-alpha (inflammatory subtype → poor SSRI response)",
"Reduced hippocampal volume (esp. with recurrence)",
"Reduced subgenual ACC volume / activity",
"Elevated baseline cortisol / flattened diurnal cortisol",
"Hypothyroidism or subclinical hypothyroidism",
"Low folate / B12 (associated with non-response)",
"Elevated homocysteine",
"Reduced anterior cingulate cortex theta activity on EEG",
"High polygenic risk score for MDD",
],
"source": "Solmi et al. 2023 (PMID 37957292); Prognosis in MDD (Transl Psychiatry 2019)",
"bio_source": "Candidate biomarkers review (PMC10168176); Liberona et al. 2024 (PMID 39456983)"
},
{
"name": "OBSESSIVE-COMPULSIVE DISORDER (OCD)",
"emoji": "🔁",
"good": [
"Short duration of illness at presentation",
"Low symptom severity (low Y-BOCS score)",
"Good insight into obsessions",
"Later age of onset",
"Female sex",
"Early and intensive treatment",
"Long-term maintenance treatment",
"Good social support / functional family",
"Absence of comorbid personality disorder",
"Pure obsessional or checking subtype",
],
"bad": [
"Early age of onset",
"Long illness duration at presentation",
"High baseline Y-BOCS score",
"Poor insight / overvalued ideation",
"Male sex",
"Hoarding or symmetry symptom subtype",
"Contamination-washing subtype (for SRI response)",
"Comorbid schizotypal or borderline PD",
"Comorbid severe depression",
"Unemployment / single / unmarried",
"Family dysfunction / high expressed emotion",
],
"bio_good": [
"Elevated CSF / blood 5-HIAA levels at baseline (→ better clomipramine response)",
"Normal orbitofrontal cortex (OFC) volume",
"Normal caudate activity on fMRI at rest",
"Normal anterior cingulate cortex volume",
"Normal serotonin transporter (5-HTT) binding on SPECT",
"Higher glutamate levels in OFC (→ predicts CBT response in some studies)",
],
"bio_bad": [
"Low baseline CSF 5-HIAA (poor SRI response predictor)",
"Reduced OFC volume / hyperactivity on fMRI",
"Caudate hyperactivity (normalises with effective Rx)",
"Poor insight correlates with orbitofrontal dysfunction",
"Elevated cerebellar activity on fMRI",
"Altered glutamatergic signalling in OFC / ACC",
"Serotonin transporter gene (SLC6A4) polymorphisms",
"Dopamine receptor D4 gene variants",
],
"source": "Sharma et al. 2014 (PMID 25295427); Sharma & Math 2019 (PMID 30745676)",
"bio_source": "Hazari et al. 2016 (PMID 27282021); Bandelow et al. 2017 (PMID 27419272)"
},
{
"name": "PTSD",
"emoji": "⚡",
"good": [
"Single, adult-onset trauma (no childhood trauma)",
"Female sex (for treatment response)",
"Good social support",
"Higher education",
"Strong executive control / cognitive function",
"High fear psychophysiology at baseline",
"Early initiation of trauma-focused therapy",
"Absence of comorbid depression / alcohol use",
"Single trauma type (not complex/multiple traumas)",
],
"bad": [
"Male sex",
"Childhood / early-onset trauma (complex PTSD)",
"Greater cumulative trauma exposure",
"Combat trauma history",
"Poor / absent social support",
"Comorbid depression",
"Comorbid alcohol / substance use disorder",
"Comorbid chronic pain",
"Comorbid sleep disorder",
"Older age at treatment",
"Higher anger dysregulation",
],
"bio_good": [
"Larger hippocampal volume (→ better fear extinction)",
"Higher baseline glucocorticoid receptor sensitivity",
"Normal HPA axis cortisol response",
"Normal amygdala reactivity (not hyperreactive)",
"Higher resting-state connectivity: PFC-amygdala",
"Normal startle habituation on neurophysiology",
"Normal error-related negativity (ERN) on EEG",
"Low polygenic risk score for PTSD",
],
"bio_bad": [
"Reduced hippocampal volume (strongest structural biomarker)",
"Reduced amygdala volume",
"Reduced total brain / corpus callosum structural connectivity",
"Low baseline cortisol / enhanced glucocorticoid suppression",
"Elevated CRP / inflammatory markers",
"Amygdala hyperreactivity to trauma cues on fMRI",
"Impaired PFC inhibitory control of amygdala",
"Elevated resting-state sympathetic tone (heart rate variability)",
"High polygenic risk score for PTSD",
"Dissociative subtype neurobiological profile",
],
"source": "Keyan et al. 2024 (PMID 38884956); Solmi et al. 2023 (PMID 37957292)",
"bio_source": "Bandelow et al. 2016/2017 (PMID 27403679, 27419272); PMC10168176"
},
{
"name": "ANXIETY DISORDERS (Panic, Social Anxiety, GAD)",
"emoji": "😰",
"good": [
"Later / adult onset (not childhood-onset)",
"Milder baseline symptom severity",
"Good social support",
"Absence of comorbid mood disorder",
"Early treatment initiation",
"Higher education / employment",
"Limited avoidance behaviour at presentation",
"Single anxiety disorder (no comorbid anxiety)",
"Good treatment adherence",
"Absence of high neuroticism trait",
],
"bad": [
"Childhood / early onset",
"Severe baseline symptoms",
"Comorbid major depressive disorder",
"Comorbid personality disorder",
"Comorbid substance use disorder",
"Multiple comorbid anxiety disorders",
"Entrenched avoidance behaviours",
"High neuroticism trait",
"Poor social support",
"Long duration of untreated disorder",
],
"bio_good": [
"Error-related negativity (ERN) - higher ERN predicts GAD first onset (screening tool)",
"Normal amygdala volume / reactivity",
"Intact resting-state PFC-amygdala connectivity",
"Normal HPA axis cortisol (less dysregulation = better Tx response)",
"Normal oxytocin levels (social anxiety)",
"Normal GABA levels in PFC on MRS",
"Low CRP / inflammatory markers at baseline",
],
"bio_bad": [
"Elevated ERN (biomarker of GAD risk / severity)",
"Amygdala hyperreactivity to threat cues on fMRI",
"Reduced insula volume (panic disorder)",
"HPA axis dysregulation / elevated morning cortisol",
"Reduced GABA in prefrontal cortex on MRS",
"Low oxytocin (social anxiety disorder)",
"Elevated CRP / IL-6 (complicates GAD course)",
"SERT gene (SLC6A4) short allele (5-HTTLPR s/s genotype)",
"Reduced benzodiazepine receptor binding on PET",
],
"source": "Solmi et al. 2023 (PMID 37957292); Wang et al. 2018 (PMID 29843662)",
"bio_source": "Bandelow et al. 2016/2017 (PMID 27403679, 27419272); PMC10168176"
},
]
transdiag_good = [
"Female sex",
"Higher education",
"Social contacts and employment",
"Early access to treatment",
"Absence of negative life events",
"Lower disease-specific symptom burden at baseline",
"Better quality of life / functioning at baseline",
"Absence of comorbid mental or physical disorders",
"Good treatment adherence",
]
transdiag_bad = [
"Male sex",
"Younger age at onset",
"Higher disease-specific symptom burden",
"Higher depressive symptoms (even as comorbidity)",
"Higher anxiety symptoms",
"Greater global illness severity",
"More prior episodes / relapses",
"Negative life events",
"Social isolation / unemployment",
]
transdiag_bio_good = [
"Higher serum BDNF (favours treatment response across mood/psychosis)",
"BDNF Val/Val genotype",
"Low CRP / IL-6 (low inflammatory burden)",
"Normal HPA axis cortisol pattern",
"Normal hippocampal volume",
"Good cognitive function / executive control",
]
transdiag_bio_bad = [
"Low serum BDNF",
"Elevated CRP / IL-6 / TNF-alpha",
"HPA axis dysregulation (elevated baseline cortisol)",
"Reduced hippocampal volume",
"High polygenic risk scores",
"Elevated homocysteine",
"Telomere shortening / accelerated biological ageing",
]
# ── PAGE TEMPLATE ─────────────────────────────────────────────────────────────
def add_page_background(canvas, doc):
canvas.saveState()
w, h = A4
canvas.setFillColor(DARK_NAVY)
canvas.rect(0, h - 2.8*cm, w, 2.8*cm, fill=1, stroke=0)
canvas.setFillColor(TEAL)
canvas.rect(0, h - 3.0*cm, w, 0.22*cm, fill=1, stroke=0)
canvas.setFillColor(colors.HexColor("#f0f0f0"))
canvas.rect(0, 0, w, 1.2*cm, fill=1, stroke=0)
canvas.setFillColor(MID_GREY)
canvas.rect(0, 1.2*cm, w, 0.05*cm, fill=1, stroke=0)
canvas.setFont("Helvetica-Oblique", 7)
canvas.setFillColor(colors.HexColor("#555555"))
canvas.drawCentredString(w/2, 0.45*cm,
"Clinical Psychiatric Prognostic Checklist v2 | Clinical use only - not a diagnostic tool | Biomarkers are research-grade; confirm availability in your institution")
canvas.drawRightString(w - 1.5*cm, 0.45*cm, f"Page {doc.page}")
canvas.restoreState()
# ── BUILD ─────────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(OUTPUT, pagesize=A4,
topMargin=3.2*cm, bottomMargin=1.6*cm,
leftMargin=1.5*cm, rightMargin=1.5*cm,
title="Psychiatric Prognostic Checklist v2",
author="Orris AI",
subject="Clinical Prognostic Factors + Biomarkers in Adult Psychiatric Disorders")
story = []
# Title block
title_data = [
[Paragraph("Clinical Prognostic Checklist (v2 + Biomarkers)", title_style)],
[Paragraph("Adult Psychiatric Disorders · Good & Poor Prognostic Factors · Biomarker Indicators", subtitle_style)],
[Paragraph("Based on systematic reviews, meta-analyses and consensus statements (2010–2025)", subtitle_style)],
]
title_table = Table(title_data, colWidths=[18.5*cm])
title_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_NAVY),
("TOPPADDING", (0,0), (-1,-1), 4), ("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 8),
]))
story.append(title_table)
story.append(Spacer(1, 0.25*cm))
# How to use
how_data = [[Paragraph(
"<b>HOW TO USE:</b> Tick applicable clinical factors (☐) and note relevant biomarkers (◇) for your patient. "
"A cluster of poor prognostic factors plus unfavourable biomarker profile should prompt intensive monitoring, "
"early treatment escalation, and proactive social support planning. "
"<b>Biomarker availability varies by institution</b> - use where accessible. "
"This checklist is a clinical aide and does not replace clinical judgement.",
note_style)]]
how_table = Table(how_data, colWidths=[18.5*cm])
how_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#fffbe6")),
("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#e0c050")),
("TOPPADDING", (0,0), (-1,-1), 6), ("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8), ("RIGHTPADDING", (0,0), (-1,-1), 8),
]))
story.append(how_table)
story.append(Spacer(1, 0.3*cm))
# Biomarker caveat box
caveat_data = [[Paragraph(
"⚠️ <b>BIOMARKER CAVEAT:</b> Psychiatric biomarkers are mostly in early validation stages. "
"Most are not yet approved as standalone clinical tools. "
"BDNF, CRP, cortisol, and thyroid function are readily available. "
"Neuroimaging (MRI, fMRI, PET) and genetic markers require specialist access. "
"Evidence strength is variable across disorders - strongest for schizophrenia, MDD and PTSD.",
warning_style)]]
caveat_table = Table(caveat_data, colWidths=[18.5*cm])
caveat_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#fdf0e0")),
("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#c07030")),
("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8), ("RIGHTPADDING", (0,0), (-1,-1), 8),
]))
story.append(caveat_table)
story.append(Spacer(1, 0.3*cm))
# Transdiagnostic section
td_header_data = [[Paragraph(" 🌐 TRANSDIAGNOSTIC FACTORS (apply across ALL disorders)", section_style)]]
td_header_table = Table(td_header_data, colWidths=[18.5*cm])
td_header_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#4a235a")),
("TOPPADDING", (0,0), (-1,-1), 7), ("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 6),
("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#2d1440")),
]))
story.append(td_header_table)
# TD clinical columns
td_col_hdr = [[Paragraph("✅ GOOD (across disorders)", col_header_good),
Paragraph("⚠️ POOR (across disorders)", col_header_bad)]]
td_col_hdr_table = Table(td_col_hdr, colWidths=[9.25*cm, 9.25*cm])
td_col_hdr_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), GOOD_GREEN), ("BACKGROUND", (1,0), (1,0), BAD_RED),
("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 4),
]))
story.append(td_col_hdr_table)
max_td = max(len(transdiag_good), len(transdiag_bad))
td_data = []
for i in range(max_td):
g = checkbox(transdiag_good[i], item_style) if i < len(transdiag_good) else Paragraph("", item_style)
b = checkbox(transdiag_bad[i], item_style) if i < len(transdiag_bad) else Paragraph("", item_style)
td_data.append([g, b])
td_table = Table(td_data, colWidths=[9.25*cm, 9.25*cm])
ts_td = TableStyle([
("LEFTPADDING",(0,0),(-1,-1),5), ("RIGHTPADDING",(0,0),(-1,-1),5),
("TOPPADDING",(0,0),(-1,-1),3), ("BOTTOMPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
("LINEAFTER",(0,0),(0,-1),0.5,MID_GREY),
("BOX",(0,0),(-1,-1),0.5,MID_GREY),
])
for i in range(max_td):
ts_td.add("BACKGROUND",(0,i),(0,i), colors.HexColor("#f2fbf2") if i%2==0 else colors.HexColor("#e8f8e8"))
ts_td.add("BACKGROUND",(1,i),(1,i), colors.HexColor("#fef5f5") if i%2==0 else colors.HexColor("#fdeaea"))
td_table.setStyle(ts_td)
story.append(td_table)
# TD biomarker columns
td_bio_hdr = [[Paragraph(" 🔬 TRANSDIAGNOSTIC BIOMARKERS", ParagraphStyle("TDBioHdr",
parent=styles["Normal"], fontSize=9, textColor=WHITE, fontName="Helvetica-Bold"))]]
td_bio_hdr_table = Table(td_bio_hdr, colWidths=[18.5*cm])
td_bio_hdr_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), BIOMARKER_HDR),
("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
story.append(td_bio_hdr_table)
td_bio_col = [[Paragraph("🔵 FAVOURABLE", bio_col_good), Paragraph("🔴 UNFAVOURABLE", bio_col_bad)]]
td_bio_col_table = Table(td_bio_col, colWidths=[9.25*cm, 9.25*cm])
td_bio_col_table.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0),BIO_GOOD_BG), ("BACKGROUND",(1,0),(1,0),BIO_BAD_BG),
("TOPPADDING",(0,0),(-1,-1),4), ("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),4),
]))
story.append(td_bio_col_table)
max_tdb = max(len(transdiag_bio_good), len(transdiag_bio_bad))
tdb_data = []
for i in range(max_tdb):
g = diamond(transdiag_bio_good[i], bio_item_style) if i < len(transdiag_bio_good) else Paragraph("", bio_item_style)
b = diamond(transdiag_bio_bad[i], bio_item_style) if i < len(transdiag_bio_bad) else Paragraph("", bio_item_style)
tdb_data.append([g, b])
tdb_table = Table(tdb_data, colWidths=[9.25*cm, 9.25*cm])
ts_tdb = TableStyle([
("LEFTPADDING",(0,0),(-1,-1),5), ("RIGHTPADDING",(0,0),(-1,-1),5),
("TOPPADDING",(0,0),(-1,-1),3), ("BOTTOMPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
("LINEAFTER",(0,0),(0,-1),0.5,MID_GREY),
("BOX",(0,0),(-1,-1),0.5,MID_GREY),
])
for i in range(max_tdb):
ts_tdb.add("BACKGROUND",(0,i),(0,i), colors.HexColor("#eaf4fb") if i%2==0 else colors.HexColor("#dceef8"))
ts_tdb.add("BACKGROUND",(1,i),(1,i), colors.HexColor("#fef0e0") if i%2==0 else colors.HexColor("#fde5c8"))
tdb_table.setStyle(ts_tdb)
story.append(tdb_table)
td_src = [[Paragraph("Source: Solmi et al. 2023 (PMID 37957292); Liberona et al. 2024 (PMID 39456983); Candidate biomarkers in psychiatry (PMC10168176)", source_style)]]
td_src_table = Table(td_src, colWidths=[18.5*cm])
td_src_table.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1), colors.HexColor("#f0f0f0")),
("TOPPADDING",(0,0),(-1,-1),3), ("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
]))
story.append(td_src_table)
story.append(Spacer(1, 0.4*cm))
# Disorder sections
for d in disorders:
section_elements = build_disorder_section(
d["name"], d["good"], d["bad"],
d["bio_good"], d["bio_bad"],
d["source"], d["bio_source"], d["emoji"]
)
story.extend(section_elements)
# Notes page
story.append(PageBreak())
notes_header = [[Paragraph(" 📋 CLINICAL NOTES", section_style)]]
notes_hdr_table = Table(notes_header, colWidths=[18.5*cm])
notes_hdr_table.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),HEADER_BG),
("TOPPADDING",(0,0),(-1,-1),7), ("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
story.append(notes_hdr_table)
story.append(Spacer(1, 0.3*cm))
for i in range(22):
story.append(Paragraph("", item_style))
story.append(HRFlowable(width="100%", thickness=0.4, color=MID_GREY))
story.append(Spacer(1, 0.45*cm))
# References
story.append(Spacer(1, 0.4*cm))
ref_header = [[Paragraph(" 📚 KEY REFERENCES", section_style)]]
ref_hdr_table = Table(ref_header, colWidths=[18.5*cm])
ref_hdr_table.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),HEADER_BG),
("TOPPADDING",(0,0),(-1,-1),6), ("BOTTOMPADDING",(0,0),(-1,-1),6),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
story.append(ref_hdr_table)
story.append(Spacer(1, 0.2*cm))
refs = [
"1. Solmi M, et al. (2023). Umbrella review of candidate predictors of response, remission, recovery, and relapse across mental disorders. Molecular Psychiatry. PMID: 37957292",
"2. Molstrom IM, et al. (2022). The prognosis of schizophrenia: systematic review & meta-analysis of 20-year follow-up studies. Schizophrenia Research. PMID: 36417817",
"3. Treuer T & Tohen M. (2010). Predicting the course and outcome of bipolar disorder: a review. European Psychiatry. PMID: 20444581",
"4. Scott K, et al. (2025). Prediction of Treatment Outcome in Bipolar Disorder. Biological Psychiatry. PMID: 39954922",
"5. Sharma E, Thennarasu K, Reddy YCJ. (2014). Long-term outcome of OCD in adults: a meta-analysis. J Clin Psychiatry. PMID: 25295427",
"6. Sharma E & Math SB. (2019). Course and outcome of OCD. Indian J Psychiatry. PMID: 30745676",
"7. Hazari N, et al. (2016). Predictors of response to serotonin reuptake inhibitors in OCD. Expert Rev Neurother. PMID: 27282021",
"8. Keyan D, et al. (2024). Predictors of response to trauma-focused psychotherapy for PTSD. Psychological Bulletin. PMID: 38884956",
"9. Wang J, et al. (2018). Loneliness, social support and outcomes in mental health problems. BMC Psychiatry. PMID: 29843662",
"10. Liberona A, et al. (2024). BDNF as a predictor of treatment response in schizophrenia & bipolar disorder. Int J Mol Sci. PMID: 39456983",
"11. Bandelow B, et al. (2016). Biological markers for anxiety disorders, OCD & PTSD - Part I: Neuroimaging & genetics. World J Biol Psychiatry. PMID: 27403679",
"12. Bandelow B, et al. (2017). Biological markers for anxiety disorders, OCD & PTSD - Part II: Neurochemistry, neurophysiology & neurocognition. World J Biol Psychiatry. PMID: 27419272",
"13. Candidate biomarkers in psychiatric disorders: state of the field (2023). PMC10168176",
"14. Sowerby C, et al. (2026). Neuroimaging & neurophysiologic biomarkers for diagnosis & prognosis of depressive disorders, bipolar disorder, anxiety disorders, OCD, PTSD. BMC Psychiatry.",
]
for ref in refs:
story.append(Paragraph(ref, source_style))
story.append(Spacer(1, 0.12*cm))
doc.build(story, onFirstPage=add_page_background, onLaterPages=add_page_background)
print("PDF v2 created:", OUTPUT)
cd /tmp/workspace/psych-checklist && python build_checklist_v2.py 2>&1
ls -lh /tmp/workspace/psych-checklist/
Psychiatric Prognostic Checklist v2
PDF Document · PDF
| Favourable | Unfavourable |
|---|---|
| Higher baseline BDNF (→ antipsychotic response) | Low serum BDNF |
| BDNF Val/Val genotype | Elevated striatal functional abnormalities (FSA) |
| Intact striatal connectivity (SCI) on fMRI | Reduced caudate volume on MRI |
| Low CRP / IL-6 | Elevated CRP / IL-6 |
| Normal ACC and PFC volumes | Hypofrontality on fMRI |
| Low polygenic risk score | Elevated homocysteine / low folate/B12 |
| Source: Liberona et al. 2024 (PMID 39456983) |
Network Failure,Behavioral Sign,Patient Subjective Report SN Hijack,"Startle response, dilated pupils, scanning.","""Everything feels like an emergency.""" DMN Hyper-activity,"Vacant stare, slow response to questions.","""I can't turn off the 'What If' movie in my head.""" ECN Decohesion,Repeating a task they just finished.,"""I know it’s clean, but I can’t stop scrubbing.""" NE Overload,"Fidgeting, ""punding,"" rapid-fire speech.","""I feel 'wired' and 'stuck' at the same time.""" This is a clinical questionnaire aimed at assessing dysfunction in the triple network model in psychiatric disorders. Can we check its validity, so that it can further be updated in the clinical interview pdf we had created
triple network model salience default mode executive control psychiatric disorders review
salience network hyperactivity default mode network rumination executive control dysfunction psychiatric
triple network model salience network default mode executive control network psychiatric disorders clinical signs symptoms review Menon 2023
salience network default mode network frontoparietal network psychopathology Menon
norepinephrine overload locus coeruleus punding wired stuck psychiatric hyperarousal
PMID: 36702660
locus coeruleus norepinephrine hyperarousal PTSD anxiety wired clinical symptoms review
~/psych-checklist/build_triple_network_module.py
"""
Triple Network Module - appended to the existing Psychiatric Prognostic Checklist v2 PDF.
Generates a standalone section PDF that covers:
- Critical appraisal of the original 4-row questionnaire
- Validated & expanded triple network clinical screening tool
- Guidance on interpretation
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from pypdf import PdfWriter, PdfReader
OUTPUT_MODULE = "/tmp/workspace/psych-checklist/triple_network_module.pdf"
OUTPUT_FINAL = "/tmp/workspace/psych-checklist/Psychiatric_Prognostic_Checklist_v3.pdf"
# ── Colour palette ────────────────────────────────────────────────────────────
DARK_NAVY = colors.HexColor("#1a2b4a")
TEAL = colors.HexColor("#007b7f")
MID_GREY = colors.HexColor("#cccccc")
WHITE = colors.white
SN_COL = colors.HexColor("#c0392b") # red - Salience Network
SN_LIGHT = colors.HexColor("#fadbd8")
DMN_COL = colors.HexColor("#1a5276") # blue - Default Mode Network
DMN_LIGHT = colors.HexColor("#d6eaf8")
ECN_COL = colors.HexColor("#1e8449") # green - Executive Control Network
ECN_LIGHT = colors.HexColor("#d5f5e3")
NE_COL = colors.HexColor("#7d6608") # amber - NE/LC system
NE_LIGHT = colors.HexColor("#fef9e7")
APPRAISAL_OK = colors.HexColor("#d5f5e3")
APPRAISAL_WARN = colors.HexColor("#fef9e7")
APPRAISAL_ISSUE = colors.HexColor("#fadbd8")
# ── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def S(name, **kw):
base = kw.pop("parent", styles["Normal"])
return ParagraphStyle(name, parent=base, **kw)
page_title = S("PT", fontSize=18, textColor=WHITE, alignment=TA_CENTER,
fontName="Helvetica-Bold")
page_sub = S("PS", fontSize=9, textColor=colors.HexColor("#c8d8f0"),
alignment=TA_CENTER, fontName="Helvetica-Oblique")
sec_hdr = S("SH", fontSize=12, textColor=WHITE, fontName="Helvetica-Bold",
leftIndent=6)
body = S("BD", fontSize=8.5, leading=12, fontName="Helvetica")
bold_body = S("BB", fontSize=8.5, leading=12, fontName="Helvetica-Bold")
italic_body = S("IB", fontSize=8.5, leading=12, fontName="Helvetica-Oblique",
textColor=colors.HexColor("#444444"))
small = S("SM", fontSize=7.5, leading=10, fontName="Helvetica",
textColor=colors.HexColor("#555555"))
small_bold = S("SMB", fontSize=7.5, leading=10, fontName="Helvetica-Bold")
note = S("NT", fontSize=8, leading=11, fontName="Helvetica",
textColor=colors.HexColor("#333333"), leftIndent=6)
warn_style = S("WS", fontSize=7.5, leading=10, fontName="Helvetica-Oblique",
textColor=colors.HexColor("#7b3a10"), leftIndent=4)
src_style = S("SS", fontSize=7, leading=10, fontName="Helvetica-Oblique",
textColor=colors.HexColor("#555555"), leftIndent=4)
network_label = S("NL", fontSize=9, fontName="Helvetica-Bold",
textColor=WHITE, alignment=TA_CENTER)
col_hdr = S("CH", fontSize=8.5, fontName="Helvetica-Bold",
alignment=TA_CENTER, textColor=DARK_NAVY)
def checkbox(t): return Paragraph(f"☐ {t}", body)
def check_b(t): return Paragraph(f"☐ <b>{t}</b>", body)
def bullet(t): return Paragraph(f"• {t}", body)
def italic(t): return Paragraph(f"<i>{t}</i>", italic_body)
def section_bar(text, bg_color):
t = Table([[Paragraph(f" {text}", sec_hdr)]], colWidths=[18.5*cm])
t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1), bg_color),
("TOPPADDING",(0,0),(-1,-1),7),
("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.5,DARK_NAVY),
]))
return t
def add_bg(canvas, doc):
canvas.saveState()
w, h = A4
canvas.setFillColor(DARK_NAVY)
canvas.rect(0, h-2.8*cm, w, 2.8*cm, fill=1, stroke=0)
canvas.setFillColor(TEAL)
canvas.rect(0, h-3.0*cm, w, 0.22*cm, fill=1, stroke=0)
canvas.setFillColor(colors.HexColor("#f0f0f0"))
canvas.rect(0, 0, w, 1.2*cm, fill=1, stroke=0)
canvas.setFillColor(MID_GREY)
canvas.rect(0, 1.2*cm, w, 0.05*cm, fill=1, stroke=0)
canvas.setFont("Helvetica-Oblique", 7)
canvas.setFillColor(colors.HexColor("#555555"))
canvas.drawCentredString(w/2, 0.42*cm,
"Triple Network Clinical Screening Module | Research-informed; not validated as a standalone diagnostic instrument")
canvas.drawRightString(w-1.5*cm, 0.42*cm, f"Page {doc.page}")
canvas.restoreState()
# ═══════════════════════════════════════════════════════════════════════════════
# CONTENT DATA
# ═══════════════════════════════════════════════════════════════════════════════
# Critical appraisal rows: [network, verdict, verdict_bg, issue, fix]
appraisal_rows = [
{
"network": "SN Hijack",
"original_signs": "Startle response, dilated pupils, scanning",
"original_report": '"Everything feels like an emergency."',
"verdict": "✅ VALID - with minor gaps",
"verdict_bg": APPRAISAL_OK,
"appraisal": (
"SN (insula + dACC) hyperactivation is well-evidenced in PTSD, anxiety, and schizophrenia "
"(Menon 2011; Menon et al. 2023). Startle response and hypervigilant scanning accurately "
"reflect SN-driven threat-detection bias. Dilated pupils are a valid sympathetic/LC-NE "
"correlate of SN overactivation. The subjective report is ecologically valid and aligns "
"with interoceptive alarm signalling.\n"
"GAP: Does not distinguish SN hyperactivity from pure LC-NE hyperarousal (which overlaps). "
"Also missing: intrusive re-experiencing (PTSD), and aberrant salience "
"(schizophrenia - assigning threat to neutral stimuli)."
),
"additions": [
"Add: misattribution of salience to neutral stimuli (aberrant salience - schizophrenia)",
"Add: intrusive memories / flashbacks (PTSD context)",
"Add: tachycardia / palpitations as observable SN-autonomic sign",
"Add: heightened interoceptive awareness (\"my heart is pounding for no reason\")",
]
},
{
"network": "DMN Hyperactivity",
"original_signs": "Vacant stare, slow response to questions",
"original_report": '"I can\'t turn off the \'What If\' movie in my head."',
"verdict": "⚠️ PARTIALLY VALID - sign is inaccurate",
"verdict_bg": APPRAISAL_WARN,
"appraisal": (
"DMN hyperactivity (persistent self-referential, past/future-oriented thought) is robustly "
"evidenced in depression and anxiety (Buckner et al. 2008; Mulders et al. 2015). "
"The subjective report is excellent - future-oriented worry / rumination is the cardinal "
"clinical expression of DMN hyperactivation.\n"
"PROBLEM: 'Vacant stare, slow response to questions' is more characteristic of "
"dissociation or psychomotor retardation (depression) than active DMN hyperactivity. "
"Pure DMN hyperactivity typically presents as mental over-engagement - the patient is "
"internally busy, not blank. A vacant stare with slow response maps better to "
"depressive psychomotor slowing or derealization, not DMN ruminative overdrive."
),
"additions": [
"CORRECT SIGN: Replace 'vacant stare' with 'appears internally preoccupied; interruptible but quickly returns to internal train of thought'",
"Add: self-referential negative cognition ('everything bad is my fault')",
"Add: difficulty engaging with present-moment tasks ('I keep drifting off')",
"Add: autobiographical memory over-retrieval of negative past events",
"Add: perseverative worry about future scenarios (GAD phenotype)",
"Keep: slow response to questions - but label it as 'attentional capture by internal narrative'",
]
},
{
"network": "ECN Decohesion",
"original_signs": "Repeating a task they just finished",
"original_report": '"I know it\'s clean, but I can\'t stop scrubbing."',
"verdict": "⚠️ PARTIALLY VALID - sign is disorder-specific, not ECN-specific",
"verdict_bg": APPRAISAL_WARN,
"appraisal": (
"ECN (dlPFC + lateral PPC) dysfunction is well-evidenced across multiple disorders - "
"impaired working memory, cognitive flexibility, and inhibitory control are the core deficits "
"(Menon 2011). The subjective report maps well to OCD specifically (preserved insight with "
"compulsive repetition = intact verbal cognition but impaired ECN inhibitory override).\n"
"PROBLEM: 'Repeating a task they just finished' is highly OCD-specific and will not "
"sensitively capture ECN decohesion in other contexts (schizophrenia = cognitive "
"disorganisation; bipolar = executive dysfunction; ADHD = working memory failures). "
"The term 'decohesion' is also non-standard - 'dysfunction' or 'failure' is preferred."
),
"additions": [
"BROADEN SIGNS: Add 'difficulty holding two things in mind simultaneously (working memory failure)'",
"Add: task-switching failure ('gets stuck on one topic in conversation')",
"Add: inability to inhibit prepotent responses ('acts before thinking')",
"Add: cognitive rigidity / perseveration (beyond OCD - also schizophrenia, severe depression)",
"Retain OCD example but label it: 'ECN inhibitory override failure (OCD subtype)'",
"Add schizophrenia subtype: disorganised speech, thought tangentiality",
]
},
{
"network": "NE Overload",
"original_signs": 'Fidgeting, "punding," rapid-fire speech',
"original_report": '"I feel \'wired\' and \'stuck\' at the same time."',
"verdict": "⚠️ CONCEPTUAL ISSUE - NE is not a 'network' in triple network model",
"verdict_bg": APPRAISAL_ISSUE,
"appraisal": (
"CORE PROBLEM: The triple network model (Menon 2011) comprises three cortical resting-state "
"networks: Salience Network (SN), Default Mode Network (DMN), and Central/Frontal-Parietal "
"Executive Network (ECN/FPN). Norepinephrine (NE) / locus coeruleus (LC) is a subcortical "
"neuromodulatory system - not a resting-state network. Including it as a 4th 'network' is "
"neuroscientifically inaccurate and will confuse clinicians familiar with the literature.\n"
"HOWEVER: The clinical content is valid. LC-NE hyperactivation IS a biologically distinct "
"and important clinical state. 'Punding' is a valid dopaminergic (not NE) sign - seen in "
"stimulant abuse and Parkinson's. Rapid-fire speech + fidgeting + 'wired and stuck' more "
"accurately reflects combined NE + dopamine dysregulation.\n"
"RECOMMENDATION: Relabel this row as 'LC-NE Hyperactivation / Neuromodulatory Dysregulation' "
"and present it as a modifier/subcomponent of SN dysfunction rather than an independent network."
),
"additions": [
"RELABEL: 'LC-NE Hyperarousal (Neuromodulatory Modifier)' - not a 4th network",
"Distinguish: NE hyperactivation (hypervigilance, startle, autonomic) from DA dysregulation (punding, compulsive seeking)",
"Remove 'punding' from NE row - reassign to dopaminergic/reward system dysfunction",
"Add: pressure of speech, psychomotor agitation (mania subtype)",
"Add: insomnia despite exhaustion as a specific NE hyperactivation sign",
"Add: 'I feel like I'm running on adrenaline but can't calm down'",
"Consider adding a 5th row: Reward/Mesolimbic Dysregulation (anhedonia vs. hyperdopaminergia)",
]
},
]
# ── Validated & expanded network rows ────────────────────────────────────────
network_rows = [
{
"network": "SN Hijack\n(Salience Network\nHyperactivation)",
"color": SN_COL,
"light": SN_LIGHT,
"anchors": "Anterior insula + dACC",
"disorders": "PTSD, GAD, Panic disorder, Schizophrenia (aberrant salience)",
"observable_signs": [
"Exaggerated startle response",
"Dilated pupils / tachycardia on exam",
"Hypervigilant environmental scanning",
"Freezing or flinching to neutral stimuli",
"Intrusive re-experiencing episodes (PTSD)",
],
"patient_reports": [
'"Everything feels like an emergency."',
'"My heart races for no reason."',
'"I keep thinking that stranger is threatening me."',
'"I can\'t relax even when I know I\'m safe."',
],
"note": "Distinguish from LC-NE hyperarousal (see modifier row below). In schizophrenia, SN hijack = aberrant salience attribution to neutral stimuli.",
"source": "Menon 2011 (Trends Cogn Sci); Menon et al. 2023 (Biol Psychiatry PMID 36702660)"
},
{
"network": "DMN Hyperactivity\n(Default Mode Network\nOverengagement)",
"color": DMN_COL,
"light": DMN_LIGHT,
"anchors": "mPFC + PCC + angular gyrus",
"disorders": "MDD, GAD, Rumination-predominant OCD, Bipolar depression",
"observable_signs": [
"Internally preoccupied; easily returns to internal train of thought when distracted",
"Slow or delayed response to clinician questions (attentional capture)",
"Flat affect with preserved verbal fluency about self-related themes",
"Excessive self-referential narrative in interview",
],
"patient_reports": [
'"I can\'t turn off the \'What If\' movie in my head."',
'"I keep replaying what went wrong last year."',
'"Everything bad always comes back to something I did."',
'"I drift off during tasks - my mind just goes back to worrying."',
],
"note": "NOTE: A vacant stare with slow responses more accurately reflects psychomotor retardation or dissociation, NOT active DMN hyperactivity. DMN over-engagement = internal busyness, not blankness.",
"source": "Buckner et al. 2008 (Ann NY Acad Sci); Mulders et al. 2015 (Neurosci Biobehav Rev); PMC10524518"
},
{
"network": "ECN Decohesion\n(Executive Control Network\nDysfunction)",
"color": ECN_COL,
"light": ECN_LIGHT,
"anchors": "dlPFC + lateral PPC",
"disorders": "OCD, Schizophrenia, ADHD, Bipolar disorder, Severe MDD",
"observable_signs": [
"OCD subtype: Repeating a completed task (inhibitory override failure)",
"Cognitive subtype: Cannot hold two concepts in mind simultaneously",
"Rigidity subtype: Gets stuck on one conversational topic; cannot task-switch",
"Impulsivity subtype: Acts before thinking; poor prepotent response inhibition",
"Disorganisation subtype: Tangential or disorganised speech (schizophrenia)",
],
"patient_reports": [
'"I know it\'s clean, but I can\'t stop scrubbing." (OCD)',
'"I started three things and can\'t finish any of them."',
'"I keep losing my train of thought mid-sentence."',
'"I say things before I realise I shouldn\'t."',
],
"note": "The term 'decohesion' is non-standard. Use 'ECN dysfunction' or 'ECN failure'. Subtypes differ by disorder: inhibitory failure (OCD), working memory failure (ADHD/schizophrenia), cognitive rigidity (schizophrenia/severe depression).",
"source": "Menon 2011 (Trends Cogn Sci); Menon et al. 2023 (PMID 36702660)"
},
{
"network": "LC-NE Hyperarousal\n(Neuromodulatory Modifier -\nnot a 4th network)",
"color": NE_COL,
"light": NE_LIGHT,
"anchors": "Locus coeruleus → NE release across cortex",
"disorders": "PTSD, Panic disorder, Mania, Stimulant intoxication/withdrawal",
"observable_signs": [
"Motor restlessness / fidgeting",
"Pressured or rapid-fire speech",
"Psychomotor agitation (cannot stay seated)",
"Insomnia despite clear exhaustion",
"Exaggerated startle (overlaps with SN hijack - distinguish by autonomic prominence)",
],
"patient_reports": [
'"I feel \'wired\' and \'stuck\' at the same time."',
'"I\'m exhausted but I can\'t slow down."',
'"My body is on high alert even when my mind knows it\'s fine."',
'"I haven\'t slept in days but I\'m not tired."',
],
"note": "⚠️ IMPORTANT: LC-NE is a neuromodulatory system, not a resting-state network. It modulates SN sensitivity and amplifies SN hijack. 'Punding' is a dopaminergic (not NE) sign - reassign to reward/mesolimbic dysfunction if included. Present this row as a modifier of SN, not an independent network.",
"source": "Aston-Jones & Cohen 2005 (Annu Rev Neurosci); Sara 2009 (Nat Rev Neurosci)"
},
]
# ═══════════════════════════════════════════════════════════════════════════════
# BUILD
# ═══════════════════════════════════════════════════════════════════════════════
doc = SimpleDocTemplate(OUTPUT_MODULE, pagesize=A4,
topMargin=3.2*cm, bottomMargin=1.6*cm,
leftMargin=1.5*cm, rightMargin=1.5*cm,
title="Triple Network Clinical Screening Module",
author="Orris AI")
story = []
# ── Title ──
title_block = [
[Paragraph("Triple Network Clinical Screening Module", page_title)],
[Paragraph("Critical Appraisal · Validated Expansion · Clinical Interview Tool", page_sub)],
[Paragraph("Appended to: Psychiatric Prognostic Checklist v2 | Based on Menon 2011 triple network model", page_sub)],
]
tb = Table(title_block, colWidths=[18.5*cm])
tb.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),DARK_NAVY),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),8),
]))
story.append(tb)
story.append(Spacer(1,0.25*cm))
# ── Background box ──
bg_data = [[Paragraph(
"<b>TRIPLE NETWORK MODEL (Menon, 2011):</b> The brain's cognitive architecture can be described through "
"three large-scale resting-state networks: (1) the <b>Salience Network (SN)</b> - insula + dACC - "
"detects and filters behaviourally relevant stimuli and acts as the switch between the other two; "
"(2) the <b>Default Mode Network (DMN)</b> - mPFC + PCC + angular gyrus - active during self-referential, "
"internally focused cognition; (3) the <b>Executive Control Network (ECN/FPN)</b> - dlPFC + lateral PPC - "
"governs working memory, cognitive flexibility, and inhibitory control. "
"Dysfunction in these networks and their dynamic interactions underlies the cognitive and emotional "
"abnormalities seen across most major psychiatric disorders.",
note)]]
bg_box = Table(bg_data, colWidths=[18.5*cm])
bg_box.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#eaf4fb")),
("BOX",(0,0),(-1,-1),0.5,colors.HexColor("#1a6b8a")),
("TOPPADDING",(0,0),(-1,-1),6),("BOTTOMPADDING",(0,0),(-1,-1),6),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
]))
story.append(bg_box)
story.append(Spacer(1,0.3*cm))
# ══════════════════════════════════════════════════════════════════
# SECTION 1: CRITICAL APPRAISAL
# ══════════════════════════════════════════════════════════════════
story.append(section_bar("SECTION 1 · Critical Appraisal of the Original Questionnaire", DARK_NAVY))
story.append(Spacer(1,0.2*cm))
for row in appraisal_rows:
# Network label + verdict header
hdr_data = [[
Paragraph(row["network"], S("NHdr", fontSize=10, fontName="Helvetica-Bold",
textColor=WHITE, alignment=TA_CENTER)),
Paragraph(row["verdict"], S("VHdr", fontSize=9, fontName="Helvetica-Bold",
alignment=TA_CENTER,
textColor=colors.HexColor("#1a3a0a") if "VALID" in row["verdict"] and "PARTIALLY" not in row["verdict"] else colors.HexColor("#7b3a10")))
]]
hdr_t = Table(hdr_data, colWidths=[5*cm, 13.5*cm])
hdr_t.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0),DARK_NAVY),
("BACKGROUND",(1,0),(1,0),row["verdict_bg"]),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),5),
("BOX",(0,0),(-1,-1),0.5,MID_GREY),
]))
story.append(hdr_t)
# Original content row
orig_data = [[
Paragraph("<b>Original signs:</b>", small_bold),
Paragraph(row["original_signs"], small),
Paragraph("<b>Original report:</b>", small_bold),
Paragraph(row["original_report"], italic_body),
]]
orig_t = Table(orig_data, colWidths=[3*cm, 5.5*cm, 3*cm, 7*cm])
orig_t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#f8f8f8")),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),4),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
("VALIGN",(0,0),(-1,-1),"TOP"),
]))
story.append(orig_t)
# Appraisal text
appraisal_para = Paragraph(row["appraisal"].replace("\n", "<br/>"), small)
appr_data = [[appraisal_para]]
appr_t = Table(appr_data, colWidths=[18.5*cm])
appr_t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#fdfdf5")),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
]))
story.append(appr_t)
# Suggested additions
add_items = [[Paragraph("<b>Suggested additions / corrections:</b>", small_bold)]]
for a in row["additions"]:
add_items.append([Paragraph(f" → {a}", small)])
add_t = Table(add_items, colWidths=[18.5*cm])
add_t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#f0f8ff")),
("TOPPADDING",(0,0),(-1,-1),2),("BOTTOMPADDING",(0,0),(-1,-1),2),
("LEFTPADDING",(0,0),(-1,-1),8),
("BOX",(0,0),(-1,-1),0.3,colors.HexColor("#1a6b8a")),
]))
story.append(add_t)
story.append(Spacer(1,0.3*cm))
# ══════════════════════════════════════════════════════════════════
# SECTION 2: VALIDATED EXPANDED SCREENING TOOL
# ══════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(section_bar("SECTION 2 · Validated & Expanded Triple Network Clinical Screening Tool", DARK_NAVY))
story.append(Spacer(1,0.2*cm))
how_use = [[Paragraph(
"<b>HOW TO USE:</b> During the clinical interview, tick observable signs (☐) and note matching patient reports. "
"A consistent pattern within one network domain suggests that network's dysfunction is clinically prominent. "
"Multiple network involvement is common and diagnostically significant. "
"This tool screens for network-level dysfunction - it does not replace formal neuropsychological or neuroimaging assessment.",
note)]]
how_t = Table(how_use, colWidths=[18.5*cm])
how_t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#fffbe6")),
("BOX",(0,0),(-1,-1),0.5,colors.HexColor("#e0c050")),
("TOPPADDING",(0,0),(-1,-1),6),("BOTTOMPADDING",(0,0),(-1,-1),6),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
]))
story.append(how_t)
story.append(Spacer(1,0.25*cm))
for net in network_rows:
# Network header bar
net_hdr = [[Paragraph(net["network"], S(f"NH{net['color']}", fontSize=11,
fontName="Helvetica-Bold", textColor=WHITE, alignment=TA_CENTER))]]
net_hdr_t = Table(net_hdr, colWidths=[18.5*cm])
net_hdr_t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),net["color"]),
("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.5,DARK_NAVY),
]))
story.append(net_hdr_t)
# Anchor + disorders row
meta_data = [[
Paragraph(f"<b>Brain anchors:</b> {net['anchors']}", small),
Paragraph(f"<b>Disorders:</b> {net['disorders']}", small),
]]
meta_t = Table(meta_data, colWidths=[7*cm, 11.5*cm])
meta_t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),net["light"]),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
]))
story.append(meta_t)
# Signs + reports columns
col_hdrs = [[
Paragraph("☐ OBSERVABLE SIGNS (clinician)", S("CH_L", fontSize=8.5,
fontName="Helvetica-Bold", textColor=net["color"])),
Paragraph("💬 PATIENT REPORTS (verbatim prompts)", S("CH_R", fontSize=8.5,
fontName="Helvetica-Bold", textColor=DARK_NAVY)),
]]
col_hdr_t = Table(col_hdrs, colWidths=[9.25*cm, 9.25*cm])
col_hdr_t.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0),net["light"]),
("BACKGROUND",(1,0),(1,0),colors.HexColor("#f5f5f5")),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),5),
("LINEAFTER",(0,0),(0,0),0.5,MID_GREY),
("LINEBELOW",(0,0),(-1,-1),0.5,MID_GREY),
]))
story.append(col_hdr_t)
max_r = max(len(net["observable_signs"]), len(net["patient_reports"]))
rows_data = []
for i in range(max_r):
s = checkbox(net["observable_signs"][i]) if i < len(net["observable_signs"]) else Paragraph("", body)
r = Paragraph(net["patient_reports"][i], italic_body) if i < len(net["patient_reports"]) else Paragraph("", body)
rows_data.append([s, r])
rows_t = Table(rows_data, colWidths=[9.25*cm, 9.25*cm])
r_ts = TableStyle([
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
("LINEAFTER",(0,0),(0,-1),0.5,MID_GREY),
("BOX",(0,0),(-1,-1),0.5,MID_GREY),
])
for i in range(max_r):
r_ts.add("BACKGROUND",(0,i),(0,i), net["light"] if i%2==0 else colors.HexColor("#fafafa"))
r_ts.add("BACKGROUND",(1,i),(1,i), colors.HexColor("#f9f9f9") if i%2==0 else WHITE)
rows_t.setStyle(r_ts)
story.append(rows_t)
# Note strip
note_data = [[Paragraph(f"<i>Note: {net['note']}</i>", warn_style),
Paragraph(f"<i>Ref: {net['source']}</i>", src_style)]]
note_t = Table(note_data, colWidths=[11*cm, 7.5*cm])
note_t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#f5f5f5")),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
("VALIGN",(0,0),(-1,-1),"TOP"),
]))
story.append(note_t)
story.append(Spacer(1,0.3*cm))
# ── Scoring / interpretation box ──
story.append(section_bar("SECTION 3 · Interpretation Guide", DARK_NAVY))
story.append(Spacer(1,0.2*cm))
interp_rows = [
["Pattern", "Likely Network Failure", "Common Disorder Context"],
["≥3 SN signs + emergency subjective report", "SN Hijack (primary)", "PTSD, Panic disorder, GAD, Schizophrenia"],
["≥3 DMN signs + ruminative/self-critical reports", "DMN Hyperactivity (primary)", "MDD, GAD, Rumination OCD"],
["≥3 ECN signs across subtypes", "ECN Dysfunction (primary)", "OCD, Schizophrenia, ADHD, Bipolar"],
["≥2 SN + ≥2 LC-NE signs", "SN Hijack + NE Amplification", "PTSD, Mania, Stimulant use"],
["≥2 DMN + ≥2 ECN signs", "DMN-ECN Anti-correlation failure", "MDD with cognitive dysfunction, Bipolar depression"],
["All 3 networks + LC-NE signs", "Global network dysregulation", "Severe MDD, Psychosis, Complex PTSD"],
]
interp_t = Table(interp_rows, colWidths=[6*cm, 6*cm, 6.5*cm])
interp_ts = TableStyle([
("BACKGROUND",(0,0),(-1,0),DARK_NAVY),
("TEXTCOLOR",(0,0),(-1,0),WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),8),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
("GRID",(0,0),(-1,-1),0.3,MID_GREY),
("VALIGN",(0,0),(-1,-1),"TOP"),
])
for i in range(1, len(interp_rows)):
bg = colors.HexColor("#f5f5f5") if i%2==0 else WHITE
interp_ts.add("BACKGROUND",(0,i),(-1,i),bg)
interp_t.setStyle(interp_ts)
story.append(interp_t)
story.append(Spacer(1,0.2*cm))
# Caveat
caveat = [[Paragraph(
"⚠️ <b>IMPORTANT LIMITATIONS:</b> This screening tool maps clinical observations onto network-level constructs "
"derived from fMRI research. Network dysfunction cannot be confirmed from clinical observation alone - "
"it requires neuroimaging. This tool is intended to prompt network-informed clinical reasoning and guide "
"targeted questioning. It has not been validated as a psychometric instrument. "
"Multiple network involvement is the rule, not the exception, in severe psychiatric illness.",
warn_style)]]
cav_t = Table(caveat, colWidths=[18.5*cm])
cav_t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#fdf0e0")),
("BOX",(0,0),(-1,-1),0.5,colors.HexColor("#c07030")),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
]))
story.append(cav_t)
story.append(Spacer(1,0.2*cm))
# References
ref_hdr = [[Paragraph(" 📚 REFERENCES", sec_hdr)]]
ref_hdr_t = Table(ref_hdr, colWidths=[18.5*cm])
ref_hdr_t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),DARK_NAVY),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
story.append(ref_hdr_t)
story.append(Spacer(1,0.1*cm))
refs = [
"1. Menon V. (2011). Large-scale brain networks and psychopathology: a unifying triple network model. Trends in Cognitive Sciences, 15(10):483-506.",
"2. Menon V, Palaniyappan L, Supekar K. (2023). Integrative brain network and salience models of psychopathology and cognitive dysfunction in schizophrenia. Biol Psychiatry. PMID: 36702660",
"3. Buckner RL, Andrews-Hanna JR, Schacter DL. (2008). The brain's default network: anatomy, function, and relevance to disease. Ann NY Acad Sci. 1124:1-38.",
"4. Mulders PC, et al. (2015). Resting-state functional connectivity in major depressive disorder: a review. Neurosci Biobehav Rev. 56:330-44.",
"5. Aston-Jones G & Cohen JD. (2005). An integrative theory of locus coeruleus-norepinephrine function: adaptive gain and optimal performance. Annu Rev Neurosci. 28:403-50.",
"6. Sara SJ. (2009). The locus coeruleus and noradrenergic modulation of cognition. Nat Rev Neurosci. 10(3):211-23.",
]
for ref in refs:
story.append(Paragraph(ref, src_style))
story.append(Spacer(1,0.1*cm))
doc.build(story, onFirstPage=add_bg, onLaterPages=add_bg)
print("Module PDF built:", OUTPUT_MODULE)
# ── Merge v2 + module → v3 ──────────────────────────────────────────────────
v2_path = "/tmp/workspace/psych-checklist/Psychiatric_Prognostic_Checklist_v2.pdf"
writer = PdfWriter()
for path in [v2_path, OUTPUT_MODULE]:
reader = PdfReader(path)
for page in reader.pages:
writer.add_page(page)
with open(OUTPUT_FINAL, "wb") as f:
writer.write(f)
print("Final merged PDF:", OUTPUT_FINAL)
cd /tmp/workspace/psych-checklist && python build_triple_network_module.py 2>&1
ls -lh /tmp/workspace/psych-checklist/
Psychiatric Prognostic Checklist v3
PDF Document · PDF
triple network module
PDF Document · PDF
Compare triple network dysfunction patterns across your 6 psychiatric disorders in a visual summary (e.g. heatmap of which networks are most impaired in each condition) in the original pdf
give me final pdf with clinical prognostic checklist, bio markers (you can remove fmri specific biomakers), and section 2 of triple network screening module, remove section 1 of triple network model
give me final pdf with clinical prognostic checklist, bio markers (you can remove fmri specific biomakers), and section 2 of triple network screening module, remove section 1 of triple network model
give me final pdf with clinical prognostic checklist, bio markers (you can remove fmri, research grade biomakers), and section 2 of triple network screening module, remove section 1 of triple network model
~/psych-checklist/build_final.py
"""
FINAL PDF: Psychiatric Prognostic Checklist
Part 1 - Clinical prognostic factors (good / bad) per disorder
Part 2 - Routine-available biomarkers (blood, EEG, genetics only - no fMRI/PET/research-only)
Part 3 - Triple Network Clinical Screening Tool (Section 2 only)
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER
OUTPUT = "/tmp/workspace/psych-checklist/Psychiatric_Clinical_Tool_FINAL.pdf"
# ── Palette ───────────────────────────────────────────────────────────────────
DARK_NAVY = colors.HexColor("#1a2b4a")
TEAL = colors.HexColor("#007b7f")
WHITE = colors.white
MID_GREY = colors.HexColor("#cccccc")
GOOD_GREEN = colors.HexColor("#d4edda")
BAD_RED = colors.HexColor("#f8d7da")
GOOD_DARK = colors.HexColor("#155724")
BAD_DARK = colors.HexColor("#721c24")
HEADER_BG = colors.HexColor("#2c3e6b")
BIO_GOOD_BG = colors.HexColor("#d6eef7")
BIO_BAD_BG = colors.HexColor("#fde8d0")
BIO_GOOD_TXT = colors.HexColor("#1a6b8a")
BIO_BAD_TXT = colors.HexColor("#7b3a10")
BIO_HDR = colors.HexColor("#1a6b8a")
SN_COL = colors.HexColor("#c0392b")
SN_LIGHT = colors.HexColor("#fadbd8")
DMN_COL = colors.HexColor("#1a5276")
DMN_LIGHT = colors.HexColor("#d6eaf8")
ECN_COL = colors.HexColor("#1e8449")
ECN_LIGHT = colors.HexColor("#d5f5e3")
NE_COL = colors.HexColor("#7d6608")
NE_LIGHT = colors.HexColor("#fef9e7")
# ── Styles ────────────────────────────────────────────────────────────────────
SS = getSampleStyleSheet()
def PS(name, **kw):
return ParagraphStyle(name, parent=kw.pop("parent", SS["Normal"]), **kw)
title_s = PS("TS", fontSize=20, textColor=WHITE, alignment=TA_CENTER, fontName="Helvetica-Bold")
sub_s = PS("SS2", fontSize=9, textColor=colors.HexColor("#c8d8f0"), alignment=TA_CENTER, fontName="Helvetica-Oblique")
sec_s = PS("SHS", fontSize=13, textColor=WHITE, fontName="Helvetica-Bold", leftIndent=6)
sec_s2 = PS("SH2", fontSize=11, textColor=WHITE, fontName="Helvetica-Bold", leftIndent=6)
col_good = PS("CG", fontSize=10, textColor=GOOD_DARK, fontName="Helvetica-Bold", alignment=TA_CENTER)
col_bad = PS("CB", fontSize=10, textColor=BAD_DARK, fontName="Helvetica-Bold", alignment=TA_CENTER)
bio_good = PS("BG", fontSize=9, textColor=BIO_GOOD_TXT, fontName="Helvetica-Bold", alignment=TA_CENTER)
bio_bad = PS("BB2", fontSize=9, textColor=BIO_BAD_TXT, fontName="Helvetica-Bold", alignment=TA_CENTER)
item_s = PS("IS", fontSize=8.5, fontName="Helvetica", leading=12, leftIndent=4)
bio_s = PS("BS", fontSize=8, fontName="Helvetica", leading=11, leftIndent=4, textColor=colors.HexColor("#1a3a4a"))
src_s = PS("SRS", fontSize=7, fontName="Helvetica-Oblique", textColor=colors.HexColor("#555555"), leftIndent=4)
note_s = PS("NS", fontSize=8, fontName="Helvetica", leading=11, leftIndent=6, textColor=colors.HexColor("#333333"))
warn_s = PS("WS", fontSize=7.5, fontName="Helvetica-Oblique", textColor=colors.HexColor("#7b3a10"), leftIndent=4, leading=10)
net_lbl = PS("NL", fontSize=11, fontName="Helvetica-Bold", textColor=WHITE, alignment=TA_CENTER)
net_small = PS("NSM", fontSize=7.5, fontName="Helvetica", textColor=colors.HexColor("#555555"), leading=10)
italic_s = PS("ITS", fontSize=8.5, fontName="Helvetica-Oblique", textColor=colors.HexColor("#444444"), leading=12)
def cb(t): return Paragraph(f"☐ {t}", item_s)
def di(t): return Paragraph(f"◇ {t}", bio_s)
def sec_bar(text, bg=HEADER_BG):
t = Table([[Paragraph(f" {text}", sec_s)]], colWidths=[18.5*cm])
t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),bg),
("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.5,DARK_NAVY),
]))
return t
def page_bg(canvas, doc):
canvas.saveState()
w, h = A4
canvas.setFillColor(DARK_NAVY)
canvas.rect(0, h-2.8*cm, w, 2.8*cm, fill=1, stroke=0)
canvas.setFillColor(TEAL)
canvas.rect(0, h-3.0*cm, w, 0.22*cm, fill=1, stroke=0)
canvas.setFillColor(colors.HexColor("#f0f0f0"))
canvas.rect(0, 0, w, 1.2*cm, fill=1, stroke=0)
canvas.setFillColor(MID_GREY)
canvas.rect(0, 1.2*cm, w, 0.05*cm, fill=1, stroke=0)
canvas.setFont("Helvetica-Oblique", 7)
canvas.setFillColor(colors.HexColor("#555555"))
canvas.drawCentredString(w/2, 0.42*cm,
"Psychiatric Clinical Tool (Final) | Clinical use only - not a standalone diagnostic instrument")
canvas.drawRightString(w-1.5*cm, 0.42*cm, f"Page {doc.page}")
canvas.restoreState()
# ═══════════════════════════════════════════════════════════════════════════════
# DATA
# ═══════════════════════════════════════════════════════════════════════════════
disorders = [
{
"name":"SCHIZOPHRENIA","emoji":"🧠",
"good":[
"Female sex",
"Later / acute onset of illness",
"Predominantly positive symptoms",
"Good premorbid functioning",
"Short duration of untreated psychosis (DUP)",
"Good insight into illness",
"Strong social support network",
"Employment / higher education",
"Early and intensive treatment initiation",
"Absence of negative symptoms at baseline",
"No comorbid substance use disorder",
"Married / stable intimate relationship",
],
"bad":[
"Male sex",
"Insidious / early onset",
"Predominantly negative symptoms",
"Poor premorbid functioning",
"Long duration of untreated psychosis",
"Poor insight / anosognosia",
"Social isolation / lack of support",
"Unemployment / low education",
"Delayed treatment initiation",
"Prominent cognitive deficits",
"Comorbid substance use disorder",
"Family history of schizophrenia",
],
"bio_good":[
"Higher serum BDNF (→ better antipsychotic response)",
"BDNF Val/Val genotype",
"Low CRP / IL-6 at baseline",
"Low serum homocysteine",
"Normal serum folate and B12",
"Low polygenic risk score",
],
"bio_bad":[
"Low serum BDNF",
"BDNF Met carrier (Val/Met or Met/Met)",
"Elevated CRP / IL-6",
"Elevated homocysteine",
"Low folate / B12",
"High polygenic risk score for schizophrenia",
],
"src":"Solmi et al. 2023 (PMID 37957292); Molstrom et al. 2022 (PMID 36417817); Liberona et al. 2024 (PMID 39456983)",
},
{
"name":"BIPOLAR DISORDER","emoji":"🔄",
"good":[
"Later age of onset",
"Fewer prior mood episodes",
"Good psychosocial functioning at baseline",
"Long-term symptomatic remission achieved",
"Good treatment adherence",
"Strong social / family support",
"Absence of psychotic features",
"No comorbid anxiety or substance use",
"Higher education / employment",
],
"bad":[
"Early age of onset",
"High number of prior episodes",
"Poor interepisode functioning",
"Rapid cycling course",
"Psychotic features present",
"Comorbid ADHD",
"Comorbid anxiety disorder",
"Comorbid substance use disorder",
"Comorbid personality disorder",
"Cognitive impairment",
"Poor medication adherence",
],
"bio_good":[
"Higher serum BDNF (→ better lithium response)",
"BDNF Val/Val genotype",
"Low CRP / IL-6",
"Normal cortisol awakening response",
"Normal serum lithium level (therapeutic range)",
"Low polygenic risk score for schizophrenia / MDD",
"Normal TSH (euthyroid)",
],
"bio_bad":[
"Low serum BDNF",
"Elevated CRP / IL-6 / TNF-alpha",
"Elevated morning cortisol / flattened diurnal cortisol",
"Elevated homocysteine",
"Hypothyroidism / subclinical hypothyroidism (TSH elevated)",
"High polygenic risk score for schizophrenia or MDD",
"Abnormal lithium erythrocyte:plasma ratio",
],
"src":"Treuer & Tohen 2010 (PMID 20444581); Scott et al. 2025 (PMID 39954922); Liberona et al. 2024 (PMID 39456983)",
},
{
"name":"MAJOR DEPRESSIVE DISORDER (MDD)","emoji":"🌧️",
"good":[
"Female sex",
"Single depressive episode (not recurrent)",
"Acute onset with identifiable trigger",
"Mild-to-moderate baseline severity",
"Early improvement within first 2 weeks of treatment",
"Good social support",
"Higher education / employment",
"Absence of residual symptoms at remission",
"No childhood maltreatment history",
],
"bad":[
"Recurrent episode history",
"Residual depressive symptoms after treatment",
"Childhood maltreatment / adverse events",
"Comorbid anxiety disorder",
"Comorbid physical illness (chronic pain, etc.)",
"Comorbid personality disorder",
"High baseline symptom severity",
"Long duration of untreated depression",
"Poor social support / social isolation",
"Negative life events",
"Suicidality at presentation",
],
"bio_good":[
"Higher serum BDNF at baseline",
"BDNF Val/Val genotype",
"Low CRP / IL-6 (→ better SSRI response)",
"Normal TSH (euthyroid)",
"Normal serum folate / B12",
"Normal homocysteine",
"Rising BDNF after 2 weeks of antidepressant",
],
"bio_bad":[
"Low serum BDNF",
"Elevated CRP / IL-6 / TNF-alpha (inflammatory subtype → poor SSRI response)",
"Hypothyroidism / subclinical hypothyroidism",
"Low folate / B12",
"Elevated homocysteine",
"High polygenic risk score for MDD",
"Anterior cingulate theta activity deficit on EEG (if available)",
],
"src":"Solmi et al. 2023 (PMID 37957292); Transl Psychiatry 2019; Liberona et al. 2024 (PMID 39456983)",
},
{
"name":"OBSESSIVE-COMPULSIVE DISORDER (OCD)","emoji":"🔁",
"good":[
"Short duration of illness at presentation",
"Low symptom severity (low Y-BOCS score)",
"Good insight into obsessions",
"Later age of onset",
"Female sex",
"Early and intensive treatment",
"Long-term maintenance treatment",
"Good social support / functional family",
"Absence of comorbid personality disorder",
"Pure obsessional or checking subtype",
],
"bad":[
"Early age of onset",
"Long illness duration at presentation",
"High baseline Y-BOCS score",
"Poor insight / overvalued ideation",
"Male sex",
"Hoarding or symmetry symptom subtype",
"Contamination-washing subtype (for SRI response)",
"Comorbid schizotypal or borderline PD",
"Comorbid severe depression",
"Unemployment / single / unmarried",
"Family dysfunction / high expressed emotion",
],
"bio_good":[
"Elevated blood/CSF serotonin metabolite 5-HIAA (→ better clomipramine response)",
"Normal platelet serotonin transporter binding",
"Low CRP / inflammatory markers",
"Absence of SLC6A4 short allele (5-HTTLPR L/L genotype)",
],
"bio_bad":[
"Low 5-HIAA (serotonin metabolite) - predicts poor SRI response",
"SLC6A4 short allele (5-HTTLPR s/s or s/L genotype)",
"DRD4 receptor gene variants (dopamine D4)",
"Elevated CRP",
"Low serum BDNF",
],
"src":"Sharma et al. 2014 (PMID 25295427); Sharma & Math 2019 (PMID 30745676); Hazari et al. 2016 (PMID 27282021); Bandelow et al. 2017 (PMID 27419272)",
},
{
"name":"PTSD","emoji":"⚡",
"good":[
"Single, adult-onset trauma (no childhood trauma)",
"Female sex (for treatment response)",
"Good social support",
"Higher education",
"Strong executive control / cognitive function",
"Early initiation of trauma-focused therapy",
"Absence of comorbid depression / alcohol use",
"Single trauma type (not complex/multiple traumas)",
],
"bad":[
"Male sex",
"Childhood / early-onset trauma (complex PTSD)",
"Greater cumulative trauma exposure",
"Combat trauma history",
"Poor / absent social support",
"Comorbid depression",
"Comorbid alcohol / substance use disorder",
"Comorbid chronic pain",
"Comorbid sleep disorder",
"Older age at treatment",
"Higher anger dysregulation",
],
"bio_good":[
"Normal serum cortisol (or slightly elevated - not suppressed)",
"Normal heart rate variability (HRV) at rest",
"Normal glucocorticoid receptor sensitivity",
"Low CRP / inflammatory markers",
"Low polygenic risk score for PTSD",
],
"bio_bad":[
"Low baseline cortisol / enhanced dexamethasone suppression (HPA paradox)",
"Reduced heart rate variability (HRV) - sympathetic hypertonicity",
"Elevated CRP / IL-6",
"High polygenic risk score for PTSD",
"Elevated norepinephrine metabolites (MHPG) in urine/plasma",
],
"src":"Keyan et al. 2024 (PMID 38884956); Solmi et al. 2023 (PMID 37957292); Bandelow et al. 2016/2017 (PMID 27403679, 27419272)",
},
{
"name":"ANXIETY DISORDERS (Panic · Social Anxiety · GAD)","emoji":"😰",
"good":[
"Later / adult onset (not childhood-onset)",
"Milder baseline symptom severity",
"Good social support",
"Absence of comorbid mood disorder",
"Early treatment initiation",
"Higher education / employment",
"Limited avoidance behaviour at presentation",
"Single anxiety disorder (no comorbid anxiety)",
"Good treatment adherence",
"Absence of high neuroticism trait",
],
"bad":[
"Childhood / early onset",
"Severe baseline symptoms",
"Comorbid major depressive disorder",
"Comorbid personality disorder",
"Comorbid substance use disorder",
"Multiple comorbid anxiety disorders",
"Entrenched avoidance behaviours",
"High neuroticism trait",
"Poor social support",
"Long duration of untreated disorder",
],
"bio_good":[
"Normal morning cortisol / normal diurnal pattern",
"Normal HRV at rest",
"Normal serum oxytocin (social anxiety)",
"Low CRP / IL-6",
"5-HTTLPR L/L genotype (lower anxiety vulnerability)",
],
"bio_bad":[
"Elevated morning cortisol / HPA dysregulation",
"Reduced HRV (autonomic hyperarousal)",
"Low serum oxytocin (social anxiety disorder)",
"Elevated CRP / IL-6",
"5-HTTLPR s/s genotype (anxiety vulnerability)",
"Error-related negativity (ERN) elevation on EEG - GAD severity marker",
],
"src":"Solmi et al. 2023 (PMID 37957292); Wang et al. 2018 (PMID 29843662); Bandelow et al. 2017 (PMID 27419272)",
},
]
transdiag_good = [
"Female sex",
"Higher education",
"Social contacts and employment",
"Early access to and initiation of treatment",
"Absence of negative life events",
"Lower disease-specific symptom burden at baseline",
"Better quality of life / functioning at baseline",
"Absence of comorbid mental or physical disorders",
"Good treatment adherence",
]
transdiag_bad = [
"Male sex",
"Younger age at onset",
"Higher disease-specific symptom burden",
"Higher depressive or anxiety symptoms (comorbid)",
"Greater global illness severity",
"More prior episodes / relapses",
"Negative life events",
"Social isolation / unemployment",
"Comorbid mental or physical disorders",
]
transdiag_bio_good = [
"Higher serum BDNF (favours Rx response across mood/psychosis)",
"BDNF Val/Val genotype",
"Low CRP / IL-6 (low inflammatory burden)",
"Normal HPA axis cortisol pattern",
"Normal TSH (euthyroid)",
"Normal folate / B12 / homocysteine",
]
transdiag_bio_bad = [
"Low serum BDNF",
"Elevated CRP / IL-6 / TNF-alpha",
"HPA axis dysregulation (elevated or suppressed cortisol)",
"Hypothyroidism",
"Low folate / B12 / elevated homocysteine",
"High polygenic risk scores (disorder-specific)",
]
network_rows = [
{
"network":"SN Hijack\n(Salience Network Hyperactivation)",
"color":SN_COL,"light":SN_LIGHT,
"anchors":"Anterior insula + dorsal ACC",
"disorders":"PTSD, GAD, Panic disorder, Schizophrenia (aberrant salience)",
"signs":[
"Exaggerated startle response",
"Dilated pupils / tachycardia on exam",
"Hypervigilant environmental scanning",
"Freezing or flinching to neutral stimuli",
"Intrusive re-experiencing episodes (PTSD context)",
"Misattribution of threat to neutral stimuli (schizophrenia)",
],
"reports":[
'"Everything feels like an emergency."',
'"My heart races for no reason."',
'"I keep thinking that stranger is threatening me."',
'"I can\'t relax even when I know I\'m safe."',
],
"note":"Distinguish from LC-NE hyperarousal (see below). In schizophrenia, SN hijack = aberrant salience: neutral stimuli feel threatening or significant.",
"src":"Menon 2011 (Trends Cogn Sci); Menon et al. 2023 (PMID 36702660)",
},
{
"network":"DMN Hyperactivity\n(Default Mode Network Overengagement)",
"color":DMN_COL,"light":DMN_LIGHT,
"anchors":"mPFC + posterior cingulate cortex + angular gyrus",
"disorders":"MDD, GAD, Rumination-predominant OCD, Bipolar depression",
"signs":[
"Internally preoccupied; quickly returns to internal train of thought when distracted",
"Delayed response to questions (attentional capture by internal narrative)",
"Excessive self-referential speech in interview",
"Flat affect but preserved fluency about self-related worries",
],
"reports":[
'"I can\'t turn off the \'What If\' movie in my head."',
'"I keep replaying what went wrong."',
'"Everything bad always comes back to something I did."',
'"My mind keeps drifting back to worrying even when I try to focus."',
],
"note":"NOTE: Vacant stare + slow responses = psychomotor retardation or dissociation, NOT DMN hyperactivity. DMN over-engagement is internal busyness, not blankness.",
"src":"Buckner et al. 2008 (Ann NY Acad Sci); Mulders et al. 2015 (Neurosci Biobehav Rev)",
},
{
"network":"ECN Dysfunction\n(Executive Control Network Failure)",
"color":ECN_COL,"light":ECN_LIGHT,
"anchors":"Dorsolateral PFC + lateral parietal cortex",
"disorders":"OCD, Schizophrenia, ADHD, Bipolar disorder, Severe MDD",
"signs":[
"[OCD] Repeating a task already completed (inhibitory override failure)",
"[Cognitive] Cannot hold two concepts in mind simultaneously (WM failure)",
"[Rigidity] Gets stuck on one conversational topic; cannot task-switch",
"[Impulsivity] Acts before thinking; poor prepotent response inhibition",
"[Disorganisation] Tangential or disorganised speech (schizophrenia subtype)",
],
"reports":[
'"I know it\'s clean, but I can\'t stop scrubbing." (OCD)',
'"I started three things and can\'t finish any of them."',
'"I keep losing my train of thought mid-sentence."',
'"I say things before I realise I shouldn\'t."',
],
"note":"Subtypes differ by disorder: inhibitory failure (OCD), working memory failure (ADHD/schizophrenia), cognitive rigidity (schizophrenia/severe MDD), impulsivity (mania/ADHD).",
"src":"Menon 2011 (Trends Cogn Sci); Menon et al. 2023 (PMID 36702660)",
},
{
"network":"LC-NE Hyperarousal\n(Neuromodulatory Modifier - not a 4th network)",
"color":NE_COL,"light":NE_LIGHT,
"anchors":"Locus coeruleus → norepinephrine release across cortex",
"disorders":"PTSD, Panic disorder, Mania, Stimulant intoxication / withdrawal",
"signs":[
"Motor restlessness / fidgeting / inability to sit still",
"Pressured or rapid-fire speech",
"Psychomotor agitation",
"Insomnia despite clear exhaustion",
"Exaggerated startle (overlaps SN - distinguish by autonomic prominence)",
],
"reports":[
'"I feel \'wired\' and \'stuck\' at the same time."',
'"I\'m exhausted but I can\'t slow down."',
'"My body is on high alert even when my mind knows it\'s fine."',
'"I haven\'t slept in days but I\'m not tired."',
],
"note":"LC-NE is a neuromodulatory system, not a resting-state network. It amplifies SN sensitivity. Present as a modifier of SN dysfunction, not a standalone network.",
"src":"Aston-Jones & Cohen 2005 (Annu Rev Neurosci); Sara 2009 (Nat Rev Neurosci)",
},
]
interp_rows = [
["Pattern observed","Likely network failure","Common disorder context"],
["≥3 SN signs + emergency report","SN Hijack (primary)","PTSD, Panic, GAD, Schizophrenia"],
["≥3 DMN signs + ruminative reports","DMN Hyperactivity (primary)","MDD, GAD, Rumination OCD"],
["≥3 ECN signs across subtypes","ECN Dysfunction (primary)","OCD, Schizophrenia, ADHD, Bipolar"],
["≥2 SN + ≥2 LC-NE signs","SN Hijack + NE Amplification","PTSD, Mania, Stimulant use"],
["≥2 DMN + ≥2 ECN signs","DMN-ECN failure","MDD with cognitive dysfunction, Bipolar depression"],
["All 3 networks + LC-NE","Global network dysregulation","Severe MDD, Psychosis, Complex PTSD"],
]
refs = [
"1. Solmi M, et al. (2023). Umbrella review of candidate predictors across mental disorders. Mol Psychiatry. PMID 37957292",
"2. Molstrom IM, et al. (2022). Prognosis of schizophrenia - 20-year meta-analysis. Schizophr Res. PMID 36417817",
"3. Treuer T & Tohen M. (2010). Predicting course and outcome of bipolar disorder. Eur Psychiatry. PMID 20444581",
"4. Scott K, et al. (2025). Treatment outcome prediction in bipolar disorder. Biol Psychiatry. PMID 39954922",
"5. Sharma E, Thennarasu K, Reddy YCJ. (2014). Long-term outcome of OCD - meta-analysis. J Clin Psychiatry. PMID 25295427",
"6. Sharma E & Math SB. (2019). Course and outcome of OCD. Indian J Psychiatry. PMID 30745676",
"7. Hazari N, et al. (2016). Predictors of SRI response in OCD. Expert Rev Neurother. PMID 27282021",
"8. Keyan D, et al. (2024). Predictors of trauma-focused psychotherapy response in PTSD. Psychol Bull. PMID 38884956",
"9. Wang J, et al. (2018). Loneliness, social support and outcomes in mental health. BMC Psychiatry. PMID 29843662",
"10. Liberona A, et al. (2024). BDNF as predictor of treatment response in schizophrenia & bipolar. Int J Mol Sci. PMID 39456983",
"11. Bandelow B, et al. (2016/2017). Biological markers for anxiety disorders, OCD & PTSD - Parts I & II. World J Biol Psychiatry. PMID 27403679, 27419272",
"12. Menon V. (2011). Large-scale brain networks and psychopathology: triple network model. Trends Cogn Sci. 15(10):483-506",
"13. Menon V, Palaniyappan L, Supekar K. (2023). Integrative brain network & salience models in schizophrenia. Biol Psychiatry. PMID 36702660",
]
# ═══════════════════════════════════════════════════════════════════════════════
# BUILD
# ═══════════════════════════════════════════════════════════════════════════════
doc = SimpleDocTemplate(OUTPUT, pagesize=A4,
topMargin=3.2*cm, bottomMargin=1.6*cm,
leftMargin=1.5*cm, rightMargin=1.5*cm,
title="Psychiatric Clinical Tool - Final",
author="Orris AI")
story = []
# ── TITLE ──
tb = Table([
[Paragraph("Psychiatric Clinical Assessment Tool", title_s)],
[Paragraph("Prognostic Factors · Routine Biomarkers · Triple Network Screening", sub_s)],
[Paragraph("Evidence-based | Adult Psychiatric Disorders | Clinical use only", sub_s)],
], colWidths=[18.5*cm])
tb.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),DARK_NAVY),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),8),
]))
story.append(tb)
story.append(Spacer(1,0.25*cm))
# ── HOW TO USE ──
how = Table([[Paragraph(
"<b>HOW TO USE:</b> Tick applicable clinical factors (☐) and note relevant biomarkers (◇) for each patient. "
"Biomarkers listed are all obtainable from <b>routine blood tests, EEG, or genetics</b> - fMRI and research-only markers have been removed. "
"The Triple Network tool (Part 3) uses observable signs and patient reports to identify predominant network dysfunction. "
"This is a clinical aide - it does not replace clinical or diagnostic judgement.",
note_s)]], colWidths=[18.5*cm])
how.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#fffbe6")),
("BOX",(0,0),(-1,-1),0.5,colors.HexColor("#e0c050")),
("TOPPADDING",(0,0),(-1,-1),6),("BOTTOMPADDING",(0,0),(-1,-1),6),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
]))
story.append(how)
story.append(Spacer(1,0.3*cm))
# ════════════════════════════════════════════════
# PART 1 + 2: PROGNOSTIC CHECKLIST + BIOMARKERS
# ════════════════════════════════════════════════
part1_hdr = Table([[Paragraph(
" PART 1 & 2 · Prognostic Factors + Routine Biomarkers", sec_s)]],
colWidths=[18.5*cm])
part1_hdr.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#1a3a6b")),
("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
story.append(part1_hdr)
story.append(Spacer(1,0.2*cm))
# ── Transdiagnostic section ──
td_hdr = Table([[Paragraph(" 🌐 TRANSDIAGNOSTIC FACTORS (apply across ALL disorders)", sec_s)]],
colWidths=[18.5*cm])
td_hdr.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#4a235a")),
("TOPPADDING",(0,0),(-1,-1),6),("BOTTOMPADDING",(0,0),(-1,-1),6),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.5,colors.HexColor("#2d1440")),
]))
story.append(td_hdr)
# TD clinical cols
tc = Table([[Paragraph("✅ GOOD", col_good), Paragraph("⚠️ POOR", col_bad)]],
colWidths=[9.25*cm,9.25*cm])
tc.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0),GOOD_GREEN),("BACKGROUND",(1,0),(1,0),BAD_RED),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),4),
]))
story.append(tc)
max_td = max(len(transdiag_good),len(transdiag_bad))
td_data=[]
for i in range(max_td):
g = cb(transdiag_good[i]) if i<len(transdiag_good) else Paragraph("",item_s)
b = cb(transdiag_bad[i]) if i<len(transdiag_bad) else Paragraph("",item_s)
td_data.append([g,b])
td_t = Table(td_data, colWidths=[9.25*cm,9.25*cm])
ts = TableStyle([
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
("LINEAFTER",(0,0),(0,-1),0.5,MID_GREY),
("BOX",(0,0),(-1,-1),0.5,MID_GREY),
])
for i in range(max_td):
ts.add("BACKGROUND",(0,i),(0,i),colors.HexColor("#f2fbf2") if i%2==0 else colors.HexColor("#e8f8e8"))
ts.add("BACKGROUND",(1,i),(1,i),colors.HexColor("#fef5f5") if i%2==0 else colors.HexColor("#fdeaea"))
td_t.setStyle(ts)
story.append(td_t)
# TD biomarker
tb2_hdr = Table([[Paragraph(" 🔬 TRANSDIAGNOSTIC BIOMARKERS (routine blood / genetics)",
PS("TB2H", fontSize=9, fontName="Helvetica-Bold", textColor=WHITE))]],
colWidths=[18.5*cm])
tb2_hdr.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),BIO_HDR),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
story.append(tb2_hdr)
tb2c = Table([[Paragraph("🔵 FAVOURABLE", bio_good), Paragraph("🔴 UNFAVOURABLE", bio_bad)]],
colWidths=[9.25*cm,9.25*cm])
tb2c.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0),BIO_GOOD_BG),("BACKGROUND",(1,0),(1,0),BIO_BAD_BG),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),4),
]))
story.append(tb2c)
max_tdb = max(len(transdiag_bio_good),len(transdiag_bio_bad))
tbd=[]
for i in range(max_tdb):
g = di(transdiag_bio_good[i]) if i<len(transdiag_bio_good) else Paragraph("",bio_s)
b = di(transdiag_bio_bad[i]) if i<len(transdiag_bio_bad) else Paragraph("",bio_s)
tbd.append([g,b])
tbd_t = Table(tbd, colWidths=[9.25*cm,9.25*cm])
ts2 = TableStyle([
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
("LINEAFTER",(0,0),(0,-1),0.5,MID_GREY),
("BOX",(0,0),(-1,-1),0.5,MID_GREY),
])
for i in range(max_tdb):
ts2.add("BACKGROUND",(0,i),(0,i),colors.HexColor("#eaf4fb") if i%2==0 else colors.HexColor("#dceef8"))
ts2.add("BACKGROUND",(1,i),(1,i),colors.HexColor("#fef0e0") if i%2==0 else colors.HexColor("#fde5c8"))
tbd_t.setStyle(ts2)
story.append(tbd_t)
src_td = Table([[Paragraph("Source: Solmi et al. 2023 (PMID 37957292); Liberona et al. 2024 (PMID 39456983)", src_s)]],
colWidths=[18.5*cm])
src_td.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#f0f0f0")),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
]))
story.append(src_td)
story.append(Spacer(1,0.4*cm))
# ── Per-disorder sections ──
def build_disorder(d):
els=[]
# Header
hdr = Table([[Paragraph(f" {d['emoji']} {d['name']}", sec_s)]],colWidths=[18.5*cm])
hdr.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),HEADER_BG),
("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.5,DARK_NAVY),
]))
els.append(hdr)
# Clinical col headers
ch = Table([[Paragraph("✅ GOOD PROGNOSTIC FACTORS",col_good),Paragraph("⚠️ POOR PROGNOSTIC FACTORS",col_bad)]],
colWidths=[9.25*cm,9.25*cm])
ch.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0),GOOD_GREEN),("BACKGROUND",(1,0),(1,0),BAD_RED),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),4),
("LINEBELOW",(0,0),(-1,-1),0.5,MID_GREY),
]))
els.append(ch)
# Clinical rows
ml = max(len(d["good"]),len(d["bad"]))
rows=[]
for i in range(ml):
g = cb(d["good"][i]) if i<len(d["good"]) else Paragraph("",item_s)
b = cb(d["bad"][i]) if i<len(d["bad"]) else Paragraph("",item_s)
rows.append([g,b])
ct = Table(rows,colWidths=[9.25*cm,9.25*cm])
ts3=TableStyle([
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
("LINEAFTER",(0,0),(0,-1),0.5,MID_GREY),
("BOX",(0,0),(-1,-1),0.5,MID_GREY),
])
for i in range(ml):
ts3.add("BACKGROUND",(0,i),(0,i),colors.HexColor("#f2fbf2") if i%2==0 else colors.HexColor("#e8f8e8"))
ts3.add("BACKGROUND",(1,i),(1,i),colors.HexColor("#fef5f5") if i%2==0 else colors.HexColor("#fdeaea"))
ct.setStyle(ts3)
els.append(ct)
# Biomarker header
bh = Table([[Paragraph(" 🔬 ROUTINE BIOMARKERS (blood / EEG / genetics)",
PS(f"BH{d['name'][:3]}", fontSize=9, fontName="Helvetica-Bold", textColor=WHITE))]],
colWidths=[18.5*cm])
bh.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),BIO_HDR),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
els.append(bh)
bch = Table([[Paragraph("🔵 FAVOURABLE PROFILE",bio_good),Paragraph("🔴 UNFAVOURABLE PROFILE",bio_bad)]],
colWidths=[9.25*cm,9.25*cm])
bch.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0),BIO_GOOD_BG),("BACKGROUND",(1,0),(1,0),BIO_BAD_BG),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),4),
("LINEBELOW",(0,0),(-1,-1),0.5,MID_GREY),
]))
els.append(bch)
mb = max(len(d["bio_good"]),len(d["bio_bad"]))
brows=[]
for i in range(mb):
g = di(d["bio_good"][i]) if i<len(d["bio_good"]) else Paragraph("",bio_s)
b = di(d["bio_bad"][i]) if i<len(d["bio_bad"]) else Paragraph("",bio_s)
brows.append([g,b])
bt = Table(brows,colWidths=[9.25*cm,9.25*cm])
ts4=TableStyle([
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
("LINEAFTER",(0,0),(0,-1),0.5,MID_GREY),
("BOX",(0,0),(-1,-1),0.5,MID_GREY),
])
for i in range(mb):
ts4.add("BACKGROUND",(0,i),(0,i),colors.HexColor("#eaf4fb") if i%2==0 else colors.HexColor("#dceef8"))
ts4.add("BACKGROUND",(1,i),(1,i),colors.HexColor("#fef0e0") if i%2==0 else colors.HexColor("#fde5c8"))
bt.setStyle(ts4)
els.append(bt)
# Source strip
st = Table([[Paragraph(f"Source: {d['src']}", src_s)]],colWidths=[18.5*cm])
st.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#f0f0f0")),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
]))
els.append(st)
els.append(Spacer(1,0.35*cm))
return els
for d in disorders:
story.extend(build_disorder(d))
# ════════════════════════════════════════════════
# PART 3: TRIPLE NETWORK SCREENING (SECTION 2)
# ════════════════════════════════════════════════
story.append(PageBreak())
part3_hdr = Table([[Paragraph(
" PART 3 · Triple Network Clinical Screening Tool", sec_s)]],
colWidths=[18.5*cm])
part3_hdr.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#1a3a6b")),
("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
story.append(part3_hdr)
story.append(Spacer(1,0.2*cm))
# Background box
bg_box = Table([[Paragraph(
"<b>TRIPLE NETWORK MODEL (Menon, 2011):</b> Three large-scale brain networks govern cognition and emotional regulation. "
"The <b>Salience Network (SN)</b> - insula + dACC - detects and filters threats and switches between the other two networks. "
"The <b>Default Mode Network (DMN)</b> - mPFC + PCC - drives self-referential, internally focused thought. "
"The <b>Executive Control Network (ECN)</b> - dlPFC + lateral PPC - governs working memory, cognitive flexibility, and inhibitory control. "
"Dysfunction in these networks and their interactions underlies cognitive and emotional abnormalities across major psychiatric disorders.",
note_s)]], colWidths=[18.5*cm])
bg_box.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#eaf4fb")),
("BOX",(0,0),(-1,-1),0.5,BIO_HDR),
("TOPPADDING",(0,0),(-1,-1),6),("BOTTOMPADDING",(0,0),(-1,-1),6),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
]))
story.append(bg_box)
story.append(Spacer(1,0.2*cm))
how2 = Table([[Paragraph(
"<b>HOW TO USE:</b> During the clinical interview, tick (☐) observable signs and note matching patient reports. "
"A consistent pattern within one domain suggests that network is clinically prominent. "
"Multiple network involvement is common in severe illness.",
note_s)]], colWidths=[18.5*cm])
how2.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#fffbe6")),
("BOX",(0,0),(-1,-1),0.5,colors.HexColor("#e0c050")),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
]))
story.append(how2)
story.append(Spacer(1,0.25*cm))
for net in network_rows:
# Network header
nh = Table([[Paragraph(net["network"], net_lbl)]],colWidths=[18.5*cm])
nh.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),net["color"]),
("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.5,DARK_NAVY),
]))
story.append(nh)
# Anchors + disorders
meta = Table([[
Paragraph(f"<b>Brain anchors:</b> {net['anchors']}", net_small),
Paragraph(f"<b>Disorders:</b> {net['disorders']}", net_small),
]], colWidths=[6.5*cm,12*cm])
meta.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),net["light"]),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
]))
story.append(meta)
# Column headers
nch = Table([[
Paragraph(f"☐ OBSERVABLE SIGNS (clinician)", PS(f"NSH{net['color']}",
fontSize=8.5, fontName="Helvetica-Bold", textColor=net["color"])),
Paragraph("💬 PATIENT REPORTS (verbatim prompts)", PS("NPH",
fontSize=8.5, fontName="Helvetica-Bold", textColor=DARK_NAVY)),
]], colWidths=[9.25*cm,9.25*cm])
nch.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0),net["light"]),
("BACKGROUND",(1,0),(1,0),colors.HexColor("#f5f5f5")),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),5),
("LINEAFTER",(0,0),(0,0),0.5,MID_GREY),
("LINEBELOW",(0,0),(-1,-1),0.5,MID_GREY),
]))
story.append(nch)
# Sign/report rows
maxr = max(len(net["signs"]),len(net["reports"]))
nrows=[]
for i in range(maxr):
s = Paragraph(f"☐ {net['signs'][i]}", item_s) if i<len(net["signs"]) else Paragraph("",item_s)
r = Paragraph(net["reports"][i], italic_s) if i<len(net["reports"])else Paragraph("",italic_s)
nrows.append([s,r])
nt = Table(nrows,colWidths=[9.25*cm,9.25*cm])
nts=TableStyle([
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("VALIGN",(0,0),(-1,-1),"TOP"),
("LINEAFTER",(0,0),(0,-1),0.5,MID_GREY),
("BOX",(0,0),(-1,-1),0.5,MID_GREY),
])
for i in range(maxr):
nts.add("BACKGROUND",(0,i),(0,i),net["light"] if i%2==0 else colors.HexColor("#fafafa"))
nts.add("BACKGROUND",(1,i),(1,i),colors.HexColor("#f9f9f9") if i%2==0 else WHITE)
nt.setStyle(nts)
story.append(nt)
# Note strip
note_row = Table([[
Paragraph(f"<i>{net['note']}</i>", warn_s),
Paragraph(f"Ref: {net['src']}", src_s),
]], colWidths=[11*cm,7.5*cm])
note_row.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#f5f5f5")),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
("VALIGN",(0,0),(-1,-1),"TOP"),
]))
story.append(note_row)
story.append(Spacer(1,0.3*cm))
# ── Interpretation table ──
story.append(sec_bar("INTERPRETATION GUIDE"))
story.append(Spacer(1,0.15*cm))
interp_t = Table(interp_rows, colWidths=[6*cm,6*cm,6.5*cm])
its=TableStyle([
("BACKGROUND",(0,0),(-1,0),DARK_NAVY),
("TEXTCOLOR",(0,0),(-1,0),WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),8),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
("GRID",(0,0),(-1,-1),0.3,MID_GREY),
("VALIGN",(0,0),(-1,-1),"TOP"),
])
for i in range(1,len(interp_rows)):
its.add("BACKGROUND",(0,i),(-1,i),colors.HexColor("#f5f5f5") if i%2==0 else WHITE)
interp_t.setStyle(its)
story.append(interp_t)
story.append(Spacer(1,0.2*cm))
# Limitations
lim = Table([[Paragraph(
"⚠️ <b>LIMITATIONS:</b> Network dysfunction cannot be confirmed from clinical observation alone - neuroimaging is required for definitive characterisation. "
"This tool prompts network-informed clinical reasoning and focused questioning. It has not been validated as a psychometric instrument. "
"Multiple network involvement is the rule, not the exception, in severe psychiatric illness.",
warn_s)]], colWidths=[18.5*cm])
lim.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#fdf0e0")),
("BOX",(0,0),(-1,-1),0.5,colors.HexColor("#c07030")),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
]))
story.append(lim)
story.append(Spacer(1,0.3*cm))
# ── NOTES PAGE ──
story.append(PageBreak())
nh2 = Table([[Paragraph(" 📋 CLINICAL NOTES", sec_s)]],colWidths=[18.5*cm])
nh2.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),HEADER_BG),
("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
story.append(nh2)
story.append(Spacer(1,0.3*cm))
for _ in range(22):
story.append(Paragraph("", item_s))
story.append(HRFlowable(width="100%", thickness=0.4, color=MID_GREY))
story.append(Spacer(1,0.45*cm))
# ── REFERENCES ──
story.append(Spacer(1,0.3*cm))
rh = Table([[Paragraph(" 📚 KEY REFERENCES", sec_s)]],colWidths=[18.5*cm])
rh.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),HEADER_BG),
("TOPPADDING",(0,0),(-1,-1),6),("BOTTOMPADDING",(0,0),(-1,-1),6),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
story.append(rh)
story.append(Spacer(1,0.15*cm))
for r in refs:
story.append(Paragraph(r, src_s))
story.append(Spacer(1,0.1*cm))
doc.build(story, onFirstPage=page_bg, onLaterPages=page_bg)
print("DONE:", OUTPUT)
cd /tmp/workspace/psych-checklist && python build_final.py 2>&1
ls -lh /tmp/workspace/psych-checklist/Psychiatric_Clinical_Tool_FINAL.pdf
Psychiatric Clinical Tool FINAL
PDF Document · PDF
looks excellent, can we put each disorder on a single page or adjust each disorder on a single page in the pdf
~/psych-checklist/build_final_v2.py
"""
FINAL PDF v2 - One disorder per page
Layout per disorder page:
- Full-width header bar (disorder name)
- 3-column table: Good Prognostic | Poor Prognostic | Routine Biomarkers
- Source strip footer
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER
OUTPUT = "/tmp/workspace/psych-checklist/Psychiatric_Clinical_Tool_FINAL_v2.pdf"
# ── Palette ───────────────────────────────────────────────────────────────────
DARK_NAVY = colors.HexColor("#1a2b4a")
TEAL = colors.HexColor("#007b7f")
WHITE = colors.white
MID_GREY = colors.HexColor("#cccccc")
LIGHT_GREY = colors.HexColor("#f5f5f5")
GOOD_GREEN = colors.HexColor("#d4edda")
BAD_RED = colors.HexColor("#f8d7da")
GOOD_DARK = colors.HexColor("#155724")
BAD_DARK = colors.HexColor("#721c24")
HEADER_BG = colors.HexColor("#2c3e6b")
BIO_GOOD_BG = colors.HexColor("#d6eef7")
BIO_BAD_BG = colors.HexColor("#fde8d0") # kept for transdiag section
BIO_BG = colors.HexColor("#eaf4fb")
BIO_HDR = colors.HexColor("#1a6b8a")
BIO_TXT = colors.HexColor("#1a3a4a")
SN_COL = colors.HexColor("#c0392b"); SN_LIGHT = colors.HexColor("#fadbd8")
DMN_COL = colors.HexColor("#1a5276"); DMN_LIGHT = colors.HexColor("#d6eaf8")
ECN_COL = colors.HexColor("#1e8449"); ECN_LIGHT = colors.HexColor("#d5f5e3")
NE_COL = colors.HexColor("#7d6608"); NE_LIGHT = colors.HexColor("#fef9e7")
# ── Styles ────────────────────────────────────────────────────────────────────
SS = getSampleStyleSheet()
def PS(name, **kw):
return ParagraphStyle(name, parent=kw.pop("parent", SS["Normal"]), **kw)
title_s = PS("TS", fontSize=20, textColor=WHITE, alignment=TA_CENTER, fontName="Helvetica-Bold")
sub_s = PS("SS2", fontSize=9, textColor=colors.HexColor("#c8d8f0"), alignment=TA_CENTER, fontName="Helvetica-Oblique")
sec_s = PS("SHS", fontSize=13, textColor=WHITE, fontName="Helvetica-Bold", leftIndent=6)
dis_s = PS("DS", fontSize=14, textColor=WHITE, fontName="Helvetica-Bold", alignment=TA_CENTER)
col_hdr_g= PS("CHG", fontSize=9, textColor=GOOD_DARK, fontName="Helvetica-Bold", alignment=TA_CENTER)
col_hdr_b= PS("CHB", fontSize=9, textColor=BAD_DARK, fontName="Helvetica-Bold", alignment=TA_CENTER)
col_hdr_bio=PS("CHBio",fontSize=9,textColor=BIO_HDR, fontName="Helvetica-Bold", alignment=TA_CENTER)
item_s = PS("IS", fontSize=8, fontName="Helvetica", leading=11, leftIndent=3, textColor=colors.HexColor("#222222"))
bio_s = PS("BS", fontSize=8, fontName="Helvetica", leading=11, leftIndent=3, textColor=BIO_TXT)
src_s = PS("SRS", fontSize=6.5,fontName="Helvetica-Oblique", textColor=colors.HexColor("#555555"), leftIndent=4)
note_s = PS("NS", fontSize=8, fontName="Helvetica", leading=11, leftIndent=6, textColor=colors.HexColor("#333333"))
warn_s = PS("WS", fontSize=7.5,fontName="Helvetica-Oblique", textColor=colors.HexColor("#7b3a10"), leftIndent=4, leading=10)
italic_s = PS("ITS", fontSize=8, fontName="Helvetica-Oblique", textColor=colors.HexColor("#444444"), leading=11)
net_lbl = PS("NL", fontSize=11, fontName="Helvetica-Bold", textColor=WHITE, alignment=TA_CENTER)
net_tiny = PS("NT", fontSize=7, fontName="Helvetica", textColor=colors.HexColor("#555555"), leading=9)
td_good = PS("TDG", fontSize=9, textColor=GOOD_DARK, fontName="Helvetica-Bold", alignment=TA_CENTER)
td_bad = PS("TDB", fontSize=9, textColor=BAD_DARK, fontName="Helvetica-Bold", alignment=TA_CENTER)
td_bio = PS("TDBIO",fontSize=9, textColor=BIO_HDR, fontName="Helvetica-Bold", alignment=TA_CENTER)
# Column widths for 3-col layout
CW = 18.5*cm
C1 = 6.1*cm # Good
C2 = 6.1*cm # Bad
C3 = 6.3*cm # Biomarkers
def cb(t): return Paragraph(f"☐ {t}", item_s)
def di(t): return Paragraph(f"◇ {t}", bio_s)
def sec_bar(text, bg=HEADER_BG):
t = Table([[Paragraph(f" {text}", sec_s)]], colWidths=[CW])
t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),bg),
("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.5,DARK_NAVY),
]))
return t
def page_bg(canvas, doc):
canvas.saveState()
w, h = A4
canvas.setFillColor(DARK_NAVY)
canvas.rect(0, h-2.8*cm, w, 2.8*cm, fill=1, stroke=0)
canvas.setFillColor(TEAL)
canvas.rect(0, h-3.0*cm, w, 0.22*cm, fill=1, stroke=0)
canvas.setFillColor(colors.HexColor("#f0f0f0"))
canvas.rect(0, 0, w, 1.2*cm, fill=1, stroke=0)
canvas.setFillColor(MID_GREY)
canvas.rect(0, 1.2*cm, w, 0.05*cm, fill=1, stroke=0)
canvas.setFont("Helvetica-Oblique", 7)
canvas.setFillColor(colors.HexColor("#555555"))
canvas.drawCentredString(w/2, 0.42*cm,
"Psychiatric Clinical Assessment Tool (Final) | Clinical use only - not a standalone diagnostic instrument")
canvas.drawRightString(w-1.5*cm, 0.42*cm, f"Page {doc.page}")
canvas.restoreState()
# ═══════════════════════════════════════════════════════════════════════════════
# DATA
# ═══════════════════════════════════════════════════════════════════════════════
disorders = [
{
"name":"SCHIZOPHRENIA","emoji":"🧠",
"good":[
"Female sex",
"Later / acute onset of illness",
"Predominantly positive symptoms",
"Good premorbid functioning",
"Short duration of untreated psychosis",
"Good insight into illness",
"Strong social support network",
"Employment / higher education",
"Early and intensive treatment",
"Absence of negative symptoms",
"No comorbid substance use",
"Married / stable relationship",
],
"bad":[
"Male sex",
"Insidious / early onset",
"Predominantly negative symptoms",
"Poor premorbid functioning",
"Long duration of untreated psychosis",
"Poor insight / anosognosia",
"Social isolation",
"Unemployment / low education",
"Delayed treatment initiation",
"Prominent cognitive deficits",
"Comorbid substance use disorder",
"Family history of schizophrenia",
],
"bio":[
"🔵 Higher serum BDNF (→ antipsychotic response)",
"🔵 BDNF Val/Val genotype",
"🔵 Low CRP / IL-6",
"🔵 Normal folate & B12",
"🔵 Low homocysteine",
"🔵 Low polygenic risk score",
"─────────────────",
"🔴 Low serum BDNF",
"🔴 BDNF Met carrier",
"🔴 Elevated CRP / IL-6",
"🔴 Elevated homocysteine",
"🔴 Low folate / B12",
"🔴 High polygenic risk score",
],
"src":"Solmi et al. 2023 (PMID 37957292); Molstrom et al. 2022 (PMID 36417817); Liberona et al. 2024 (PMID 39456983)",
},
{
"name":"BIPOLAR DISORDER","emoji":"🔄",
"good":[
"Later age of onset",
"Fewer prior mood episodes",
"Good baseline psychosocial functioning",
"Long-term symptomatic remission",
"Good treatment adherence",
"Strong social / family support",
"Absence of psychotic features",
"No comorbid anxiety or substance use",
"Higher education / employment",
],
"bad":[
"Early age of onset",
"High number of prior episodes",
"Poor interepisode functioning",
"Rapid cycling course",
"Psychotic features present",
"Comorbid ADHD",
"Comorbid anxiety disorder",
"Comorbid substance use disorder",
"Comorbid personality disorder",
"Cognitive impairment",
"Poor medication adherence",
],
"bio":[
"🔵 Higher serum BDNF (→ lithium response)",
"🔵 BDNF Val/Val genotype",
"🔵 Low CRP / IL-6",
"🔵 Normal cortisol awakening response",
"🔵 Normal TSH (euthyroid)",
"🔵 Therapeutic serum lithium level",
"🔵 Low polygenic risk score",
"─────────────────",
"🔴 Low serum BDNF",
"🔴 Elevated CRP / IL-6 / TNF-alpha",
"🔴 Elevated cortisol / flattened diurnal",
"🔴 Hypothyroidism (elevated TSH)",
"🔴 Elevated homocysteine",
"🔴 High polygenic risk (schiz / MDD)",
],
"src":"Treuer & Tohen 2010 (PMID 20444581); Scott et al. 2025 (PMID 39954922); Liberona et al. 2024 (PMID 39456983)",
},
{
"name":"MAJOR DEPRESSIVE DISORDER (MDD)","emoji":"🌧️",
"good":[
"Female sex",
"Single depressive episode",
"Acute onset with identifiable trigger",
"Mild-to-moderate baseline severity",
"Early improvement in first 2 weeks",
"Good social support",
"Higher education / employment",
"No residual symptoms at remission",
"No childhood maltreatment",
],
"bad":[
"Recurrent episode history",
"Residual depressive symptoms after Rx",
"Childhood maltreatment",
"Comorbid anxiety disorder",
"Comorbid physical illness / chronic pain",
"Comorbid personality disorder",
"High baseline symptom severity",
"Long duration of untreated depression",
"Poor social support",
"Negative life events",
"Suicidality at presentation",
],
"bio":[
"🔵 Higher serum BDNF",
"🔵 BDNF Val/Val genotype",
"🔵 Low CRP / IL-6 (→ better SSRI response)",
"🔵 Normal TSH (euthyroid)",
"🔵 Normal folate / B12",
"🔵 Normal homocysteine",
"🔵 Rising BDNF after 2 weeks of Rx",
"─────────────────",
"🔴 Low serum BDNF",
"🔴 Elevated CRP / IL-6 (→ poor SSRI response)",
"🔴 Hypothyroidism",
"🔴 Low folate / B12",
"🔴 Elevated homocysteine",
"🔴 High polygenic risk score",
"🔴 ACC theta deficit on EEG",
],
"src":"Solmi et al. 2023 (PMID 37957292); Transl Psychiatry 2019; Liberona et al. 2024 (PMID 39456983)",
},
{
"name":"OCD (Obsessive-Compulsive Disorder)","emoji":"🔁",
"good":[
"Short illness duration at presentation",
"Low symptom severity (low Y-BOCS)",
"Good insight into obsessions",
"Later age of onset",
"Female sex",
"Early and intensive treatment",
"Long-term maintenance treatment",
"Good social / family support",
"No comorbid personality disorder",
"Pure obsessional or checking subtype",
],
"bad":[
"Early age of onset",
"Long illness duration at presentation",
"High baseline Y-BOCS score",
"Poor insight / overvalued ideation",
"Male sex",
"Hoarding or symmetry subtype",
"Contamination-washing subtype (SRI response)",
"Comorbid schizotypal or borderline PD",
"Comorbid severe depression",
"Unemployment / single / unmarried",
"Family dysfunction / high EE",
],
"bio":[
"🔵 Elevated blood 5-HIAA (serotonin metabolite)",
"🔵 Normal platelet 5-HTT binding",
"🔵 Low CRP",
"🔵 SLC6A4 L/L genotype (5-HTTLPR)",
"─────────────────",
"🔴 Low 5-HIAA (→ poor SRI response)",
"🔴 SLC6A4 s/s or s/L genotype",
"🔴 DRD4 receptor gene variants",
"🔴 Elevated CRP",
"🔴 Low serum BDNF",
],
"src":"Sharma et al. 2014 (PMID 25295427); Sharma & Math 2019 (PMID 30745676); Hazari et al. 2016 (PMID 27282021); Bandelow et al. 2017 (PMID 27419272)",
},
{
"name":"PTSD","emoji":"⚡",
"good":[
"Single, adult-onset trauma",
"Female sex (for treatment response)",
"Good social support",
"Higher education",
"Strong executive control / cognition",
"Early initiation of trauma-focused Rx",
"No comorbid depression / alcohol use",
"Single trauma type",
],
"bad":[
"Male sex",
"Childhood / early-onset trauma (complex PTSD)",
"Greater cumulative trauma exposure",
"Combat trauma history",
"Poor / absent social support",
"Comorbid depression",
"Comorbid alcohol / substance use",
"Comorbid chronic pain",
"Comorbid sleep disorder",
"Older age at treatment",
"Higher anger dysregulation",
],
"bio":[
"🔵 Normal serum cortisol",
"🔵 Normal heart rate variability (HRV)",
"🔵 Normal glucocorticoid receptor sensitivity",
"🔵 Low CRP / IL-6",
"🔵 Low polygenic risk score for PTSD",
"─────────────────",
"🔴 Low cortisol / enhanced dex suppression",
"🔴 Reduced HRV (sympathetic hypertonicity)",
"🔴 Elevated CRP / IL-6",
"🔴 Elevated urinary MHPG (NE metabolite)",
"🔴 High polygenic risk score for PTSD",
],
"src":"Keyan et al. 2024 (PMID 38884956); Solmi et al. 2023 (PMID 37957292); Bandelow et al. 2016/2017 (PMID 27403679, 27419272)",
},
{
"name":"ANXIETY DISORDERS (Panic · Social Anxiety · GAD)","emoji":"😰",
"good":[
"Later / adult onset",
"Milder baseline symptom severity",
"Good social support",
"No comorbid mood disorder",
"Early treatment initiation",
"Higher education / employment",
"Limited avoidance at presentation",
"Single anxiety disorder",
"Good treatment adherence",
"Absent high neuroticism trait",
],
"bad":[
"Childhood / early onset",
"Severe baseline symptoms",
"Comorbid major depressive disorder",
"Comorbid personality disorder",
"Comorbid substance use disorder",
"Multiple comorbid anxiety disorders",
"Entrenched avoidance behaviours",
"High neuroticism trait",
"Poor social support",
"Long duration untreated",
],
"bio":[
"🔵 Normal morning cortisol / diurnal pattern",
"🔵 Normal HRV at rest",
"🔵 Normal serum oxytocin (social anxiety)",
"🔵 Low CRP / IL-6",
"🔵 5-HTTLPR L/L genotype",
"─────────────────",
"🔴 Elevated morning cortisol / HPA dysreg.",
"🔴 Reduced HRV (autonomic hyperarousal)",
"🔴 Low serum oxytocin (social anxiety)",
"🔴 Elevated CRP / IL-6",
"🔴 5-HTTLPR s/s genotype",
"🔴 ERN elevation on EEG (GAD severity)",
],
"src":"Solmi et al. 2023 (PMID 37957292); Wang et al. 2018 (PMID 29843662); Bandelow et al. 2017 (PMID 27419272)",
},
]
# ── Transdiagnostic data ───────────────────────────────────────────────────────
td_good = [
"Female sex",
"Higher education",
"Social contacts and employment",
"Early access to treatment",
"Low baseline symptom burden",
"Better baseline quality of life",
"Absence of comorbid mental / physical disorders",
"Good treatment adherence",
"Absence of negative life events",
]
td_bad = [
"Male sex",
"Younger age at onset",
"Higher disease-specific symptom burden",
"Higher depressive / anxiety symptoms (comorbid)",
"Greater global illness severity",
"More prior episodes / relapses",
"Negative life events",
"Social isolation / unemployment",
"Comorbid mental or physical disorders",
]
td_bio = [
"🔵 Higher serum BDNF",
"🔵 BDNF Val/Val genotype",
"🔵 Low CRP / IL-6 / TNF-alpha",
"🔵 Normal HPA axis cortisol",
"🔵 Normal TSH (euthyroid)",
"🔵 Normal folate / B12 / homocysteine",
"─────────────────",
"🔴 Low serum BDNF",
"🔴 Elevated CRP / IL-6 / TNF-alpha",
"🔴 HPA axis dysregulation",
"🔴 Hypothyroidism",
"🔴 Low folate / B12 / elevated homocysteine",
"🔴 High disorder-specific polygenic risk score",
]
# ── Triple network data ────────────────────────────────────────────────────────
network_rows = [
{
"network":"SN Hijack\n(Salience Network Hyperactivation)",
"color":SN_COL,"light":SN_LIGHT,
"anchors":"Anterior insula + dorsal ACC",
"disorders":"PTSD, GAD, Panic, Schizophrenia",
"signs":[
"Exaggerated startle response",
"Dilated pupils / tachycardia on exam",
"Hypervigilant environmental scanning",
"Freezing / flinching to neutral stimuli",
"Intrusive re-experiencing (PTSD)",
"Misattribution of threat to neutral stimuli",
],
"reports":[
'"Everything feels like an emergency."',
'"My heart races for no reason."',
'"I keep thinking that stranger is threatening me."',
'"I can\'t relax even when I know I\'m safe."',
],
"note":"In schizophrenia: SN hijack = aberrant salience (neutral stimuli feel significant/threatening). Distinguish from LC-NE hyperarousal by autonomic prominence.",
"src":"Menon 2011 (Trends Cogn Sci); Menon et al. 2023 (PMID 36702660)",
},
{
"network":"DMN Hyperactivity\n(Default Mode Network Overengagement)",
"color":DMN_COL,"light":DMN_LIGHT,
"anchors":"mPFC + posterior cingulate cortex + angular gyrus",
"disorders":"MDD, GAD, Rumination OCD, Bipolar depression",
"signs":[
"Internally preoccupied; returns quickly to internal train of thought",
"Delayed response to questions (attentional capture)",
"Excessive self-referential speech",
"Flat affect but preserved fluency about self-related worries",
],
"reports":[
'"I can\'t turn off the \'What If\' movie."',
'"I keep replaying what went wrong."',
'"Everything bad comes back to something I did."',
'"My mind keeps drifting back to worrying."',
],
"note":"Vacant stare + slow response = psychomotor retardation or dissociation, NOT DMN hyperactivity. DMN over-engagement is internal busyness, not blankness.",
"src":"Buckner et al. 2008; Mulders et al. 2015",
},
{
"network":"ECN Dysfunction\n(Executive Control Network Failure)",
"color":ECN_COL,"light":ECN_LIGHT,
"anchors":"Dorsolateral PFC + lateral parietal cortex",
"disorders":"OCD, Schizophrenia, ADHD, Bipolar, Severe MDD",
"signs":[
"[OCD] Repeating completed task (inhibitory failure)",
"[WM] Cannot hold two concepts simultaneously",
"[Rigid] Stuck on one topic; cannot task-switch",
"[Impulsive] Acts before thinking",
"[Disorg.] Tangential / disorganised speech",
],
"reports":[
'"I know it\'s clean but I can\'t stop scrubbing."',
'"I started 3 things and can\'t finish any."',
'"I keep losing my train of thought."',
'"I say things before I realise I shouldn\'t."',
],
"note":"Subtypes: inhibitory failure (OCD), WM failure (ADHD/schizophrenia), rigidity (schizophrenia/MDD), impulsivity (mania/ADHD), disorganisation (schizophrenia).",
"src":"Menon 2011; Menon et al. 2023 (PMID 36702660)",
},
{
"network":"LC-NE Hyperarousal\n(Neuromodulatory Modifier - not a 4th network)",
"color":NE_COL,"light":NE_LIGHT,
"anchors":"Locus coeruleus → norepinephrine across cortex",
"disorders":"PTSD, Panic disorder, Mania, Stimulant use",
"signs":[
"Motor restlessness / fidgeting",
"Pressured or rapid-fire speech",
"Psychomotor agitation",
"Insomnia despite clear exhaustion",
"Exaggerated startle (distinguish by autonomic prominence)",
],
"reports":[
'"I feel \'wired\' and \'stuck\' at the same time."',
'"I\'m exhausted but I can\'t slow down."',
'"My body is on high alert."',
'"I haven\'t slept in days but I\'m not tired."',
],
"note":"LC-NE is a neuromodulatory system, not a resting-state network. It amplifies SN sensitivity. Treat as a modifier of SN dysfunction, not an independent network.",
"src":"Aston-Jones & Cohen 2005; Sara 2009 (Nat Rev Neurosci)",
},
]
interp_rows = [
["Pattern observed","Likely network failure","Disorder context"],
["≥3 SN signs + emergency report","SN Hijack","PTSD, Panic, GAD, Schizophrenia"],
["≥3 DMN signs + ruminative reports","DMN Hyperactivity","MDD, GAD, Rumination OCD"],
["≥3 ECN signs across subtypes","ECN Dysfunction","OCD, Schizophrenia, ADHD, Bipolar"],
["≥2 SN + ≥2 LC-NE signs","SN + NE Amplification","PTSD, Mania, Stimulant use"],
["≥2 DMN + ≥2 ECN signs","DMN-ECN failure","MDD with cognitive sx, Bipolar depression"],
["All 3 networks + LC-NE","Global dysregulation","Severe MDD, Psychosis, Complex PTSD"],
]
refs = [
"1. Solmi M et al. (2023). Umbrella review of predictors across mental disorders. Mol Psychiatry. PMID 37957292",
"2. Molstrom IM et al. (2022). Prognosis of schizophrenia - 20-year meta-analysis. Schizophr Res. PMID 36417817",
"3. Treuer T & Tohen M. (2010). Course and outcome of bipolar disorder. Eur Psychiatry. PMID 20444581",
"4. Scott K et al. (2025). Treatment outcome in bipolar disorder. Biol Psychiatry. PMID 39954922",
"5. Sharma E et al. (2014). Long-term outcome of OCD - meta-analysis. J Clin Psychiatry. PMID 25295427",
"6. Sharma E & Math SB. (2019). Course and outcome of OCD. Indian J Psychiatry. PMID 30745676",
"7. Hazari N et al. (2016). Predictors of SRI response in OCD. Expert Rev Neurother. PMID 27282021",
"8. Keyan D et al. (2024). Predictors of PTSD psychotherapy response. Psychol Bull. PMID 38884956",
"9. Wang J et al. (2018). Social support and outcomes in mental health. BMC Psychiatry. PMID 29843662",
"10. Liberona A et al. (2024). BDNF as predictor in schizophrenia & bipolar. Int J Mol Sci. PMID 39456983",
"11. Bandelow B et al. (2016/2017). Biological markers for anxiety, OCD & PTSD. World J Biol Psychiatry. PMID 27403679, 27419272",
"12. Menon V. (2011). Triple network model. Trends Cogn Sci. 15(10):483-506",
"13. Menon V et al. (2023). Brain network models in schizophrenia. Biol Psychiatry. PMID 36702660",
]
# ═══════════════════════════════════════════════════════════════════════════════
# HELPERS
# ═══════════════════════════════════════════════════════════════════════════════
def build_3col_rows(col_a, col_b, col_c, style_a, style_b, style_c, bg_a, bg_b, bg_c):
"""Build a 3-column alternating-row table from three item lists."""
maxr = max(len(col_a), len(col_b), len(col_c))
rows = []
for i in range(maxr):
a = Paragraph(f"☐ {col_a[i]}", style_a) if i < len(col_a) else Paragraph("", style_a)
b = Paragraph(f"☐ {col_b[i]}", style_b) if i < len(col_b) else Paragraph("", style_b)
c = Paragraph(col_c[i], style_c) if i < len(col_c) else Paragraph("", style_c)
rows.append([a, b, c])
t = Table(rows, colWidths=[C1, C2, C3])
ts = TableStyle([
("LEFTPADDING",(0,0),(-1,-1),4),("RIGHTPADDING",(0,0),(-1,-1),4),
("TOPPADDING",(0,0),(-1,-1),2.5),("BOTTOMPADDING",(0,0),(-1,-1),2.5),
("VALIGN",(0,0),(-1,-1),"TOP"),
("LINEAFTER",(0,0),(0,-1),0.4,MID_GREY),
("LINEAFTER",(1,0),(1,-1),0.4,MID_GREY),
("BOX",(0,0),(-1,-1),0.5,MID_GREY),
])
for i in range(maxr):
ts.add("BACKGROUND",(0,i),(0,i), colors.HexColor("#f2fbf2") if i%2==0 else colors.HexColor("#e8f8e8"))
ts.add("BACKGROUND",(1,i),(1,i), colors.HexColor("#fef5f5") if i%2==0 else colors.HexColor("#fdeaea"))
ts.add("BACKGROUND",(2,i),(2,i), colors.HexColor("#eaf4fb") if i%2==0 else colors.HexColor("#dceef8"))
t.setStyle(ts)
return t
def build_disorder_page(d):
els = []
# ── Full-width disorder header ──
hdr = Table([[Paragraph(f" {d['emoji']} {d['name']}", dis_s)]], colWidths=[CW])
hdr.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),HEADER_BG),
("TOPPADDING",(0,0),(-1,-1),9),("BOTTOMPADDING",(0,0),(-1,-1),9),
("LEFTPADDING",(0,0),(-1,-1),8),
("BOX",(0,0),(-1,-1),0.5,DARK_NAVY),
]))
els.append(hdr)
# ── 3-col header row ──
ch = Table([[
Paragraph("✅ GOOD PROGNOSTIC FACTORS", col_hdr_g),
Paragraph("⚠️ POOR PROGNOSTIC FACTORS", col_hdr_b),
Paragraph("🔬 ROUTINE BIOMARKERS", col_hdr_bio),
]], colWidths=[C1,C2,C3])
ch.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0),GOOD_GREEN),
("BACKGROUND",(1,0),(1,0),BAD_RED),
("BACKGROUND",(2,0),(2,0),BIO_GOOD_BG),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),4),
("LINEAFTER",(0,0),(0,0),0.4,MID_GREY),
("LINEAFTER",(1,0),(1,0),0.4,MID_GREY),
("LINEBELOW",(0,0),(-1,-1),0.4,MID_GREY),
]))
els.append(ch)
# ── 3-col body ──
els.append(build_3col_rows(
d["good"], d["bad"], d["bio"],
item_s, item_s, bio_s,
None, None, None # bgs handled inside
))
# ── Source strip ──
st = Table([[Paragraph(f"Sources: {d['src']}", src_s)]], colWidths=[CW])
st.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),LIGHT_GREY),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
]))
els.append(st)
return els
# ═══════════════════════════════════════════════════════════════════════════════
# BUILD STORY
# ═══════════════════════════════════════════════════════════════════════════════
doc = SimpleDocTemplate(OUTPUT, pagesize=A4,
topMargin=3.2*cm, bottomMargin=1.6*cm,
leftMargin=1.5*cm, rightMargin=1.5*cm,
title="Psychiatric Clinical Tool - Final v2",
author="Orris AI")
story = []
# ══ COVER / TITLE PAGE ════════════════════════════════════════════════════════
tb = Table([
[Paragraph("Psychiatric Clinical Assessment Tool", title_s)],
[Paragraph("Prognostic Factors · Routine Biomarkers · Triple Network Screening", sub_s)],
[Paragraph("Evidence-based | Adult Psychiatric Disorders | Clinical use only", sub_s)],
], colWidths=[CW])
tb.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),DARK_NAVY),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),8),
]))
story.append(tb)
story.append(Spacer(1,0.25*cm))
# How to use
how = Table([[Paragraph(
"<b>HOW TO USE:</b> Each disorder occupies one page. "
"Tick (☐) applicable clinical factors across all three columns. "
"<b>Routine biomarkers</b> (blood, EEG, genetics only - no fMRI/PET) are in the third column. "
"🔵 = favourable profile; 🔴 = unfavourable profile. "
"The Triple Network tool (Part 3) screens for predominant network dysfunction using clinical observation and patient reports.",
note_s)]], colWidths=[CW])
how.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#fffbe6")),
("BOX",(0,0),(-1,-1),0.5,colors.HexColor("#e0c050")),
("TOPPADDING",(0,0),(-1,-1),6),("BOTTOMPADDING",(0,0),(-1,-1),6),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
]))
story.append(how)
story.append(Spacer(1,0.3*cm))
# ══ TRANSDIAGNOSTIC PAGE ══════════════════════════════════════════════════════
story.append(sec_bar(" 🌐 TRANSDIAGNOSTIC FACTORS (apply across all 6 disorders)",
colors.HexColor("#4a235a")))
ch_td = Table([[
Paragraph("✅ GOOD (across disorders)", td_good),
Paragraph("⚠️ POOR (across disorders)", td_bad),
Paragraph("🔬 TRANSDIAGNOSTIC BIOMARKERS", td_bio),
]], colWidths=[C1,C2,C3])
ch_td.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0),GOOD_GREEN),
("BACKGROUND",(1,0),(1,0),BAD_RED),
("BACKGROUND",(2,0),(2,0),BIO_GOOD_BG),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),4),
("LINEAFTER",(0,0),(0,0),0.4,MID_GREY),
("LINEAFTER",(1,0),(1,0),0.4,MID_GREY),
("LINEBELOW",(0,0),(-1,-1),0.4,MID_GREY),
]))
story.append(ch_td)
story.append(build_3col_rows(td_good, td_bad, td_bio, item_s, item_s, bio_s, None, None, None))
td_src = Table([[Paragraph("Source: Solmi et al. 2023 (PMID 37957292); Liberona et al. 2024 (PMID 39456983)", src_s)]], colWidths=[CW])
td_src.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),LIGHT_GREY),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),6),("BOX",(0,0),(-1,-1),0.3,MID_GREY),
]))
story.append(td_src)
# ══ ONE PAGE PER DISORDER ══════════════════════════════════════════════════════
for d in disorders:
story.append(PageBreak())
story.extend(build_disorder_page(d))
# ══ TRIPLE NETWORK SECTION ════════════════════════════════════════════════════
story.append(PageBreak())
part3_hdr = Table([[Paragraph(" PART 3 · Triple Network Clinical Screening Tool", sec_s)]], colWidths=[CW])
part3_hdr.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#1a3a6b")),
("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
story.append(part3_hdr)
story.append(Spacer(1,0.2*cm))
bg_box = Table([[Paragraph(
"<b>TRIPLE NETWORK MODEL (Menon, 2011):</b> "
"<b>Salience Network (SN)</b> - insula + dACC - detects threats and switches between networks. "
"<b>Default Mode Network (DMN)</b> - mPFC + PCC - drives self-referential thought and rumination. "
"<b>Executive Control Network (ECN)</b> - dlPFC + lateral PPC - governs working memory, flexibility, and inhibitory control. "
"Dysfunction in these networks underlies cognitive and emotional symptoms across major psychiatric disorders.",
note_s)]], colWidths=[CW])
bg_box.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#eaf4fb")),
("BOX",(0,0),(-1,-1),0.5,BIO_HDR),
("TOPPADDING",(0,0),(-1,-1),6),("BOTTOMPADDING",(0,0),(-1,-1),6),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
]))
story.append(bg_box)
story.append(Spacer(1,0.15*cm))
how2 = Table([[Paragraph(
"<b>HOW TO USE:</b> Tick (☐) observable signs and note matching patient reports. "
"A consistent pattern within one domain suggests that network is clinically prominent. "
"Multiple network involvement is common in severe illness.",
note_s)]], colWidths=[CW])
how2.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#fffbe6")),
("BOX",(0,0),(-1,-1),0.5,colors.HexColor("#e0c050")),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
]))
story.append(how2)
story.append(Spacer(1,0.2*cm))
for net in network_rows:
nh = Table([[Paragraph(net["network"], net_lbl)]], colWidths=[CW])
nh.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),net["color"]),
("TOPPADDING",(0,0),(-1,-1),6),("BOTTOMPADDING",(0,0),(-1,-1),6),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.5,DARK_NAVY),
]))
story.append(nh)
meta = Table([[
Paragraph(f"<b>Anchors:</b> {net['anchors']}", net_tiny),
Paragraph(f"<b>Disorders:</b> {net['disorders']}", net_tiny),
]], colWidths=[6.5*cm, 12*cm])
meta.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),net["light"]),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
]))
story.append(meta)
nch = Table([[
Paragraph("☐ OBSERVABLE SIGNS",
PS(f"NSH{str(net['color'])}",fontSize=8.5,fontName="Helvetica-Bold",textColor=net["color"])),
Paragraph("💬 PATIENT REPORTS (verbatim prompts)",
PS("NPH",fontSize=8.5,fontName="Helvetica-Bold",textColor=DARK_NAVY)),
]], colWidths=[9.25*cm, 9.25*cm])
nch.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0),net["light"]),
("BACKGROUND",(1,0),(1,0),LIGHT_GREY),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),5),
("LINEAFTER",(0,0),(0,0),0.5,MID_GREY),
("LINEBELOW",(0,0),(-1,-1),0.5,MID_GREY),
]))
story.append(nch)
maxr = max(len(net["signs"]), len(net["reports"]))
nrows=[]
for i in range(maxr):
s = Paragraph(f"☐ {net['signs'][i]}", item_s) if i<len(net["signs"]) else Paragraph("",item_s)
r = Paragraph(net["reports"][i], italic_s) if i<len(net["reports"]) else Paragraph("",italic_s)
nrows.append([s,r])
nt = Table(nrows, colWidths=[9.25*cm,9.25*cm])
nts = TableStyle([
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
("TOPPADDING",(0,0),(-1,-1),2.5),("BOTTOMPADDING",(0,0),(-1,-1),2.5),
("VALIGN",(0,0),(-1,-1),"TOP"),
("LINEAFTER",(0,0),(0,-1),0.5,MID_GREY),
("BOX",(0,0),(-1,-1),0.5,MID_GREY),
])
for i in range(maxr):
nts.add("BACKGROUND",(0,i),(0,i), net["light"] if i%2==0 else colors.HexColor("#fafafa"))
nts.add("BACKGROUND",(1,i),(1,i), colors.HexColor("#f9f9f9") if i%2==0 else WHITE)
nt.setStyle(nts)
story.append(nt)
note_row = Table([[
Paragraph(f"<i>{net['note']}</i>", warn_s),
Paragraph(f"Ref: {net['src']}", src_s),
]], colWidths=[11*cm, 7.5*cm])
note_row.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),LIGHT_GREY),
("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3),
("LEFTPADDING",(0,0),(-1,-1),6),
("BOX",(0,0),(-1,-1),0.3,MID_GREY),
("VALIGN",(0,0),(-1,-1),"TOP"),
]))
story.append(note_row)
story.append(Spacer(1,0.25*cm))
# Interpretation table
story.append(sec_bar("INTERPRETATION GUIDE"))
story.append(Spacer(1,0.12*cm))
it = Table(interp_rows, colWidths=[6*cm,5.5*cm,7*cm])
its = TableStyle([
("BACKGROUND",(0,0),(-1,0),DARK_NAVY),
("TEXTCOLOR",(0,0),(-1,0),WHITE),
("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"),
("FONTSIZE",(0,0),(-1,-1),8),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
("GRID",(0,0),(-1,-1),0.3,MID_GREY),
("VALIGN",(0,0),(-1,-1),"TOP"),
])
for i in range(1,len(interp_rows)):
its.add("BACKGROUND",(0,i),(-1,i),LIGHT_GREY if i%2==0 else WHITE)
it.setStyle(its)
story.append(it)
story.append(Spacer(1,0.15*cm))
lim = Table([[Paragraph(
"⚠️ <b>LIMITATIONS:</b> Network dysfunction cannot be confirmed from clinical observation alone - neuroimaging is required for definitive characterisation. "
"This tool prompts network-informed clinical reasoning. It has not been validated as a psychometric instrument. "
"Multiple network involvement is the rule in severe illness.",
warn_s)]], colWidths=[CW])
lim.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#fdf0e0")),
("BOX",(0,0),(-1,-1),0.5,colors.HexColor("#c07030")),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
]))
story.append(lim)
# ══ NOTES PAGE ════════════════════════════════════════════════════════════════
story.append(PageBreak())
nh2 = Table([[Paragraph(" 📋 CLINICAL NOTES", sec_s)]], colWidths=[CW])
nh2.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),HEADER_BG),
("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
story.append(nh2)
story.append(Spacer(1,0.3*cm))
for _ in range(22):
story.append(Paragraph("", item_s))
story.append(HRFlowable(width="100%", thickness=0.4, color=MID_GREY))
story.append(Spacer(1,0.45*cm))
# ══ REFERENCES ════════════════════════════════════════════════════════════════
story.append(Spacer(1,0.3*cm))
rh = Table([[Paragraph(" 📚 REFERENCES", sec_s)]], colWidths=[CW])
rh.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),HEADER_BG),
("TOPPADDING",(0,0),(-1,-1),6),("BOTTOMPADDING",(0,0),(-1,-1),6),
("LEFTPADDING",(0,0),(-1,-1),6),
]))
story.append(rh)
story.append(Spacer(1,0.15*cm))
for r in refs:
story.append(Paragraph(r, src_s))
story.append(Spacer(1,0.1*cm))
doc.build(story, onFirstPage=page_bg, onLaterPages=page_bg)
print("DONE:", OUTPUT)
cd /tmp/workspace/psych-checklist && python build_final_v2.py 2>&1
td_good, td_bad, td_bio are lists but also used as style names in Paragraph(). Quick fix:~/psych-checklist/build_final_v2.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/psych-checklist/build_final_v2.py'
~/psych-checklist/build_final_v2.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/psych-checklist/build_final_v2.py'
cd /tmp/workspace/psych-checklist && python build_final_v2.py 2>&1
ls -lh /tmp/workspace/psych-checklist/Psychiatric_Clinical_Tool_FINAL_v2.pdf
Psychiatric Clinical Tool FINAL v2
PDF Document · PDF
─────) to divide favourable from unfavourable entries, keeping everything on one page without needing a sub-header row