Draw the diagram of malignant melanoma and rhinosporidiosis
Medical diagram of malignant melanoma showing cross-section of skin layers with labeled components: epidermis, dermis, subcutaneous tissue, melanocytes, tumor cells invading deeper layers, radial growth phase, vertical growth phase, Clark levels (I through V), Breslow thickness measurement, irregular pigmented tumor mass with satellite lesions, lymphatic invasion, and blood vessel involvement. Educational medical illustration style with clear labels.
Medical diagram of rhinosporidiosis showing cross-section of nasal mucosa with labeled components: Rhinosporidium seeberi organism life cycle, sporangia (mature and immature) in tissue, endospores being released, polyp formation in nasal cavity, host tissue reaction with lymphocytes and plasma cells, epithelial covering of polyp, vascular stroma, goblet cells, and sporangiospores. Educational medical illustration style with clear labels and annotations.


Create a PDF quick reference guide comparing melanoma stages and prognosis
Clean medical infographic diagram of malignant melanoma AJCC staging system showing Stage 0 (in situ), Stage IA, IB, IIA, IIB, IIC, Stage III (lymph node involvement), and Stage IV (distant metastasis). Cross-section of skin showing Breslow thickness levels, with corresponding 5-year survival percentages. Include Clark levels I-V on the left side. Professional medical education style, white background, color-coded by stage severity from green (early) to red (advanced).
mkdir -p /tmp/workspace/melanoma-guide && ls /tmp/workspace/melanoma-guide/
cd /tmp/workspace/melanoma-guide && python3 -c " import urllib.request url = 'https://cdn.orris.care/image-gen/655c59c95d2844ceaacc35879c378da2.png' urllib.request.urlretrieve(url, 'melanoma_staging_diagram.png') print('Downloaded diagram') "
~/melanoma-guide/build_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, Image, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import PageBreak
import os
OUTPUT = "/tmp/workspace/melanoma-guide/Melanoma_Quick_Reference_Guide.pdf"
DIAGRAM = "/tmp/workspace/melanoma-guide/melanoma_staging_diagram.png"
# ── Colour palette ──────────────────────────────────────────────────────────
C_DARK = colors.HexColor("#1A2B45") # deep navy
C_ACCENT = colors.HexColor("#C0392B") # deep red (melanoma awareness)
C_MID = colors.HexColor("#2980B9") # medium blue
C_LIGHT = colors.HexColor("#EBF5FB") # very light blue bg
C_GREEN = colors.HexColor("#1A7A4A")
C_ORANGE = colors.HexColor("#E67E22")
C_RED = colors.HexColor("#C0392B")
C_GRAY = colors.HexColor("#F0F3F4")
C_WHITE = colors.white
# Stage colours: 0 → IV
STAGE_COLS = {
"0": colors.HexColor("#27AE60"),
"IA": colors.HexColor("#2ECC71"),
"IB": colors.HexColor("#82E0AA"),
"IIA": colors.HexColor("#F9E79F"),
"IIB": colors.HexColor("#F39C12"),
"IIC": colors.HexColor("#E67E22"),
"IIIA": colors.HexColor("#EB984E"),
"IIIB": colors.HexColor("#E74C3C"),
"IIIC": colors.HexColor("#C0392B"),
"IIID": colors.HexColor("#922B21"),
"IV": colors.HexColor("#641E16"),
}
# ── Document setup ───────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=1.8*cm, rightMargin=1.8*cm,
topMargin=1.5*cm, bottomMargin=1.5*cm,
title="Malignant Melanoma - Quick Reference Guide",
author="Orris Medical Reference",
subject="Melanoma Staging and Prognosis (AJCC 8th Edition)"
)
W, H = A4
CONTENT_W = W - 3.6*cm
styles = getSampleStyleSheet()
def style(name, **kw):
s = ParagraphStyle(name, **kw)
return s
S_TITLE = style("Title2",
fontName="Helvetica-Bold", fontSize=22, textColor=C_WHITE,
alignment=TA_CENTER, leading=28, spaceAfter=4)
S_SUBTITLE = style("Subtitle2",
fontName="Helvetica", fontSize=11, textColor=C_LIGHT,
alignment=TA_CENTER, leading=16)
S_HEAD1 = style("H1",
fontName="Helvetica-Bold", fontSize=13, textColor=C_WHITE,
backColor=C_DARK, leading=18, spaceBefore=10, spaceAfter=4,
leftIndent=6, rightIndent=6, borderPad=5)
S_HEAD2 = style("H2",
fontName="Helvetica-Bold", fontSize=11, textColor=C_DARK,
leading=16, spaceBefore=8, spaceAfter=3)
S_BODY = style("Body2",
fontName="Helvetica", fontSize=9, textColor=C_DARK,
leading=13, spaceAfter=2, alignment=TA_JUSTIFY)
S_BULLET = style("Bullet2",
fontName="Helvetica", fontSize=9, textColor=C_DARK,
leading=13, spaceAfter=2, leftIndent=12, bulletIndent=4)
S_SMALL = style("Small2",
fontName="Helvetica", fontSize=7.5, textColor=colors.HexColor("#555555"),
leading=11, spaceAfter=1)
S_TABLE_H = style("TH",
fontName="Helvetica-Bold", fontSize=8.5, textColor=C_WHITE,
leading=12, alignment=TA_CENTER)
S_TABLE_B = style("TB",
fontName="Helvetica", fontSize=8, textColor=C_DARK,
leading=11, alignment=TA_LEFT)
S_TABLE_C = style("TC",
fontName="Helvetica", fontSize=8, textColor=C_DARK,
leading=11, alignment=TA_CENTER)
S_CAPTION = style("Cap",
fontName="Helvetica-Oblique", fontSize=8, textColor=colors.HexColor("#555"),
alignment=TA_CENTER, leading=11, spaceAfter=4)
S_WARNING = style("Warn",
fontName="Helvetica-Bold", fontSize=8.5, textColor=C_ACCENT,
leading=13, spaceAfter=2)
S_FOOTER = style("Footer",
fontName="Helvetica-Oblique", fontSize=7.5, textColor=colors.HexColor("#888"),
alignment=TA_CENTER, leading=10)
def header_block(title_text, subtitle_text):
"""Full-width coloured header."""
data = [[Paragraph(title_text, S_TITLE)],
[Paragraph(subtitle_text, S_SUBTITLE)]]
t = Table(data, colWidths=[CONTENT_W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_DARK),
("TOPPADDING", (0,0), (-1,-1), 12),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS", [6]),
]))
return t
def section_header(text):
data = [[Paragraph(text, S_HEAD1)]]
t = Table(data, colWidths=[CONTENT_W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), C_DARK),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
("LEFTPADDING", (0,0),(-1,-1), 8),
("RIGHTPADDING", (0,0),(-1,-1), 8),
]))
return t
# ── Helper: coloured cell ────────────────────────────────────────────────────
def stage_pill(stage_label, text_col=C_WHITE):
bg = STAGE_COLS.get(stage_label, C_MID)
s = style(f"Pill_{stage_label}",
fontName="Helvetica-Bold", fontSize=8.5,
textColor=text_col if bg != colors.HexColor("#F9E79F") else C_DARK,
alignment=TA_CENTER, leading=12)
return Paragraph(stage_label, s)
# ─────────────────────────────────────────────────────────────────────────────
# BUILD CONTENT
# ─────────────────────────────────────────────────────────────────────────────
story = []
# ── HEADER ───────────────────────────────────────────────────────────────────
story.append(header_block(
"MALIGNANT MELANOMA",
"Quick Reference Guide: Staging & Prognosis | AJCC 8th Edition (2017)"
))
story.append(Spacer(1, 10))
# ── OVERVIEW BOX ─────────────────────────────────────────────────────────────
overview_data = [[
Paragraph("<b>What is Melanoma?</b>", S_HEAD2),
]]
overview_text = (
"Melanoma is a malignant neoplasm arising from melanocytes, primarily in the skin. It accounts for "
"only ~1-2% of all skin cancers but is responsible for the majority of skin cancer deaths. "
"Staging is based on the AJCC TNM system (8th edition, 2017), which incorporates <b>tumour thickness "
"(Breslow depth)</b>, <b>ulceration status</b>, <b>mitotic rate</b>, <b>nodal involvement</b>, "
"and <b>distant metastasis site + LDH level</b>."
)
story.append(KeepTogether([
section_header("OVERVIEW"),
Spacer(1, 4),
Paragraph(overview_text, S_BODY),
Spacer(1, 6),
]))
# ── ABCDE RULE ───────────────────────────────────────────────────────────────
abcde_data = [
[Paragraph("<b>A</b>", S_TABLE_H), Paragraph("Asymmetry", S_TABLE_B),
Paragraph("One half does not match the other", S_TABLE_B)],
[Paragraph("<b>B</b>", S_TABLE_H), Paragraph("Border", S_TABLE_B),
Paragraph("Irregular, ragged, notched, or blurred edges", S_TABLE_B)],
[Paragraph("<b>C</b>", S_TABLE_H), Paragraph("Colour", S_TABLE_B),
Paragraph("Variation in colour — shades of brown, black, red, white, blue", S_TABLE_B)],
[Paragraph("<b>D</b>", S_TABLE_H), Paragraph("Diameter", S_TABLE_B),
Paragraph(">6 mm (size of a pencil eraser) at diagnosis; may be smaller", S_TABLE_B)],
[Paragraph("<b>E</b>", S_TABLE_H), Paragraph("Evolving", S_TABLE_B),
Paragraph("Any change in size, shape, colour, or new symptom (bleeding, itching)", S_TABLE_B)],
]
abcde_t = Table(abcde_data, colWidths=[1.2*cm, 3.0*cm, CONTENT_W - 4.2*cm])
abcde_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), C_ACCENT),
("BACKGROUND", (1,0), (-1,-1), C_GRAY),
("ROWBACKGROUNDS",(1,0), (-1,-1), [C_GRAY, C_WHITE]),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#CCCCCC")),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
]))
story.append(KeepTogether([
section_header("ABCDE DIAGNOSTIC CRITERIA"),
Spacer(1, 4),
abcde_t,
Spacer(1, 8),
]))
# ── T CLASSIFICATION ─────────────────────────────────────────────────────────
t_header = [
Paragraph("T Category", S_TABLE_H),
Paragraph("Breslow Thickness", S_TABLE_H),
Paragraph("Ulceration Status", S_TABLE_H),
]
t_data = [
[Paragraph("Tis", S_TABLE_C), Paragraph("In situ (confined to epidermis)", S_TABLE_B), Paragraph("N/A", S_TABLE_C)],
[Paragraph("T1a", S_TABLE_C), Paragraph("<0.8 mm", S_TABLE_B), Paragraph("Without ulceration", S_TABLE_C)],
[Paragraph("T1b", S_TABLE_C), Paragraph("<0.8 mm\n0.8 - <1.0 mm", S_TABLE_B), Paragraph("With ulceration\nWith or without ulceration", S_TABLE_C)],
[Paragraph("T2a", S_TABLE_C), Paragraph("1.0 - <2.0 mm", S_TABLE_B), Paragraph("Without ulceration", S_TABLE_C)],
[Paragraph("T2b", S_TABLE_C), Paragraph("1.0 - <2.0 mm", S_TABLE_B), Paragraph("With ulceration", S_TABLE_C)],
[Paragraph("T3a", S_TABLE_C), Paragraph("2.0 - <4.0 mm", S_TABLE_B), Paragraph("Without ulceration", S_TABLE_C)],
[Paragraph("T3b", S_TABLE_C), Paragraph("2.0 - <4.0 mm", S_TABLE_B), Paragraph("With ulceration", S_TABLE_C)],
[Paragraph("T4a", S_TABLE_C), Paragraph(">4.0 mm", S_TABLE_B), Paragraph("Without ulceration", S_TABLE_C)],
[Paragraph("T4b", S_TABLE_C), Paragraph(">4.0 mm", S_TABLE_B), Paragraph("With ulceration", S_TABLE_C)],
]
t_col_w = [2.2*cm, 5.5*cm, CONTENT_W - 7.7*cm]
t_table = Table([t_header] + t_data, colWidths=t_col_w, repeatRows=1)
t_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK),
("ROWBACKGROUNDS",(0,1), (-1,-1), [C_GRAY, C_WHITE]),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#BBBBBB")),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("ALIGN", (1,1), (1,-1), "LEFT"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
]))
story.append(KeepTogether([
section_header("T CLASSIFICATION (Primary Tumour)"),
Spacer(1, 4),
t_table,
Spacer(1, 4),
Paragraph("* Mitotic rate is no longer a staging criterion in AJCC-8 but remains an independent prognostic factor.", S_SMALL),
Spacer(1, 8),
]))
# ── STAGING TABLE WITH PROGNOSIS ─────────────────────────────────────────────
stage_header = [
Paragraph("Stage", S_TABLE_H),
Paragraph("T", S_TABLE_H),
Paragraph("N", S_TABLE_H),
Paragraph("M", S_TABLE_H),
Paragraph("5-Year Survival", S_TABLE_H),
Paragraph("Key Features", S_TABLE_H),
]
stage_rows = [
# [stage, T, N, M, 5yr, desc, row_bg_key]
["0", "Tis", "N0", "M0", "~100%", "Melanoma in situ; confined to epidermis", "0"],
["IA", "T1a", "N0", "M0", "99%", "Thin (<0.8mm), no ulceration; excellent prognosis", "IA"],
["IB", "T1b/T2a", "N0", "M0", "97%", "Thin with ulceration OR 1-2mm no ulceration", "IB"],
["IIA", "T2b/T3a", "N0", "M0", "94%", "1-2mm ulcerated OR 2-4mm no ulceration", "IIA"],
["IIB", "T3b/T4a", "N0", "M0", "87%", "2-4mm ulcerated OR >4mm no ulceration", "IIB"],
["IIC", "T4b", "N0", "M0", "82%", "Thick (>4mm) with ulceration; worst localised stage", "IIC"],
["IIIA", "T1a-T2a", "N1a/N2a", "M0", "93%", "Non-ulcerated + up to 3 nodal micrometastases", "IIIA"],
["IIIB", "T1a-T3a", "N1b/N2b", "M0", "83%", "Non-ulcerated + macrometastases or in-transit disease", "IIIB"],
["IIIC", "T1a-T4b", "N2c/N3", "M0", "69%", "Any ulcerated + multiple nodes or lymphatic spread", "IIIC"],
["IIID", "T4b", "N3a/b/c", "M0", "32%", "Thick ulcerated + >=4 nodes; worst regional stage", "IIID"],
["IV", "Any T", "Any N", "M1", "<20%", "Distant metastases; prognosis by site + LDH level", "IV"],
]
def make_stage_row(row):
s_key = row[6]
bg = STAGE_COLS.get(s_key, C_MID)
text_c = C_WHITE if s_key not in ("IIA", "IB") else C_DARK
pill_style = style(f"PillS_{s_key}",
fontName="Helvetica-Bold", fontSize=9, textColor=text_c,
alignment=TA_CENTER, leading=12)
return [
Paragraph(row[0], pill_style),
Paragraph(row[1], S_TABLE_C),
Paragraph(row[2], S_TABLE_C),
Paragraph(row[3], S_TABLE_C),
Paragraph(row[4], S_TABLE_C),
Paragraph(row[5], S_TABLE_B),
], bg
stage_col_w = [1.4*cm, 2.2*cm, 2.2*cm, 1.4*cm, 2.4*cm, CONTENT_W - 9.6*cm]
stage_table_data = [stage_header]
row_bgs = [C_DARK]
for r in stage_rows:
row_data, bg = make_stage_row(r)
stage_table_data.append(row_data)
row_bgs.append(bg)
stage_table = Table(stage_table_data, colWidths=stage_col_w, repeatRows=1)
ts = TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#BBBBBB")),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("ALIGN", (5,1), (5,-1), "LEFT"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
])
for i, bg in enumerate(row_bgs[1:], start=1):
ts.add("BACKGROUND", (0,i), (0,i), bg)
ts.add("BACKGROUND", (1,i), (-1,i), colors.HexColor("#F8F9FA") if i % 2 == 0 else C_WHITE)
stage_table.setStyle(ts)
story.append(KeepTogether([
section_header("AJCC 8TH EDITION STAGING TABLE WITH 5-YEAR SURVIVAL"),
Spacer(1, 4),
stage_table,
Spacer(1, 4),
Paragraph(
"Survival data from AJCC-8 international melanoma database (Gershenwald et al., 2017). "
"Modern immunotherapy and targeted therapy have improved outcomes, particularly for stages III-IV.",
S_SMALL),
Spacer(1, 8),
]))
# ── DIAGRAM ──────────────────────────────────────────────────────────────────
if os.path.exists(DIAGRAM):
img = Image(DIAGRAM, width=CONTENT_W * 0.90, height=CONTENT_W * 0.52)
story.append(KeepTogether([
section_header("STAGING DIAGRAM"),
Spacer(1, 4),
img,
Spacer(1, 3),
Paragraph("Figure: AJCC melanoma staging with Breslow thickness and Clark levels. Colours indicate stage severity (green = early, red = advanced).", S_CAPTION),
Spacer(1, 8),
]))
# ── METASTASIS (M) TABLE ─────────────────────────────────────────────────────
m_header = [Paragraph("M Category", S_TABLE_H),
Paragraph("Site of Distant Metastasis", S_TABLE_H),
Paragraph("LDH", S_TABLE_H),
Paragraph("Median OS", S_TABLE_H)]
m_data = [
[Paragraph("M0", S_TABLE_C), Paragraph("No distant metastasis", S_TABLE_B), Paragraph("Normal", S_TABLE_C), Paragraph("N/A", S_TABLE_C)],
[Paragraph("M1a", S_TABLE_C), Paragraph("Skin, soft tissue, non-regional lymph nodes", S_TABLE_B), Paragraph("Normal/Any", S_TABLE_C), Paragraph("~18-24 mo", S_TABLE_C)],
[Paragraph("M1b", S_TABLE_C), Paragraph("Lung metastases (± M1a sites)", S_TABLE_B), Paragraph("Normal/Any", S_TABLE_C), Paragraph("~12-18 mo", S_TABLE_C)],
[Paragraph("M1c", S_TABLE_C), Paragraph("Non-CNS visceral metastases (± M1a/b)", S_TABLE_B), Paragraph("Elevated = worse", S_TABLE_C), Paragraph("~9-12 mo", S_TABLE_C)],
[Paragraph("M1d", S_TABLE_C), Paragraph("CNS metastases (± any above sites)", S_TABLE_B), Paragraph("Elevated = worse", S_TABLE_C), Paragraph("~4-6 mo", S_TABLE_C)],
]
m_col_w = [2.0*cm, 6.5*cm, 3.0*cm, CONTENT_W - 11.5*cm]
m_table = Table([m_header] + m_data, colWidths=m_col_w, repeatRows=1)
m_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK),
("ROWBACKGROUNDS",(0,1), (-1,-1), [C_GRAY, C_WHITE]),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#BBBBBB")),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("ALIGN", (1,1), (1,-1), "LEFT"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("BACKGROUND", (0,4), (-1,4), colors.HexColor("#FADBD8")), # M1d highlight
]))
story.append(KeepTogether([
section_header("DISTANT METASTASIS (M) CLASSIFICATION & PROGNOSIS"),
Spacer(1, 4),
m_table,
Spacer(1, 4),
Paragraph("OS data are pre-immunotherapy historical estimates; modern checkpoint inhibitors (anti-PD-1/anti-CTLA-4) have substantially improved survival in stages III-IV.", S_SMALL),
Spacer(1, 8),
]))
# ── PROGNOSTIC FACTORS ───────────────────────────────────────────────────────
prog_items = [
("<b>1. Breslow Thickness</b>", "Single most important factor. Every mm increase in depth worsens prognosis significantly."),
("<b>2. Ulceration</b>", "Presence of ulceration in the primary tumour upstages the T category and confers a markedly worse prognosis."),
("<b>3. Mitotic Rate</b>", "Independent prognostic factor even though not in AJCC-8 staging. Rate >=11/mm2 reduces 5-year survival to ~84% even in node-negative disease."),
("<b>4. Nodal Status</b>", "Number of involved nodes, tumour burden (micro vs macro), and in-transit/satellite metastases all independently affect stage III prognosis."),
("<b>5. LDH Level</b>", "Elevated serum LDH in stage IV disease worsens prognosis and adds M(1) suffix. Prior to modern immunotherapy, elevated LDH was associated with <10% 5-year survival."),
("<b>6. Site of Metastasis</b>", "Non-visceral (skin, subcutaneous, distant nodes) > lung > non-CNS visceral > CNS metastases in terms of prognosis."),
("<b>7. Sex & Age</b>", "Women with stage I/II have better melanoma-specific survival than men. Older age and axial anatomic location (head, neck, trunk) confer worse prognosis."),
("<b>8. Primary Site</b>", "Scalp, head, neck, and trunk primaries have worse outcomes than extremity primaries."),
]
prog_rows = []
for title, body in prog_items:
prog_rows.append([
Paragraph(title, S_WARNING),
Paragraph(body, S_BODY),
])
prog_table = Table(prog_rows, colWidths=[4.5*cm, CONTENT_W - 4.5*cm])
prog_table.setStyle(TableStyle([
("ROWBACKGROUNDS", (0,0), (-1,-1), [C_GRAY, C_WHITE]),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#CCCCCC")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
]))
story.append(KeepTogether([
section_header("KEY PROGNOSTIC FACTORS"),
Spacer(1, 4),
prog_table,
Spacer(1, 8),
]))
# ── TREATMENT OVERVIEW ───────────────────────────────────────────────────────
tx_data = [
[Paragraph("Stage", S_TABLE_H), Paragraph("Primary Treatment", S_TABLE_H), Paragraph("Adjuvant / Additional Therapy", S_TABLE_H)],
[Paragraph("0", S_TABLE_C),
Paragraph("Wide local excision (5mm margins)", S_TABLE_B),
Paragraph("None required", S_TABLE_B)],
[Paragraph("I", S_TABLE_C),
Paragraph("Wide local excision (1cm margins)", S_TABLE_B),
Paragraph("SLNB for T1b+. No adjuvant therapy routine", S_TABLE_B)],
[Paragraph("II", S_TABLE_C),
Paragraph("Wide local excision (1-2cm margins)", S_TABLE_B),
Paragraph("SLNB recommended. Adjuvant therapy (pembrolizumab, nivolumab) for high-risk IIB/IIC", S_TABLE_B)],
[Paragraph("III", S_TABLE_C),
Paragraph("Wide excision + lymph node dissection or nodal observation post-SLNB", S_TABLE_B),
Paragraph("Adjuvant targeted therapy (dabrafenib + trametinib if BRAF V600+) or immunotherapy (pembrolizumab/nivolumab)", S_TABLE_B)],
[Paragraph("IV", S_TABLE_C),
Paragraph("Systemic therapy; surgery if oligometastatic", S_TABLE_B),
Paragraph("Anti-PD-1 +/- anti-CTLA-4 (ipilimumab); BRAF/MEK inhibitors if BRAF V600+; radiation for CNS mets", S_TABLE_B)],
]
tx_col_w = [1.5*cm, 5.5*cm, CONTENT_W - 7.0*cm]
tx_table = Table(tx_data, colWidths=tx_col_w, repeatRows=1)
tx_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK),
("ROWBACKGROUNDS",(0,1), (-1,-1), [C_GRAY, C_WHITE]),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#BBBBBB")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("ALIGN", (0,0), (0,-1), "CENTER"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
]))
story.append(KeepTogether([
section_header("TREATMENT OVERVIEW BY STAGE"),
Spacer(1, 4),
tx_table,
Spacer(1, 4),
Paragraph("SLNB = sentinel lymph node biopsy. Margins and systemic therapy per NCCN guidelines. Always confirm BRAF mutation status for stages III-IV.", S_SMALL),
Spacer(1, 8),
]))
# ── SURVIVAL SUMMARY BOX ─────────────────────────────────────────────────────
surv_data = [
[Paragraph("Stage 0", style("s0", fontName="Helvetica-Bold", fontSize=9, textColor=C_WHITE, alignment=TA_CENTER, leading=12)),
Paragraph("Stage IA", style("sia", fontName="Helvetica-Bold", fontSize=9, textColor=C_WHITE, alignment=TA_CENTER, leading=12)),
Paragraph("Stage IB", style("sib", fontName="Helvetica-Bold", fontSize=9, textColor=C_DARK, alignment=TA_CENTER, leading=12)),
Paragraph("Stage IIA", style("siia",fontName="Helvetica-Bold", fontSize=9, textColor=C_DARK, alignment=TA_CENTER, leading=12)),
Paragraph("Stage IIB", style("siib",fontName="Helvetica-Bold", fontSize=9, textColor=C_WHITE, alignment=TA_CENTER, leading=12)),
Paragraph("Stage IIC", style("siic",fontName="Helvetica-Bold", fontSize=9, textColor=C_WHITE, alignment=TA_CENTER, leading=12)),
],
[Paragraph("~100%", S_TABLE_C), Paragraph("99%", S_TABLE_C),
Paragraph("97%", S_TABLE_C), Paragraph("94%", S_TABLE_C),
Paragraph("87%", S_TABLE_C), Paragraph("82%", S_TABLE_C)],
[Paragraph("Stage IIIA", style("s3a",fontName="Helvetica-Bold", fontSize=9, textColor=C_WHITE, alignment=TA_CENTER, leading=12)),
Paragraph("Stage IIIB", style("s3b",fontName="Helvetica-Bold", fontSize=9, textColor=C_WHITE, alignment=TA_CENTER, leading=12)),
Paragraph("Stage IIIC", style("s3c",fontName="Helvetica-Bold", fontSize=9, textColor=C_WHITE, alignment=TA_CENTER, leading=12)),
Paragraph("Stage IIID", style("s3d",fontName="Helvetica-Bold", fontSize=9, textColor=C_WHITE, alignment=TA_CENTER, leading=12)),
Paragraph("Stage IV", style("siv", fontName="Helvetica-Bold", fontSize=9, textColor=C_WHITE, alignment=TA_CENTER, leading=12)),
Paragraph("Stage IV\n(elevated LDH)", style("sivl",fontName="Helvetica-Bold", fontSize=8, textColor=C_WHITE, alignment=TA_CENTER, leading=11)),
],
[Paragraph("93%", S_TABLE_C), Paragraph("83%", S_TABLE_C),
Paragraph("69%", S_TABLE_C), Paragraph("32%", S_TABLE_C),
Paragraph("<20%", S_TABLE_C), Paragraph("<10%", S_TABLE_C)],
]
cw6 = [CONTENT_W/6]*6
surv_t = Table(surv_data, colWidths=cw6)
surv_t.setStyle(TableStyle([
# Row 0 bg per stage
("BACKGROUND", (0,0),(0,0), STAGE_COLS["0"]),
("BACKGROUND", (1,0),(1,0), STAGE_COLS["IA"]),
("BACKGROUND", (2,0),(2,0), STAGE_COLS["IB"]),
("BACKGROUND", (3,0),(3,0), STAGE_COLS["IIA"]),
("BACKGROUND", (4,0),(4,0), STAGE_COLS["IIB"]),
("BACKGROUND", (5,0),(5,0), STAGE_COLS["IIC"]),
# Row 1 light
("BACKGROUND", (0,1),(-1,1), C_LIGHT),
# Row 2 bg per stage
("BACKGROUND", (0,2),(0,2), STAGE_COLS["IIIA"]),
("BACKGROUND", (1,2),(1,2), STAGE_COLS["IIIB"]),
("BACKGROUND", (2,2),(2,2), STAGE_COLS["IIIC"]),
("BACKGROUND", (3,2),(3,2), STAGE_COLS["IIID"]),
("BACKGROUND", (4,2),(4,2), STAGE_COLS["IV"]),
("BACKGROUND", (5,2),(5,2), STAGE_COLS["IV"]),
# Row 3 light
("BACKGROUND", (0,3),(-1,3), C_LIGHT),
("GRID", (0,0),(-1,-1), 0.5, C_WHITE),
("ALIGN", (0,0),(-1,-1), "CENTER"),
("VALIGN",(0,0),(-1,-1), "MIDDLE"),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
("FONTNAME", (0,1),(-1,1), "Helvetica-Bold"),
("FONTSIZE", (0,1),(-1,1), 11),
("FONTNAME", (0,3),(-1,3), "Helvetica-Bold"),
("FONTSIZE", (0,3),(-1,3), 11),
]))
story.append(KeepTogether([
section_header("5-YEAR SURVIVAL SUMMARY (AJCC-8 DATA)"),
Spacer(1, 4),
surv_t,
Spacer(1, 4),
Paragraph("Note: Survival rates reflect historical AJCC-8 database figures. Current immunotherapy (anti-PD-1, anti-CTLA-4) and targeted (BRAF/MEK inhibitor) combinations are significantly improving outcomes for stages III-IV.", S_SMALL),
Spacer(1, 8),
]))
# ── CLARK LEVELS ─────────────────────────────────────────────────────────────
clark_data = [
[Paragraph("Clark Level", S_TABLE_H), Paragraph("Anatomic Level of Invasion", S_TABLE_H), Paragraph("Equivalent Breslow Depth", S_TABLE_H)],
[Paragraph("I", S_TABLE_C), Paragraph("Confined to epidermis (in situ)", S_TABLE_B), Paragraph("<0.1 mm", S_TABLE_C)],
[Paragraph("II", S_TABLE_C), Paragraph("Invades papillary dermis", S_TABLE_B), Paragraph("<0.75 mm", S_TABLE_C)],
[Paragraph("III", S_TABLE_C), Paragraph("Fills papillary dermis; compresses reticular dermis", S_TABLE_B), Paragraph("~0.76-1.5 mm", S_TABLE_C)],
[Paragraph("IV", S_TABLE_C), Paragraph("Invades reticular dermis", S_TABLE_B), Paragraph("~1.5-4.0 mm", S_TABLE_C)],
[Paragraph("V", S_TABLE_C), Paragraph("Invades subcutaneous tissue", S_TABLE_B), Paragraph(">4.0 mm", S_TABLE_C)],
]
clark_cw = [2.0*cm, 8.0*cm, CONTENT_W - 10.0*cm]
clark_t = Table(clark_data, colWidths=clark_cw, repeatRows=1)
clark_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK),
("ROWBACKGROUNDS",(0,1), (-1,-1), [C_GRAY, C_WHITE]),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#BBBBBB")),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("ALIGN", (1,1), (1,-1), "LEFT"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
]))
story.append(KeepTogether([
section_header("CLARK LEVELS OF INVASION"),
Spacer(1, 4),
clark_t,
Spacer(1, 4),
Paragraph("Clark level has largely been replaced by Breslow thickness in AJCC-8 as the primary T-staging criterion, but remains relevant in thin (T1) melanomas.", S_SMALL),
Spacer(1, 8),
]))
# ── FOOTER NOTE ──────────────────────────────────────────────────────────────
story.append(HRFlowable(width=CONTENT_W, thickness=0.5, color=C_MID))
story.append(Spacer(1, 4))
story.append(Paragraph(
"Sources: Dermatology 2-Volume Set 5e (Elsevier) | Current Surgical Therapy 14e | Goldman-Cecil Medicine | AJCC Cancer Staging Manual 8th Ed. (Springer, 2017) | Orris Medical Reference. "
"Prepared for educational purposes only. Clinical decisions must be made in consultation with current national guidelines and specialist input.",
S_FOOTER))
story.append(Spacer(1, 3))
story.append(Paragraph("Generated by Orris | August 2026", S_FOOTER))
# ── BUILD ────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF saved to: {OUTPUT}")
cd /tmp/workspace/melanoma-guide && python3 build_pdf.py 2>&1
ls -lh /tmp/workspace/melanoma-guide/
Melanoma Quick Reference Guide
PDF Document · PDF
| Section | Content |
|---|---|
| Header | Title block with AJCC 8th edition labelling |
| Overview | What melanoma is, key staging determinants |
| ABCDE Criteria | Colour-coded diagnostic criteria table |
| T Classification | All T categories (Tis, T1a-T4b) with Breslow depth and ulceration status |
| Staging Table | Full AJCC 8 stages (0 - IV) with TNM criteria and 5-year survival % |
| Staging Diagram | Generated infographic of Clark levels and Breslow thickness by stage |
| M Classification | M0-M1d with site of metastasis, LDH role, and median OS estimates |
| Key Prognostic Factors | 8 factors: thickness, ulceration, mitotic rate, nodal status, LDH, metastasis site, sex/age, primary location |
| Treatment Overview | Stage-by-stage management (surgery margins, SLNB, adjuvant immunotherapy/targeted therapy) |
| 5-Year Survival Summary | Visual colour-coded grid from Stage 0 (~100%) to Stage IV (<10%) |
| Clark Levels | All 5 levels with anatomic description and approximate Breslow equivalent |
| References | Dermatology 5e, Current Surgical Therapy 14e, Goldman-Cecil Medicine, AJCC 8th Ed. |