Explain in this where there are nodules and opacity
| Finding | Detail |
|---|---|
| Tracheal deviation | Mild shift to the right - suggests volume loss from the RUL consolidation |
| Right hemidiaphragm | Slightly elevated - consistent with right lung volume loss |
| Right costophrenic angle | Partially blunted - possible small reactive pleural effusion |
| Left lung | Largely clear, no focal opacities or nodules |
| Left costophrenic angle | Sharp and normal |
Create a summary document comparing TB vs lung cancer presentation in elderly patients
mkdir -p /home/daytona/workspace/tb-vs-lungca && ls /home/daytona/workspace/tb-v…
~/tb-vs-lungca/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import PageBreak
OUTPUT = "/home/daytona/workspace/tb-vs-lungca/TB_vs_LungCancer_Elderly.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=2.2*cm, bottomMargin=2*cm
)
W, H = A4
styles = getSampleStyleSheet()
# ---- custom styles ----
title_style = ParagraphStyle(
"DocTitle", fontSize=20, leading=26, alignment=TA_CENTER,
textColor=colors.HexColor("#1a3c5e"), fontName="Helvetica-Bold",
spaceAfter=4
)
subtitle_style = ParagraphStyle(
"SubTitle", fontSize=11, leading=14, alignment=TA_CENTER,
textColor=colors.HexColor("#4a4a4a"), fontName="Helvetica",
spaceAfter=2
)
date_style = ParagraphStyle(
"DateStyle", fontSize=9, alignment=TA_CENTER,
textColor=colors.HexColor("#888888"), spaceAfter=12
)
section_style = ParagraphStyle(
"Section", fontSize=13, leading=18, fontName="Helvetica-Bold",
textColor=colors.HexColor("#1a3c5e"), spaceBefore=14, spaceAfter=4
)
sub_section_style = ParagraphStyle(
"SubSection", fontSize=10.5, leading=14, fontName="Helvetica-Bold",
textColor=colors.HexColor("#2c5f8a"), spaceBefore=8, spaceAfter=3
)
body_style = ParagraphStyle(
"Body", fontSize=9.5, leading=13.5, fontName="Helvetica",
textColor=colors.HexColor("#222222"), spaceAfter=4, alignment=TA_JUSTIFY
)
bullet_style = ParagraphStyle(
"Bullet", fontSize=9.5, leading=13, fontName="Helvetica",
textColor=colors.HexColor("#222222"), spaceAfter=2,
leftIndent=14, bulletIndent=4
)
footer_style = ParagraphStyle(
"Footer", fontSize=7.5, alignment=TA_CENTER,
textColor=colors.HexColor("#aaaaaa")
)
source_style = ParagraphStyle(
"Source", fontSize=8, leading=11, fontName="Helvetica-Oblique",
textColor=colors.HexColor("#555555"), spaceAfter=2, leftIndent=10
)
callout_style = ParagraphStyle(
"Callout", fontSize=9.5, leading=13, fontName="Helvetica-BoldOblique",
textColor=colors.HexColor("#8b0000"), alignment=TA_CENTER,
spaceAfter=2, spaceBefore=4
)
# ---- colour palette ----
TB_COL = colors.HexColor("#e8f4f8") # light blue
LC_COL = colors.HexColor("#fff3e0") # light orange
HDR_BLUE = colors.HexColor("#1a3c5e")
HDR_ORG = colors.HexColor("#b85c00")
ROW_ALT = colors.HexColor("#f4f9fb")
GRID = colors.HexColor("#c0c0c0")
def hr(c="#c0c0c0", t=0.5):
return HRFlowable(width="100%", thickness=t, color=colors.HexColor(c), spaceAfter=6, spaceBefore=2)
def section(text):
return Paragraph(text, section_style)
def subsection(text):
return Paragraph(text, sub_section_style)
def body(text):
return Paragraph(text, body_style)
def bullet(text):
return Paragraph(f"• {text}", bullet_style)
def source(text):
return Paragraph(f"<i>Source: {text}</i>", source_style)
# =====================================================================
story = []
# --- HEADER BANNER ---
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("TB vs Lung Cancer in Elderly Patients", title_style))
story.append(Paragraph("A Clinical Comparison Summary", subtitle_style))
story.append(Paragraph("Prepared July 2026 | Based on Harrison's, Goldman-Cecil, Fishman's, Grainger & Allison's", date_style))
story.append(hr("#1a3c5e", 1.5))
story.append(Spacer(1, 0.2*cm))
# --- INTRODUCTION ---
story.append(section("1. Background & Clinical Relevance"))
story.append(body(
"Pulmonary tuberculosis (TB) and primary lung cancer share a striking overlap in their clinical and "
"radiological presentations, particularly in elderly patients (age >60 years). Both conditions can "
"manifest with chronic cough, haemoptysis, weight loss, fever, and upper lobe opacities on chest "
"radiograph. Misdiagnosis is common and delays treatment. A 67-year-old patient with haemoptysis, "
"right chest pain, fever, and right upper lobe consolidation (as in this case) represents the classic "
"diagnostic dilemma."
))
story.append(body(
"In high-prevalence TB settings, TB is assumed first. In low-prevalence settings, obstructive malignancy "
"must always be excluded. Either condition can co-exist: TB-associated scar carcinoma and post-TB "
"bronchiectasis are well-recognised entities."
))
story.append(Spacer(1, 0.3*cm))
# =====================================================================
# SECTION 2 - BIG COMPARISON TABLE
# =====================================================================
story.append(section("2. Side-by-Side Comparison"))
story.append(hr())
col_w = [3.8*cm, 7.5*cm, 7.5*cm]
table_data = [
# header
[
Paragraph("<b>Feature</b>", ParagraphStyle("H", fontSize=10, fontName="Helvetica-Bold", textColor=colors.white, alignment=TA_CENTER)),
Paragraph("<b>Pulmonary Tuberculosis</b>", ParagraphStyle("H", fontSize=10, fontName="Helvetica-Bold", textColor=colors.white, alignment=TA_CENTER)),
Paragraph("<b>Lung Cancer (NSCLC/SCLC)</b>", ParagraphStyle("H", fontSize=10, fontName="Helvetica-Bold", textColor=colors.white, alignment=TA_CENTER)),
],
# demographics
[
Paragraph("<b>Typical age</b>", bullet_style),
Paragraph("Any age; reactivation TB peaks in elderly males", body_style),
Paragraph("Peak 50-70 years; strong male predominance in smokers", body_style),
],
[
Paragraph("<b>Risk factors</b>", bullet_style),
Paragraph("HIV, diabetes, malnutrition, immunosuppression, crowding, prior TB infection, silicosis", body_style),
Paragraph("Cigarette smoking (80-90%), asbestos, radon, COPD, family history, occupational carcinogens", body_style),
],
[
Paragraph("<b>Onset</b>", bullet_style),
Paragraph("Subacute to chronic — weeks to months of progressive symptoms", body_style),
Paragraph("Insidious; 25% asymptomatic at diagnosis (found incidentally on CXR/CT)", body_style),
],
# symptoms
[
Paragraph("<b>Cough</b>", bullet_style),
Paragraph("Present in most; productive, mucopurulent, may be blood-streaked", body_style),
Paragraph("8-75% frequency; can be dry or productive; new persistent cough in a smoker is a red flag", body_style),
],
[
Paragraph("<b>Haemoptysis</b>", bullet_style),
Paragraph("Common (up to 30%); classically small-volume, blood-streaked sputum; massive haemoptysis in cavitary disease", body_style),
Paragraph("6-35% frequency; often small-volume; massive haemoptysis less typical than TB", body_style),
],
[
Paragraph("<b>Fever</b>", bullet_style),
Paragraph("Low-grade evening fever ('hectic fever') is classic; may have drenching night sweats", body_style),
Paragraph("0-20%; fever suggests post-obstructive pneumonia or tumour necrosis, not the primary tumour", body_style),
],
[
Paragraph("<b>Weight loss</b>", bullet_style),
Paragraph("Progressive, often significant; part of classic triad with fever and night sweats", body_style),
Paragraph("0-68%; weight loss comparable to cough as a presenting symptom; cancer cachexia in advanced disease", body_style),
],
[
Paragraph("<b>Night sweats</b>", bullet_style),
Paragraph("Classic constitutional symptom; drenching night sweats in active TB", body_style),
Paragraph("Uncommon as primary symptom; may occur with paraneoplastic syndromes or post-obstructive infection", body_style),
],
[
Paragraph("<b>Chest pain</b>", bullet_style),
Paragraph("Pleuritic pain if pleural involvement; generally mild", body_style),
Paragraph("20-49%; pain from pleural/chest wall invasion; Pancoast tumour causes shoulder/arm pain", body_style),
],
[
Paragraph("<b>Dyspnoea</b>", bullet_style),
Paragraph("Late feature unless extensive parenchymal disease or effusion", body_style),
Paragraph("3-60%; endobronchial obstruction causes air-trapping, wheezing, or lobar collapse", body_style),
],
[
Paragraph("<b>Clubbing</b>", bullet_style),
Paragraph("Rare; can occur in chronic pulmonary TB or empyema", body_style),
Paragraph("0-20%; hypertrophic pulmonary osteoarthropathy (HPOA) associated particularly with adenocarcinoma", body_style),
],
# radiology
[
Paragraph("<b>CXR location</b>", bullet_style),
Paragraph("Classically upper lobe (posterior segment RUL and apical-posterior LUL); bilateral apical disease in miliary TB", body_style),
Paragraph("No fixed lobar predilection, but right upper lobe is most common for NSCLC; central vs peripheral varies by histology", body_style),
],
[
Paragraph("<b>CXR character</b>", bullet_style),
Paragraph("Patchy/nodular infiltrates, ill-defined consolidation; cavitation is characteristic of reactivation TB", body_style),
Paragraph("Solitary pulmonary nodule or mass; hilar enlargement; post-obstructive consolidation; pleural effusion", body_style),
],
[
Paragraph("<b>Cavitation</b>", bullet_style),
Paragraph("Highly characteristic of reactivation TB; thick-walled cavity with air-fluid level", body_style),
Paragraph("Can occur (particularly squamous cell carcinoma); thick irregular wall; no satellite nodules", body_style),
],
[
Paragraph("<b>Fissure sign</b>", bullet_style),
Paragraph("Minor fissure elevation with RUL collapse/consolidation; 'Golden S sign' not typical for TB", body_style),
Paragraph("'Golden S sign' (reversed S-shape of minor fissure) is classic for central obstructing RUL mass", body_style),
],
[
Paragraph("<b>Lymphadenopathy</b>", bullet_style),
Paragraph("Hilar and paratracheal nodes; may calcify ('egg-shell' calcification in some)", body_style),
Paragraph("Mediastinal and hilar nodes common; non-calcified; central location suggests N2/N3 disease", body_style),
],
[
Paragraph("<b>Pleural effusion</b>", bullet_style),
Paragraph("Exudative; lymphocyte-predominant; ADA elevated; culture-positive in <30%", body_style),
Paragraph("Exudative; malignant cells on cytology; poor prognostic sign (stage IIIB/IV)", body_style),
],
# labs
[
Paragraph("<b>Sputum AFB smear</b>", bullet_style),
Paragraph("Positive in 50-80% of smear-positive TB; 3 samples on separate mornings recommended", body_style),
Paragraph("Negative (unless co-existing TB)", body_style),
],
[
Paragraph("<b>GeneXpert MTB/RIF</b>", bullet_style),
Paragraph(">95% sensitive in smear-positive; ~70% in smear-negative; also detects rifampicin resistance", body_style),
Paragraph("Negative", body_style),
],
[
Paragraph("<b>Sputum cytology</b>", bullet_style),
Paragraph("Negative for malignant cells", body_style),
Paragraph("Positive in 20-30%; low sensitivity for peripheral lesions; useful for central tumours", body_style),
],
[
Paragraph("<b>Bronchoscopy</b>", bullet_list),
Paragraph("BAL AFB culture; transbronchial biopsy for miliary/endobronchial TB; therapeutic if haemoptysis", body_style),
Paragraph("Tissue biopsy for histology and molecular profiling (EGFR, ALK, PD-L1); essential for treatment planning", body_style),
],
[
Paragraph("<b>CT chest</b>", bullet_style),
Paragraph("Centrilobular nodules, 'tree-in-bud' pattern, upper lobe fibrosis, cavitation, satellite lesions", body_style),
Paragraph("Spiculated mass with pleural tethering; post-obstructive collapse; mediastinal invasion; PET/CT for staging", body_style),
],
[
Paragraph("<b>Blood markers</b>", bullet_style),
Paragraph("ESR elevated; lymphocytosis; ADA in pleural fluid; IGRA/TST for latent TB (cannot confirm active disease)", body_style),
Paragraph("CEA, CYFRA 21-1, NSE (limited specificity); LDH elevated; hyponatraemia (SIADH in SCLC)", body_style),
],
# treatment
[
Paragraph("<b>Treatment</b>", bullet_style),
Paragraph("HRZE x 2 months then HR x 4 months (6-month standard regimen); DOT recommended in elderly for adherence", body_style),
Paragraph("NSCLC: surgery (early), platinum-doublet chemo, immunotherapy (PD-1/L1), targeted therapy (EGFR/ALK); SCLC: chemo-radiation", body_style),
],
[
Paragraph("<b>Prognosis</b>", bullet_style),
Paragraph("Cure rate >85% with drug-sensitive TB and good adherence; worse in elderly with co-morbidities", body_style),
Paragraph("5-year survival ~20% overall; stage I resected NSCLC ~70%; SCLC <10% at 5 years", body_style),
],
]
ts = TableStyle([
# Header row
("BACKGROUND", (0, 0), (-1, 0), HDR_BLUE),
("BACKGROUND", (1, 0), (1, 0), HDR_BLUE),
("BACKGROUND", (2, 0), (2, 0), HDR_ORG),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, 0), 10),
# Feature column
("BACKGROUND", (0, 1), (0, -1), colors.HexColor("#f0f4f8")),
# Alternating row colours
*[("BACKGROUND", (1, r), (1, r), TB_COL if r % 2 == 0 else colors.white) for r in range(1, len(table_data))],
*[("BACKGROUND", (2, r), (2, r), LC_COL if r % 2 == 0 else colors.HexColor("#fffaf5")) for r in range(1, len(table_data))],
# Grid
("GRID", (0, 0), (-1, -1), 0.4, GRID),
("LINEBELOW", (0, 0), (-1, 0), 1.5, HDR_BLUE),
# Padding
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
("LEFTPADDING", (0, 0), (-1, -1), 7),
("RIGHTPADDING", (0, 0), (-1, -1), 7),
# Vertical alignment
("VALIGN", (0, 0), (-1, -1), "TOP"),
])
main_table = Table(table_data, colWidths=col_w, repeatRows=1)
main_table.setStyle(ts)
story.append(main_table)
story.append(Spacer(1, 0.4*cm))
# =====================================================================
# SECTION 3 - DISTINGUISHING FEATURES IN ELDERLY
# =====================================================================
story.append(PageBreak())
story.append(section("3. Special Considerations in Elderly Patients (Age ≥60)"))
story.append(hr())
story.append(subsection("3.1 Why the Elderly are Diagnostically Challenging"))
for txt in [
"Atypical TB presentations are more common in elderly: fewer night sweats, less fever, predominance of lower lobe disease (mimicking bacterial pneumonia), and negative TST due to immunosenescence.",
"Elderly smokers with COPD have chronic cough as a baseline — new haemoptysis or change in cough character must always prompt CT chest.",
"Both conditions cause significant weight loss and fatigue, which may be attributed to 'normal ageing' and delay investigation.",
"Multiple co-morbidities (DM, renal impairment, cardiac disease) complicate both diagnosis (reduced sputum production, renal dose adjustments for anti-TB drugs) and treatment planning for cancer.",
"Cognitive impairment may reduce symptom reporting accuracy, leading to late-stage presentation for both diseases.",
]:
story.append(bullet(txt))
story.append(Spacer(1, 0.2*cm))
story.append(subsection("3.2 Red Flags Favouring Lung Cancer Over TB"))
cancer_flags = [
"No fever or low-grade fever without night sweats (TB fever is classically hectic and nocturnal)",
"Sputum AFB smear x3 negative with GeneXpert MTB/RIF negative",
"Mass lesion with spiculated margins, pleural tethering, or satellite nodules on CT",
"Hilar or mediastinal lymphadenopathy without calcification",
"SVC obstruction (facial oedema, arm swelling, dilated chest wall veins)",
"Hoarseness (recurrent laryngeal nerve involvement by mediastinal nodes)",
"Bone pain or neurological symptoms suggesting metastases",
"Clubbing or hypertrophic pulmonary osteoarthropathy",
"Hyponatraemia (SIADH - common paraneoplastic syndrome in SCLC)",
"Golden S sign on CXR - reversed S-curve of minor fissure",
"Non-resolving pneumonia after two courses of antibiotics",
"Heavy smoking history (>40 pack-years)",
]
for f in cancer_flags:
story.append(bullet(f))
story.append(Spacer(1, 0.2*cm))
story.append(subsection("3.3 Red Flags Favouring TB Over Lung Cancer"))
tb_flags = [
"Exposure history: contact with known TB case, residence in endemic area, prior TB",
"Drenching night sweats + hectic fever + progressive weight loss (classic TB triad)",
"Tree-in-bud nodules on CT (centrilobular spread of infection - highly specific for TB/NTM)",
"Cavitatory upper lobe lesion with satellite nodules",
"Sputum AFB smear or GeneXpert MTB/RIF positive",
"ADA >40 U/L in pleural/CSF fluid",
"Response to empirical anti-TB therapy (diagnostic ex-juvantibus)",
"HIV positive (dramatically increases TB risk)",
"Bilateral apical infiltrates",
"History of immunosuppressive therapy (steroids, biologics, TNF-alpha inhibitors)",
]
for f in tb_flags:
story.append(bullet(f))
story.append(Spacer(1, 0.3*cm))
# =====================================================================
# SECTION 4 - DIAGNOSTIC APPROACH
# =====================================================================
story.append(section("4. Recommended Diagnostic Approach"))
story.append(hr())
story.append(body(
"When TB and lung cancer cannot be distinguished clinically, a stepwise investigation approach "
"is recommended. Both diagnoses must be actively excluded before committing to one treatment."
))
step_data = [
[Paragraph("<b>Step</b>", ParagraphStyle("SH", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white, alignment=TA_CENTER)),
Paragraph("<b>Investigation</b>", ParagraphStyle("SH", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white, alignment=TA_CENTER)),
Paragraph("<b>Key findings</b>", ParagraphStyle("SH", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white, alignment=TA_CENTER))],
["1", "Chest PA radiograph", "Lobar distribution, cavitation, mass effect, fissure signs, hilar enlargement"],
["2", "Sputum AFB smear x3 + GeneXpert MTB/RIF", "Confirms TB and detects rifampicin resistance if positive"],
["3", "Sputum cytology x3", "Malignant cells in ~20-30% of central lung cancers"],
["4", "CT chest (contrast-enhanced)", "Spiculation, pleural tethering, tree-in-bud, cavitation morphology, nodes, staging"],
["5", "Bronchoscopy + BAL + biopsy", "Histology for malignancy; BAL culture/PCR for TB; direct visualisation of endobronchial lesion"],
["6", "PET-CT (if malignancy suspected)", "Metabolic staging; identifies distant metastases; guides resectability"],
["7", "Pleural fluid (if effusion present)", "ADA, LDH, protein, cytology, culture"],
["8", "IGRA / TST", "Evidence of TB exposure (cannot confirm active disease)"],
["9", "Molecular profiling (biopsy)", "EGFR, ALK, ROS1, KRAS, PD-L1 expression for targeted therapy eligibility"],
]
step_ts = TableStyle([
("BACKGROUND", (0, 0), (-1, 0), HDR_BLUE),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
*[("BACKGROUND", (0, r), (-1, r), ROW_ALT if r % 2 == 0 else colors.white) for r in range(1, len(step_data))],
("GRID", (0, 0), (-1, -1), 0.4, GRID),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
("LEFTPADDING", (0, 0), (-1, -1), 7),
("RIGHTPADDING", (0, 0), (-1, -1), 7),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("ALIGN", (0, 0), (0, -1), "CENTER"),
])
step_table = Table(step_data, colWidths=[1.2*cm, 5.5*cm, 12.1*cm])
step_table.setStyle(step_ts)
story.append(step_table)
story.append(Spacer(1, 0.4*cm))
# =====================================================================
# SECTION 5 - COEXISTENCE
# =====================================================================
story.append(section("5. When Both Coexist: TB-Associated Lung Cancer"))
story.append(hr())
for txt in [
"Patients with prior pulmonary TB have a 2-3 fold increased risk of developing lung cancer (scar carcinoma). The fibrotic scar from healed TB creates a microenvironment promoting malignant transformation.",
"Simultaneous active TB and lung cancer occurs in 1-3% of lung cancer patients in endemic areas. CT findings of malignancy (spiculated mass) superimposed on background TB fibrosis are the key indicator.",
"Never assume a negative sputum culture rules out malignancy. Conversely, a positive GeneXpert does not rule out an underlying obstructive cancer causing post-obstructive TB.",
"In this scenario, a contrast-enhanced CT chest with bronchoscopy and biopsy is essential to characterise both processes simultaneously.",
]:
story.append(bullet(txt))
story.append(Spacer(1, 0.3*cm))
# =====================================================================
# SECTION 6 - KEY REFERENCES
# =====================================================================
story.append(section("6. References"))
story.append(hr())
refs = [
"Harrison's Principles of Internal Medicine, 22nd Edition (2025) - Chapter 83: Lung Cancer; Chapter 167: Tuberculosis",
"Goldman-Cecil Medicine International Edition - Chapter 85: Pulmonary Tuberculosis",
"Fishman's Pulmonary Diseases and Disorders, 2-Volume Set - Chapter 112: Lung Cancer Staging",
"Grainger & Allison's Diagnostic Radiology - Chapter 8: Lung Cancer Imaging & Staging",
"Symptom to Diagnosis: An Evidence-Based Guide, 4th Edition - Chapter 32: Weight Loss",
"Sabiston Textbook of Surgery, 11th Edition - Chapter on NSCLC Diagnosis",
"Textbook of Family Medicine, 9th Edition - Pulmonary Nodule and Cancer Presentation",
]
for r in refs:
story.append(source(r))
story.append(Spacer(1, 0.5*cm))
story.append(hr("#1a3c5e", 1))
story.append(Paragraph(
"This document is a clinical education summary generated for educational purposes. "
"Clinical decisions should always be based on individual patient assessment and current institutional guidelines.",
footer_style
))
# BUILD
doc.build(story)
print(f"PDF saved to {OUTPUT}")
cd /home/daytona/workspace/tb-vs-lungca && python generate_pdf.py 2>&1
~/tb-vs-lungca/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/tb-vs-lungca/generate_pdf.py'
cd /home/daytona/workspace/tb-vs-lungca && python generate_pdf.py 2>&1
ls -lh /home/daytona/workspace/tb-vs-lungca/TB_vs_LungCancer_Elderly.pdf
TB vs LungCancer Elderly
PDF Document · PDF