~/psych-reference/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame
from reportlab.pdfgen import canvas as rl_canvas
from reportlab.lib.units import inch
OUTPUT = "/tmp/workspace/psych-reference/Psychiatric_Signs_and_Symptoms_Reference.pdf"
# ─── Colour Palette ────────────────────────────────────────────────────────────
NAVY = colors.HexColor("#1a2e4a")
TEAL = colors.HexColor("#0d7377")
TEAL_LIGHT= colors.HexColor("#e8f7f7")
GOLD = colors.HexColor("#c8960c")
GREY_DARK = colors.HexColor("#333333")
GREY_MID = colors.HexColor("#666666")
GREY_LIGHT= colors.HexColor("#f4f6f8")
WHITE = colors.white
RED_SOFT = colors.HexColor("#c0392b")
PURPLE = colors.HexColor("#6c3483")
PURPLE_LIGHT = colors.HexColor("#f0eaf8")
PAGE_W, PAGE_H = A4
# ─── Header / Footer ───────────────────────────────────────────────────────────
def on_page(canvas, doc):
canvas.saveState()
# Top bar
canvas.setFillColor(NAVY)
canvas.rect(0, PAGE_H - 28*mm, PAGE_W, 28*mm, fill=1, stroke=0)
canvas.setFillColor(GOLD)
canvas.rect(0, PAGE_H - 30*mm, PAGE_W, 2*mm, fill=1, stroke=0)
# Header text
canvas.setFont("Helvetica-Bold", 11)
canvas.setFillColor(WHITE)
canvas.drawString(18*mm, PAGE_H - 16*mm, "PSYCHIATRIC SIGNS & SYMPTOMS")
canvas.setFont("Helvetica", 8)
canvas.setFillColor(GOLD)
canvas.drawRightString(PAGE_W - 18*mm, PAGE_H - 16*mm, "Clinical Quick Reference | Kaplan & Sadock")
# Bottom bar
canvas.setFillColor(NAVY)
canvas.rect(0, 0, PAGE_W, 12*mm, fill=1, stroke=0)
canvas.setFillColor(GOLD)
canvas.rect(0, 12*mm, PAGE_W, 0.8*mm, fill=1, stroke=0)
canvas.setFont("Helvetica", 7.5)
canvas.setFillColor(WHITE)
canvas.drawString(18*mm, 4*mm, "For clinical reference only. Based on Kaplan & Sadock's Synopsis of Psychiatry, 12th Ed.")
canvas.setFillColor(GOLD)
canvas.drawRightString(PAGE_W - 18*mm, 4*mm, f"Page {doc.page}")
canvas.restoreState()
def on_first_page(canvas, doc):
canvas.saveState()
# Full-page gradient background
canvas.setFillColor(NAVY)
canvas.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
# Gold accent bar left
canvas.setFillColor(GOLD)
canvas.rect(0, 0, 8*mm, PAGE_H, fill=1, stroke=0)
# Teal accent bar
canvas.setFillColor(TEAL)
canvas.rect(8*mm, 0, 4*mm, PAGE_H, fill=1, stroke=0)
# White content area
canvas.setFillColor(WHITE)
canvas.roundRect(22*mm, 20*mm, PAGE_W - 40*mm, PAGE_H - 40*mm, 6*mm, fill=1, stroke=0)
# Title block
canvas.setFillColor(NAVY)
canvas.roundRect(30*mm, PAGE_H - 90*mm, PAGE_W - 56*mm, 55*mm, 4*mm, fill=1, stroke=0)
canvas.setFont("Helvetica-Bold", 24)
canvas.setFillColor(WHITE)
canvas.drawCentredString(PAGE_W/2, PAGE_H - 55*mm, "PSYCHIATRIC SIGNS")
canvas.drawCentredString(PAGE_W/2, PAGE_H - 70*mm, "& SYMPTOMS")
canvas.setFont("Helvetica", 11)
canvas.setFillColor(GOLD)
canvas.drawCentredString(PAGE_W/2, PAGE_H - 82*mm, "Clinical Quick Reference Guide")
# Subtitle line
canvas.setFillColor(GOLD)
canvas.rect(50*mm, PAGE_H - 97*mm, PAGE_W - 96*mm, 1*mm, fill=1, stroke=0)
# Source
canvas.setFont("Helvetica-Oblique", 9)
canvas.setFillColor(GREY_MID)
canvas.drawCentredString(PAGE_W/2, PAGE_H - 106*mm, "Based on Kaplan & Sadock's Synopsis of Psychiatry, 12th Edition")
# Domain list
domains = [
"Consciousness & Orientation", "Attention & Concentration",
"Mood & Affect", "Perception (Hallucinations & Illusions)",
"Thought Form & Content", "Memory",
"Language & Speech", "Motor Behavior",
"Self-Perception", "Insight & Judgment",
"Negative Signs of Schizophrenia","Quick Reference Table",
]
canvas.setFont("Helvetica-Bold", 8.5)
canvas.setFillColor(TEAL)
canvas.drawCentredString(PAGE_W/2, PAGE_H - 120*mm, "DOMAINS COVERED")
canvas.setFillColor(NAVY)
canvas.rect(55*mm, PAGE_H - 122.5*mm, PAGE_W - 106*mm, 0.5*mm, fill=1, stroke=0)
col1 = [domains[i] for i in range(0, len(domains), 2)]
col2 = [domains[i] for i in range(1, len(domains), 2)]
y = PAGE_H - 130*mm
canvas.setFont("Helvetica", 8)
canvas.setFillColor(GREY_DARK)
for a, b in zip(col1, col2):
canvas.drawString(32*mm, y, u"\u2022 " + a)
canvas.drawString(PAGE_W/2 + 2*mm, y, u"\u2022 " + b)
y -= 7*mm
# Footer note
canvas.setFont("Helvetica-Oblique", 7.5)
canvas.setFillColor(GREY_MID)
canvas.drawCentredString(PAGE_W/2, 35*mm, "For clinical reference only. Not a substitute for professional clinical judgment.")
canvas.drawCentredString(PAGE_W/2, 29*mm, "Generated by Orris AI | August 2026")
canvas.restoreState()
# ─── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def make_styles():
s = {}
s['section'] = ParagraphStyle('section',
fontName='Helvetica-Bold', fontSize=13, textColor=WHITE,
spaceBefore=6, spaceAfter=4, leftIndent=0, leading=18)
s['term'] = ParagraphStyle('term',
fontName='Helvetica-Bold', fontSize=8.5, textColor=NAVY,
spaceBefore=2, spaceAfter=0, leading=11)
s['defn'] = ParagraphStyle('defn',
fontName='Helvetica', fontSize=8, textColor=GREY_DARK,
spaceBefore=0, spaceAfter=3, leading=11, leftIndent=6)
s['note'] = ParagraphStyle('note',
fontName='Helvetica-Oblique', fontSize=7.5, textColor=RED_SOFT,
spaceBefore=0, spaceAfter=2, leading=10, leftIndent=6)
s['body'] = ParagraphStyle('body',
fontName='Helvetica', fontSize=8.5, textColor=GREY_DARK,
spaceBefore=2, spaceAfter=2, leading=12)
s['intro'] = ParagraphStyle('intro',
fontName='Helvetica-Oblique', fontSize=8.5, textColor=GREY_MID,
spaceBefore=0, spaceAfter=6, leading=12)
s['table_hdr'] = ParagraphStyle('table_hdr',
fontName='Helvetica-Bold', fontSize=8, textColor=WHITE, leading=10)
s['table_cell'] = ParagraphStyle('table_cell',
fontName='Helvetica', fontSize=7.5, textColor=GREY_DARK, leading=10)
s['table_term'] = ParagraphStyle('table_term',
fontName='Helvetica-Bold', fontSize=7.5, textColor=NAVY, leading=10)
return s
ST = make_styles()
# ─── Helper builders ──────────────────────────────────────────────────────────
def section_header(title, color=TEAL):
tbl = Table([[Paragraph(title, ST['section'])]], colWidths=[16.6*cm])
tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), color),
('ROUNDEDCORNERS', [4, 4, 4, 4]),
('LEFTPADDING', (0,0), (-1,-1), 10),
('RIGHTPADDING', (0,0), (-1,-1), 10),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
]))
return tbl
def term_block(term, definition, clinical_note=None):
items = [
Paragraph(term, ST['term']),
Paragraph(definition, ST['defn']),
]
if clinical_note:
items.append(Paragraph(f"Clinic: {clinical_note}", ST['note']))
return KeepTogether(items)
def two_col_terms(data_list):
"""data_list = list of (term, defn) or (term, defn, note). Renders 2-column."""
rows = []
for i in range(0, len(data_list), 2):
left = data_list[i]
right = data_list[i+1] if i+1 < len(data_list) else None
lc = _make_cell(left)
rc = _make_cell(right) if right else Paragraph("", ST['body'])
rows.append([lc, rc])
col_w = 8.1*cm
tbl = Table(rows, colWidths=[col_w, col_w])
tbl.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('LEFTPADDING', (0,0), (-1,-1), 4),
('RIGHTPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 3),
('BOTTOMPADDING', (0,0), (-1,-1), 3),
('ROWBACKGROUNDS', (0,0), (-1,-1), [WHITE, GREY_LIGHT]),
('LINEBELOW', (0,0), (-1,-1), 0.3, colors.HexColor('#dddddd')),
]))
return tbl
def _make_cell(item):
if item is None:
return Paragraph("", ST['body'])
if len(item) == 2:
t, d = item
n = None
else:
t, d, n = item
parts = [Paragraph(t, ST['term']), Paragraph(d, ST['defn'])]
if n:
parts.append(Paragraph(f"Seen in: {n}", ST['note']))
from reportlab.platypus import KeepInFrame
return KeepInFrame(8.1*cm, 999, parts, mode='shrink')
def summary_table(headers, rows, col_widths):
data = [[Paragraph(h, ST['table_hdr']) for h in headers]]
for row in rows:
data.append([Paragraph(str(c), ST['table_cell']) if i>0 else Paragraph(str(c), ST['table_term'])
for i, c in enumerate(row)])
tbl = Table(data, colWidths=col_widths, repeatRows=1)
style = TableStyle([
('BACKGROUND', (0,0), (-1,0), NAVY),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, TEAL_LIGHT]),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('LINEBELOW', (0,0), (-1,-1), 0.3, colors.HexColor('#cccccc')),
('GRID', (0,0), (-1,-1), 0.3, colors.HexColor('#cccccc')),
])
tbl.setStyle(style)
return tbl
# ─── Content Sections ─────────────────────────────────────────────────────────
def build_story():
story = []
# Blank first page (cover is drawn by on_first_page)
story.append(Spacer(1, PAGE_H)) # placeholder; cover handled in canvas
# ── INTRO ──────────────────────────────────────────────────────────────────
story.append(Spacer(1, 4*mm))
story.append(Paragraph(
"This reference covers all major domains of psychiatric signs and symptoms as assessed "
"during the Mental Status Examination (MSE). Definitions are drawn from "
"<i>Kaplan & Sadock's Synopsis of Psychiatry, 12th Edition</i>. "
"Entries are organized by clinical domain for rapid bedside lookup.",
ST['intro']))
story.append(HRFlowable(width="100%", thickness=1, color=GOLD, spaceAfter=6))
# ── 1. CONSCIOUSNESS ───────────────────────────────────────────────────────
story.append(section_header("1. CONSCIOUSNESS & ORIENTATION"))
story.append(Spacer(1, 3*mm))
c_terms = [
("Clouding of consciousness",
"Any disturbance in which the person is not fully awake, alert, and oriented.",
"Delirium, dementia, cognitive disorders"),
("Confusion",
"Disturbance of consciousness with disordered orientation to time, place, or person.",
"Delirium, toxic-metabolic states"),
("Coma",
"State of profound unconsciousness from which a person cannot be roused; minimal or no response to stimuli.",
"Brain injury, metabolic (DKA, uremia), drug intoxication, severe catatonia"),
("Coma vigil",
"Coma in which the patient appears asleep but cannot be aroused. A persistent vegetative state.",
None),
("Sensorium",
"The perceptual awareness or sensory apparatus considered as a whole; sometimes used interchangeably with consciousness.",
None),
("Stupor",
"Unresponsive state from which the patient can be aroused only by vigorous, repeated stimulation.",
"Severe depression, catatonia, organic brain lesions"),
]
story.append(two_col_terms(c_terms))
story.append(Spacer(1, 5*mm))
# ── 2. ATTENTION ───────────────────────────────────────────────────────────
story.append(section_header("2. ATTENTION & CONCENTRATION"))
story.append(Spacer(1, 3*mm))
a_terms = [
("Attention",
"The amount of effort exerted in focusing on certain aspects of experience or activity. Usually impaired in anxiety and depression.",
None),
("Hypervigilance",
"Excessive attention and focus on all internal and external stimuli.",
"Delusional or paranoid states, PTSD"),
("Distractibility",
"Inability to maintain focused attention; the mind is easily drawn to unimportant stimuli.",
"Mania, ADHD, anxiety"),
("Selective inattention",
"Blocking out anxiety-provoking stimuli; a defense mechanism.",
"Anxiety disorders"),
]
story.append(two_col_terms(a_terms))
story.append(Spacer(1, 5*mm))
# ── 3. MOOD & AFFECT ───────────────────────────────────────────────────────
story.append(section_header("3. MOOD & AFFECT", color=PURPLE))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Mood</b> = pervasive, sustained <i>internal</i> feeling tone. "
"<b>Affect</b> = <i>external</i> expression of that tone.",
ST['intro']))
story.append(Spacer(1, 2*mm))
mood_terms = [
("Depression",
"Psychopathological feeling of sadness. One of the most common psychiatric symptoms.",
None),
("Elation",
"Feelings of joy, euphoria, triumph, intense self-satisfaction; pathologic when not grounded in reality.",
"Mania, substance intoxication"),
("Elevated mood",
"More cheerful than normal but not necessarily pathologic.",
None),
("Dysphoria",
"Feeling of unpleasantness or discomfort; general dissatisfaction and restlessness.",
"Depression, anxiety, dysthymia"),
("Euphoria",
"Exaggerated feeling of well-being inappropriate to real events.",
"Mania, substance intoxication, multiple sclerosis"),
("Hypomania",
"Mood qualitatively similar to mania but less intense; does not cause marked impairment.",
"Cyclothymia, bipolar II"),
("Anhedonia",
"Loss of interest or inability to experience pleasure from normally enjoyable activities.",
"Depression, schizophrenia, PTSD"),
("Grief / Mourning",
"Sadness appropriate to a real loss; includes preoccupation with the lost individual, weeping, repeated reliving of memories.",
None),
("Mood swings",
"Oscillation of emotional tone between periods of elation and depression.",
"Bipolar disorder, borderline personality"),
("Irritability",
"State of excessive and easily provoked anger or annoyance.",
"Mania, depression, organic disorders"),
]
story.append(two_col_terms(mood_terms))
story.append(Spacer(1, 3*mm))
# Affect sub-table
affect_rows = [
["Flat affect", "Absence or near-absence of emotional expression.", "Schizophrenia (negative symptom)"],
["Blunted affect", "Significant reduction in intensity of emotional expression.", "Schizophrenia, depression"],
["Constricted/Restricted affect", "Reduction in intensity, less severe than blunted.", "Depression, PTSD"],
["Labile affect", "Rapidly shifting, unstable emotional responsiveness.", "Mania, PBA, borderline personality"],
["Appropriate affect", "Emotional tone in harmony with accompanying thought/speech.", "Normal"],
["Inappropriate affect", "Emotional tone out of harmony with accompanying thought.", "Schizophrenia"],
["Apathy", "Dulled emotional tone with detachment and indifference.", "Schizophrenia, depression, frontal lobe lesions"],
["Emotional lability", "Excessive, unstable, rapidly changing emotional responses.", "Mania, PBA, borderline"],
]
story.append(summary_table(
["Affect Type", "Definition", "Seen in"],
affect_rows,
[4.5*cm, 7.5*cm, 4.6*cm]
))
story.append(Spacer(1, 5*mm))
# ── 4. PERCEPTION ──────────────────────────────────────────────────────────
story.append(section_header("4. PERCEPTION: HALLUCINATIONS & ILLUSIONS", color=TEAL))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Hallucination</b> = false perception with <i>no</i> external stimulus. "
"<b>Illusion</b> = misinterpretation of a <i>real</i> external stimulus.",
ST['intro']))
story.append(Spacer(1, 2*mm))
hall_rows = [
["Auditory", "False perception of sound, usually voices or other noises (most common hallucination in psychiatry).", "Schizophrenia, psychotic depression"],
["Visual", "False perception involving sight.", "Organic states, delirium, substance intoxication/withdrawal"],
["Olfactory", "False perception of smell.", "Temporal lobe epilepsy, schizophrenia"],
["Tactile (haptic)", "False perception of touch; e.g., formication (insects crawling).", "Cocaine/alcohol withdrawal, delirium"],
["Gustatory", "False perception of taste.", "Temporal lobe epilepsy, schizophrenia"],
["Command", "Perceived orders the patient may feel compelled to obey.", "Schizophrenia (high-risk for harm)"],
["Hypnagogic", "Occurring while falling asleep.", "Not ordinarily pathologic; narcolepsy"],
["Hypnopompic", "Occurring while awakening from sleep.", "Not ordinarily pathologic; narcolepsy"],
["Audible thoughts", "Patient's own thoughts are heard repeated by voices (thought echo).", "Schizophrenia (first-rank symptom)"],
["Autoscopy", "Seeing oneself or a double as a hallucinatory experience.", "Epilepsy, migraines, schizophrenia"],
["Lilliputian / Micropsia", "Objects perceived as smaller than real (Alice in Wonderland effect).", "Organic, substance use"],
["Macropsia", "Objects perceived as larger than real.", "Organic, substance use"],
]
story.append(summary_table(
["Type", "Definition", "Typical Context"],
hall_rows,
[3.2*cm, 8.5*cm, 4.9*cm]
))
story.append(Spacer(1, 5*mm))
# ── 5. THOUGHT ─────────────────────────────────────────────────────────────
story.append(section_header("5. THOUGHT: FORM & CONTENT", color=NAVY))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Thought form (process)</b> relates to <i>how</i> a person thinks. "
"<b>Thought content</b> relates to <i>what</i> a person thinks.",
ST['intro']))
story.append(Spacer(1, 2*mm))
form_terms = [
("Flight of ideas",
"Rapid succession of fragmentary thoughts; nearly continuous flow with rapid shifts between topics.",
"Mania"),
("Loosening of associations",
"Breakdown in the logical connection between thoughts; ideas shift without logical sequence.",
"Schizophrenia"),
("Tangentiality",
"Thinking digresses from the topic without reaching the intended goal.",
"Schizophrenia, mania"),
("Circumstantiality",
"Over-inclusive, indirect speech that eventually reaches the intended goal.",
"OCD, anxiety, mania"),
("Word salad (incoherence)",
"Incomprehensible mixture of words and phrases.",
"Severe schizophrenia"),
("Clang association",
"Grouping of words by sound (rhyming) rather than meaning.",
"Schizophrenia, mania"),
("Perseveration",
"Pathological repetition of same word, phrase, or idea in response to different questions.",
"Schizophrenia, brain damage"),
("Neologism",
"New word or phrase created by the patient, meaningful only to them.",
"Schizophrenia"),
("Echolalia",
"Psychopathological repetition of words or phrases of another person.",
"Catatonic schizophrenia"),
("Blocking",
"Sudden cessation of flow of thought before a thought is completed.",
"Schizophrenia, severe anxiety"),
("Concrete thinking",
"Thinking by actual things and events rather than abstractions; inability to generalize.",
"Schizophrenia, cognitive disorders"),
("Autistic thinking",
"Narcissistic, egocentric thinking without regard for reality; used with dereism.",
"Schizophrenia, autism"),
("Confabulation",
"Unconscious filling of memory gaps with imagined events; differentiated from lying.",
"Amnestic syndromes, Korsakoff psychosis"),
("Asyndesis",
"Language disorder combining unconnected ideas and images.",
"Schizophrenia"),
]
story.append(two_col_terms(form_terms))
story.append(Spacer(1, 4*mm))
# Thought content
story.append(Paragraph("THOUGHT CONTENT", ParagraphStyle('sub',
fontName='Helvetica-Bold', fontSize=9, textColor=NAVY, spaceBefore=4, spaceAfter=4)))
content_rows = [
["Delusion (general)", "False, fixed belief not shared by the patient's culture, resistant to reasoning.", "Schizophrenia, psychotic disorders"],
["Delusion of grandeur", "Exaggerated belief in one's own importance or identity.", "Mania, schizophrenia"],
["Delusion of persecution", "Belief of being singled out for harm or harassment.", "Schizophrenia, paranoid disorder"],
["Delusion of reference", "Belief that events/objects have direct personal significance.", "Schizophrenia, mania"],
["Delusion of control", "Belief that thoughts, feelings, or actions are controlled by an external force.", "Schizophrenia (first-rank)"],
["Erotomania", "Delusional belief that someone (often of higher status) is in love with the patient (de Clerambault).", "Schizophrenia, bipolar"],
["Mood-congruent delusion", "Delusion with content consistent with the patient's mood state.", "Psychotic depression, mania"],
["Mood-incongruent delusion", "Delusion with content not associated with the patient's mood.", "Schizophrenia"],
["Idea of reference", "Misinterpretation of events as having direct personal reference; when systematized becomes delusion.", "Schizophrenia, paranoid PD"],
["Obsession", "Persistent, unwanted, intrusive thought or impulse that cannot be eliminated by logic.", "OCD, anxiety"],
["Compulsion", "Pathological need to act on an impulse; repetitive behavior to reduce anxiety.", "OCD"],
["Suicidal ideation", "Thoughts of taking one's own life; may be passive or active.", "Depression, schizophrenia, BPD"],
["Phobia", "Persistent, unreasonable, intense fear of an object or situation.", "Phobic disorders"],
["Hypochondria", "Exaggerated health concern based on unrealistic interpretation of physical signs.", "Somatic symptom disorder"],
["Rumination", "Constant preoccupation with a single idea or theme.", "OCD, depression"],
]
story.append(summary_table(
["Thought Content", "Definition", "Seen in"],
content_rows,
[4.0*cm, 8.0*cm, 4.6*cm]
))
story.append(Spacer(1, 5*mm))
# ── 6. MEMORY ──────────────────────────────────────────────────────────────
story.append(section_header("6. MEMORY", color=colors.HexColor("#1a5276")))
story.append(Spacer(1, 3*mm))
mem_terms = [
("Amnesia",
"Partial or total inability to recall past experiences.",
"Organic brain lesions, dissociative disorders"),
("Anterograde amnesia",
"Loss of ability to form new memories after the onset of the amnestic event.",
"Korsakoff, head trauma, benzodiazepine overdose"),
("Retrograde amnesia",
"Loss of memory for events preceding the onset of amnesia.",
"Head trauma, ECT, neurological events"),
("Immediate memory",
"Recall of material within seconds after presentation.",
None),
("Recent memory",
"Recall of events over the past few days.",
"Impaired early in Alzheimer's disease"),
("Recent past memory",
"Recall of events over the past few months.",
None),
("Remote memory",
"Recall of events in the distant past; often preserved longest in dementia.",
None),
("Hypermnesia",
"Exaggerated degree of retention and recall.",
"OCD, some schizophrenia, mania"),
("Dysmnesia",
"Impaired memory.",
None),
("Confabulation",
"Unconscious filling of memory gaps with imagined events; differentiate from deliberate lying.",
"Korsakoff psychosis, amnestic syndromes"),
("Paramnesia",
"Distorted recall; memory is recognized but incorrectly placed in time.",
None),
("Retrospective falsification",
"Memory unintentionally distorted by being filtered through the person's current emotional state.",
None),
]
story.append(two_col_terms(mem_terms))
story.append(Spacer(1, 5*mm))
# ── 7. LANGUAGE & SPEECH ───────────────────────────────────────────────────
story.append(section_header("7. LANGUAGE & SPEECH", color=colors.HexColor("#117a65")))
story.append(Spacer(1, 3*mm))
lang_rows = [
["Aphasia", "Disturbance in comprehension or expression of language due to brain lesion.", "Stroke, TBI"],
["Motor (Broca's) aphasia", "Understanding intact but ability to speak is lost (nonfluent).", "Left frontal lobe lesion"],
["Receptive (Wernicke's) aphasia", "Loss of ability to comprehend; speech is fluent but incoherent.", "Left temporal-parietal lesion"],
["Dysphasia", "Difficulty in comprehending oral language or expressing verbal language.", "Brain lesions"],
["Dysphonia", "Difficulty in speaking or singing.", "Laryngeal, neurological"],
["Aphonia", "Loss of voice.", "Conversion disorder"],
["Mutism", "Organic or functional absence of speech.", "Catatonia, severe depression, conversion"],
["Logorrhea", "Copious, accelerated, pressured speech.", "Mania"],
["Echolalia", "Repetition of words or phrases of another person.", "Catatonic schizophrenia, autism"],
["Coprolalia", "Involuntary use of vulgar or obscene language.", "Tourette syndrome, some schizophrenia"],
["Neologism", "New, privately meaningful words created by the patient.", "Schizophrenia"],
["Acataphasia", "Disordered speech; statements incorrectly formulated.", "Schizophrenia"],
["Aculalia", "Nonsense speech with severely impaired comprehension.", "Mania, schizophrenia"],
["Dysprosody", "Loss of normal speech melody (prosody).", "Depression, Parkinson's"],
["Cluttering", "Abnormally rapid, erratic rhythm of speech.", "Developmental, organic"],
["Agrammatism", "Speech formed without regard for grammatical rules.", "Expressive aphasia"],
]
story.append(summary_table(
["Term", "Definition", "Context"],
lang_rows,
[4.0*cm, 8.5*cm, 4.1*cm]
))
story.append(Spacer(1, 5*mm))
# ── 8. MOTOR BEHAVIOR ──────────────────────────────────────────────────────
story.append(section_header("8. MOTOR BEHAVIOR", color=colors.HexColor("#6e2f1a")))
story.append(Spacer(1, 3*mm))
motor_terms = [
("Agitation",
"Severe anxiety associated with motor restlessness; non-goal-directed excess movement.",
"Mania, anxiety disorders, delirium, akathisia"),
("Psychomotor retardation",
"Visible slowing of thought, speech, and movement.",
"Depression (core feature), schizophrenia"),
("Catatonia",
"State of unresponsiveness; may include rigidity, stupor, waxy flexibility, or catatonic excitement.",
"Schizophrenia, affective disorders, organic"),
("Waxy flexibility",
"Patient maintains postures in which they are placed, as if made of wax (cerea flexibilitas).",
"Catatonic schizophrenia"),
("Muscle rigidity",
"Muscles remain immovable on passive movement.",
"Schizophrenia, NMS, Parkinson's"),
("Echopraxia",
"Mimicking or repeating the movements of another person.",
"Catatonic schizophrenia, autism"),
("Automatic obedience",
"Strict obedience to commands without critical judgment.",
"Catatonic schizophrenia, hypnosis"),
("Negativism",
"Verbal or nonverbal resistance to outside suggestions or commands.",
"Catatonic schizophrenia"),
("Stereotypy",
"Constant, mechanical repetition of speech or motor activity.",
"Schizophrenia, autism, intellectual disability"),
("Mannerism",
"Ingrained, habitual, involuntary movement.",
"Schizophrenia"),
("Tic",
"Involuntary, spasmodic, repetitive motor movement.",
"Tourette syndrome, tic disorders"),
("Tremor",
"Rhythmic, involuntary muscular contraction and relaxation.",
"Parkinson's, essential tremor, lithium toxicity"),
("Dystonia",
"Slow, sustained contractions of axial or appendicular musculature; acute reactions with antipsychotics.",
"Antipsychotic side effect, Wilson's disease"),
("Ataxia",
"Lack of muscular coordination. Intrapsychic ataxia = lack of coordination between feelings and thoughts.",
"Cerebellar lesion, intoxication, schizophrenia (intrapsychic)"),
("Automatism",
"Activity carried out without conscious knowledge.",
"Dissociation, epilepsy, sleepwalking"),
("Acting out",
"Behavioral response to an unconscious drive or impulse that provides temporary relief of inner tension.",
"Borderline personality disorder"),
("Hyperactivity",
"Increased, abnormally sustained activity with a short attention span.",
"ADHD, mania, anxiety"),
("Hypoactivity",
"Decreased motor and cognitive activity; psychomotor retardation.",
"Depression, hypothyroidism"),
]
story.append(two_col_terms(motor_terms))
story.append(Spacer(1, 5*mm))
# ── 9. SELF-PERCEPTION ─────────────────────────────────────────────────────
story.append(section_header("9. SELF-PERCEPTION & IDENTITY", color=colors.HexColor("#512e5f")))
story.append(Spacer(1, 3*mm))
self_terms = [
("Depersonalization",
"Sensation that one's own reality is temporarily changed or lost; feeling of unreality about oneself.",
"Anxiety disorders, PTSD, dissociative disorders"),
("Derealization",
"Feeling of changed reality; the external world seems strange and unreal.",
"Anxiety disorders, schizophrenia, dissociation"),
("Acenesthesia",
"Loss of sensation of physical existence.",
"Schizophrenia"),
("Asomatopagnosia",
"Inability to recognize a part of one's own body as one's own (also called somatoparaphrenia).",
"Neurological; parietal lobe lesion"),
("Body image disturbance",
"Distorted perception of one's own body size, shape, or weight.",
"Anorexia nervosa, body dysmorphic disorder"),
("Autoscopy",
"Hallucination of seeing oneself or one's double from outside the body.",
"Epilepsy, NDE, schizophrenia"),
]
story.append(two_col_terms(self_terms))
story.append(Spacer(1, 5*mm))
# ── 10. INSIGHT & JUDGMENT ─────────────────────────────────────────────────
story.append(section_header("10. INSIGHT & JUDGMENT", color=colors.HexColor("#1a5276")))
story.append(Spacer(1, 3*mm))
ij_terms = [
("Impaired insight",
"Diminished ability to understand the objective reality of one's situation (anosognosia in severe form).",
"Schizophrenia, mania, dementia"),
("Impaired judgment",
"Diminished ability to understand a situation correctly and act appropriately.",
"Mania, delirium, dementia, intoxication"),
("Emotional insight",
"Level of awareness that one has emotional problems; facilitates positive change when present.",
None),
("Ego-dystonic",
"Aspects of personality viewed as repugnant or inconsistent with one's total personality.",
"OCD symptoms, ego-dystonic homosexuality"),
("Ego-syntonic",
"Aspects of personality viewed as acceptable and consistent with one's total personality.",
"Personality traits (e.g., narcissistic, paranoid PD)"),
]
story.append(two_col_terms(ij_terms))
story.append(Spacer(1, 5*mm))
# ── 11. NEGATIVE SIGNS ─────────────────────────────────────────────────────
story.append(section_header("11. NEGATIVE SIGNS OF SCHIZOPHRENIA", color=RED_SOFT))
story.append(Spacer(1, 3*mm))
story.append(Paragraph(
"Negative symptoms represent a <i>diminution or loss</i> of normal function. "
"They are contrasted with <b>positive symptoms</b> (hallucinations, delusions, disorganized speech) "
"which represent an <i>excess or distortion</i> of normal functions. "
"Negative symptoms respond poorly to first-generation antipsychotics and predict worse functional outcomes.",
ST['intro']))
story.append(Spacer(1, 3*mm))
neg_rows = [
["Flat affect", "Absence or near-absence of emotional expression in face, voice, and gesture.", "Core negative symptom"],
["Alogia", "Poverty of speech (quantity) or speech content; brief, empty replies.", "Core negative symptom"],
["Abulia / Avolition", "Reduced impulse to act and think; indifference about consequences of action.", "Core negative symptom"],
["Apathy / Anhedonia", "Dulled emotional tone with detachment; inability to experience pleasure.", "Core negative symptom"],
["Social withdrawal", "Progressive withdrawal from social interactions and relationships.", "Associated feature"],
["Attentional impairment", "Difficulty sustaining focused attention on tasks.", "Associated feature"],
]
story.append(summary_table(
["Symptom", "Description", "Category"],
neg_rows,
[4.0*cm, 9.5*cm, 3.1*cm]
))
story.append(Spacer(1, 5*mm))
# ── 12. QUICK REFERENCE TABLE ──────────────────────────────────────────────
story.append(PageBreak())
story.append(section_header("12. QUICK REFERENCE: SYMPTOMS BY DOMAIN", color=NAVY))
story.append(Spacer(1, 3*mm))
qr_rows = [
["Consciousness", "Confusion, clouding, coma, stupor, coma vigil, delirium"],
["Attention", "Distractibility, hypervigilance, selective inattention"],
["Mood", "Depression, mania, hypomania, dysphoria, euphoria, anhedonia, irritability"],
["Affect", "Flat, blunted, restricted, labile, inappropriate, apathy, emotional lability"],
["Perception", "Auditory/visual/command hallucinations, illusions, micropsia, depersonalization, derealization"],
["Thought Form", "Flight of ideas, loose associations, tangentiality, circumstantiality, word salad, blocking, neologism, echolalia, perseveration"],
["Thought Content", "Delusions (persecutory, grandiose, reference, control), obsessions, compulsions, phobias, suicidal ideation, rumination, hypochondria"],
["Memory", "Anterograde/retrograde amnesia, confabulation, paramnesia, hypermnesia, dysmnesia"],
["Language/Speech", "Aphasia, mutism, logorrhea, echolalia, coprolalia, dysprosody, cluttering, agrammatism"],
["Motor Behavior", "Catatonia, waxy flexibility, agitation, psychomotor retardation, stereotypy, echopraxia, dystonia, tics, automatism"],
["Self-Perception", "Depersonalization, derealization, acenesthesia, body image disturbance, autoscopy"],
["Insight/Judgment", "Impaired insight, impaired judgment, ego-dystonic vs. ego-syntonic features"],
["Negative (Schizophrenia)", "Flat affect, alogia, abulia/avolition, apathy, anhedonia, social withdrawal"],
]
story.append(summary_table(
["Domain", "Key Signs & Symptoms"],
qr_rows,
[5.0*cm, 11.6*cm]
))
story.append(Spacer(1, 6*mm))
story.append(HRFlowable(width="100%", thickness=1, color=GOLD))
story.append(Spacer(1, 4*mm))
story.append(Paragraph(
"<b>Source:</b> Kaplan & Sadock's Synopsis of Psychiatry, 12th Edition, "
"Chapter: Glossary of Terms Relating to Signs and Symptoms. "
"This document is intended as a rapid clinical reference aid and does not replace "
"formal psychiatric assessment or clinical judgment.",
ParagraphStyle('footer_note', fontName='Helvetica-Oblique', fontSize=7.5,
textColor=GREY_MID, leading=10)))
return story
# ─── Build Document ────────────────────────────────────────────────────────────
def build():
MARGIN_TOP = 34*mm # below header bar
MARGIN_BOTTOM = 18*mm # above footer bar
MARGIN_LR = 18*mm
doc = BaseDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=MARGIN_LR,
rightMargin=MARGIN_LR,
topMargin=MARGIN_TOP,
bottomMargin=MARGIN_BOTTOM,
title="Psychiatric Signs and Symptoms - Clinical Reference",
author="Orris AI",
subject="Psychiatry - Mental Status Examination",
)
frame_normal = Frame(
MARGIN_LR, MARGIN_BOTTOM,
PAGE_W - 2*MARGIN_LR, PAGE_H - MARGIN_TOP - MARGIN_BOTTOM,
id='normal'
)
# First page has full-page cover: use a large top margin to push content down
frame_cover = Frame(
MARGIN_LR, MARGIN_BOTTOM,
PAGE_W - 2*MARGIN_LR, PAGE_H - MARGIN_TOP - MARGIN_BOTTOM,
id='cover'
)
doc.addPageTemplates([
PageTemplate(id='cover', frames=[frame_cover], onPage=on_first_page),
PageTemplate(id='normal', frames=[frame_normal], onPage=on_page),
])
story = build_story()
# First item triggers cover template; after the spacer, switch to normal
from reportlab.platypus import NextPageTemplate
story.insert(1, NextPageTemplate('normal'))
doc.build(story)
print(f"PDF saved to: {OUTPUT}")
if __name__ == "__main__":
build()