Tell about actinic keratosis in detailed
"actinic keratosis"[MeSH Terms] AND treatment

| Subtype | Features |
|---|---|
| Classic (atrophic) | Pink-red scaly macule/papule; subtle scale on erythematous base |
| Hypertrophic (hyperkeratotic) | Thick papule/plaque with white or yellow-brown scale; may develop a cutaneous horn; hardest to distinguish from early SCC |
| Pigmented | Hyperpigmented, reticulated appearance; lacks obvious erythema; mimics lentigo maligna or reticulated seborrheic keratosis |
| Lichenoid | More erythema at the base; pruritic or tender; lichenoid infiltrate on histology |
| Actinic cheilitis | Affects lower vermilion lip; presents as erythematous patches, scale, and leukoplakia; higher SCC risk than skin AKs |
| Bowenoid | Full-thickness epidermal atypia; considered SCC in situ (Bowen disease) |
| Agent | Mechanism | Regimen | Notes |
|---|---|---|---|
| 5-Fluorouracil (5-FU) | Pyrimidine antimetabolite; inhibits thymidylate synthase | 5% cream (Efudex) daily x 4 weeks; 0.5% cream daily x 4 weeks | Standard field therapy; causes significant inflammation; better cosmetic clearance |
| Imiquimod | TLR-7/8 agonist; induces interferon-alpha and innate immune response | 5% cream 3-5x/week x 12-16 weeks; 3.75% cream daily x 2+2 weeks (Zyclara) | Immunomodulator; 5% approved for 25 cm² only |
| Diclofenac 3% gel | COX-2 inhibitor; anti-proliferative | Twice daily x 60-90 days (Solaraze) | Milder side effects than 5-FU; good tolerability |
| Ingenol mebutate | Induces necrosis and immune-mediated destruction | 0.015% gel x 3 days (face/scalp); 0.05% gel x 2 days (trunk/extremities) | Very short course; note: EU withdrew approval in 2020 due to SCC risk signal |
| Photodynamic therapy (PDT) | ALA or methyl-ALA → protoporphyrin IX → activated by blue/red light → reactive O₂ species | Office procedure; 1-3 hour ALA incubation + 16 min light activation | Single treatment clearance 50-70%; up to 90% with additional sessions; facial lesions respond better; better cosmetic outcome than cryotherapy per Cochrane data |
| Feature | Details |
|---|---|
| Cause | Chronic UV radiation (UVB > UVA) |
| Key mutation | TP53 |
| Age | Elderly; increases with cumulative sun exposure |
| Phototype | Fitzpatrick I-III (fair skin) |
| Sites | Face, bald scalp, dorsal hands, ears, forearms |
| Morphology | Rough, erythematous, scaly macule/papule |
| Histology | Basal atypia, parakeratosis, hyperkeratosis, solar elastosis |
| Risk of SCC | ~0.1-2.6% per lesion per year |
| 1st-line Rx (few lesions) | Cryotherapy |
| 1st-line Rx (field) | 5-FU, imiquimod, PDT, diclofenac |
| Biopsy indicated | Indurated, large, recurrent, or ulcerated lesions |
Create a PDF quick reference guide on AK diagnosis and treatment protocols
mkdir -p /home/daytona/workspace/ak-guide && ls /home/daytona/workspace/ak-guide
~/ak-guide/generate_ak_guide.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus import PageBreak
OUTPUT = "/home/daytona/workspace/ak-guide/AK_Quick_Reference_Guide.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
TEAL = colors.HexColor("#1A6B72")
TEAL_LIGHT = colors.HexColor("#E6F4F5")
ORANGE = colors.HexColor("#E07B39")
ORANGE_LT = colors.HexColor("#FDF0E8")
RED = colors.HexColor("#C0392B")
RED_LT = colors.HexColor("#FDEDEC")
GREEN = colors.HexColor("#1E8449")
GREEN_LT = colors.HexColor("#E9F7EF")
GREY_DARK = colors.HexColor("#2C3E50")
GREY_MID = colors.HexColor("#566573")
GREY_LIGHT = colors.HexColor("#ECF0F1")
WHITE = colors.white
PAGE_W, PAGE_H = A4
MARGIN = 18 * mm
# ── Document setup ───────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=20 * mm, bottomMargin=18 * mm,
title="Actinic Keratosis Quick Reference Guide",
author="Orris Medical Reference"
)
styles = getSampleStyleSheet()
# Custom styles
def S(name, parent="Normal", **kw):
return ParagraphStyle(name, parent=styles[parent], **kw)
H1 = S("H1", fontSize=22, textColor=WHITE, fontName="Helvetica-Bold",
spaceAfter=4, leading=26, alignment=TA_CENTER)
H2 = S("H2", fontSize=12, textColor=WHITE, fontName="Helvetica-Bold",
spaceAfter=2, leading=15)
H3 = S("H3", fontSize=10, textColor=TEAL, fontName="Helvetica-Bold",
spaceAfter=3, leading=13)
BODY = S("BODY", fontSize=8.5, textColor=GREY_DARK, leading=12,
spaceAfter=3, alignment=TA_JUSTIFY)
BODY_SM = S("BODY_SM", fontSize=7.8, textColor=GREY_DARK, leading=11, spaceAfter=2)
BULLET = S("BULLET", fontSize=8.5, textColor=GREY_DARK, leading=12,
leftIndent=10, spaceAfter=2,
bulletIndent=2, bulletText="\u2022")
CAPTION = S("CAP", fontSize=7, textColor=GREY_MID, leading=9,
alignment=TA_CENTER, spaceAfter=4)
WARN = S("WARN", fontSize=8, textColor=RED, fontName="Helvetica-Bold",
leading=11, spaceAfter=2)
LABEL = S("LABEL", fontSize=7.5, textColor=GREY_MID, fontName="Helvetica-Bold",
leading=10)
TH = S("TH", fontSize=8, textColor=WHITE, fontName="Helvetica-Bold",
leading=11, alignment=TA_CENTER)
TD = S("TD", fontSize=8, textColor=GREY_DARK, leading=11, alignment=TA_LEFT)
TD_C = S("TD_C", fontSize=8, textColor=GREY_DARK, leading=11, alignment=TA_CENTER)
USABLE_W = PAGE_W - 2 * MARGIN
# ── Helper: section header band ─────────────────────────────────────────────
def section_header(title, bg=TEAL, icon=""):
text = f"{icon} {title}" if icon else title
tbl = Table([[Paragraph(text, H2)]], colWidths=[USABLE_W])
tbl.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), bg),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("RIGHTPADDING", (0, 0), (-1, -1), 8),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING",(0, 0), (-1, -1), 5),
("ROUNDEDCORNERS", [4]),
]))
return tbl
def spacer(h=4):
return Spacer(1, h * mm)
def hr(color=TEAL_LIGHT, thickness=0.8):
return HRFlowable(width="100%", thickness=thickness, color=color, spaceAfter=3)
# ── PAGE 1 ───────────────────────────────────────────────────────────────────
story = []
# ---- Title banner ----
title_tbl = Table(
[[Paragraph("ACTINIC KERATOSIS", H1)],
[Paragraph("Quick Reference Guide for Diagnosis & Treatment",
S("sub", fontSize=11, textColor=TEAL_LIGHT, fontName="Helvetica",
alignment=TA_CENTER, leading=14))]],
colWidths=[USABLE_W]
)
title_tbl.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), TEAL),
("TOPPADDING", (0, 0), (-1, -1), 10),
("BOTTOMPADDING", (0, 0), (-1, -1), 10),
("LEFTPADDING", (0, 0), (-1, -1), 12),
("RIGHTPADDING", (0, 0), (-1, -1), 12),
]))
story.append(title_tbl)
story.append(spacer(3))
# ---- Subtitle strip ----
sub_data = [
[Paragraph("Dermatology Clinical Reference", S("ss", fontSize=7.5, textColor=GREY_MID,
alignment=TA_CENTER, leading=10)),
Paragraph("Sources: Fitzpatrick's | Dermatology 5e | Robbins Pathology | Andrews'",
S("ss2", fontSize=7.5, textColor=GREY_MID, alignment=TA_CENTER, leading=10))]
]
sub_tbl = Table(sub_data, colWidths=[USABLE_W/2]*2)
sub_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GREY_LIGHT),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
]))
story.append(sub_tbl)
story.append(spacer(4))
# ════════════════════ ROW 1: OVERVIEW + RISK FACTORS ════════════════════
# ---- OVERVIEW box ----
overview_content = [
[Paragraph("OVERVIEW", S("ovh", fontSize=9, textColor=TEAL, fontName="Helvetica-Bold", leading=12))],
[Paragraph(
"<b>Actinic keratosis (AK)</b> is a UV-induced intraepidermal premalignant "
"keratinocytic lesion arising on chronically sun-damaged skin. AKs are "
"true precursor lesions of cutaneous squamous cell carcinoma (cSCC). "
"Atypical keratinocytes are confined to the epidermis; invasion does not "
"occur until progression to invasive cSCC.",
BODY_SM
)],
[Paragraph(
"<b>Synonyms:</b> Solar keratosis (non-preferred), senile keratosis (non-preferred). "
"The abbreviation <i>SK</i> is avoided because it conflicts with seborrheic keratosis.",
BODY_SM
)],
[Paragraph(
"<b>Malignant transformation risk:</b> 0.1% - 2.6% per lesion per year; "
"cumulative risk is higher in patients with multiple lesions (field cancerization).",
BODY_SM
)],
]
overview_tbl = Table(overview_content, colWidths=[USABLE_W * 0.52])
overview_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL_LIGHT),
("BACKGROUND", (0,1), (-1,-1), colors.white),
("BOX", (0,0), (-1,-1), 0.8, TEAL),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
]))
# ---- RISK FACTORS box ----
rf_rows = [
[Paragraph("RISK FACTORS", S("rfh", fontSize=9, textColor=ORANGE, fontName="Helvetica-Bold", leading=12))],
]
risk_items = [
("Patient factors", "Fair skin (Fitzpatrick I-III), elderly, male sex, immunosuppression"),
("Sun exposure", "Chronic cumulative UVB > UVA; outdoor workers, geographic latitude"),
("Medical Hx", "Prior AK or skin cancer, genodermatoses (xeroderma pigmentosum)"),
("Drugs", "Immunosuppressants (cyclosporine, azathioprine) - transplant pts at high risk"),
("Infections", "HPV co-infection in immunocompromised"),
]
for label, val in risk_items:
rf_rows.append([Paragraph(f"<b>{label}:</b> {val}", BODY_SM)])
rf_tbl = Table(rf_rows, colWidths=[USABLE_W * 0.44])
rf_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), ORANGE_LT),
("BACKGROUND", (0,1), (-1,-1), colors.white),
("BOX", (0,0), (-1,-1), 0.8, ORANGE),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
]))
row1 = Table([[overview_tbl, rf_tbl]], colWidths=[USABLE_W*0.55, USABLE_W*0.45])
row1.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
("INNERGRID", (0,0), (-1,-1), 0, colors.white),
]))
story.append(row1)
story.append(spacer(3))
# ════════════════════ CLINICAL FEATURES ════════════════════
story.append(section_header("CLINICAL FEATURES & SUBTYPES"))
story.append(spacer(2))
# Subtype table
st_header = [
Paragraph("SUBTYPE", TH),
Paragraph("APPEARANCE", TH),
Paragraph("KEY FEATURES", TH),
Paragraph("DIFFERENTIAL", TH),
]
st_rows = [
["Classic / Atrophic", "Pink-red macule/papule with fine white scale", "Rough texture on palpation; 'sandpaper feel'", "Psoriasis, eczema"],
["Hypertrophic", "Thick papule/plaque; white-yellow scale-crust on erythematous base", "May develop cutaneous horn; most commonly mimics cSCC", "Invasive SCC, wart, seborrheic keratosis"],
["Pigmented", "Hyperpigmented, reticulated; may lack obvious erythema", "Dermoscopy needed; grey dots/annular-granular pattern", "Lentigo maligna, seborrheic keratosis"],
["Lichenoid", "Erythematous base; similar to classic but more inflamed", "Pruritic/tender; lichenoid infiltrate on histology", "Lichen planus-like keratosis"],
["Actinic Cheilitis", "Erythema, scale, leukoplakia on lower vermilion lip", "Higher malignant risk than skin AKs", "Lip SCC, leukoplakia, contact cheilitis"],
["Bowenoid (SCC in situ)", "Erythematous scaly plaque; sharp border", "Full-thickness epidermal atypia = Bowen disease", "Psoriasis, SCC in situ"],
]
st_data = [st_header] + [
[Paragraph(r[0], S("stb", fontSize=7.8, textColor=TEAL, fontName="Helvetica-Bold", leading=11)),
Paragraph(r[1], BODY_SM),
Paragraph(r[2], BODY_SM),
Paragraph(r[3], BODY_SM)] for r in st_rows
]
cw = [USABLE_W * x for x in [0.18, 0.27, 0.32, 0.23]]
st_tbl = Table(st_data, colWidths=cw, repeatRows=1)
st_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, TEAL_LIGHT]),
("BOX", (0,0), (-1,-1), 0.6, TEAL),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(st_tbl)
story.append(spacer(3))
# ════════════════════ DIAGNOSIS PROTOCOL ════════════════════
story.append(section_header("DIAGNOSIS PROTOCOL", bg=GREY_DARK))
story.append(spacer(2))
# 3-column diagnosis layout
col_w = (USABLE_W - 4) / 3
# Column 1: Examination
exam_rows = [
[Paragraph("STEP 1: EXAMINATION", S("exh", fontSize=8.5, textColor=GREY_DARK, fontName="Helvetica-Bold", leading=12))],
[Paragraph("<b>Visual inspection + palpation</b><br/>Sandpaper texture is the key tactile clue - many lesions felt before seen.", BODY_SM)],
[Paragraph("<b>Sites to examine:</b>", BODY_SM)],
[Paragraph("\u2022 Face (forehead, temples, nose, ears)", BODY_SM)],
[Paragraph("\u2022 Bald scalp", BODY_SM)],
[Paragraph("\u2022 Dorsal hands / extensor forearms", BODY_SM)],
[Paragraph("\u2022 Shins, lower lip (cheilitis)", BODY_SM)],
[Paragraph("\u2022 Upper chest / V-neck area", BODY_SM)],
[Paragraph("<b>Note:</b> Background erythema from rosacea or photodamage can mask lesions.", BODY_SM)],
]
exam_tbl = Table(exam_rows, colWidths=[col_w])
exam_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), GREY_LIGHT),
("BOX", (0,0), (-1,-1), 0.6, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
# Column 2: Dermoscopy
derm_rows = [
[Paragraph("STEP 2: DERMOSCOPY", S("dh", fontSize=8.5, textColor=GREY_DARK, fontName="Helvetica-Bold", leading=12))],
[Paragraph("<b>Facial AK - Strawberry pattern:</b><br/>Pseudo-network of red lines + dots around follicular openings on erythematous background.", BODY_SM)],
[Paragraph("<b>Pigmented AK:</b><br/>Annular-granular structures, grey dots/globules; must exclude lentigo maligna.", BODY_SM)],
[Paragraph("<b>Hypertrophic AK:</b><br/>White-yellow structureless scale overlying vessels; exclude SCC.", BODY_SM)],
[Paragraph("<b>Tip:</b> Dermoscopy improves diagnostic accuracy and guides biopsy decision.", BODY_SM)],
]
derm_tbl = Table(derm_rows, colWidths=[col_w])
derm_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), GREY_LIGHT),
("BOX", (0,0), (-1,-1), 0.6, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
# Column 3: Biopsy indications
biopsy_rows = [
[Paragraph("STEP 3: BIOPSY INDICATIONS", S("bh", fontSize=8.5, textColor=RED, fontName="Helvetica-Bold", leading=12))],
[Paragraph("<b>Biopsy is mandatory when:</b>", BODY_SM)],
[Paragraph("\u2022 Lesion is indurated or firm", BODY_SM)],
[Paragraph("\u2022 Rapid growth", BODY_SM)],
[Paragraph("\u2022 Ulceration or spontaneous bleeding", BODY_SM)],
[Paragraph("\u2022 Diameter >1 cm", BODY_SM)],
[Paragraph("\u2022 Recurrence after topical therapy", BODY_SM)],
[Paragraph("\u2022 Diagnosis uncertain (suspect melanoma)", BODY_SM)],
[Paragraph("\u2022 Immunosuppressed patient", BODY_SM)],
[Paragraph("<b>Preferred:</b> Shave or punch biopsy including dermis to assess invasion depth.", BODY_SM)],
]
biopsy_tbl = Table(biopsy_rows, colWidths=[col_w])
biopsy_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED_LT),
("BOX", (0,0), (-1,-1), 0.8, RED),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
diag_row = Table([[exam_tbl, derm_tbl, biopsy_tbl]], colWidths=[col_w+2]*3)
diag_row.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 1),
("RIGHTPADDING", (0,0), (-1,-1), 1),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
]))
story.append(diag_row)
story.append(spacer(3))
# ════════════════════ HISTOPATHOLOGY ════════════════════
story.append(section_header("HISTOPATHOLOGY", bg=GREY_DARK))
story.append(spacer(2))
histo_data = [
[Paragraph("FEATURE", TH), Paragraph("DESCRIPTION", TH),
Paragraph("KIN GRADE", TH), Paragraph("SIGNIFICANCE", TH)],
["Basal cell atypia", "Nuclear pleomorphism, hyperchromasia, increased mitoses in lower epidermis",
"KIN I (lower 1/3)", "Earliest change; minimal invasion risk"],
["Parakeratosis", "Retention of nuclei in stratum corneum",
"All grades", "Abnormal cornification; hallmark of AK"],
["Hyperkeratosis", "Thickened stratum corneum",
"All grades", "More prominent in hypertrophic variant"],
["Solar elastosis", "Blue-gray thickened elastic fibres in dermis",
"All grades", "Marker of chronic UV damage"],
["Mid-epidermal atypia", "Atypia extending to middle third of epidermis",
"KIN II", "Moderate risk"],
["Full-thickness atypia", "Atypia throughout entire epidermis (bowenoid change)",
"KIN III = SCC in situ", "High risk; reclassify as Bowen disease"],
["Follicular sparing", "Atypia spares adnexal epithelium",
"AK (not SCC)", "Useful to distinguish AK from invasive SCC"],
]
h_data = [[Paragraph(c if isinstance(c, str) else c,
BODY_SM if i > 0 else TH)
for c in row] for i, row in enumerate(histo_data)]
histo_tbl = Table(h_data, colWidths=[USABLE_W*x for x in [0.22, 0.34, 0.22, 0.22]])
histo_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), GREY_DARK),
("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, GREY_LIGHT]),
("BOX", (0,0), (-1,-1), 0.6, GREY_DARK),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(histo_tbl)
story.append(PageBreak())
# ════════════════════ PAGE 2 ════════════════════
# ---- Continuation header ----
cont_tbl = Table([[Paragraph("ACTINIC KERATOSIS - Quick Reference Guide",
S("cont", fontSize=10, textColor=TEAL, fontName="Helvetica-Bold",
alignment=TA_CENTER, leading=13))]],
colWidths=[USABLE_W])
cont_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL_LIGHT),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("BOX", (0,0),(-1,-1), 0.8, TEAL),
]))
story.append(cont_tbl)
story.append(spacer(3))
# ════════════════════ TREATMENT PROTOCOLS ════════════════════
story.append(section_header("TREATMENT PROTOCOLS", bg=TEAL))
story.append(spacer(2))
# Decision guide intro
story.append(Paragraph(
"Treatment selection is based on the <b>number and distribution of lesions</b>. "
"Isolated or few lesions (1-5) favour lesion-directed therapy. "
"Multiple or diffuse lesions within a photodamaged field favour field-directed therapy. "
"These approaches can be combined.",
BODY
))
story.append(spacer(2))
# --- LESION-DIRECTED ---
story.append(Paragraph("LESION-DIRECTED THERAPIES (1-5 discrete lesions)", H3))
ld_header = [Paragraph(h, TH) for h in ["TREATMENT", "TECHNIQUE", "EFFICACY", "NOTES / SIDE EFFECTS"]]
ld_rows = [
("Cryotherapy\n(Liquid N2)",
"Open spray; 1 freeze-thaw cycle\n8-10 sec (standard)\nLonger for hypertrophic; shorter for thin skin\n1-2 mm freeze margin",
"Complete response:\n<5 sec: ~39%\n5-20 sec: ~69%\n>20 sec: ~83%",
"Most widely used; fast; office procedure\nSide effects: hypopigmentation, blistering, scarring\nHypertrophic lesions: pre-treat with emollient or curette"),
("Curettage +\nElectrodesiccation",
"Superficial curette to remove AK\nFollow with electrodesiccation of base",
"High for discrete hypertrophic AKs",
"Provides tissue specimen for histology\nUseful when SCC cannot be excluded\nMay scar"),
("Shave/Punch\nBiopsy + Excision",
"Shave biopsy for lesions suspicious for SCC\nPunch or excision for deeper assessment",
"Definitive for single lesions",
"Mandatory for indurated, ulcerated, or recurrent lesions\nAllows staging if invasive SCC found"),
("PDT\n(single lesion)",
"Topical ALA or mALA (1-3 h)\nBlue or red light activation (~16 min)",
"Single Tx: 50-70%\n2 sessions: up to 90%\nFacial > acral sites",
"Better cosmetic outcome than cryotherapy (Cochrane)\nmALA: improved penetration, less pain\nPatient avoids sunlight 48 h post-Tx"),
]
ld_data = [ld_header]
for r in ld_rows:
ld_data.append([Paragraph(r[0], S("ldb", fontSize=8, textColor=TEAL, fontName="Helvetica-Bold", leading=11)),
Paragraph(r[1].replace("\n","<br/>"), BODY_SM),
Paragraph(r[2].replace("\n","<br/>"), BODY_SM),
Paragraph(r[3].replace("\n","<br/>"), BODY_SM)])
ld_tbl = Table(ld_data, colWidths=[USABLE_W*x for x in [0.18, 0.28, 0.18, 0.36]], repeatRows=1)
ld_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, TEAL_LIGHT]),
("BOX", (0,0), (-1,-1), 0.6, TEAL),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(ld_tbl)
story.append(spacer(3))
# --- FIELD-DIRECTED ---
story.append(Paragraph("FIELD-DIRECTED THERAPIES (multiple/diffuse lesions - field cancerization)", H3))
fd_header = [Paragraph(h, TH) for h in ["AGENT", "MECHANISM", "REGIMEN", "AREA LIMIT", "ADVERSE EFFECTS"]]
fd_rows = [
("5-Fluorouracil\n(5-FU)\nEfudex 5%\nCarac 0.5%",
"Pyrimidine antimetabolite\nInhibits thymidylate\nsynthase; kills\nproliferating cells",
"5% cream: daily x 4 wks\n0.5% cream: daily x 4 wks\nIf erythema/crust at 2-3 wks,\ncourse may be sufficient",
"No formal\narea limit\n(prescription\nproduct)",
"Intense inflammation, erythema,\nerosions, crusting\nHealing takes 2-4 wks post-Tx\nPetroleum jelly reduces discomfort"),
("Imiquimod\n5% (Aldara)\n3.75% (Zyclara)",
"TLR-7/8 agonist\nInduces IFN-alpha\nand innate immune\nresponse",
"5%: 3-5x/week x 12-16 wks\n3.75%: daily x 2 wks,\nthen 2 wks off,\nthen daily x 2 wks",
"5%: 25 cm2 only\n3.75%: larger\nareas approved",
"Local skin reactions (erythema,\nflu-like symptoms\nHypopigmentation at cleared sites\nExpensive; less practical for large areas"),
("Diclofenac\n3% gel\n(Solaraze)",
"COX-2 inhibitor\nAnti-proliferative\nPro-apoptotic",
"Twice daily x 60-90 days",
"No formal limit",
"Milder than 5-FU; good tolerability\nLocal erythema, dryness\nSlower onset of action"),
("Photodynamic\nTherapy (PDT)\n(field)",
"ALA/mALA → PpIX\n→ activated by light\n→ reactive O2 species",
"ALA 1-3 h incubation\nBlue or red light 16 min\nCan repeat at 4-8 wks",
"Limited by\npracticality\n(office-based)",
"Pain/stinging during light activation\nErythema, crusting post-Tx\nPatient must avoid sunlight 48 h\nBest cosmetic outcome"),
("Tirbanibulin\n1% ointment",
"Dual Src kinase /\ntubulin polymerization\ninhibitor",
"Daily x 5 days\n(face/scalp only)",
"25 cm2",
"Local reactions; well-tolerated\nNewer agent (FDA approved 2020)\nShort course is an advantage"),
]
fd_data = [fd_header]
for r in fd_rows:
fd_data.append([
Paragraph(r[0].replace("\n","<br/>"), S("fdb", fontSize=7.8, textColor=TEAL, fontName="Helvetica-Bold", leading=11)),
Paragraph(r[1].replace("\n","<br/>"), BODY_SM),
Paragraph(r[2].replace("\n","<br/>"), BODY_SM),
Paragraph(r[3].replace("\n","<br/>"), BODY_SM),
Paragraph(r[4].replace("\n","<br/>"), BODY_SM),
])
fd_tbl = Table(fd_data, colWidths=[USABLE_W*x for x in [0.16, 0.20, 0.22, 0.12, 0.30]], repeatRows=1)
fd_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, TEAL_LIGHT]),
("BOX", (0,0), (-1,-1), 0.6, TEAL),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#BFC9CA")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(fd_tbl)
story.append(spacer(3))
# ════════════════════ CLINICAL DECISION ALGORITHM ════════════════════
story.append(section_header("CLINICAL DECISION ALGORITHM", bg=ORANGE))
story.append(spacer(2))
algo_data = [
[Paragraph("CLINICAL SCENARIO", S("ah", fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold", leading=11, alignment=TA_CENTER)),
Paragraph("FIRST-LINE", S("ah", fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold", leading=11, alignment=TA_CENTER)),
Paragraph("ALTERNATIVE", S("ah", fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold", leading=11, alignment=TA_CENTER)),
Paragraph("SPECIAL CONSIDERATIONS", S("ah", fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold", leading=11, alignment=TA_CENTER))],
["1-5 discrete, thin AKs", "Cryotherapy", "PDT", "Fastest; most practical in office"],
["1-5 hypertrophic AKs", "Cryotherapy (longer freeze) or curettage+ED", "Excision if SCC suspected", "Biopsy first if indurated or ulcerated"],
["Multiple AKs, photodamaged field (face/scalp)", "5-FU 5% or Imiquimod", "PDT (better cosmesis)", "Field Tx addresses subclinical lesions"],
["Multiple AKs on extremities/trunk", "5-FU or diclofenac", "Cryotherapy per lesion", "PDT less effective at acral sites"],
["Actinic cheilitis", "PDT or 5-FU topical", "Laser ablation, vermilionectomy", "Higher SCC risk; close follow-up"],
["Immunosuppressed / transplant patient", "5-FU field + cryotherapy for individual lesions", "PDT; consider oral retinoids (chemoprevention)", "Aggressive surveillance q3-6 months; low threshold to biopsy"],
["Pigmented AK (melanoma not excluded)", "Biopsy first", "-", "Do NOT treat empirically; exclude lentigo maligna"],
["Recurrent / treatment-resistant AK", "Biopsy mandatory", "Combination Tx after histology", "Exclude invasive SCC"],
]
a_data = []
for i, row in enumerate(algo_data):
if i == 0:
a_data.append([Paragraph(c, S("ah", fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold", leading=11)) for c in row])
else:
a_data.append([
Paragraph(row[0], S("asc", fontSize=8, textColor=GREY_DARK, fontName="Helvetica-Bold", leading=11)),
Paragraph(row[1], S("afl", fontSize=8, textColor=GREEN, fontName="Helvetica-Bold", leading=11)),
Paragraph(row[2], BODY_SM),
Paragraph(row[3], BODY_SM),
])
algo_tbl = Table(a_data, colWidths=[USABLE_W*x for x in [0.27, 0.21, 0.19, 0.33]], repeatRows=1)
algo_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), ORANGE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, ORANGE_LT]),
("BOX", (0,0), (-1,-1), 0.6, ORANGE),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#EDBB99")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(algo_tbl)
story.append(spacer(3))
# ════════════════════ BOTTOM ROW: Follow-up + Prevention + Red Flags ════════════════════
fu_w = USABLE_W * 0.35
prev_w = USABLE_W * 0.35
rf_w = USABLE_W * 0.30
# Follow-up
fu_content = [
[Paragraph("FOLLOW-UP SCHEDULE", S("fuh", fontSize=9, textColor=GREEN, fontName="Helvetica-Bold", leading=12))],
[Paragraph("<b>Standard patients:</b> Full skin exam every 6-12 months", BODY_SM)],
[Paragraph("<b>Immunosuppressed / transplant:</b> Every 3-6 months minimum", BODY_SM)],
[Paragraph("<b>After treatment:</b>", BODY_SM)],
[Paragraph("\u2022 Reassess at 4-8 weeks", BODY_SM)],
[Paragraph("\u2022 Incomplete clearance: retreat or biopsy", BODY_SM)],
[Paragraph("\u2022 New lesions in same field: field treatment", BODY_SM)],
[Paragraph("<b>Cheilitis:</b> Follow every 3-4 months; low biopsy threshold", BODY_SM)],
]
fu_tbl = Table(fu_content, colWidths=[fu_w])
fu_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), GREEN_LT),
("BOX", (0,0), (-1,-1), 0.8, GREEN),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
# Prevention
prev_content = [
[Paragraph("SUN PROTECTION COUNSELLING", S("pvh", fontSize=9, textColor=TEAL, fontName="Helvetica-Bold", leading=12))],
[Paragraph("<b>Sunscreen:</b> Broad-spectrum SPF 30+ applied daily; reapply every 2 hours outdoors", BODY_SM)],
[Paragraph("<b>Protective clothing:</b> Wide-brimmed hat, long sleeves, UV-protective clothing (UPF 50+)", BODY_SM)],
[Paragraph("<b>Behavior:</b> Avoid peak sun (10 AM - 4 PM); seek shade", BODY_SM)],
[Paragraph("<b>Chemoprevention:</b> Oral retinoids (acitretin) considered in immunosuppressed patients with multiple AKs and high SCC burden", BODY_SM)],
[Paragraph("<b>Education:</b> Advise patients that new AKs may develop despite treatment - regular surveillance is essential", BODY_SM)],
]
prev_tbl = Table(prev_content, colWidths=[prev_w])
prev_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL_LIGHT),
("BOX", (0,0), (-1,-1), 0.8, TEAL),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
# Red Flags
rf_content = [
[Paragraph("RED FLAGS: SUSPECT SCC", S("rfh2", fontSize=9, textColor=RED, fontName="Helvetica-Bold", leading=12))],
[Paragraph("\u26a0 Induration or firmness", WARN)],
[Paragraph("\u26a0 Ulceration or bleeding", WARN)],
[Paragraph("\u26a0 Diameter >1 cm", WARN)],
[Paragraph("\u26a0 Rapid growth", WARN)],
[Paragraph("\u26a0 Perineural symptoms (pain/numbness)", WARN)],
[Paragraph("\u26a0 Failure of standard topical therapy", WARN)],
[Paragraph("\u26a0 Lymphadenopathy", WARN)],
[Paragraph(
"Any of the above: <b>BIOPSY before or instead of empirical treatment.</b>",
S("rfb", fontSize=8, textColor=RED, fontName="Helvetica-Bold", leading=11)
)],
]
rflag_tbl = Table(rf_content, colWidths=[rf_w])
rflag_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED_LT),
("BOX", (0,0), (-1,-1), 1.0, RED),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
bottom_row = Table([[fu_tbl, prev_tbl, rflag_tbl]],
colWidths=[fu_w + 2, prev_w + 2, rf_w + 2])
bottom_row.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 1),
("RIGHTPADDING", (0,0), (-1,-1), 1),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
]))
story.append(bottom_row)
story.append(spacer(3))
# ════════════════════ FOOTER ════════════════════
footer_tbl = Table([[
Paragraph(
"Sources: Fitzpatrick's Dermatology (Vols 1-2) | Dermatology 2-Volume Set 5e | "
"Robbins & Kumar Basic Pathology | Andrews' Diseases of the Skin | "
"Textbook of Family Medicine 9e | Sabiston Textbook of Surgery",
S("ft", fontSize=6.5, textColor=GREY_MID, leading=9, alignment=TA_CENTER)
)
]], colWidths=[USABLE_W])
footer_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GREY_LIGHT),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
("BOX", (0,0),(-1,-1), 0.5, GREY_MID),
]))
story.append(footer_tbl)
# ── Build ────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF saved to: {OUTPUT}")
cd /home/daytona/workspace/ak-guide && python generate_ak_guide.py 2>&1
~/ak-guide/generate_ak_guide.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ak-guide/generate_ak_guide.py'
cd /home/daytona/workspace/ak-guide && python generate_ak_guide.py 2>&1
~/ak-guide/generate_ak_guide.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ak-guide/generate_ak_guide.py'
~/ak-guide/generate_ak_guide.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ak-guide/generate_ak_guide.py'
cd /home/daytona/workspace/ak-guide && python generate_ak_guide.py 2>&1
ls -lh /home/daytona/workspace/ak-guide/AK_Quick_Reference_Guide.pdf
AK Quick Reference Guide
PDF Document · PDF