What are the causes of koilonychia in picture the black color is mehndi bit nails are becoming flat
koilonychia causes etiology

| Test | What to check |
|---|---|
| CBC | Hemoglobin, MCV (low in iron deficiency) |
| Serum ferritin | Most sensitive marker for iron stores |
| Serum iron + TIBC | Low iron, high TIBC = iron deficiency |
| Transferrin saturation | Low (<16%) |
Find clinical images of koilonychia in different severities
koilonychia spoon nails clinical appearance severity


"Spoon nails are thin and concave, with the edges everted so that if a drop of water were placed on the nail, it would not run off." — Andrews' Diseases of the Skin


This illustrates that koilonychia resolves slowly even after the underlying cause is corrected — nails regenerate at ~3 mm/month.
| Severity | Nail Change | Likely Stage |
|---|---|---|
| Mild | Flat, loss of convexity, pale | Early iron depletion |
| Moderate | Clearly concave, ridged, edges everted | Established iron deficiency |
| Severe | Deep spooning, brittle, discolored | Chronic/advanced deficiency (Plummer-Vinson) |
| Resolving | Gradual return of convexity | Post iron supplementation |
Create a quick reference PDF on nail dystrophies and systemic causes
nail dystrophies systemic disease clinical signs
Terry nails half and half nails Lindsay nails renal failure hepatic cirrhosis
nail clubbing digital clubbing pulmonary cardiac disease
Beau lines nail transverse grooves after illness chemotherapy
nail pitting psoriasis onycholysis subungual hyperkeratosis
mkdir -p /home/daytona/workspace/nail-dystrophies && ls /home/daytona/workspace/…
~/nail-dystrophies/generate_pdf.py
#!/usr/bin/env python3
"""
Quick Reference PDF: Nail Dystrophies & Systemic Causes
Uses ReportLab for full layout control with images and tables.
"""
import requests
import os
from io import BytesIO
from PIL import Image as PILImage
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.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
Image, HRFlowable, KeepTogether, PageBreak
)
from reportlab.platypus.flowables import BalancedColumns
from reportlab.lib.colors import HexColor, white, black
OUTPUT = "/home/daytona/workspace/nail-dystrophies/Nail_Dystrophies_Quick_Reference.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
TEAL = HexColor("#1A6B72")
TEAL_LIGHT = HexColor("#E8F4F5")
AMBER = HexColor("#D97706")
AMBER_LITE = HexColor("#FEF3C7")
RED = HexColor("#B91C1C")
RED_LITE = HexColor("#FEE2E2")
PURPLE = HexColor("#6D28D9")
PURPLE_LT = HexColor("#EDE9FE")
GREY_DARK = HexColor("#1F2937")
GREY_MID = HexColor("#6B7280")
GREY_LIGHT = HexColor("#F3F4F6")
GREY_LINE = HexColor("#D1D5DB")
WHITE = white
W, H = A4
MARGIN = 1.8*cm
# ── Image downloader ────────────────────────────────────────────────────────
IMG_DIR = "/home/daytona/workspace/nail-dystrophies/imgs"
os.makedirs(IMG_DIR, exist_ok=True)
def fetch_img(url, fname, max_w_cm, max_h_cm):
"""Download image, resize to fit box, return ReportLab Image or None."""
path = os.path.join(IMG_DIR, fname)
if not os.path.exists(path):
try:
r = requests.get(url, timeout=15)
r.raise_for_status()
img = PILImage.open(BytesIO(r.content)).convert("RGB")
img.save(path, "JPEG", quality=85)
except Exception as e:
print(f" ✗ {fname}: {e}")
return None
try:
img = PILImage.open(path)
ow, oh = img.size
mw = max_w_cm * cm
mh = max_h_cm * cm
scale = min(mw / ow, mh / oh)
return Image(path, width=ow*scale, height=oh*scale)
except Exception as e:
print(f" ✗ resize {fname}: {e}")
return None
# ── Style sheet ─────────────────────────────────────────────────────────────
base = getSampleStyleSheet()
def style(name, parent="Normal", **kw):
s = ParagraphStyle(name, parent=base[parent], **kw)
return s
S = {
"doc_title": style("DocTitle", "Title",
fontSize=26, textColor=WHITE, leading=30,
alignment=TA_CENTER, spaceAfter=4),
"doc_sub": style("DocSub", "Normal",
fontSize=12, textColor=HexColor("#CBD5E1"),
alignment=TA_CENTER, spaceAfter=2),
"section": style("Section", "Normal",
fontSize=13, textColor=WHITE, leading=16,
fontName="Helvetica-Bold", spaceBefore=2, spaceAfter=2),
"heading2": style("H2", "Normal",
fontSize=11, textColor=TEAL, leading=14,
fontName="Helvetica-Bold", spaceBefore=8, spaceAfter=3),
"body": style("Body", "Normal",
fontSize=9, textColor=GREY_DARK, leading=13,
spaceBefore=2, spaceAfter=2),
"body_sm": style("BodySm", "Normal",
fontSize=8, textColor=GREY_DARK, leading=11),
"caption": style("Caption", "Normal",
fontSize=7.5, textColor=GREY_MID, leading=10,
alignment=TA_CENTER, spaceAfter=4),
"bullet": style("Bullet", "Normal",
fontSize=8.5, textColor=GREY_DARK, leading=12,
leftIndent=10, bulletIndent=0),
"tag_teal": style("TagTeal", "Normal",
fontSize=7.5, textColor=TEAL, fontName="Helvetica-Bold"),
"footnote": style("Footnote", "Normal",
fontSize=7, textColor=GREY_MID, leading=9,
alignment=TA_CENTER),
}
def bullet(text):
return Paragraph(f"• {text}", S["bullet"])
def sp(h=0.2):
return Spacer(1, h*cm)
# ═══════════════════════════════════════════════════════════════════════════
# BUILD DOCUMENT
# ═══════════════════════════════════════════════════════════════════════════
story = []
# ── COVER BANNER ────────────────────────────────────────────────────────────
def cover_banner(canvas, doc):
canvas.saveState()
# Top banner
canvas.setFillColor(TEAL)
canvas.rect(0, H - 5.5*cm, W, 5.5*cm, fill=1, stroke=0)
# Decorative accent strip
canvas.setFillColor(AMBER)
canvas.rect(0, H - 5.5*cm, W, 0.35*cm, fill=1, stroke=0)
# Footer bar
canvas.setFillColor(GREY_DARK)
canvas.rect(0, 0, W, 1.1*cm, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica", 7.5)
canvas.drawCentredString(W/2, 0.4*cm,
"Sources: Goldman-Cecil Medicine • Andrews' Diseases of the Skin • Fitzpatrick's Dermatology • "
"Dermatology 2-Vol Set 5e • Harrison's Principles of Internal Medicine 22e")
canvas.restoreState()
def later_pages(canvas, doc):
canvas.saveState()
# Thin top accent
canvas.setFillColor(TEAL)
canvas.rect(0, H - 0.6*cm, W, 0.6*cm, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica-Bold", 7.5)
canvas.drawString(MARGIN, H - 0.42*cm,
"NAIL DYSTROPHIES & SYSTEMIC CAUSES — Quick Reference")
canvas.drawRightString(W - MARGIN, H - 0.42*cm, f"Page {doc.page}")
# Footer
canvas.setFillColor(GREY_DARK)
canvas.rect(0, 0, W, 0.9*cm, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica", 7)
canvas.drawCentredString(W/2, 0.32*cm,
"Sources: Goldman-Cecil Medicine • Andrews' Diseases of the Skin • Fitzpatrick's Dermatology • "
"Dermatology 2-Vol Set 5e • Harrison's Principles of Internal Medicine 22e")
canvas.restoreState()
# ── Title block (placed on page 1 via offset) ───────────────────────────────
story.append(Spacer(1, 5.8*cm)) # push below cover banner
story.append(Paragraph("NAIL DYSTROPHIES", S["doc_title"]))
story.append(Paragraph("& SYSTEMIC CAUSES", S["doc_title"]))
story.append(Paragraph("Quick Reference Guide for Clinicians", S["doc_sub"]))
story.append(Spacer(1, 0.5*cm))
# ── INTRO ───────────────────────────────────────────────────────────────────
intro = Table([[Paragraph(
"<b>Nails as a diagnostic window:</b> Nail changes are among the most visible and accessible "
"physical signs of systemic disease. A systematic examination of the nail plate, nail bed, "
"lunula, and periungual tissue can point directly toward haematological, hepatic, renal, "
"pulmonary, dermatological, and nutritional disorders. This guide provides a rapid visual "
"and clinical reference for the major nail dystrophies encountered in practice.",
S["body"])]],
colWidths=[W - 2*MARGIN])
intro.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL_LIGHT),
("BOX", (0,0), (-1,-1), 0.8, TEAL),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING",(0,0), (-1,-1), 8),
("ROUNDEDCORNERS", (0,0), (-1,-1), [4,4,4,4]),
]))
story.append(intro)
story.append(sp(0.4))
# ═══════════════════════════════════════════════════════════════════════════
# HELPER: section header bar
# ═══════════════════════════════════════════════════════════════════════════
def section_bar(title, color=TEAL):
t = Table([[Paragraph(title, S["section"])]],
colWidths=[W - 2*MARGIN])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING",(0,0), (-1,-1), 6),
]))
return t
# ═══════════════════════════════════════════════════════════════════════════
# HELPER: two-column image + text card
# ═══════════════════════════════════════════════════════════════════════════
def img_card(url, fname, cap, title, body_paras, accent=TEAL, img_w=5.5, img_h=4.0):
img = fetch_img(url, fname, img_w, img_h)
title_p = Paragraph(title, style("CT", "Normal",
fontSize=10, textColor=accent, fontName="Helvetica-Bold", spaceAfter=3))
cap_p = Paragraph(cap, S["caption"])
left_content = [title_p] + body_paras
left_cell = left_content
if img:
right_cell = [img, cap_p]
else:
right_cell = [Paragraph("(image unavailable)", S["caption"])]
t = Table([[left_cell, right_cell]],
colWidths=[(W-2*MARGIN)*0.58, (W-2*MARGIN)*0.42])
t.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (0,-1), 0),
("RIGHTPADDING", (0,0), (0,-1), 8),
("LEFTPADDING", (1,0), (1,-1), 4),
("RIGHTPADDING", (1,0), (1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
]))
return t
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 1 — KOILONYCHIA
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_bar("1 | KOILONYCHIA (Spoon Nails)"))
story.append(sp(0.3))
koi_body = [
Paragraph("Nail plate becomes thin, flat, and centrally depressed with everted lateral and distal edges — classically spoon-shaped. A water drop placed on the nail will not run off.", S["body"]),
sp(0.15),
Paragraph("<b>Progression:</b> Normal → Brittle/ridged → Flat → Concave (spoon)", S["body"]),
sp(0.2),
Paragraph("<b>Systemic causes:</b>", S["body"]),
bullet("<b>Iron deficiency anaemia</b> — most common systemic cause"),
bullet("Plummer-Vinson syndrome (iron deficiency + dysphagia + glossitis)"),
bullet("Haemochromatosis (faulty iron metabolism)"),
bullet("Hypothyroidism / hyperthyroidism"),
bullet("Lichen planus, psoriasis"),
bullet("Raynaud syndrome / scleroderma"),
bullet("Familial (autosomal dominant)"),
bullet("Physiologic — toes of children aged 1–4 yrs (self-resolving)"),
bullet("Occupational: chronic cold exposure, chemical damage"),
sp(0.1),
Paragraph("<b>Key exam tip:</b> Index and middle fingernails most severely involved. "
"Resolves slowly even after iron replacement begins.", S["body_sm"]),
]
story.append(img_card(
url="https://cdn.orris.care/cdss_images/pmc_clinical_VQA_f068a64e060f02ac65027cef8190c0bdbbd47fa6182f8dfc1b1f2fac848d31f0.jpg",
fname="koilonychia_severe.jpg",
cap="Severe koilonychia — both hands, iron deficiency anaemia\n(Source: PMC Clinical VQA)",
title="KOILONYCHIA",
body_paras=koi_body,
accent=AMBER,
img_w=6, img_h=4.5
))
story.append(sp(0.4))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 2 — NAIL CLUBBING
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_bar("2 | NAIL CLUBBING (Hippocratic Fingers)", color=RED))
story.append(sp(0.3))
club_body = [
Paragraph("Bulbous enlargement of the distal phalanges with increased nail curvature. "
"The Lovibond angle (nail-to-skin angle) becomes ≥ 180°. "
"<b>Schamroth sign:</b> absence of the normal diamond window when opposing nails are placed together.", S["body"]),
sp(0.15),
Paragraph("<b>Causes by system:</b>", S["body"]),
bullet("<b>Pulmonary (most common):</b> lung cancer, interstitial lung disease, bronchiectasis, cystic fibrosis, empyema"),
bullet("<b>Cardiac:</b> cyanotic congenital heart disease, infective endocarditis"),
bullet("<b>GI/Hepatic:</b> cirrhosis, inflammatory bowel disease, coeliac disease"),
bullet("<b>Endocrine:</b> thyroid acropachy (Graves' disease)"),
bullet("<b>Idiopathic / familial</b>"),
sp(0.1),
Paragraph("<b>Grade 0–4 scale:</b> 0 = normal; 1 = fluctuant nail bed; "
"2 = obliterated Lovibond angle; 3 = drumstick digit; 4 = periosteal new bone.", S["body_sm"]),
]
story.append(img_card(
url="https://cdn.orris.care/cdss_images/pmc_clinical_VQA_c1417ca016d4a73a335e093b49b2c1a5775f15cabbbe97cac2307c7d3231a750.jpg",
fname="clubbing.jpg",
cap="Classic digital clubbing — obliterated Lovibond angle\n(Source: PMC Clinical VQA)",
title="CLUBBING",
body_paras=club_body,
accent=RED,
img_w=6, img_h=4.5
))
story.append(sp(0.4))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 3 — BEAU LINES
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_bar("3 | BEAU LINES & ONYCHOMADESIS"))
story.append(sp(0.3))
beau_body = [
Paragraph("Transverse grooves/depressions across the nail plate resulting from temporary "
"interruption of nail matrix growth. In systemic disease, all nails are affected "
"at the <b>same level</b>. Repeated lines indicate repeated insults (e.g. chemotherapy cycles). "
"Timing can be calculated: nails grow ~3 mm/month.", S["body"]),
sp(0.15),
Paragraph("<b>Systemic causes:</b>", S["body"]),
bullet("Severe febrile illness / sepsis"),
bullet("Chemotherapy (especially taxanes, cyclophosphamide)"),
bullet("Major surgery or trauma"),
bullet("Malnutrition, zinc deficiency"),
bullet("Kawasaki disease (children)"),
bullet("COVID-19 (post-infectious)"),
sp(0.1),
Paragraph("<b>Onychomadesis</b> = complete nail shedding; represents severe matrix arrest. "
"Same causes but more severe disruption.", S["body_sm"]),
]
story.append(img_card(
url="https://cdn.orris.care/cdss_images/pmc_clinical_VQA_6d2475d51b662e3db2d3cc9ced04ad5416e9526d180b85bfd3b9bd2f049388eb.jpg",
fname="beau_mees.jpg",
cap="Beau lines + Mees' lines post-chemotherapy — multiple cycles visible\n(Source: PMC Clinical VQA)",
title="BEAU LINES",
body_paras=beau_body,
accent=TEAL,
img_w=6, img_h=4.5
))
story.append(sp(0.4))
# ═══════════════════════════════════════════════════════════════════════════
# PAGE BREAK
# ═══════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 4 — TERRY'S NAILS / LEUKONYCHIA
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_bar("4 | NAIL LEUKONYCHIA — Terry, Lindsay & Muehrcke", color=PURPLE))
story.append(sp(0.3))
leuko_body = [
Paragraph("<b>Terry's nails:</b> Ground-glass whitening of proximal nail bed; narrow (2–3 mm) "
"distal pink/brown band. Lunula not visible.", S["body"]),
bullet("Causes: liver cirrhosis, CCF, diabetes mellitus, hypoalbuminaemia, ageing"),
sp(0.1),
Paragraph("<b>Lindsay (half-and-half) nails:</b> Proximal white + distal red-brown; "
"sharp demarcation at mid-nail.", S["body"]),
bullet("Causes: <b>chronic kidney disease / uraemia</b> (classic), also liver disease"),
sp(0.1),
Paragraph("<b>Muehrcke's lines:</b> Paired white transverse bands on nail <i>bed</i> "
"(not plate) — fade with pressure, do not move with nail growth.", S["body"]),
bullet("Causes: hypoalbuminaemia (<2 g/dL), systemic chemotherapy, nephrotic syndrome"),
sp(0.1),
Paragraph("<b>Mees' lines:</b> Single white transverse bands on the nail <i>plate</i> "
"(move with nail growth).", S["body"]),
bullet("Causes: arsenic/thallium poisoning, renal failure, chemotherapy"),
sp(0.1),
Paragraph("<b>True leukonychia:</b> Opaque white nail plate — trauma, fungal, genetic.", S["body_sm"]),
]
story.append(img_card(
url="https://cdn.orris.care/cdss_images/DermNetNZ_1760031552024_4bf2ecd3-bea0-4b0b-8db1-1faf59479ab3.jpg",
fname="terry_nails.jpg",
cap="Terry's nails — proximal whitening with distal pink band\n(Source: DermNet NZ)",
title="LEUKONYCHIA PATTERNS",
body_paras=leuko_body,
accent=PURPLE,
img_w=5.5, img_h=4.0
))
story.append(sp(0.3))
# Small Lindsay/half-and-half image inline
lindsay_img = fetch_img(
"https://cdn.orris.care/cdss_images/DermNetNZ_1760033364276_9e2dff60-90fd-4516-9506-481207ae2e84.jpg",
"lindsay_nails.jpg", 5.5, 3.5
)
if lindsay_img:
cap2 = Paragraph("Lindsay (half-and-half) nails — CKD/uraemia (DermNet NZ)", S["caption"])
t = Table([[lindsay_img, cap2]],
colWidths=[(W-2*MARGIN)*0.42, (W-2*MARGIN)*0.58])
t.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("LEFTPADDING", (0,0), (-1,-1), 4),
]))
story.append(t)
story.append(sp(0.4))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 5 — PSORIATIC NAILS
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_bar("5 | PSORIATIC NAIL DISEASE", color=AMBER))
story.append(sp(0.3))
pso_body = [
Paragraph("Nail involvement occurs in <b>~50% of psoriasis patients</b> and up to <b>80% of psoriatic "
"arthritis</b> patients. Nail matrix disease vs nail bed disease produce distinct signs.", S["body"]),
sp(0.15),
Paragraph("<b>Matrix involvement:</b>", S["body"]),
bullet("Pitting (most characteristic — shallow pinhole depressions)"),
bullet("Leukonychia (white spots/patches)"),
bullet("Onychorrhexis (longitudinal ridging)"),
bullet("Beau lines"),
sp(0.1),
Paragraph("<b>Nail bed involvement:</b>", S["body"]),
bullet("Onycholysis (distal plate separation)"),
bullet("Oil-drop / salmon patch sign (translucent yellow-pink)"),
bullet("Subungual hyperkeratosis"),
bullet("Splinter haemorrhages"),
sp(0.1),
Paragraph("<b>Clinical significance:</b> Nail psoriasis predicts psoriatic arthritis. "
"NAPSI score used to quantify severity. Treat with topical steroids, "
"vitamin D analogues, biologics (TNF-α inhibitors).", S["body_sm"]),
]
story.append(img_card(
url="https://cdn.orris.care/cdss_images/DermNetNZ_1760035388113_ba6309ba-8f4f-4489-9b44-8a3d4265bcc3.jpg",
fname="psoriatic_nails.jpg",
cap="Psoriatic nail dystrophy: onycholysis + subungual hyperkeratosis\n(Source: DermNet NZ)",
title="PSORIATIC NAILS",
body_paras=pso_body,
accent=AMBER,
img_w=6, img_h=4.5
))
story.append(sp(0.4))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 6 — LICHEN PLANUS
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_bar("6 | NAIL LICHEN PLANUS"))
story.append(sp(0.3))
lp_body = [
Paragraph("Lichenoid inflammation of the nail matrix produces characteristic changes. "
"Can precede cutaneous or mucosal LP. Risk of permanent scarring (pterygium).", S["body"]),
sp(0.15),
bullet("Onychorrhexis (longitudinal ridging — hallmark)"),
bullet("Thinning of nail plate (onychoatrophy)"),
bullet("Fissuring and splitting"),
bullet("Dorsal pterygium formation (scarring from matrix to plate)"),
bullet("Onycholysis, subungual hyperkeratosis"),
bullet("Twenty-nail dystrophy (trachyonychia) — all nails rough/sandpaper"),
sp(0.1),
Paragraph("<b>Associated conditions:</b> Discoid/systemic lupus, graft-vs-host disease, "
"dyskeratosis congenita.", S["body"]),
sp(0.1),
Paragraph("<b>Treatment:</b> Topical / intralesional corticosteroids, systemic "
"immunomodulators for severe disease.", S["body_sm"]),
]
story.append(img_card(
url="https://cdn.orris.care/cdss_images/DermNetNZ_1760033664188_9a020e78-3208-4f1d-a630-a44d60f80a51.jpg",
fname="lichen_planus_nails.jpg",
cap="Nail lichen planus: longitudinal ridging, thinning, pterygium\n(Source: DermNet NZ)",
title="LICHEN PLANUS",
body_paras=lp_body,
accent=TEAL,
img_w=6, img_h=4.5
))
story.append(sp(0.4))
# ═══════════════════════════════════════════════════════════════════════════
# PAGE BREAK — Comprehensive reference table
# ═══════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════
# COMPREHENSIVE REFERENCE TABLE
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_bar("COMPREHENSIVE QUICK-REFERENCE TABLE — Nail Signs & Systemic Causes", color=GREY_DARK))
story.append(sp(0.3))
hdr_style = ParagraphStyle("TH", fontSize=8, textColor=WHITE,
fontName="Helvetica-Bold", leading=10)
cell_style = ParagraphStyle("TC", fontSize=7.5, textColor=GREY_DARK,
leading=10, spaceBefore=1, spaceAfter=1)
tag_style = ParagraphStyle("Tag", fontSize=7, textColor=TEAL,
fontName="Helvetica-Bold", leading=9)
def th(t): return Paragraph(t, hdr_style)
def tc(t): return Paragraph(t, cell_style)
def tag(t, c=TEAL_LIGHT, tc_=None):
ts = ParagraphStyle("Tg2", fontSize=7, textColor=c if tc_ is None else tc_,
fontName="Helvetica-Bold")
return Paragraph(t, ts)
rows = [
# header
[th("Nail Sign"), th("Appearance"), th("Systemic Causes"), th("Investigation")],
# Koilonychia
[tc("Koilonychia\n(Spoon nails)"),
tc("Concave nail plate;\nedges everted upward"),
tc("• Iron deficiency anaemia\n• Plummer-Vinson syndrome\n• Haemochromatosis\n• Thyroid disorders\n• Raynaud / scleroderma"),
tc("CBC, serum ferritin,\nserum iron, TIBC,\ntransferrin saturation")],
# Clubbing
[tc("Clubbing\n(Hippocratic nails)"),
tc("Increased nail convexity;\nLovibond angle ≥180°;\nbulbous fingertips"),
tc("• Lung cancer, IPF, bronchiectasis\n• Cyanotic heart disease\n• Liver cirrhosis, IBD\n• Thyroid acropachy"),
tc("CXR, CT chest,\nechocardiogram,\nSpO2, LFTs")],
# Beau lines
[tc("Beau Lines"),
tc("Transverse grooves;\nall nails at same level\nin systemic disease"),
tc("• Febrile illness / sepsis\n• Chemotherapy\n• Malnutrition, zinc def.\n• COVID-19 (post-acute)"),
tc("Date of insult from\nnail position (3 mm/mo);\nzinc, albumin")],
# Terry's nails
[tc("Terry's Nails"),
tc("Proximal white;\nnarrow distal pink band;\nno lunula visible"),
tc("• Liver cirrhosis\n• Congestive heart failure\n• Diabetes mellitus\n• Hypoalbuminaemia"),
tc("LFTs, albumin,\nBNP/Echo,\nHbA1c")],
# Lindsay (half-and-half)
[tc("Lindsay Nails\n(Half-and-half)"),
tc("Proximal white;\ndistal reddish-brown;\nsharp demarcation"),
tc("• Chronic kidney disease\n• Uraemia / dialysis\n• Liver disease"),
tc("eGFR, creatinine,\nurea, urinalysis")],
# Muehrcke lines
[tc("Muehrcke's Lines"),
tc("Paired white transverse\nbands on nail bed;\nfade with pressure"),
tc("• Hypoalbuminaemia (<2 g/dL)\n• Chemotherapy\n• Nephrotic syndrome"),
tc("Serum albumin,\nurine protein,\n24h protein")],
# Mees' lines
[tc("Mees' Lines"),
tc("Single white transverse\nbands on nail plate;\nmove with growth"),
tc("• Arsenic / thallium poisoning\n• Renal failure\n• Chemotherapy"),
tc("Heavy metals panel,\nrenal function,\nchemo history")],
# Splinter haemorrhages
[tc("Splinter\nHaemorrhages"),
tc("Dark red-brown linear\nstreaks under nail;\ndistal = trauma;\nproximal = systemic"),
tc("• Infective endocarditis\n• Vasculitis (SLE, RA)\n• Antiphospholipid syndrome\n• Trauma (distal)"),
tc("Blood cultures, Echo,\nANA, ANCA,\naPL antibodies")],
# Onycholysis
[tc("Onycholysis"),
tc("Nail plate separated\nfrom bed distally;\nwhite/yellow appearance"),
tc("• Psoriasis (oil-drop sign)\n• Thyrotoxicosis\n• Drug-induced (taxanes)\n• Fungal infection"),
tc("TFTs, KOH/culture,\nmedication review")],
# Yellow nail syndrome
[tc("Yellow Nail\nSyndrome"),
tc("Thickened, curved,\nyellow-green nails;\nslow growth; no lunula"),
tc("• Lymphoedema\n• Respiratory disease (pleural effusion, bronchiectasis)\n• Rarely malignancy"),
tc("CXR, CT chest,\nlymphoscintigraphy")],
# Pitting
[tc("Nail Pitting"),
tc("Small punched-out\ndepressions on\nnail surface"),
tc("• Psoriasis (most common)\n• Alopecia areata\n• Eczema\n• Reactive arthritis"),
tc("Clinical correlation;\njoint exam for PsA;\nskip biopsy for classic")],
# Onychomycosis
[tc("Onychomycosis"),
tc("Thickened, discoloured,\nfriable nail; subungual\nhyperkeratosis;\nyellow-white-brown"),
tc("• Tinea unguium (T. rubrum)\n• Candida (fingers > toes)\n• Non-dermatophyte mould"),
tc("KOH microscopy,\nfungal culture,\nPCR/nail clipping\nbiopsy")],
]
col_w = [(W-2*MARGIN)*x for x in [0.18, 0.22, 0.37, 0.23]]
tbl = Table(rows, colWidths=col_w, repeatRows=1)
ts = TableStyle([
# Header
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 8),
("TOPPADDING", (0,0), (-1,0), 6),
("BOTTOMPADDING",(0,0), (-1,0), 6),
# Alternating rows
("BACKGROUND", (0,1), (-1,1), TEAL_LIGHT),
("BACKGROUND", (0,2), (-1,2), WHITE),
("BACKGROUND", (0,3), (-1,3), TEAL_LIGHT),
("BACKGROUND", (0,4), (-1,4), WHITE),
("BACKGROUND", (0,5), (-1,5), TEAL_LIGHT),
("BACKGROUND", (0,6), (-1,6), WHITE),
("BACKGROUND", (0,7), (-1,7), TEAL_LIGHT),
("BACKGROUND", (0,8), (-1,8), WHITE),
("BACKGROUND", (0,9), (-1,9), TEAL_LIGHT),
("BACKGROUND", (0,10),(-1,10), WHITE),
("BACKGROUND", (0,11),(-1,11), TEAL_LIGHT),
("BACKGROUND", (0,12),(-1,12), WHITE),
# Grid
("GRID", (0,0), (-1,-1), 0.4, GREY_LINE),
("LINEABOVE", (0,0), (-1,0), 1.0, TEAL),
("LINEBELOW", (0,-1),(-1,-1), 1.0, TEAL),
# Padding
("TOPPADDING", (0,1), (-1,-1), 5),
("BOTTOMPADDING",(0,1), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
])
tbl.setStyle(ts)
story.append(tbl)
story.append(sp(0.5))
# ═══════════════════════════════════════════════════════════════════════════
# SYSTEMATIC EXAMINATION BOX
# ═══════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(section_bar("SYSTEMATIC NAIL EXAMINATION APPROACH", color=GREY_DARK))
story.append(sp(0.3))
exam_data = [
["STEP", "EXAMINE", "LOOK FOR"],
["1", "Nail plate", "Colour, thickness, surface texture, shape (flat/spoon/curved), ridging, pitting"],
["2", "Nail bed", "Colour (white/red/brown bands), onycholysis, subungual debris, haemorrhages"],
["3", "Lunula", "Size, shape, visibility; absent in Terry's nails"],
["4", "Nail fold\n(proximal + lateral)", "Cuticle integrity, pterygium formation, nail fold capillaries, paronychia"],
["5", "Fingertip", "Clubbing (Schamroth sign), calcinosis, digital infarcts"],
["6", "All 20 nails", "Symmetry; if all nails affected → systemic cause; one nail → local cause"],
]
hd2 = ParagraphStyle("TH2", fontSize=8, textColor=WHITE,
fontName="Helvetica-Bold", leading=10)
tc2 = ParagraphStyle("TC2", fontSize=8, textColor=GREY_DARK, leading=11)
exam_rows = []
for i, row in enumerate(exam_data):
if i == 0:
exam_rows.append([Paragraph(c, hd2) for c in row])
else:
exam_rows.append([Paragraph(c, tc2) for c in row])
exam_col_w = [(W-2*MARGIN)*x for x in [0.07, 0.22, 0.71]]
etbl = Table(exam_rows, colWidths=exam_col_w, repeatRows=1)
etbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("BACKGROUND", (0,1), (-1,-1), GREY_LIGHT),
("ROWBACKGROUNDS",(0,1),(-1,-1), [WHITE, GREY_LIGHT]),
("GRID", (0,0), (-1,-1), 0.4, GREY_LINE),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), TEAL),
("FONTSIZE", (0,1), (0,-1), 10),
("ALIGN", (0,0), (0,-1), "CENTER"),
]))
story.append(etbl)
story.append(sp(0.4))
# ── Lab workup summary box ──────────────────────────────────────────────────
story.append(Paragraph("<b>KEY INVESTIGATIONS FOR NAIL DYSTROPHIES</b>", S["heading2"]))
story.append(sp(0.1))
labs = [
("Haematology", "CBC with differential, serum ferritin, serum iron, TIBC, transferrin saturation"),
("Biochemistry", "LFTs (albumin, bilirubin, GGT), renal function (eGFR, creatinine, urea), TFTs (TSH, free T4), HbA1c"),
("Immunology", "ANA, ANCA, anti-dsDNA, antiphospholipid antibodies, complement (C3/C4)"),
("Microbiology","KOH microscopy, fungal culture, nail clipping biopsy, blood cultures (if endocarditis suspected)"),
("Imaging", "CXR / CT chest (clubbing, yellow nail), echocardiogram (endocarditis, cardiac causes), lymphoscintigraphy"),
("Toxicology", "Serum arsenic, thallium (Mees' lines with unexplained aetiology)"),
]
lab_rows = [[Paragraph(f"<b>{k}</b>", cell_style), Paragraph(v, cell_style)] for k, v in labs]
ltbl = Table(lab_rows, colWidths=[(W-2*MARGIN)*0.22, (W-2*MARGIN)*0.78])
ltbl.setStyle(TableStyle([
("GRID", (0,0), (-1,-1), 0.4, GREY_LINE),
("BACKGROUND", (0,0), (0,-1), TEAL_LIGHT),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(ltbl)
story.append(sp(0.5))
# ── Footnote ────────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=0.5, color=GREY_LINE))
story.append(sp(0.15))
story.append(Paragraph(
"This quick reference was compiled from authoritative medical textbooks including Goldman-Cecil Medicine, "
"Andrews' Diseases of the Skin, Fitzpatrick's Dermatology, Dermatology 2-Volume Set 5e, and Harrison's Principles "
"of Internal Medicine 22e. Images © DermNet NZ and PMC Clinical VQA. For clinical decisions always confirm "
"with current guidelines and individual patient context.",
S["footnote"]
))
# ═══════════════════════════════════════════════════════════════════════════
# BUILD PDF
# ═══════════════════════════════════════════════════════════════════════════
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=MARGIN + 0.4*cm, bottomMargin=1.3*cm,
title="Nail Dystrophies & Systemic Causes — Quick Reference",
author="Orris Medical AI",
subject="Dermatology / Internal Medicine"
)
doc.build(story,
onFirstPage=cover_banner,
onLaterPages=later_pages)
print(f"\n✅ PDF created: {OUTPUT}")
cd /home/daytona/workspace/nail-dystrophies && python3 generate_pdf.py 2>&1
ls -lh /home/daytona/workspace/nail-dystrophies/Nail_Dystrophies_Quick_Reference…
Nail Dystrophies Quick Reference
PDF Document · PDF
| Nail Sign | Appearance | Systemic Causes | Investigation |
|---|---|---|---|
| Koilonychia | Spoon-shaped | Iron deficiency, Plummer-Vinson... | Ferritin, TIBC |
| Clubbing | Lovibond ≥180° | Lung Ca, IPF, CCF... | CXR, Echo |
| Beau lines | Transverse grooves | Sepsis, chemo, COVID... | Nail timing |
| Terry's | Proximal white | Cirrhosis, CCF, DM... | LFTs, albumin |
| Lindsay | Half-and-half | CKD / uraemia | eGFR, creatinine |
| … and 7 more |