Generate a materia medica reference card for sympthom medicine

Asking for Preferences
Asking for Preferences
Reading File
Running Command

mkdir -p /home/daytona/workspace/materia-medica && ls /home/daytona/workspace/ma…

Writing File

~/materia-medica/build_card.py

#!/usr/bin/env python3
"""
Homeopathic Materia Medica Reference Card
Generates a multi-page PDF using ReportLab.
"""

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm, mm
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

OUTPUT = "/home/daytona/workspace/materia-medica/Homeopathic_Materia_Medica_Reference_Card.pdf"

# ─────────────────────────────────────────────
# COLOUR PALETTE
# ─────────────────────────────────────────────
DARK_TEAL   = colors.HexColor("#1A5F7A")
MID_TEAL    = colors.HexColor("#2E86AB")
LIGHT_TEAL  = colors.HexColor("#D4EEF7")
AMBER       = colors.HexColor("#C8871A")
AMBER_LIGHT = colors.HexColor("#FFF3DC")
CREAM       = colors.HexColor("#FAFAF6")
DARK_GRAY   = colors.HexColor("#2D2D2D")
MED_GRAY    = colors.HexColor("#555555")
DIVIDER     = colors.HexColor("#CCCCCC")
WHITE       = colors.white

# ─────────────────────────────────────────────
# REMEDY DATA
# ─────────────────────────────────────────────
remedies = [
    {
        "name": "Aconitum napellus",
        "abbr": "Acon.",
        "category": "Acute",
        "constitution": "Full-blooded, plethoric, robust; sudden onset after cold dry wind or shock",
        "keynotes": [
            "Sudden, violent onset — fear of death, great anxiety and restlessness",
            "High fever, dry burning heat; skin hot and dry without sweat",
            "Intense thirst for cold water",
            "Tingling and numbness of extremities",
            "Complaints arise from exposure to dry cold wind or fright/shock",
        ],
        "mentals": "Extreme fear, panic attacks, predicts time of own death; anguish",
        "modalities": {"Worse": "Cold dry wind, night (esp. midnight), fright, shock, touch",
                       "Better": "Open air, rest, wine"},
        "miasm": "Psoric acute",
        "compare": "Belladonna (hot/red but no fear), Coffea (sleeplessness from joy)",
    },
    {
        "name": "Apis mellifica",
        "abbr": "Apis.",
        "category": "Acute / Polychrest",
        "constitution": "Busy, industrious, jealous; oedematous, watery constitution",
        "keynotes": [
            "Burning, stinging pains; oedema — puffy, watery swellings",
            "Absence of thirst even with fever",
            "Right-sided affections (right ovary, right tonsil)",
            "Sudden piercing screams (brain cry in meningitis)",
            "Urticaria, bee-sting reactions, angio-oedema",
        ],
        "mentals": "Jealous, fidgety, hard to please; apathy or busy-ness alternating; weeping without cause",
        "modalities": {"Worse": "Heat in any form, touch, pressure, right side, late afternoon",
                       "Better": "Cold applications, open air, uncovering"},
        "miasm": "Psoric / Sycotic",
        "compare": "Belladonna (hot, red, dry vs Apis moist), Arsenicum (restless + thirsty)",
    },
    {
        "name": "Argentum nitricum",
        "abbr": "Arg-n.",
        "category": "Polychrest",
        "constitution": "Hurried, anxious, thin; craves sweets that disagree; warm-blooded",
        "keynotes": [
            "Anticipatory anxiety — diarrhoea before exams, events",
            "Craving for sweets, sugar, salt; aggravation from sweets",
            "Impulsive thoughts: to jump from heights, to do something rash",
            "Mucous membranes inflamed; stringy mucus, green discharges",
            "Flatulence and belching relieving anxiety",
        ],
        "mentals": "Hurry, time passes slowly; claustrophobia, agoraphobia; fear of heights",
        "modalities": {"Worse": "Sweets, anticipation, crowds, heat, left side",
                       "Better": "Fresh air, cold, pressure, belching"},
        "miasm": "Sycotic",
        "compare": "Arsenicum (anxiety + neat vs Arg-n impulsive), Gelsemium (paralysis from anticipation)",
    },
    {
        "name": "Arsenicum album",
        "abbr": "Ars.",
        "category": "Polychrest",
        "constitution": "Fastidious, anxious, chilly; thin, pale, waxy; fears death and poverty",
        "keynotes": [
            "Burning pains relieved by heat (paradox)",
            "Great prostration out of proportion to disease",
            "Intense restlessness with anxiety; tosses about",
            "Watery, burning, acrid discharges (eyes, nose)",
            "Food poisoning, gastroenteritis at midnight",
            "Thirst for small sips of warm water often",
        ],
        "mentals": "Anxiety about health (hypochondriac), death; fastidiousness, miserliness; fear of being alone",
        "modalities": {"Worse": "Cold, midnight (12–2 am), exertion, wet weather, right side",
                       "Better": "Heat, warm drinks, sitting up, company"},
        "miasm": "Psoro-syphilitic",
        "compare": "Phosphorus (burning but warm-blooded), Secale (burning but wants cold)",
    },
    {
        "name": "Belladonna",
        "abbr": "Bell.",
        "category": "Acute / Polychrest",
        "constitution": "Full-blooded, vigorous; hot, red, throbbing; right-sided",
        "keynotes": [
            "Sudden onset: hot, red, dry skin; flushed face; glaring eyes",
            "Throbbing, pulsating pains; high fever",
            "Dilated pupils; photophobia; delirium with vivid visions",
            "Convulsions or spasms from heat, suppressed eruptions",
            "Hypersensitivity to noise, light, touch, jarring",
        ],
        "mentals": "Delirium with seeing monsters, biting, striking; excitement, then stupor",
        "modalities": {"Worse": "Touch, jar, noise, light, draught on head, afternoon",
                       "Better": "Semi-erect posture, rest in dark quiet room"},
        "miasm": "Psoric acute",
        "compare": "Stramonium (more violent terror), Hyoscyamus (more lascivious delirium)",
    },
    {
        "name": "Bryonia alba",
        "abbr": "Bry.",
        "category": "Acute / Polychrest",
        "constitution": "Irritable, business-minded, wants to go home; dry fibrous constitution",
        "keynotes": [
            "All complaints aggravated by slightest motion; must keep still",
            "Dryness of all mucous membranes; great thirst for large cold drinks",
            "Stitching pains worse from motion, better rest and pressure",
            "Rheumatic and pleuritic pains",
            "Constipation: large, dry, hard, dark stool as if burnt",
        ],
        "mentals": "Irritable when disturbed; wants to be left alone; anxiety about business",
        "modalities": {"Worse": "Motion, exertion, morning, heat of room, inspiration",
                       "Better": "Rest, lying on painful side, cold, pressure"},
        "miasm": "Psoric",
        "compare": "Rhus tox (better motion vs Bry worse), Nux vomica (irritable but chilly)",
    },
    {
        "name": "Calcarea carbonica",
        "abbr": "Calc.",
        "category": "Polychrest",
        "constitution": "Fair, fat, flabby, sweaty; slow, stubborn; Carbonitrogenoid",
        "keynotes": [
            "Profuse sour-smelling perspiration of head (soaks pillow)",
            "Craving for eggs, sweets, indigestible things (chalk, coal)",
            "Slow dentition, late milestones in children",
            "Cold, damp feet; cold sweaty palms",
            "Fear of observation; own insanity; that something bad will happen",
        ],
        "mentals": "Obstinate, sluggish; apprehension; fear of dark, animals; forgetful",
        "modalities": {"Worse": "Cold wet weather, milk, full moon, exertion, morning",
                       "Better": "Dry climate, constipation, lying on painful side"},
        "miasm": "Psoric (deep anti-psoric)",
        "compare": "Silica (similar constitution but more refined/yielding), Sulphur (complementary)",
    },
    {
        "name": "Carbo vegetabilis",
        "abbr": "Carb-v.",
        "category": "Polychrest / Acute",
        "constitution": "Sluggish, debilitated, cold; oxygen hunger; collapsed states",
        "keynotes": [
            "'Corpse reviver' - indicated when vital force is low",
            "Extreme flatulence, belching relieves temporarily",
            "Cold breath, cold sweat, cold body yet wants to be fanned",
            "Blueness, cyanosis; venous stasis",
            "Never well since prior illness, operation, or loss of fluids",
        ],
        "mentals": "Indifference, sluggishness; fear of dark and ghosts",
        "modalities": {"Worse": "Warm damp weather, fat, wine, coffee, evening",
                       "Better": "Belching, fanning, cool fresh air, elevating feet"},
        "miasm": "Psoric / Sycotic",
        "compare": "China (vital force depleted from fluid loss), Veratrum (collapse + cold sweat)",
    },
    {
        "name": "Chamomilla",
        "abbr": "Cham.",
        "category": "Acute / Polychrest",
        "constitution": "Hypersensitive; irritable infants and children; dentition",
        "keynotes": [
            "Extreme pain sensitivity — pain seems unbearable, drives to despair",
            "Teething: one cheek red and hot, one pale and cold",
            "Greenish diarrhoea smelling like bad eggs / hot grass",
            "Child wants to be carried — screams if set down",
            "Convulsions from anger; irritability after morphine",
        ],
        "mentals": "Imperious, contrary; nothing pleases; snapping and rude",
        "modalities": {"Worse": "Anger, teething, night, warmth, wind",
                       "Better": "Being carried, warm wet weather, fasting"},
        "miasm": "Psoric",
        "compare": "Cina (also irritable child, but worm symptoms), Belladonna (convulsions)",
    },
    {
        "name": "Gelsemium sempervirens",
        "abbr": "Gels.",
        "category": "Acute / Polychrest",
        "constitution": "Lax, heavy, drowsy, dull; warm-blooded; anticipatory paralysis",
        "keynotes": [
            "3 Ds: Dullness, Dizziness, Drowsiness",
            "Heaviness and weakness of limbs; eyelids droop (ptosis)",
            "Slow onset of influenza with chills running up and down spine",
            "Stage fright / anticipatory anxiety with diarrhoea and paralysis",
            "Headache starts at occiput, extends over head to eyes",
            "Absence of thirst even with fever",
        ],
        "mentals": "Dull, confused; apathy; wants to be left alone; fears: crowds, falling, ordeal",
        "modalities": {"Worse": "Damp weather, fog, anticipation, bad news, spring",
                       "Better": "Profuse urination, sweating, bending forward, open air"},
        "miasm": "Psoric",
        "compare": "Baptisia (more septic, stupor), Nux vomica (more irritable)",
    },
    {
        "name": "Hepar sulphuris",
        "abbr": "Hep.",
        "category": "Acute / Polychrest",
        "constitution": "Chilly, irritable, hypersensitive; suppurative tendency",
        "keynotes": [
            "Extreme hypersensitivity to cold air, slightest draught",
            "Sticking, splinter-like pain in throat and skin",
            "Suppuration: every injury tends to suppurate; abscesses",
            "Offensive sweat and discharges smelling of old cheese",
            "Croup with rattling, loose cough; croupy voice",
        ],
        "mentals": "Hasty, irritable; violent impulses; anger at trifles; easily offended",
        "modalities": {"Worse": "Cold dry air, touch, draught, uncovering, night",
                       "Better": "Warmth, wrapping up, damp wet weather, eating"},
        "miasm": "Psoric / Syphilitic",
        "compare": "Silica (slow suppuration, yielding) vs Hep (fast, irritable)",
    },
    {
        "name": "Ignatia amara",
        "abbr": "Ign.",
        "category": "Acute / Polychrest",
        "constitution": "Sensitive, emotional, nervous; grief constitution; paradoxical",
        "keynotes": [
            "Ailments from grief, shock, disappointment, fright",
            "Paradoxical symptoms: sore throat better swallowing solids; empty stomach relieved eating",
            "Sighing and sobbing; silent grief; hysterical weeping",
            "Sensation of lump in throat (globus hystericus)",
            "Spasms and convulsions from grief or excitement",
        ],
        "mentals": "Rapid alternation of moods; introverted grief; desires solitude; blames self",
        "modalities": {"Worse": "Grief, emotions, touch, tobacco smoke, coffee, morning",
                       "Better": "Eating, change of position, swallowing, warmth"},
        "miasm": "Psoric",
        "compare": "Natrum muriaticum (chronic grief), Pulsatilla (open weeping vs Ign silent)",
    },
    {
        "name": "Lachesis mutus",
        "abbr": "Lach.",
        "category": "Polychrest",
        "constitution": "Loquacious, jealous, suspicious; left-sided; climacteric",
        "keynotes": [
            "All complaints worse on waking and from sleep",
            "Left-sided or left-to-right progression of symptoms",
            "Cannot bear constriction around neck or waist",
            "Bluish-purple discolouration of affected parts",
            "Haemorrhagic tendency; blood thin and dark",
            "Hot flushes of menopause with palpitations",
        ],
        "mentals": "Jealousy, suspicion, talkativeness; religious mania; fears death, disease",
        "modalities": {"Worse": "Sleep, morning on waking, heat, tight clothing, left side",
                       "Better": "Discharges (menses, ulcer drainage), open air, cold drinks"},
        "miasm": "Syphilitic / Psoric",
        "compare": "Crotalus (more haemorrhagic), Naja (cardiac affections)",
    },
    {
        "name": "Lycopodium clavatum",
        "abbr": "Lyc.",
        "category": "Polychrest",
        "constitution": "Intellectual but lacking self-confidence; right-sided; liver/digestive",
        "keynotes": [
            "Right-sided or right-to-left progression",
            "Flatulence and bloating even after small amounts of food",
            "4–8 pm aggravation (characteristic time modality)",
            "Craving for sweets, hot food and drinks",
            "Anxiety before undertaking anything; cowardly at home, domineering away",
        ],
        "mentals": "Anticipatory anxiety hides behind arrogance; fear of failure and new undertakings",
        "modalities": {"Worse": "4–8 pm, right side, warm room, flatulent food, oysters",
                       "Better": "Motion, warm food and drink, cold applications, urination"},
        "miasm": "Psoro-Sycotic",
        "compare": "Nux vomica (irritable + liver), Sulphur (anti-psoric partner)",
    },
    {
        "name": "Mercurius solubilis",
        "abbr": "Merc.",
        "category": "Polychrest",
        "constitution": "Moist, sweating; glandular; sensitive to temperature extremes",
        "keynotes": [
            "Profuse, offensive perspiration that does NOT relieve",
            "Profuse salivation; metallic taste; imprint of teeth on tongue",
            "Trembling; worse from heat AND cold (sensitive to both extremes)",
            "Slimy, bloody, tenesmic dysentery; never-get-done sensation",
            "Suppuration of glands, abscesses; greenish-yellow discharges",
        ],
        "mentals": "Slow in answering; suspicious; restless at night; hurried and impulsive",
        "modalities": {"Worse": "Night, sweating, warmth of bed, extremes of temperature, lying on right side",
                       "Better": "Moderate temperature, rest"},
        "miasm": "Syphilitic",
        "compare": "Hepar (more chilly, irritable), Nitric acid (similar dysentery)",
    },
    {
        "name": "Natrum muriaticum",
        "abbr": "Nat-m.",
        "category": "Polychrest",
        "constitution": "Reserved, introverted; chronic grief; emaciated despite eating",
        "keynotes": [
            "Ailments from grief, disappointment, humiliation (chronic Ignatia)",
            "Great thirst; craving for salt",
            "Headache: hammering, bursting; worse from 10 am–3 pm (sun headache)",
            "Mapped tongue; cold sores (herpes) during or after fever",
            "Emaciation especially of neck despite adequate diet",
        ],
        "mentals": "Closed, dwells on past grief; aversion to consolation; weeps alone; holds grudges",
        "modalities": {"Worse": "Sun, 10 am, heat, consolation, seashore (some), exertion",
                       "Better": "Open air, cold bathing, going without meals, lying on right side"},
        "miasm": "Psoric",
        "compare": "Sepia (indifference, yielding vs Nat-m held emotions), Ignatia (acute grief)",
    },
    {
        "name": "Nux vomica",
        "abbr": "Nux-v.",
        "category": "Polychrest",
        "constitution": "Irritable, driven, ambitious; chilly; digestive/liver type; urban excess",
        "keynotes": [
            "Over-indulgence: rich food, alcohol, coffee, drugs; sedentary habit",
            "Hypersensitivity to noise, light, smell, criticism",
            "Ineffectual urging: rectum, bladder, labour",
            "Morning nausea; wants to vomit but cannot",
            "Chilliness: cannot get warm; chilly on uncovering",
        ],
        "mentals": "Irritable, fault-finding, impatient; workaholic; quarrelsome; suicidal thoughts from anger",
        "modalities": {"Worse": "Morning, cold, mental exertion, stimulants, 3–4 am",
                       "Better": "Rest, warmth, damp wet weather, strong pressure, evening"},
        "miasm": "Psoric / Sycotic",
        "compare": "Sulphur (laziness vs Nux ambition), Strychninum (more convulsive)",
    },
    {
        "name": "Phosphorus",
        "abbr": "Phos.",
        "category": "Polychrest",
        "constitution": "Tall, thin, fair; affectionate, open, artistic; warm-blooded",
        "keynotes": [
            "Great thirst for cold water which is vomited once it warms in stomach",
            "Burning pains: stomach, chest, spine",
            "Haemorrhagic tendency: bright red blood from any orifice",
            "Hepatitis, pneumonia with hepatisation of lung",
            "Strong desire for company and reassurance; fear of being alone",
        ],
        "mentals": "Sympathetic, clairvoyant, fears: dark, being alone, thunderstorms; rapidly alternating moods",
        "modalities": {"Worse": "Thunderstorms, evening, warm food, lying on left or painful side",
                       "Better": "Eating, cold food and drink, rubbing, company, sleep"},
        "miasm": "Tuberculinic / Sycotic",
        "compare": "Arsenicum (burning + chilly), Sulphur (burning + hot-blooded)",
    },
    {
        "name": "Pulsatilla nigricans",
        "abbr": "Puls.",
        "category": "Polychrest",
        "constitution": "Mild, yielding, weeping; warm-blooded; changeable symptoms",
        "keynotes": [
            "Changeable, shifting symptoms - never twice the same",
            "Thick, bland, yellow-green discharges (eyes, nose, vagina)",
            "Absence of thirst even in fever",
            "Strong desire for open air; worse in warm stuffy room",
            "Hormonal affections: late/suppressed menses, PMS, menopause",
        ],
        "mentals": "Weeps easily, seeks sympathy; easily influenced; jealous; cannot make decisions",
        "modalities": {"Worse": "Heat, warm room, rich fatty food, evening, rest",
                       "Better": "Open air, motion, cold applications, sympathy, cold food"},
        "miasm": "Psoric",
        "compare": "Silica (also yielding but chilly), Natrum mur (weeps but averse to consolation)",
    },
    {
        "name": "Rhus toxicodendron",
        "abbr": "Rhus-t.",
        "category": "Acute / Polychrest",
        "constitution": "Restless, anxious; rheumatic/arthritic; chilly and wet-sensitive",
        "keynotes": [
            "Extreme restlessness — must keep moving; stiffness on first motion then better with continued motion",
            "Classic 'rusty gate' remedy: stiff on rising, loosens up",
            "Herpes zoster (shingles) with intense itching and burning",
            "Sprains, over-lifting; periarticular tissue affinity",
            "Triangular red tip of tongue",
        ],
        "mentals": "Restless, anxious; sadness with thoughts of suicide at night; superstitious",
        "modalities": {"Worse": "Rest, initial motion, cold wet weather, night, right side",
                       "Better": "Continued motion, warmth, hot bath, dry weather, rubbing"},
        "miasm": "Psoric / Sycotic",
        "compare": "Bryonia (worse ALL motion), Ruta (more tendon/periosteum affinity)",
    },
    {
        "name": "Sepia officinalis",
        "abbr": "Sep.",
        "category": "Polychrest",
        "constitution": "Dark-haired, thin; bearing-down sensation; indifference to loved ones",
        "keynotes": [
            "Bearing-down sensation: uterus, rectum; must cross legs to prevent prolapse",
            "Indifference to family and loved ones; aversion to being touched or consoled",
            "Yellowish-brown saddle across nose (chloasma); yellow-brown spots",
            "Hot flushes with perspiration during menopause",
            "Nausea at sight or smell of food; morning sickness",
        ],
        "mentals": "Indifferent, detached, overwhelmed; weeps when telling symptoms; better vigorous exercise",
        "modalities": {"Worse": "Cold air, morning and evening, before menses, consolation, standing",
                       "Better": "Vigorous exercise, warmth, cold drinks, drawing limbs up"},
        "miasm": "Psoric / Sycotic",
        "compare": "Natrum mur (holds grief vs Sep indifference), Staphysagria (suppressed anger)",
    },
    {
        "name": "Silica (Silicea)",
        "abbr": "Sil.",
        "category": "Polychrest",
        "constitution": "Fine-boned, fastidious, yielding; chilly; suppurative tendency",
        "keynotes": [
            "Lack of vital heat and stamina; yields easily; cannot push through",
            "Slow suppuration to resolution; expels foreign bodies (splinters, glass)",
            "Profuse, offensive foot-sweat; sweaty head",
            "Slow dentition; delayed bone healing; brittle nails",
            "Constipation: stool slips back when partially expelled ('shy stool')",
        ],
        "mentals": "Timid, yielding, lack of confidence; yet stubborn and fixed on small matters; anticipatory anxiety",
        "modalities": {"Worse": "Cold, uncovering head, damp, new moon, noise, milk",
                       "Better": "Warmth, wrapping head, profuse urination, summer"},
        "miasm": "Psoric",
        "compare": "Calcarea (similar constitution, fatter and lazier), Hep sulph (irritable vs Sil yielding)",
    },
    {
        "name": "Sulphur",
        "abbr": "Sul.",
        "category": "Polychrest",
        "constitution": "Philosophical, untidy; hot-blooded; itching eruptions; lean or pot-bellied",
        "keynotes": [
            "Burning sensations in all orifices (eyes, anus, soles, palms)",
            "Offensive odour of body; discharges burning and acrid",
            "Worse from washing; skin itches and burns after bathing",
            "Hungry at 11 am; cannot wait for lunch",
            "Relapsing conditions: removes psoric block, revives reaction",
        ],
        "mentals": "Philosopher, theoriser; untidy, selfish; self-satisfied; religious mania",
        "modalities": {"Worse": "Warmth, bathing, standing, 11 am, suppressions, alcohol",
                       "Better": "Open air, motion, warm dry weather, lying on right side"},
        "miasm": "Psoric (primary anti-psoric)",
        "compare": "Calc (complementary), Lycopodium (deep acting triumvirate with Sulphur-Calc)",
    },
    {
        "name": "Thuja occidentalis",
        "abbr": "Thuj.",
        "category": "Polychrest",
        "constitution": "Secretive, hurried; sycotic; warts and condylomata",
        "keynotes": [
            "Warts, condylomata, papillomata on any surface",
            "Ailments from vaccination or suppressed gonorrhoea (sycotic miasm)",
            "Fixed ideas: body is made of glass, a strange person walks by their side",
            "Sweat only on uncovered parts; offensive perspiration",
            "Divided sensation: two people, two wills",
        ],
        "mentals": "Low self-esteem; hurried; fixed ideas; secretive about illness; religious",
        "modalities": {"Worse": "Cold damp air, 3 am and 3 pm, onions, right side, vaccination",
                       "Better": "Left side, sneezing, rubbing, stretching"},
        "miasm": "Sycotic (primary anti-sycotic)",
        "compare": "Medorrhinum (deeper sycotic), Nitric acid (warts/condylomata with bleeding)",
    },
]

# ─────────────────────────────────────────────
# STYLES
# ─────────────────────────────────────────────
def build_styles():
    base = getSampleStyleSheet()

    title_style = ParagraphStyle(
        "CardTitle",
        parent=base["Normal"],
        fontSize=22,
        leading=28,
        textColor=WHITE,
        fontName="Helvetica-Bold",
        alignment=TA_CENTER,
    )
    subtitle_style = ParagraphStyle(
        "CardSubtitle",
        parent=base["Normal"],
        fontSize=11,
        leading=16,
        textColor=colors.HexColor("#D4EEF7"),
        fontName="Helvetica",
        alignment=TA_CENTER,
    )
    remedy_name_style = ParagraphStyle(
        "RemedyName",
        parent=base["Normal"],
        fontSize=13,
        leading=17,
        textColor=WHITE,
        fontName="Helvetica-Bold",
        alignment=TA_LEFT,
        leftIndent=4,
    )
    abbr_style = ParagraphStyle(
        "Abbr",
        parent=base["Normal"],
        fontSize=10,
        leading=14,
        textColor=AMBER_LIGHT,
        fontName="Helvetica-Oblique",
        alignment=TA_LEFT,
        leftIndent=4,
    )
    section_label = ParagraphStyle(
        "SectionLabel",
        parent=base["Normal"],
        fontSize=7.5,
        leading=10,
        textColor=MID_TEAL,
        fontName="Helvetica-Bold",
        spaceAfter=1,
    )
    body_style = ParagraphStyle(
        "BodyText",
        parent=base["Normal"],
        fontSize=8,
        leading=11,
        textColor=DARK_GRAY,
        fontName="Helvetica",
        spaceBefore=0,
        spaceAfter=0,
    )
    bullet_style = ParagraphStyle(
        "Bullet",
        parent=base["Normal"],
        fontSize=8,
        leading=11,
        textColor=DARK_GRAY,
        fontName="Helvetica",
        leftIndent=10,
        bulletIndent=2,
        spaceBefore=0,
        spaceAfter=0,
    )
    mod_label = ParagraphStyle(
        "ModLabel",
        parent=base["Normal"],
        fontSize=7.5,
        leading=10,
        textColor=colors.HexColor("#8B0000"),
        fontName="Helvetica-Bold",
    )
    return dict(
        title=title_style,
        subtitle=subtitle_style,
        remedy_name=remedy_name_style,
        abbr=abbr_style,
        section_label=section_label,
        body=body_style,
        bullet=bullet_style,
        mod_label=mod_label,
    )

# ─────────────────────────────────────────────
# HEADER / FOOTER
# ─────────────────────────────────────────────
def on_page(canvas, doc):
    W, H = A4
    # Footer
    canvas.saveState()
    canvas.setFillColor(DARK_TEAL)
    canvas.rect(0, 0, W, 1.2*cm, fill=1, stroke=0)
    canvas.setFont("Helvetica", 7)
    canvas.setFillColor(WHITE)
    canvas.drawString(1.5*cm, 0.45*cm,
        "Homeopathic Materia Medica Reference Card  |  Classical Homeopathy  |  For educational use only")
    canvas.drawRightString(W - 1.5*cm, 0.45*cm, f"Page {doc.page}")
    canvas.restoreState()

# ─────────────────────────────────────────────
# REMEDY BLOCK
# ─────────────────────────────────────────────
def remedy_block(r, styles):
    """Build a KeepTogether block for one remedy."""
    elems = []

    # ── Header band ──
    cat_color = AMBER if r["category"] == "Polychrest" else (
        MID_TEAL if r["category"] == "Acute / Polychrest" else colors.HexColor("#5A8A5A")
    )
    header_data = [[
        Paragraph(r["name"], styles["remedy_name"]),
        Paragraph(r["abbr"] + "  |  " + r["category"], styles["abbr"]),
    ]]
    header_table = Table(header_data, colWidths=["60%", "40%"])
    header_table.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, -1), DARK_TEAL),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("TOPPADDING", (0, 0), (-1, -1), 5),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
        ("LEFTPADDING", (0, 0), (0, 0), 6),
        ("RIGHTPADDING", (-1, 0), (-1, 0), 6),
        ("ROUNDEDCORNERS", [4, 4, 0, 0]),
    ]))
    elems.append(header_table)

    # ── Body: two-column layout ──
    left_cells = []
    right_cells = []

    # Constitution
    left_cells.append(Paragraph("CONSTITUTION / TYPE", styles["section_label"]))
    left_cells.append(Paragraph(r["constitution"], styles["body"]))
    left_cells.append(Spacer(1, 4))

    # Keynotes
    left_cells.append(Paragraph("KEYNOTES", styles["section_label"]))
    for kn in r["keynotes"]:
        left_cells.append(Paragraph(u"\u2022 " + kn, styles["bullet"]))
    left_cells.append(Spacer(1, 4))

    # Mentals
    left_cells.append(Paragraph("MENTALS / MIND", styles["section_label"]))
    left_cells.append(Paragraph(r["mentals"], styles["body"]))

    # Modalities (right column)
    right_cells.append(Paragraph("MODALITIES", styles["section_label"]))
    for label, text in r["modalities"].items():
        color = "#8B0000" if label == "Worse" else "#1A5F7A"
        sym = u"\u25bc " if label == "Worse" else u"\u25b2 "
        right_cells.append(Paragraph(
            f'<font color="{color}"><b>{sym} {label}:</b></font> {text}',
            styles["body"]
        ))
    right_cells.append(Spacer(1, 4))

    # Miasm
    right_cells.append(Paragraph("MIASM", styles["section_label"]))
    right_cells.append(Paragraph(r["miasm"], styles["body"]))
    right_cells.append(Spacer(1, 4))

    # Compare
    right_cells.append(Paragraph("COMPARE / DIFFERENTIATE", styles["section_label"]))
    right_cells.append(Paragraph(r["compare"], styles["body"]))

    # Combine into a two-column table
    from reportlab.platypus import ListFlowable
    body_data = [[left_cells, right_cells]]
    body_table = Table(body_data, colWidths=["55%", "45%"])
    body_table.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, -1), CREAM),
        ("VALIGN", (0, 0), (-1, -1), "TOP"),
        ("TOPPADDING", (0, 0), (-1, -1), 6),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 6),
        ("LEFTPADDING", (0, 0), (0, 0), 8),
        ("RIGHTPADDING", (0, 0), (0, 0), 6),
        ("LEFTPADDING", (1, 0), (1, 0), 6),
        ("RIGHTPADDING", (1, 0), (1, 0), 8),
        ("LINEAFTER", (0, 0), (0, 0), 0.5, DIVIDER),
        ("ROUNDEDCORNERS", [0, 0, 4, 4]),
        ("BOX", (0, 0), (-1, -1), 0.5, DIVIDER),
    ]))
    elems.append(body_table)
    elems.append(Spacer(1, 8))

    return KeepTogether(elems)

# ─────────────────────────────────────────────
# LEGEND / INTRO PAGE ELEMENTS
# ─────────────────────────────────────────────
def intro_elements(styles):
    elems = []

    # Hero banner
    banner_data = [[
        Paragraph("Homeopathic Materia Medica", styles["title"]),
    ]]
    banner_table = Table(banner_data, colWidths=["100%"])
    banner_table.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, -1), DARK_TEAL),
        ("TOPPADDING", (0, 0), (-1, -1), 14),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
        ("ROUNDEDCORNERS", [6, 6, 0, 0]),
    ]))
    sub_data = [[
        Paragraph("Quick Reference Card  |  24 Key Remedies  |  Polychrests & Acute Medicines", styles["subtitle"]),
    ]]
    sub_table = Table(sub_data, colWidths=["100%"])
    sub_table.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, -1), MID_TEAL),
        ("TOPPADDING", (0, 0), (-1, -1), 5),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 10),
        ("ROUNDEDCORNERS", [0, 0, 6, 6]),
    ]))
    elems += [banner_table, sub_table, Spacer(1, 12)]

    # Legend
    legend_label = ParagraphStyle("Leg", parent=styles["section_label"], fontSize=9, textColor=DARK_TEAL)
    leg_body = ParagraphStyle("LegB", parent=styles["body"], fontSize=8.5)

    legend_items = [
        ("Polychrest", "Deep-acting constitutional remedy with a wide sphere of action."),
        ("Acute", "Primarily used for acute/short-duration conditions."),
        ("Acute / Polychrest", "Effective in both acute and constitutional prescribing."),
        (u"\u25bc Worse (Aggravation)", "Conditions or factors that intensify symptoms."),
        (u"\u25b2 Better (Amelioration)", "Conditions or factors that relieve symptoms."),
        ("Miasm", "Underlying disease tendency (Psora, Sycosis, Syphilis, Tuberculinum)."),
        ("Compare", "Remedies to differentiate from (similar but distinct)."),
    ]

    leg_data = [[Paragraph(k, legend_label), Paragraph(v, leg_body)] for k, v in legend_items]
    leg_table = Table(leg_data, colWidths=["30%", "70%"])
    leg_table.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, -1), LIGHT_TEAL),
        ("BACKGROUND", (0, 0), (0, -1), colors.HexColor("#B8DFF0")),
        ("TOPPADDING", (0, 0), (-1, -1), 4),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
        ("LEFTPADDING", (0, 0), (-1, -1), 8),
        ("RIGHTPADDING", (0, 0), (-1, -1), 8),
        ("ROWBACKGROUNDS", (0, 0), (-1, -1), [LIGHT_TEAL, colors.HexColor("#E8F6FB")]),
        ("BOX", (0, 0), (-1, -1), 0.5, MID_TEAL),
        ("INNERGRID", (0, 0), (-1, -1), 0.3, DIVIDER),
        ("ROUNDEDCORNERS", [4, 4, 4, 4]),
    ]))
    elems.append(Paragraph("HOW TO USE THIS CARD", ParagraphStyle("HowTo",
        parent=styles["section_label"], fontSize=10, textColor=DARK_TEAL, spaceAfter=4)))
    elems.append(leg_table)
    elems.append(Spacer(1, 10))

    # Miasm summary table
    miasm_title = ParagraphStyle("MiasmT", parent=styles["section_label"],
                                  fontSize=10, textColor=AMBER, spaceAfter=4)
    elems.append(Paragraph("THE THREE MIASMS AT A GLANCE", miasm_title))
    miasm_headers = [
        Paragraph("Miasm", legend_label),
        Paragraph("Core Tendency", legend_label),
        Paragraph("Key Remedies", legend_label),
        Paragraph("Tissue Action", legend_label),
    ]
    miasm_rows = [
        ["Psora", "Under-function; deficiency; itch; functional disorders",
         "Sulphur, Calc-c, Psorinum, Lycopodium", "Suppression, itch, skin"],
        ["Sycosis", "Over-function; excess; growth; infiltration",
         "Thuja, Medorrhinum, Nitric acid, Nat-s", "Warts, tumors, catarrh"],
        ["Syphilis", "Destruction; ulceration; bone/tissue erosion",
         "Mercurius, Aurum, Fluoric acid, Stillingia", "Ulcers, necrosis, insanity"],
        ["Tuberculinum", "Changeability; wandering pains; romantic; destructive",
         "Tuberculinum, Phosphorus, Calc-p, Drosera", "Glands, lungs, emaciation"],
    ]
    miasm_data = [miasm_headers] + [[Paragraph(c, leg_body) for c in row] for row in miasm_rows]
    miasm_table = Table(miasm_data, colWidths=["18%", "32%", "30%", "20%"])
    miasm_table.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), DARK_TEAL),
        ("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [AMBER_LIGHT, CREAM]),
        ("TOPPADDING", (0, 0), (-1, -1), 4),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
        ("LEFTPADDING", (0, 0), (-1, -1), 6),
        ("RIGHTPADDING", (0, 0), (-1, -1), 6),
        ("BOX", (0, 0), (-1, -1), 0.5, MID_TEAL),
        ("INNERGRID", (0, 0), (-1, -1), 0.3, DIVIDER),
        ("FONTNAME", (0, 1), (0, -1), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, -1), 8),
    ]))
    elems.append(miasm_table)
    elems.append(Spacer(1, 12))
    elems.append(HRFlowable(width="100%", thickness=1, color=MID_TEAL))
    elems.append(Spacer(1, 8))
    elems.append(Paragraph(
        "<b>REMEDY PROFILES</b>  (alphabetical order)",
        ParagraphStyle("RTitle", parent=styles["section_label"], fontSize=11, textColor=DARK_TEAL)
    ))
    elems.append(Spacer(1, 6))
    return elems

# ─────────────────────────────────────────────
# BUILD PDF
# ─────────────────────────────────────────────
def build_pdf():
    doc = SimpleDocTemplate(
        OUTPUT,
        pagesize=A4,
        rightMargin=1.5*cm,
        leftMargin=1.5*cm,
        topMargin=1.5*cm,
        bottomMargin=1.8*cm,
        title="Homeopathic Materia Medica Reference Card",
        author="Orris AI",
        subject="Classical Homeopathy",
    )

    styles = build_styles()
    story = []

    # Intro page elements
    story += intro_elements(styles)

    # Remedy blocks
    for r in sorted(remedies, key=lambda x: x["name"]):
        story.append(remedy_block(r, styles))

    doc.build(story, onFirstPage=on_page, onLaterPages=on_page)
    print(f"PDF created: {OUTPUT}")

if __name__ == "__main__":
    build_pdf()
Running Command

cd /home/daytona/workspace/materia-medica && python3 build_card.py 2>&1

Running Command

ls -lh /home/daytona/workspace/materia-medica/Homeopathic_Materia_Medica_Referen…

Your reference card is ready:

Homeopathic Materia Medica Reference Card

PDF Document · PDF


Here is what the card contains:
Cover / Intro section
  • Title banner with card description
  • Legend explaining all symbols and column labels
  • Miasm summary table (Psora, Sycosis, Syphilis, Tuberculinum) with core tendency, key remedies, and tissue action
24 Remedy Profiles (alphabetical) covering:
GroupRemedies
AcuteAconitum, Apis, Belladonna, Bryonia, Chamomilla, Gelsemium, Rhus tox
PolychrestArsenicum, Arg-n, Calcarea carb, Carbo veg, Hepar sulph, Ignatia, Lachesis, Lycopodium, Mercurius, Natrum mur, Nux vomica, Phosphorus, Pulsatilla, Sepia, Silica, Sulphur, Thuja
Each remedy card shows:
  • Name, abbreviation, and category (color-coded)
  • Constitution / patient type
  • 5-6 Keynotes (bullet list)
  • Mentals / Mind
  • Modalities (worse - red, better - blue)
  • Miasm affiliation
  • Differential comparison notes
For educational purposes only - not a substitute for professional homeopathic prescribing.
This is a shared conversation. Sign in to Orris to start your own chat.