strobe guidlines
STROBE guidelines checklist observational studies 2025
STROBE statement observational epidemiology reporting guidelines
| Item | Requirement |
|---|---|
| 1 | Indicate the study design with a commonly used term in the title or abstract; provide an informative, balanced summary of what was done and found |
| Item | Requirement |
|---|---|
| 2 | Background/rationale - Explain the scientific background and rationale for the investigation |
| 3 | Objectives - State specific objectives, including any pre-specified hypotheses |
| Item | Requirement |
|---|---|
| 4 | Study design - Present key elements of study design early in the paper |
| 5 | Setting - Describe the setting, locations, and relevant dates (recruitment, exposure, follow-up, data collection) |
| 6* | Participants - Eligibility criteria and sources/methods of participant selection (design-specific) |
| 7 | Variables - Clearly define all outcomes, exposures, predictors, potential confounders, and effect modifiers; give diagnostic criteria if applicable |
| 8 | Data sources/measurement - For each variable, describe sources of data and methods of assessment; if >1 group, describe comparability of methods |
| 9 | Bias - Describe any efforts to address potential sources of bias |
| 10 | Study size - Explain how the study size was determined |
| 11 | Quantitative variables - Explain how quantitative variables were handled in analyses; if applicable, describe grouping rationale |
| 12* | Statistical methods - Describe all statistical methods, including those used to control for confounding; methods for missing data; sensitivity analyses (design-specific elements) |
| Item | Requirement |
|---|---|
| 13* | Participants - Report numbers at each stage (eligible, excluded, analysed); reasons for non-participation; consider flow diagram |
| 14* | Descriptive data - Characteristics of participants, information on exposures and potential confounders |
| 15* | Outcome data - Report numbers of outcome events or summary measures (design-specific) |
| 16 | Main results - Report unadjusted and adjusted estimates with precision (confidence intervals); report category boundaries if continuous variables were categorized |
| 17 | Other analyses - Report other analyses done (sensitivity, subgroups, interactions) |
| Item | Requirement |
|---|---|
| 18 | Key results - Summarise key results with reference to study objectives |
| 19 | Limitations - Discuss limitations, sources of potential bias/imprecision, and both direction and magnitude of potential bias |
| 20 | Interpretation - Give a cautious overall interpretation considering objectives, limitations, multiplicity of analyses, and other relevant evidence |
| 21 | Generalisability - Discuss the generalisability (external validity) of the study results |
| Item | Requirement |
|---|---|
| 22 | Funding - Give the source of funding and the role of funders |
| Extension | Target |
|---|---|
| STROBE-NI | Neonatal infections |
| STROBE-Vet | Veterinary epidemiology |
| STROBE-ME | Molecular epidemiology |
| STROBE-MCCS | Matched case-control studies |
| RECORD | Routinely collected health data |
| STROBE-Equity | Health equity reporting (published BMJ 2025 - adds 10 equity-specific items) |
| STROBE-D | Drowning research (under development as of 2025) |
Create a STROBE checklist template I can use for my manuscript
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace does not exist"
mkdir -p /home/daytona/workspace/strobe-checklist && python3 -c "from reportlab.lib import colors; print('reportlab ok')"
~/strobe-checklist/generate_strobe.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Table, TableStyle, Paragraph,
Spacer, HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.lib.colors import HexColor
import datetime
OUTPUT = "/home/daytona/workspace/strobe-checklist/STROBE_CaseControl_Checklist.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
DARK_BLUE = HexColor("#1B3A5C")
MID_BLUE = HexColor("#2B5FA5")
LIGHT_BLUE = HexColor("#D6E4F7")
PALE_BLUE = HexColor("#EBF2FB")
ACCENT = HexColor("#E8A020")
WHITE = colors.white
LIGHT_GREY = HexColor("#F5F5F5")
MID_GREY = HexColor("#CCCCCC")
DARK_GREY = HexColor("#444444")
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=18*mm, rightMargin=18*mm,
topMargin=22*mm, bottomMargin=20*mm,
title="STROBE Checklist – Case-Control Study",
author="Orris AI Assistant",
)
styles = getSampleStyleSheet()
# Custom paragraph styles
hdr_style = ParagraphStyle(
"hdr", parent=styles["Normal"],
fontName="Helvetica-Bold", fontSize=7.5,
textColor=WHITE, leading=10, alignment=TA_LEFT,
)
cell_style = ParagraphStyle(
"cell", parent=styles["Normal"],
fontName="Helvetica", fontSize=7.2,
textColor=DARK_GREY, leading=10, alignment=TA_LEFT,
wordWrap="LTR",
)
cell_bold = ParagraphStyle(
"cellbold", parent=styles["Normal"],
fontName="Helvetica-Bold", fontSize=7.5,
textColor=DARK_BLUE, leading=10,
)
section_style = ParagraphStyle(
"section", parent=styles["Normal"],
fontName="Helvetica-Bold", fontSize=8.5,
textColor=WHITE, leading=11, alignment=TA_LEFT,
)
note_style = ParagraphStyle(
"note", parent=styles["Normal"],
fontName="Helvetica-Oblique", fontSize=6.5,
textColor=HexColor("#666666"), leading=9,
)
# ── Checklist data ─────────────────────────────────────────────────────────
# Format: (section, item_no, topic, recommendation, design_specific)
ITEMS = [
# ── TITLE & ABSTRACT ──
("TITLE & ABSTRACT", "1",
"Title and abstract",
"(a) Indicate the study's design with a commonly used term in the title or the abstract.\n"
"(b) Provide in the abstract an informative and balanced summary of what was done and what was found.",
False),
# ── INTRODUCTION ──
("INTRODUCTION", "2",
"Background / rationale",
"Explain the scientific background and rationale for the investigation being reported.",
False),
("INTRODUCTION", "3",
"Objectives",
"State specific objectives, including any pre-specified hypotheses.",
False),
# ── METHODS ──
("METHODS", "4",
"Study design",
"Present key elements of study design early in the paper.",
False),
("METHODS", "5",
"Setting",
"Describe the setting, locations, and relevant dates, including periods of recruitment, exposure, follow-up, and data collection.",
False),
("METHODS", "6",
"Participants\n[CASE-CONTROL]",
"(a) Give the eligibility criteria, and the sources and methods of case ascertainment and control selection. Give the rationale for the choice of cases and controls.\n"
"(b) For matched studies, give matching criteria and the number of controls per case.",
True),
("METHODS", "7",
"Variables",
"Clearly define all outcomes, exposures, predictors, potential confounders, and effect modifiers. Give diagnostic criteria, if applicable.",
False),
("METHODS", "8",
"Data sources / measurement",
"For each variable of interest, give sources of data and details of methods of assessment (measurement). Describe comparability of assessment methods if there is more than one group.",
False),
("METHODS", "9",
"Bias",
"Describe any efforts to address potential sources of bias.",
False),
("METHODS", "10",
"Study size",
"Explain how the study size was arrived at.",
False),
("METHODS", "11",
"Quantitative variables",
"Explain how quantitative variables were handled in the analyses. If applicable, describe which groupings were chosen and why.",
False),
("METHODS", "12",
"Statistical methods\n[CASE-CONTROL]",
"(a) Describe all statistical methods, including those used to control for confounding.\n"
"(b) Describe any methods used to examine subgroups and interactions.\n"
"(c) Explain how missing data were addressed.\n"
"(d) If applicable, explain how matching of cases and controls was addressed.\n"
"(e) Describe any sensitivity analyses.",
True),
# ── RESULTS ──
("RESULTS", "13",
"Participants\n[CASE-CONTROL]",
"(a) Report the numbers of individuals at each stage of study — e.g. numbers potentially eligible, examined for eligibility, confirmed eligible, included in the study, completing follow-up, and analysed.\n"
"(b) Give reasons for non-participation at each stage.\n"
"(c) Consider use of a flow diagram.",
True),
("RESULTS", "14",
"Descriptive data\n[CASE-CONTROL]",
"(a) Give characteristics of study participants (e.g. demographic, clinical, social) and information on exposures and potential confounders.\n"
"(b) Indicate the number of participants with missing data for each variable of interest.\n"
"(c) [Case-control] Report numbers of controls per case, if applicable.",
True),
("RESULTS", "15",
"Outcome data\n[CASE-CONTROL]",
"Report numbers in each exposure category, or summary measures of exposure.",
True),
("RESULTS", "16",
"Main results",
"(a) Give unadjusted estimates and, if applicable, confounder-adjusted estimates and their precision (e.g., 95% confidence intervals). Make clear which confounders were adjusted for and why they were included.\n"
"(b) Report category boundaries when continuous variables were categorized.\n"
"(c) If relevant, consider translating estimates of relative risk into absolute risk for a meaningful time period.",
False),
("RESULTS", "17",
"Other analyses",
"Report other analyses done — e.g. analyses of subgroups and interactions, and sensitivity analyses.",
False),
# ── DISCUSSION ──
("DISCUSSION", "18",
"Key results",
"Summarise key results with reference to study objectives.",
False),
("DISCUSSION", "19",
"Limitations",
"Discuss limitations of the study, taking into account sources of potential bias or imprecision. Discuss both direction and magnitude of any potential bias.",
False),
("DISCUSSION", "20",
"Interpretation",
"Give a cautious overall interpretation of results considering objectives, limitations, multiplicity of analyses, results from similar studies, and other relevant evidence.",
False),
("DISCUSSION", "21",
"Generalisability",
"Discuss the generalisability (external validity) of the study results.",
False),
# ── OTHER INFORMATION ──
("OTHER INFORMATION", "22",
"Funding",
"Give the source of funding and the role of the funders for the present study and, if applicable, for the original study on which the present article is based.",
False),
]
# ── Build PDF elements ─────────────────────────────────────────────────────
elements = []
# ---- Cover header ----
header_data = [[
Paragraph("STROBE CHECKLIST", ParagraphStyle(
"title", fontName="Helvetica-Bold", fontSize=18,
textColor=WHITE, alignment=TA_CENTER)),
Paragraph(
"Strengthening the Reporting of Observational Studies in Epidemiology<br/>"
"<font size='11'>Case-Control Study</font>",
ParagraphStyle("sub", fontName="Helvetica", fontSize=13,
textColor=HexColor("#BDD7F5"), alignment=TA_CENTER, leading=17)),
]]
header_tbl = Table(header_data, colWidths=[174*mm])
header_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 12),
("BOTTOMPADDING", (0,0), (-1,-1), 12),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [4, 4, 4, 4]),
]))
elements.append(header_tbl)
elements.append(Spacer(1, 5*mm))
# ---- Manuscript info box ----
info_data = [
[Paragraph("<b>Manuscript title:</b>", cell_style),
Paragraph("", cell_style),
Paragraph("<b>Date:</b>", cell_style),
Paragraph(datetime.date.today().strftime("%d %b %Y"), cell_style)],
[Paragraph("<b>Author(s):</b>", cell_style),
Paragraph("", cell_style),
Paragraph("<b>Journal:</b>", cell_style),
Paragraph("", cell_style)],
]
info_tbl = Table(info_data, colWidths=[32*mm, 72*mm, 22*mm, 48*mm])
info_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), PALE_BLUE),
("BOX", (0,0), (-1,-1), 0.5, MID_BLUE),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
elements.append(info_tbl)
elements.append(Spacer(1, 5*mm))
# ---- Instructions ----
instr = (
"<b>Instructions:</b> For each item, record the page number(s) in your manuscript where the information appears. "
"If an item is not applicable, write 'N/A'. Items marked <font color='#2B5FA5'><b>[CASE-CONTROL]</b></font> contain "
"design-specific wording. This checklist is based on the STROBE Statement (von Elm et al., "
"<i>BMJ</i> 2007;335:806). It is a <b>reporting</b> guide, not a quality-scoring tool."
)
elements.append(Paragraph(instr, ParagraphStyle(
"instr", fontName="Helvetica", fontSize=7.2, textColor=DARK_GREY,
leading=10, borderColor=ACCENT, borderWidth=0.8, borderPadding=6,
backColor=HexColor("#FFFAF0"),
)))
elements.append(Spacer(1, 4*mm))
# ---- Column headers ----
col_headers = [
Paragraph("ITEM #", hdr_style),
Paragraph("TOPIC", hdr_style),
Paragraph("RECOMMENDATION", hdr_style),
Paragraph("PAGE(S) IN MS", hdr_style),
Paragraph("NOTES / COMMENTS", hdr_style),
]
COL_W = [13*mm, 28*mm, 82*mm, 20*mm, 31*mm]
# ---- Build table rows ----
current_section = None
table_rows = [col_headers]
row_styles = []
# Header row style
row_styles += [
("BACKGROUND", (0, 0), (-1, 0), MID_BLUE),
("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, 0), 7.5),
("TOPPADDING", (0, 0), (-1, 0), 5),
("BOTTOMPADDING", (0, 0), (-1, 0), 5),
("ALIGN", (0, 0), (-1, 0), "CENTER"),
("VALIGN", (0, 0), (-1, 0), "MIDDLE"),
]
row_idx = 1 # 0 = header
for (section, item_no, topic, recommendation, design_specific) in ITEMS:
# Section separator row
if section != current_section:
current_section = section
sec_row = [
Paragraph(section, section_style),
"", "", "", ""
]
table_rows.append(sec_row)
n = row_idx
row_styles += [
("SPAN", (0, n), (-1, n)),
("BACKGROUND", (0, n), (-1, n), DARK_BLUE),
("TOPPADDING", (0, n), (-1, n), 4),
("BOTTOMPADDING", (0, n), (-1, n), 4),
("LEFTPADDING", (0, n), (-1, n), 6),
]
row_idx += 1
# Topic cell — mark design-specific
topic_text = topic.replace("\n", "<br/>")
if design_specific:
topic_para = Paragraph(
f"{topic_text} <font color='#2B5FA5' size='6.5'>●</font>",
cell_bold)
else:
topic_para = Paragraph(topic_text, cell_bold)
# Recommendation cell
rec_text = recommendation.replace("\n", "<br/>")
rec_para = Paragraph(rec_text, cell_style)
data_row = [
Paragraph(f"<b>{item_no}</b>",
ParagraphStyle("itno", fontName="Helvetica-Bold", fontSize=8,
textColor=MID_BLUE, alignment=TA_CENTER, leading=10)),
topic_para,
rec_para,
Paragraph("", cell_style), # page(s) — blank for user to fill
Paragraph("", cell_style), # notes — blank
]
table_rows.append(data_row)
# Alternate row shading
bg = LIGHT_GREY if row_idx % 2 == 0 else WHITE
row_styles += [
("BACKGROUND", (0, row_idx), (-1, row_idx), bg),
("TOPPADDING", (0, row_idx), (-1, row_idx), 4),
("BOTTOMPADDING", (0, row_idx), (-1, row_idx), 5),
("LEFTPADDING", (0, row_idx), (-1, row_idx), 4),
("RIGHTPADDING", (0, row_idx), (-1, row_idx), 4),
("VALIGN", (0, row_idx), (-1, row_idx), "TOP"),
]
row_idx += 1
# Global table styles
row_styles += [
("BOX", (0, 0), (-1, -1), 0.6, MID_BLUE),
("INNERGRID", (0, 1), (-1, -1), 0.3, MID_GREY),
("ALIGN", (0, 1), (-1, -1), "LEFT"),
("ALIGN", (3, 1), (3, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "TOP"),
]
main_table = Table(table_rows, colWidths=COL_W, repeatRows=1)
main_table.setStyle(TableStyle(row_styles))
elements.append(main_table)
elements.append(Spacer(1, 5*mm))
# ---- Legend / footnote ----
legend = (
"<font color='#2B5FA5'><b>● Blue dot</b></font> = Item contains case-control-specific wording. "
"<b>N/A</b> = Not applicable. "
"Leave <i>Page(s) in MS</i> blank — fill in before journal submission.<br/>"
"<b>Reference:</b> von Elm E, Altman DG, Egger M, et al. The Strengthening the Reporting of "
"Observational Studies in Epidemiology (STROBE) statement: guidelines for reporting observational studies. "
"<i>BMJ</i> 2007;335:806. doi:10.1136/bmj.39335.541782.AD | "
"Full checklists: <u>www.strobe-statement.org</u>"
)
elements.append(Paragraph(legend, ParagraphStyle(
"leg", fontName="Helvetica", fontSize=6.5, textColor=HexColor("#555555"),
leading=9, borderColor=MID_GREY, borderWidth=0.5, borderPadding=5,
backColor=LIGHT_GREY,
)))
# ── Build PDF ──────────────────────────────────────────────────────────────
def add_page_number(canvas, doc):
canvas.saveState()
canvas.setFont("Helvetica", 7)
canvas.setFillColor(HexColor("#888888"))
w, h = A4
canvas.drawString(18*mm, 12*mm, "STROBE Checklist – Case-Control Study")
canvas.drawRightString(w - 18*mm, 12*mm, f"Page {doc.page}")
canvas.restoreState()
doc.build(elements, onFirstPage=add_page_number, onLaterPages=add_page_number)
print(f"PDF created: {OUTPUT}")
python3 /home/daytona/workspace/strobe-checklist/generate_strobe.py
STROBE CaseControl Checklist
PDF Document · PDF
make critical appraisal as per strobe guidlines
python3 -c " import pdfplumber text = '' with pdfplumber.open('/home/daytona/workspace/attachments/3ab3f39f-f981-451c-9ddf-b905186a52a2/diabetes and tb7.pdf') as pdf: print(f'Total pages: {len(pdf.pages)}') for i, page in enumerate(pdf.pages): t = page.extract_text() if t: text += f'\n\n--- PAGE {i+1} ---\n' + t print(text[:12000]) "
python3 -c " import pdfplumber text = '' with pdfplumber.open('/home/daytona/workspace/attachments/3ab3f39f-f981-451c-9ddf-b905186a52a2/diabetes and tb7.pdf') as pdf: for i, page in enumerate(pdf.pages): t = page.extract_text() if t: text += f'\n\n--- PAGE {i+1} ---\n' + t print(text[12000:]) "
~/strobe-checklist/generate_appraisal.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.lib.colors import HexColor
import datetime
OUTPUT = "/home/daytona/workspace/strobe-checklist/STROBE_CriticalAppraisal_DiabetesTB.pdf"
# ── Colours ─────────────────────────────────────────────────────────────────
DARK_BLUE = HexColor("#1B3A5C")
MID_BLUE = HexColor("#2B5FA5")
LIGHT_BLUE = HexColor("#D6E4F7")
PALE_BLUE = HexColor("#EBF2FB")
WHITE = colors.white
LIGHT_GREY = HexColor("#F5F5F5")
MID_GREY = HexColor("#CCCCCC")
DARK_GREY = HexColor("#333333")
GREEN_MET = HexColor("#1E7A4A")
GREEN_BG = HexColor("#E6F4ED")
AMBER_MET = HexColor("#B36A00")
AMBER_BG = HexColor("#FFF3E0")
RED_MET = HexColor("#B71C1C")
RED_BG = HexColor("#FDECEA")
ACCENT = HexColor("#E8A020")
doc = SimpleDocTemplate(
OUTPUT, pagesize=A4,
leftMargin=15*mm, rightMargin=15*mm,
topMargin=20*mm, bottomMargin=18*mm,
title="STROBE Critical Appraisal – Diabetes & TB",
)
styles = getSampleStyleSheet()
def PS(name, **kw):
return ParagraphStyle(name, parent=styles["Normal"], **kw)
hdr_s = PS("hdr", fontName="Helvetica-Bold", fontSize=7, textColor=WHITE, leading=9)
cell_s = PS("cell", fontName="Helvetica", fontSize=7, textColor=DARK_GREY, leading=9.5)
bold_s = PS("bold", fontName="Helvetica-Bold", fontSize=7, textColor=DARK_BLUE, leading=9.5)
sec_s = PS("sec", fontName="Helvetica-Bold", fontSize=8, textColor=WHITE, leading=10)
judge_s = PS("jud", fontName="Helvetica-Bold", fontSize=7, leading=9)
note_s = PS("note", fontName="Helvetica-Oblique", fontSize=6.5,
textColor=HexColor("#555555"), leading=8.5)
# ── Appraisal data ───────────────────────────────────────────────────────────
# Format: (section, item, topic, requirement_summary,
# judgement, # "MET" | "PARTIAL" | "NOT MET"
# evidence_from_paper, gap_or_comment)
APPRAISAL = [
# ─── TITLE & ABSTRACT ───────────────────────────────────────────────────
("TITLE & ABSTRACT", "1",
"Title & Abstract",
"(a) State study design in title/abstract. (b) Balanced informative abstract.",
"MET",
"Title explicitly states 'A Community Based Case Control Study'. Abstract covers "
"background, objective, methods, results (OR=2.456, 95% CI 1.31-4.61) and conclusion.",
"Minor: abstract does not report confidence intervals for all secondary risk factors "
"(tobacco, family history) mentioned in the results summary."),
# ─── INTRODUCTION ────────────────────────────────────────────────────────
("INTRODUCTION", "2",
"Background / Rationale",
"Explain scientific background and rationale.",
"MET",
"Introduction cites WHO 2015-2016 global TB and diabetes burden data, references prior "
"evidence that diabetes is a risk factor for TB, and explains the public health relevance "
"of documenting the association (refs 1-9).",
"Background is adequate; however, the biological mechanism by which diabetes increases "
"TB susceptibility (e.g. impaired cell-mediated immunity) is not discussed."),
("INTRODUCTION", "3",
"Objectives",
"State specific objectives and any pre-specified hypotheses.",
"MET",
"Objective stated: 'To study if diabetes is associated with tuberculosis compared to "
"controls.' Pre-specified OR threshold used for sample size calculation (OR=2.65) implies "
"a directional hypothesis.",
"Hypothesis not stated explicitly as a formal null/alternative hypothesis. Secondary "
"objectives (other risk factors) not listed upfront despite being analysed."),
# ─── METHODS ─────────────────────────────────────────────────────────────
("METHODS", "4",
"Study Design",
"Present key elements of study design early.",
"MET",
"Design identified as 'community based case-control study' in the title, abstract, and "
"opening of the Methods section. 1:1 matching for age (±10 years) and sex is stated.",
"None."),
("METHODS", "5",
"Setting",
"Describe setting, locations, relevant dates.",
"MET",
"Setting: Dundigal TB Unit, Medchal District, Hyderabad, Telangana, India. "
"Total TU population 5,48,800; TB prevalence 180.39/lakh. "
"Study period: January 2022 to June 2022.",
"The healthcare infrastructure context (DTCO, public vs. private TB notification) is "
"mentioned briefly but not elaborated. No information about seasonal variation or local "
"TB control activities during the study period."),
("METHODS", "6",
"Participants [Case-Control]",
"(a) Eligibility criteria, case ascertainment, control selection and rationale. "
"(b) Matching criteria and controls per case.",
"PARTIAL",
"Cases: newly diagnosed TB on treatment ≤3 months, age >30 years, randomly selected from "
"DTCO list. Controls: neighbourhood, no presumptive TB symptoms, matched ±10 years and "
"sex. Exclusion criteria stated. Ratio 1:1. Presumptive TB checklist used to exclude "
"controls. Ethics approval cited (MRIMS-DHR-IEC-97/2021).",
"GAP 1 – TB case definition relies solely on treatment registration, not microbiological "
"or radiological confirmation criteria (sputum smear/culture/CBNAAT not mentioned). "
"GAP 2 – Rationale for choosing neighbourhood controls vs. other control types is not "
"explicitly justified; volunteer bias in controls possible. "
"GAP 3 – No mention of how controls were verified to be non-diabetic at baseline "
"(only TB exclusion described). "
"GAP 4 – Non-response rates and whether replacements were similar to recruited controls "
"are not reported."),
("METHODS", "7",
"Variables",
"Define all outcomes, exposures, predictors, confounders, effect modifiers; give "
"diagnostic criteria.",
"PARTIAL",
"Primary exposure: known case of diabetes (self-reported). Primary outcome: TB case "
"status. Covariates listed: sex, residence, marital status, education, occupation, "
"contact with TB, family history of TB, smoking, alcohol, tobacco chewing. "
"Continuous variables: BMI, SBP, DBP, FBG, age, per capita income.",
"GAP 1 – Diabetes definition is ambiguous: 'known case of diabetes' implies "
"self-report/prior diagnosis. FBG was measured but the cut-off for diagnosing "
"new diabetes is never stated. Were newly detected diabetics (high FBG but no "
"prior diagnosis) classified as diabetic or non-diabetic? "
"GAP 2 – Type of TB (pulmonary vs. extra-pulmonary) not defined. "
"GAP 3 – Duration of diabetes, type of diabetes (T1/T2), and glycaemic control "
"(HbA1c) not captured – important effect modifiers. "
"GAP 4 – HIV status not collected (acknowledged in discussion as a limitation)."),
("METHODS", "8",
"Data Sources / Measurement",
"Sources and methods of data collection; comparability of methods across groups.",
"PARTIAL",
"FBG measured by Apollo glucometer (model APG01) after 8-10h fast using capillary "
"blood; glucometer standardised as per WHO. Baseline characteristics from pre-designed, "
"pre-tested, semi-structured questionnaire. Data collected by direct interview and "
"direct measurement.",
"GAP 1 – No description of who administered the questionnaire or whether assessors "
"were blinded to case/control status (interviewer bias risk). "
"GAP 2 – Glucometer validation: 'standardised as per WHO' is vague; no mention of "
"parallel laboratory venous FBG for quality control. "
"GAP 3 – Pre-testing of questionnaire is mentioned but pilot study results not reported. "
"GAP 4 – Income and occupation data rely on self-report; no objective verification."),
("METHODS", "9",
"Bias",
"Describe efforts to address potential sources of bias.",
"PARTIAL",
"Authors acknowledge and briefly address: selection bias (random sampling of cases, "
"presumptive TB check for controls), recall bias (direct observation/measurements), "
"interviewer bias (direct measurements used where possible), confounding (matching + "
"logistic regression). Community-based design noted to reduce hospital bias.",
"GAP 1 – No structured bias assessment tool used (e.g., Newcastle-Ottawa Scale items "
"not cross-referenced). "
"GAP 2 – Information bias: TB patients may over-report healthier behaviours "
"(social desirability); not addressed. "
"GAP 3 – Surveillance bias: diabetics may be more likely to be screened for TB and "
"thus more likely to be a 'case' – this classic protopathic/detection bias for "
"case-control studies of DM-TB is not discussed."),
("METHODS", "10",
"Study Size",
"Explain how study size was determined.",
"MET",
"Power calculation explicitly described: OpenEpi software, alpha=5%, power=80%, "
"1:1 ratio, minimum detectable OR=2.65 based on prior literature proportions "
"(cases 13.6% diabetes, controls 5.6%). Calculated sample size: 215 per group.",
"The reference for the proportions used in the power calculation (ref 10, Pereira 2016) "
"is cited appropriately. No sample size inflation for matching or attrition was described."),
("METHODS", "11",
"Quantitative Variables",
"How quantitative variables were handled; rationale for any groupings.",
"PARTIAL",
"BMI, SBP, DBP, FBG, age and income compared as continuous means (t-test). "
"Categorical variables (education, occupation, residence) used as entered. "
"BMI retained as continuous in regression.",
"GAP 1 – FBG is a key exposure variable but is only analysed as a continuous mean "
"comparison; no cut-point analysis (e.g., ≥126 mg/dl = diabetes) is presented to "
"quantify risk attributable to newly detected diabetes. "
"GAP 2 – Age was a matching variable but is also included in the regression; the "
"statistical treatment of matched variables needs justification (conditional vs. "
"unconditional logistic regression). "
"GAP 3 – Income groupings not defined (only mean reported)."),
("METHODS", "12",
"Statistical Methods [Case-Control]",
"(a) Methods for confounding. (b) Subgroups/interactions. (c) Missing data. "
"(d) Matching accounted for. (e) Sensitivity analyses.",
"PARTIAL",
"(a) Chi-square for proportions, t-test for means, binary logistic regression for "
"multivariable analysis. Nagelkerke R²=0.438, Hosmer-Lemeshow test done, Omnibus "
"test significant. IBM SPSS v20. "
"(b) Not stated. "
"(c) Not reported. "
"(d) Age and sex matched 1:1 but unconditional logistic regression used (not "
"conditional). "
"(e) Not reported.",
"GAP 1 – Matching was 1:1 on age and sex. Standard practice requires conditional "
"logistic regression (CLR) for matched case-control data. Unconditional logistic "
"regression used here may produce biased OR estimates, especially for the matched "
"variables. This is a significant methodological concern. "
"GAP 2 – No report of missing data: how many participants had missing FBG, BMI, "
"or other variables? No imputation strategy described. "
"GAP 3 – No interaction/effect modification analysis (e.g., DM × HIV, DM × "
"tobacco) despite HIV being acknowledged as a potential confounder. "
"GAP 4 – No sensitivity analysis (e.g., excluding borderline diabetes cases, "
"restricting to pulmonary TB only)."),
# ─── RESULTS ─────────────────────────────────────────────────────────────
("RESULTS", "13",
"Participants [Case-Control]",
"(a) Numbers at each stage. (b) Reasons for non-participation. "
"(c) Flow diagram recommended.",
"NOT MET",
"Methods state 215 cases randomly selected from DTCO list and 215 neighbourhood "
"controls recruited. Results report full n=215 cases and 215 controls with no "
"attrition mentioned.",
"GAP 1 – No participant flow diagram provided. "
"GAP 2 – Total number of TB cases on DTCO list (sampling frame) not reported; "
"therefore overall response/recruitment rate cannot be calculated. "
"GAP 3 – No report of how many controls were approached and refused or were "
"ineligible before 215 were enrolled. Methods describe searching multiple houses "
"but numbers are not given. "
"GAP 4 – 'Only 12 were from rural areas' and 'more non-response from females' "
"mentioned in results without quantifying non-response."),
("RESULTS", "14",
"Descriptive Data [Case-Control]",
"(a) Participant characteristics, exposures, confounders. "
"(b) Missing data. (c) Controls per case.",
"PARTIAL",
"Table 1: case/control breakdown for all categorical sociodemographic and risk "
"factors with P values. Table 2: mean continuous variables (BMI, SBP, DBP, FBG, "
"age, income) with SD and P values. 1:1 matching confirmed (215 per group).",
"GAP 1 – Missing data not reported for any variable. "
"GAP 2 – Distributions of matching variables (age, sex) between groups are "
"described but matching adequacy is not formally tested. "
"GAP 3 – No information on TB type (pulmonary/extra-pulmonary/smear +/-) for cases. "
"GAP 4 – Duration or type of diabetes not described for the 63 diabetic cases "
"and 31 diabetic controls."),
("RESULTS", "15",
"Outcome Data [Case-Control]",
"Report numbers in each exposure category or summary exposure measures.",
"MET",
"Exposure (diabetes) frequencies given: 63/215 cases (67% of diabetics) vs. 31/215 "
"controls (33% of diabetics). Table 1 presents all binary risk factors with case/control "
"counts and row percentages. Crude OR reported (2.456, 95% CI 1.31-4.61).",
"Row percentages in Table 1 are computed within the diabetes subgroup (67% vs. 33%), "
"which is unconventional for a case-control table – prevalence of diabetes within "
"cases vs. controls is more interpretable (cases: 63/215=29.3%, controls: 31/215=14.4%). "
"This presentation may confuse readers."),
("RESULTS", "16",
"Main Results",
"(a) Unadjusted and adjusted estimates with 95% CI. (b) Category boundaries. "
"(c) Absolute risk if relevant.",
"MET",
"Table 3 provides full logistic regression output: B coefficient, P value, OR, "
"95% CI for all variables. Adjusted OR for diabetes = 2.456 (95% CI 1.31-4.61, "
"P=0.005). Unadjusted ORs implied from Table 1 chi-square analysis.",
"GAP 1 – Unadjusted (crude) ORs with 95% CI are not explicitly tabulated; only "
"chi-square P values are given in Table 1. Readers cannot directly compare crude "
"vs. adjusted ORs. "
"GAP 2 – No explanation of which confounders were selected for inclusion in the "
"regression model (all significant univariable variables entered – this stepwise "
"approach can lead to overfitting with 215 cases and 11 predictors in model)."),
("RESULTS", "17",
"Other Analyses",
"Report subgroup, interaction, sensitivity analyses.",
"NOT MET",
"Nagelkerke R², Hosmer-Lemeshow test, and Omnibus test reported as model fit "
"statistics. Predictive accuracy improvement (50% → 77.2%) reported.",
"No subgroup analyses by sex, age group, TB type, diabetes type, or HIV status. "
"No interaction terms tested. No sensitivity analysis. Model fit statistics do "
"not substitute for subgroup or sensitivity analyses."),
# ─── DISCUSSION ──────────────────────────────────────────────────────────
("DISCUSSION", "18",
"Key Results",
"Summarise key results with reference to objectives.",
"MET",
"Discussion opens by directly addressing the objective: 'diabetes was a strong "
"predictor of tuberculosis.' Main OR (2.456) restated and compared with prior "
"systematic reviews (Jeon & Murray, Foe-Essomba, Al-Rifai). BMI and tobacco "
"chewing also discussed.",
"Minor: blood pressure findings in Table 2 (significant lower SBP/DBP in cases) "
"are not discussed – this unexpected finding warrants explanation."),
("DISCUSSION", "19",
"Limitations",
"Discuss bias sources, direction and magnitude of potential bias.",
"PARTIAL",
"Authors acknowledge: limited sample size from one area, possible generalisability "
"issues, self-reported data for education/income/occupation, HIV status not "
"collected, confounding addressed by matching + regression.",
"GAP 1 – Detection/surveillance bias (diabetics more likely to be screened and "
"diagnosed with TB) not discussed despite being the most critical bias in DM-TB "
"case-control studies. "
"GAP 2 – Use of unconditional rather than conditional logistic regression for "
"matched data is not acknowledged as a limitation. "
"GAP 3 – Direction and magnitude of biases not quantified or estimated "
"(e.g., 'bias would likely lead to an overestimate/underestimate of OR'). "
"GAP 4 – Self-report of diabetes status as the primary exposure (not confirmed "
"biochemically in all participants) not discussed as a potential misclassification "
"source."),
("DISCUSSION", "20",
"Interpretation",
"Cautious interpretation considering objectives, limitations, multiplicity, "
"and other evidence.",
"PARTIAL",
"Findings contextualised against four systematic reviews/meta-analyses. "
"Public health implications discussed (NCD program, active TB screening in "
"diabetics, lifestyle modification).",
"GAP 1 – The interpretation is moderately cautious about generalisability but "
"does not sufficiently temper the conclusion given the methodological gaps "
"(unconditional regression for matched data, no HIV adjustment). "
"GAP 2 – The statement 'diabetes was an independent risk factor for tuberculosis' "
"is presented with confidence despite not adjusting for HIV – a well-known "
"confounder explicitly cited in the discussion. "
"GAP 3 – Reverse causality (TB causing weight loss → lower BMI masking diabetes) "
"not discussed."),
("DISCUSSION", "21",
"Generalisability",
"Discuss external validity.",
"PARTIAL",
"Authors acknowledge limited generalisability due to single TU from one district. "
"They counter this by noting results are consistent with systematic reviews.",
"The counter-argument (consistency with meta-analyses) is reasonable but does not "
"address structural limitations: urban-predominant sample (205/215 urban controls), "
"single ethnic/geographical population, and exclusion of HIV-positive individuals "
"all limit generalisability to rural or high-HIV-burden settings."),
# ─── OTHER INFORMATION ───────────────────────────────────────────────────
("OTHER INFORMATION", "22",
"Funding",
"Source of funding and role of funders.",
"MET",
"Funded by Tuberculosis Association of Telangana (stated in Acknowledgements and "
"Financial support section). Conflicts of interest declared as none.",
"Role of the funder in study design, data collection, analysis or decision to "
"publish is not described."),
]
# ── Count scores ─────────────────────────────────────────────────────────────
counts = {"MET": 0, "PARTIAL": 0, "NOT MET": 0}
for item in APPRAISAL:
counts[item[4]] += 1
total = len(APPRAISAL)
# ── Build PDF ─────────────────────────────────────────────────────────────────
elements = []
# ── Cover header ─────────────────────────────────────────────────────────────
header_data = [[
Paragraph("STROBE CRITICAL APPRAISAL REPORT",
PS("t1", fontName="Helvetica-Bold", fontSize=16,
textColor=WHITE, alignment=TA_CENTER)),
Paragraph(
"Diabetes Mellitus as a Risk Factor for Tuberculosis:<br/>"
"<font size='10'>A Community Based Case-Control Study</font><br/>"
"<font size='9' color='#BDD7F5'>Kokiwar PR & Soodi Reddy AK — Indian J Community Med 2024;49:617-21</font>",
PS("t2", fontName="Helvetica", fontSize=11,
textColor=HexColor("#BDD7F5"), alignment=TA_CENTER, leading=15)),
]]
header_tbl = Table(header_data, colWidths=[180*mm])
header_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 12),
("BOTTOMPADDING", (0,0), (-1,-1), 12),
]))
elements.append(header_tbl)
elements.append(Spacer(1, 4*mm))
# ── Summary scorecard ────────────────────────────────────────────────────────
sc_hdr = [
Paragraph("APPRAISAL SUMMARY", PS("sch", fontName="Helvetica-Bold",
fontSize=8, textColor=WHITE, alignment=TA_CENTER)),
Paragraph(f"MET\n{counts['MET']}/{total}", PS("scmet",
fontName="Helvetica-Bold", fontSize=13,
textColor=GREEN_MET, alignment=TA_CENTER, leading=16)),
Paragraph(f"PARTIAL\n{counts['PARTIAL']}/{total}", PS("scpart",
fontName="Helvetica-Bold", fontSize=13,
textColor=AMBER_MET, alignment=TA_CENTER, leading=16)),
Paragraph(f"NOT MET\n{counts['NOT MET']}/{total}", PS("scnot",
fontName="Helvetica-Bold", fontSize=13,
textColor=RED_MET, alignment=TA_CENTER, leading=16)),
Paragraph("STUDY DESIGN\nCase-Control", PS("scsd",
fontName="Helvetica-Bold", fontSize=8,
textColor=MID_BLUE, alignment=TA_CENTER, leading=11)),
Paragraph("APPRAISER\nOrris AI — STROBE 2007", PS("scap",
fontName="Helvetica-Oblique", fontSize=7,
textColor=HexColor("#555555"), alignment=TA_CENTER, leading=10)),
]
sc_tbl = Table([sc_hdr], colWidths=[30*mm, 27*mm, 27*mm, 27*mm, 35*mm, 34*mm])
sc_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), DARK_BLUE),
("BACKGROUND", (1,0), (1,0), GREEN_BG),
("BACKGROUND", (2,0), (2,0), AMBER_BG),
("BACKGROUND", (3,0), (3,0), RED_BG),
("BACKGROUND", (4,0), (4,0), PALE_BLUE),
("BACKGROUND", (5,0), (5,0), LIGHT_GREY),
("BOX", (0,0), (-1,-1), 0.5, MID_BLUE),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
]))
elements.append(sc_tbl)
elements.append(Spacer(1, 4*mm))
# ── Legend ───────────────────────────────────────────────────────────────────
legend_txt = (
"<b>Judgement key:</b> "
"<font color='#1E7A4A'><b>MET</b></font> = STROBE requirement fully satisfied; "
"<font color='#B36A00'><b>PARTIAL</b></font> = requirement partially satisfied with identifiable gaps; "
"<font color='#B71C1C'><b>NOT MET</b></font> = requirement absent or inadequately addressed. "
"This appraisal evaluates <i>reporting quality</i>, not study quality."
)
elements.append(Paragraph(legend_txt, PS("leg", fontName="Helvetica",
fontSize=6.8, textColor=HexColor("#444444"), leading=9,
borderColor=MID_GREY, borderWidth=0.4, borderPadding=5,
backColor=LIGHT_GREY)))
elements.append(Spacer(1, 4*mm))
# ── Column headers ────────────────────────────────────────────────────────────
COL_W = [11*mm, 25*mm, 16*mm, 60*mm, 68*mm]
col_hdr_row = [
Paragraph("ITEM", hdr_s),
Paragraph("TOPIC", hdr_s),
Paragraph("JUDGEMENT", hdr_s),
Paragraph("EVIDENCE FROM PAPER", hdr_s),
Paragraph("GAPS / CRITICAL COMMENTS", hdr_s),
]
table_rows = [col_hdr_row]
row_styles = [
("BACKGROUND", (0,0), (-1,0), MID_BLUE),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,0), "MIDDLE"),
("TOPPADDING", (0,0), (-1,0), 5),
("BOTTOMPADDING", (0,0), (-1,0), 5),
]
current_section = None
row_idx = 1
for (section, item, topic, req, judgement, evidence, gap) in APPRAISAL:
# Section row
if section != current_section:
current_section = section
sec_row = [Paragraph(section, sec_s), "", "", "", ""]
table_rows.append(sec_row)
n = row_idx
row_styles += [
("SPAN", (0,n), (-1,n)),
("BACKGROUND", (0,n), (-1,n), DARK_BLUE),
("TOPPADDING", (0,n), (-1,n), 3),
("BOTTOMPADDING", (0,n), (-1,n), 3),
("LEFTPADDING", (0,n), (-1,n), 5),
]
row_idx += 1
# Judgement badge colour
if judgement == "MET":
j_color = GREEN_MET; j_bg = GREEN_BG
elif judgement == "PARTIAL":
j_color = AMBER_MET; j_bg = AMBER_BG
else:
j_color = RED_MET; j_bg = RED_BG
judge_para = Paragraph(
judgement,
PS(f"j{row_idx}", fontName="Helvetica-Bold", fontSize=7.5,
textColor=j_color, alignment=TA_CENTER, leading=10))
data_row = [
Paragraph(f"<b>{item}</b>", PS("in", fontName="Helvetica-Bold",
fontSize=8, textColor=MID_BLUE, alignment=TA_CENTER, leading=10)),
Paragraph(f"<b>{topic}</b>", bold_s),
judge_para,
Paragraph(evidence, cell_s),
Paragraph(gap if gap else "—", cell_s),
]
table_rows.append(data_row)
bg = HexColor("#FAFAFA") if row_idx % 2 == 0 else WHITE
row_styles += [
("BACKGROUND", (0, row_idx), (-1, row_idx), bg),
("BACKGROUND", (2, row_idx), (2, row_idx), j_bg),
("TOPPADDING", (0, row_idx), (-1, row_idx), 4),
("BOTTOMPADDING", (0, row_idx), (-1, row_idx), 5),
("LEFTPADDING", (0, row_idx), (-1, row_idx), 3),
("RIGHTPADDING", (0, row_idx), (-1, row_idx), 3),
("VALIGN", (0, row_idx), (-1, row_idx), "TOP"),
]
row_idx += 1
row_styles += [
("BOX", (0,0), (-1,-1), 0.6, MID_BLUE),
("INNERGRID", (0,1), (-1,-1), 0.3, MID_GREY),
("ALIGN", (0,1), (-1,-1), "LEFT"),
("ALIGN", (2,1), (2,-1), "CENTER"),
]
main_table = Table(table_rows, colWidths=COL_W, repeatRows=1)
main_table.setStyle(TableStyle(row_styles))
elements.append(main_table)
elements.append(Spacer(1, 5*mm))
# ── Overall assessment narrative ──────────────────────────────────────────────
summary_txt = (
"<b>OVERALL APPRAISAL SUMMARY</b><br/><br/>"
f"This case-control study satisfies <font color='#1E7A4A'><b>{counts['MET']} of 22</b></font> STROBE items fully, "
f"<font color='#B36A00'><b>{counts['PARTIAL']}</b></font> partially, and "
f"<font color='#B71C1C'><b>{counts['NOT MET']}</b></font> are not met. "
"The study is well-structured and its main finding (adjusted OR 2.456, 95% CI 1.31-4.61 for "
"diabetes as a TB risk factor) is credible and consistent with prior meta-analyses. "
"However, four reporting gaps are of particular methodological importance:<br/><br/>"
"<b>1. Matching and statistical analysis (Item 12):</b> A 1:1 age/sex-matched design requires "
"conditional logistic regression. The use of unconditional logistic regression may bias "
"the OR estimates for the matched variables.<br/>"
"<b>2. Participant flow (Item 13):</b> No flow diagram and no reporting of non-participation "
"rates prevent assessment of selection bias and sampling adequacy.<br/>"
"<b>3. Exposure definition (Item 7):</b> Diabetes was primarily classified as 'known case' "
"(self-report). The FBG data were not used to reclassify participants, leaving potential "
"exposure misclassification unaddressed.<br/>"
"<b>4. Missing data (Item 12c):</b> No report of missing data for any variable, which is "
"essential for transparency in observational research.<br/><br/>"
"These gaps do not invalidate the conclusions but should be addressed in a revision "
"or acknowledged as limitations."
)
sum_tbl = Table([[Paragraph(summary_txt, PS("sum", fontName="Helvetica",
fontSize=7.2, textColor=DARK_GREY, leading=10))]],
colWidths=[180*mm])
sum_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), PALE_BLUE),
("BOX", (0,0), (-1,-1), 0.8, MID_BLUE),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
]))
elements.append(sum_tbl)
elements.append(Spacer(1, 4*mm))
elements.append(Paragraph(
"<b>Reference:</b> von Elm E et al. STROBE statement: guidelines for reporting "
"observational studies. <i>BMJ</i> 2007;335:806. | "
"Appraised by Orris AI on " + datetime.date.today().strftime("%d %b %Y"),
PS("ref", fontName="Helvetica-Oblique", fontSize=6.5,
textColor=HexColor("#666666"), leading=9)))
# ── Page numbers ──────────────────────────────────────────────────────────────
def footer(canvas, doc):
canvas.saveState()
canvas.setFont("Helvetica", 6.5)
canvas.setFillColor(HexColor("#888888"))
w, _ = A4
canvas.drawString(15*mm, 11*mm,
"STROBE Critical Appraisal — Diabetes & TB (Kokiwar & Soodi Reddy, 2024)")
canvas.drawRightString(w - 15*mm, 11*mm, f"Page {doc.page}")
canvas.restoreState()
doc.build(elements, onFirstPage=footer, onLaterPages=footer)
print(f"PDF created: {OUTPUT}")
python3 /home/daytona/workspace/strobe-checklist/generate_appraisal.py
STROBE CriticalAppraisal DiabetesTB
PDF Document · PDF
| Judgement | Count |
|---|---|
| ✅ MET | 8 / 22 |
| 🟡 PARTIAL | 11 / 22 |
| ❌ NOT MET | 3 / 22 |
Create a summary table showing which adjustments would improve reporting compliance
~/strobe-checklist/generate_improvement_table.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.enums import TA_LEFT, TA_CENTER
from reportlab.lib.colors import HexColor
import datetime
OUTPUT = "/home/daytona/workspace/strobe-checklist/STROBE_ImprovementTable_DiabetesTB.pdf"
# ── Colours ──────────────────────────────────────────────────────────────────
DARK_BLUE = HexColor("#1B3A5C")
MID_BLUE = HexColor("#2B5FA5")
PALE_BLUE = HexColor("#EBF2FB")
LIGHT_BLUE = HexColor("#D6E4F7")
WHITE = colors.white
LIGHT_GREY = HexColor("#F5F5F5")
MID_GREY = HexColor("#CCCCCC")
DARK_GREY = HexColor("#333333")
GREEN_MET = HexColor("#1E7A4A")
GREEN_BG = HexColor("#E6F4ED")
AMBER_MET = HexColor("#B36A00")
AMBER_BG = HexColor("#FFF3E0")
RED_MET = HexColor("#B71C1C")
RED_BG = HexColor("#FDECEA")
PURPLE = HexColor("#6A1B9A")
PURPLE_BG = HexColor("#F3E5F5")
TEAL = HexColor("#00695C")
TEAL_BG = HexColor("#E0F2F1")
# Priority colours
HIGH_C = HexColor("#C62828")
HIGH_BG = HexColor("#FFEBEE")
MED_C = HexColor("#E65100")
MED_BG = HexColor("#FFF3E0")
LOW_C = HexColor("#1565C0")
LOW_BG = HexColor("#E3F2FD")
doc = SimpleDocTemplate(
OUTPUT, pagesize=A4,
leftMargin=13*mm, rightMargin=13*mm,
topMargin=20*mm, bottomMargin=18*mm,
title="STROBE Improvement Table – Diabetes & TB",
)
styles = getSampleStyleSheet()
def PS(name, **kw):
return ParagraphStyle(name, parent=styles["Normal"], **kw)
hdr_s = PS("hdr", fontName="Helvetica-Bold", fontSize=7, textColor=WHITE, leading=9)
cell_s = PS("cell", fontName="Helvetica", fontSize=6.8, textColor=DARK_GREY, leading=9.2)
bold_s = PS("bold", fontName="Helvetica-Bold", fontSize=7, textColor=DARK_BLUE, leading=9.2)
sec_s = PS("sec", fontName="Helvetica-Bold", fontSize=8, textColor=WHITE, leading=10)
sm_s = PS("sm", fontName="Helvetica", fontSize=6.3, textColor=HexColor("#555555"), leading=8.5)
# ── Improvement data ─────────────────────────────────────────────────────────
# Columns:
# item_no, topic, current_status, current_gap, recommended_adjustment,
# where_to_add, priority, expected_outcome_after_fix
#
# Priority: HIGH = affects result validity / core STROBE requirement
# MEDIUM = important for transparency / reproducibility
# LOW = minor reporting enhancement
IMPROVEMENTS = [
# ─── NOT MET → MET ────────────────────────────────────────────────────────
("13",
"Participant Flow",
"NOT MET",
"No flow diagram. Total sampling frame, refusals, and ineligible counts not reported.",
"Add a CONSORT-style participant flow diagram showing: total TB cases on DTCO list → "
"randomly selected → approached → consented → enrolled (215). For controls: houses "
"visited → eligible → agreed → enrolled (215). Report non-response rate.",
"Methods (end of Participants subsection) + Figure 1",
"HIGH",
"NOT MET → MET"),
("17",
"Other Analyses",
"NOT MET",
"No subgroup, interaction, or sensitivity analyses reported.",
"(1) Run sex-stratified ORs for diabetes-TB association. "
"(2) Test DM × tobacco chewing interaction term in regression. "
"(3) Sensitivity analysis excluding the 12 rural participants. "
"(4) Sensitivity analysis restricting to sputum-confirmed TB cases if data available. "
"Report as Table 4 or Supplementary.",
"Results section — new subsection 'Subgroup and Sensitivity Analyses'",
"MEDIUM",
"NOT MET → MET"),
# ─── PARTIAL → MET ────────────────────────────────────────────────────────
("6",
"Participants — Case Definition",
"PARTIAL",
"TB case definition relies on treatment registration only; "
"microbiological confirmation criteria not stated.",
"Specify the RNTCP/NTP diagnostic criteria used: state whether cases were "
"bacteriologically confirmed (smear/CBNAAT/culture positive) or clinically diagnosed. "
"Report proportions of each type. Add a sentence: 'All cases were notified under "
"the DTCO Medchal; X% were bacteriologically confirmed.'",
"Methods — Participants subsection",
"HIGH",
"PARTIAL → MET"),
("7",
"Diabetes Exposure Definition",
"PARTIAL",
"Diabetes classified as 'known case' (self-report). FBG measured but not used "
"to reclassify participants. Diabetes type/duration not captured.",
"(1) Define the exposure explicitly: 'Known diabetic = prior physician diagnosis "
"OR FBG ≥ 126 mg/dL on study day (ADA 2023 criteria).' "
"(2) Reclassify participants using FBG cut-off and report as: known diabetic / "
"newly detected diabetic / non-diabetic. "
"(3) Add a supplementary table for diabetes duration and type (T1/T2) distribution.",
"Methods — Variables subsection + Results Table 1 (add new diabetes rows)",
"HIGH",
"PARTIAL → MET"),
("8",
"Data Collection / Measurement",
"PARTIAL",
"No information on assessor blinding; glucometer validation described vaguely; "
"pre-test results not reported.",
"(1) State whether interviewers were blinded to case/control status. "
"(2) Report glucometer QC: 'Glucometer readings were validated against venous "
"laboratory FBG in a random 10% sub-sample; Bland-Altman agreement was [X].' "
"(3) Add one sentence summarising pilot/pre-test findings.",
"Methods — Data Sources/Measurement subsection",
"MEDIUM",
"PARTIAL → MET"),
("9",
"Bias Assessment",
"PARTIAL",
"Detection/surveillance bias (diabetics screened more for TB) not addressed. "
"Direction and magnitude of biases not estimated.",
"(1) Add a paragraph discussing detection bias: 'Diabetics attending clinics may "
"be more likely to receive TB screening, potentially inflating case count among "
"diabetics (upward bias on OR).' "
"(2) For each major bias, state direction: e.g., 'recall bias would likely "
"underestimate tobacco exposure equally in both groups, minimally affecting OR.' "
"(3) Reference the Newcastle-Ottawa Scale assessment if performed.",
"Methods — Bias subsection + Discussion — Limitations",
"MEDIUM",
"PARTIAL → MET"),
("11",
"Quantitative Variables",
"PARTIAL",
"FBG analysed only as continuous mean; no cut-point analysis for diabetes "
"diagnosis. Conditional vs. unconditional regression not justified.",
"(1) Present FBG as categorical: <100, 100-125, ≥126 mg/dL with case/control "
"frequencies and OR per category. "
"(2) Justify use of unconditional logistic regression for matched data, or switch "
"to conditional logistic regression (CLR) using SPSS 'CLOGIT' procedure. "
"(3) Define income groups used.",
"Methods — Quantitative Variables + Results Table 2",
"HIGH",
"PARTIAL → MET"),
("12",
"Statistical Methods — Matching",
"PARTIAL",
"Unconditional logistic regression used for 1:1 matched design. "
"No missing data report. No sensitivity analyses.",
"(1) Re-run analysis using conditional logistic regression (matched pairs). "
"Compare CLR-OR with unconditional OR and report both. "
"(2) Report missing data per variable as footnote to Tables 1 and 2: "
"'X participants had missing FBG and were excluded from FBG comparison.' "
"(3) Add at least one sensitivity analysis (see Item 17 above).",
"Methods — Statistical Methods subsection + Results Tables 1-3",
"HIGH",
"PARTIAL → MET"),
("14",
"Descriptive Data",
"PARTIAL",
"Missing data not reported for any variable. TB type not described. "
"Diabetes characteristics not described for diabetic subgroup.",
"(1) Add column 'n missing' to Tables 1 and 2 for each variable. "
"(2) Add a row in Table 1 for TB type (pulmonary/extra-pulmonary). "
"(3) Add Table footnote or supplementary panel: diabetes duration, type, "
"and treatment (OHA/insulin) among the 63 diabetic cases and 31 diabetic controls.",
"Results — Tables 1 and 2 (add missing data column and TB type row)",
"MEDIUM",
"PARTIAL → MET"),
("16",
"Main Results — Crude ORs",
"PARTIAL",
"Crude (unadjusted) ORs with 95% CI not explicitly tabulated; only P values "
"from chi-square in Table 1. Confounder selection not justified.",
"(1) Add a column to Table 1 (or new Table 3a) showing crude OR (95% CI) "
"for each binary risk factor. "
"(2) Add one sentence in Methods explaining confounder selection strategy: "
"'Variables significant at P<0.05 on univariable analysis were entered into "
"multivariable model' — and cite potential overfitting concern "
"(events per variable = 215/11 ≈ 20, acceptable but borderline).",
"Results — Table 1 (add crude OR column) + Methods — Statistical Methods",
"MEDIUM",
"PARTIAL → MET"),
("19",
"Limitations",
"PARTIAL",
"Detection bias not discussed. Unconditional regression not acknowledged "
"as limitation. Diabetes misclassification not discussed.",
"(1) Add paragraph: 'A key limitation is the potential for detection/surveillance "
"bias: diabetics under regular clinical follow-up may be disproportionately "
"screened for TB, inflating the observed association.' "
"(2) Acknowledge use of unconditional logistic regression: 'Conditional logistic "
"regression would be the preferred method for matched designs; our unconditional "
"approach may slightly bias estimates for the matched variables.' "
"(3) Discuss diabetes self-report as exposure misclassification.",
"Discussion — Limitations subsection",
"HIGH",
"PARTIAL → MET"),
("20",
"Interpretation",
"PARTIAL",
"Conclusion overstated given lack of HIV adjustment and matching regression issue. "
"Reverse causality not discussed.",
"(1) Temper the independence claim: 'Diabetes remained associated with TB after "
"adjustment for available confounders; however, absence of HIV data means residual "
"confounding cannot be excluded.' "
"(2) Add one sentence on reverse causality: 'We cannot fully exclude the "
"possibility that active TB-associated weight loss influenced BMI values measured "
"after diagnosis.'",
"Discussion — Interpretation paragraph",
"MEDIUM",
"PARTIAL → MET"),
("21",
"Generalisability",
"PARTIAL",
"Urban predominance (205/215 urban) and exclusion of HIV-positive individuals "
"not discussed as specific generalisability limits.",
"Add explicit sentence: 'Results may not be generalisable to rural populations "
"or settings with high HIV prevalence, as 95% of our sample was urban and "
"HIV-positive individuals were not studied.'",
"Discussion — Generalisability paragraph",
"LOW",
"PARTIAL → MET"),
# ─── MET items — minor enhancements ──────────────────────────────────────
("1",
"Title & Abstract",
"MET",
"Abstract omits 95% CI for secondary outcomes (tobacco OR, family history OR).",
"Add CIs for the two secondary independent risk factors in the abstract results "
"sentence: 'Tobacco chewing (OR X.X, 95% CI X.X-X.X) and family history of TB "
"(OR X.X, 95% CI X.X-X.X) were also independently associated.'",
"Abstract — Results sentence",
"LOW",
"MET (enhanced)"),
("2",
"Background / Rationale",
"MET",
"Biological mechanism of DM-TB link not described.",
"Add 1-2 sentences on mechanism: 'Diabetes impairs T-lymphocyte and macrophage "
"function, reduces IFN-γ production, and impairs mucociliary clearance, "
"collectively increasing susceptibility to Mycobacterium tuberculosis.'",
"Introduction — Background paragraph",
"LOW",
"MET (enhanced)"),
("22",
"Funding",
"MET",
"Role of funder not described.",
"Add: 'The funder had no role in study design, data collection, analysis, "
"interpretation, or the decision to submit for publication.'",
"Acknowledgements / Financial support section",
"LOW",
"MET (enhanced)"),
]
# ── Priority count ─────────────────────────────────────────────────────────────
p_counts = {"HIGH": 0, "MEDIUM": 0, "LOW": 0}
for row in IMPROVEMENTS:
p_counts[row[6]] += 1
# ── Build PDF ─────────────────────────────────────────────────────────────────
elements = []
# ── Header ────────────────────────────────────────────────────────────────────
hdr_data = [[
Paragraph("STROBE COMPLIANCE IMPROVEMENT TABLE",
PS("t1", fontName="Helvetica-Bold", fontSize=15,
textColor=WHITE, alignment=TA_CENTER)),
Paragraph(
"Diabetes Mellitus as a Risk Factor for Tuberculosis<br/>"
"<font size='9' color='#BDD7F5'>Kokiwar & Soodi Reddy — Indian J Community Med 2024;49:617-21</font>",
PS("t2", fontName="Helvetica", fontSize=10.5,
textColor=HexColor("#BDD7F5"), alignment=TA_CENTER, leading=14)),
]]
hdr_tbl = Table(hdr_data, colWidths=[184*mm])
hdr_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 11),
("BOTTOMPADDING", (0,0), (-1,-1), 11),
]))
elements.append(hdr_tbl)
elements.append(Spacer(1, 4*mm))
# ── Priority scorecard ────────────────────────────────────────────────────────
sc = [
Paragraph("ITEMS REQUIRING ADJUSTMENT", PS("sc0", fontName="Helvetica-Bold",
fontSize=7.5, textColor=WHITE, alignment=TA_CENTER)),
Paragraph(f"HIGH PRIORITY\n{p_counts['HIGH']} items",
PS("sch", fontName="Helvetica-Bold", fontSize=12,
textColor=HIGH_C, alignment=TA_CENTER, leading=15)),
Paragraph(f"MEDIUM PRIORITY\n{p_counts['MEDIUM']} items",
PS("scm", fontName="Helvetica-Bold", fontSize=12,
textColor=MED_C, alignment=TA_CENTER, leading=15)),
Paragraph(f"LOW PRIORITY\n{p_counts['LOW']} items",
PS("scl", fontName="Helvetica-Bold", fontSize=12,
textColor=LOW_C, alignment=TA_CENTER, leading=15)),
Paragraph(
"HIGH = affects result validity\nMEDIUM = transparency/reproducibility\nLOW = minor enhancement",
PS("scn", fontName="Helvetica-Oblique", fontSize=6.5,
textColor=HexColor("#555555"), alignment=TA_CENTER, leading=9)),
]
sc_tbl = Table([sc], colWidths=[40*mm, 38*mm, 38*mm, 38*mm, 30*mm])
sc_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), DARK_BLUE),
("BACKGROUND", (1,0), (1,0), HIGH_BG),
("BACKGROUND", (2,0), (2,0), MED_BG),
("BACKGROUND", (3,0), (3,0), LOW_BG),
("BACKGROUND", (4,0), (4,0), LIGHT_GREY),
("BOX", (0,0), (-1,-1), 0.5, MID_BLUE),
("INNERGRID", (0,0), (-1,-1), 0.3, MID_GREY),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
]))
elements.append(sc_tbl)
elements.append(Spacer(1, 4*mm))
# ── Column headers ────────────────────────────────────────────────────────────
# Cols: Item | Topic | Current | Gap | Recommended Adjustment | Where to Add | Priority | Expected Outcome
COL_W = [10*mm, 22*mm, 14*mm, 36*mm, 46*mm, 26*mm, 14*mm, 16*mm]
col_hdr_row = [
Paragraph("ITEM", hdr_s),
Paragraph("TOPIC", hdr_s),
Paragraph("CURRENT STATUS", hdr_s),
Paragraph("IDENTIFIED GAP", hdr_s),
Paragraph("RECOMMENDED ADJUSTMENT", hdr_s),
Paragraph("WHERE TO ADD IN MANUSCRIPT", hdr_s),
Paragraph("PRIORITY", hdr_s),
Paragraph("EXPECTED OUTCOME", hdr_s),
]
table_rows = [col_hdr_row]
row_styles = [
("BACKGROUND", (0,0), (-1,0), MID_BLUE),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,0), "MIDDLE"),
("TOPPADDING", (0,0), (-1,0), 5),
("BOTTOMPADDING", (0,0), (-1,0), 5),
]
# Group rows by current status
GROUP_ORDER = ["NOT MET", "PARTIAL", "MET"]
GROUP_LABELS = {
"NOT MET": "GROUP A — NOT MET ITEMS (Highest Impact: Upgrade to MET)",
"PARTIAL": "GROUP B — PARTIAL ITEMS (Upgrade to MET with targeted additions)",
"MET": "GROUP C — MET ITEMS (Minor Enhancements for Best Practice)",
}
GROUP_BG = {
"NOT MET": RED_MET,
"PARTIAL": AMBER_MET,
"MET": GREEN_MET,
}
def status_para(status):
if status == "MET":
c, bg = GREEN_MET, GREEN_BG
elif status == "PARTIAL":
c, bg = AMBER_MET, AMBER_BG
else:
c, bg = RED_MET, RED_BG
return Paragraph(status, PS(f"st{status}", fontName="Helvetica-Bold",
fontSize=6.5, textColor=c, alignment=TA_CENTER, leading=9)), bg
def priority_para(priority):
if priority == "HIGH":
c, bg = HIGH_C, HIGH_BG
elif priority == "MEDIUM":
c, bg = MED_C, MED_BG
else:
c, bg = LOW_C, LOW_BG
return Paragraph(priority, PS(f"pr{priority}", fontName="Helvetica-Bold",
fontSize=6.5, textColor=c, alignment=TA_CENTER, leading=9)), bg
row_idx = 1
for group in GROUP_ORDER:
group_items = [r for r in IMPROVEMENTS if r[2] == group]
if not group_items:
continue
# Section header row
sec_row = [Paragraph(GROUP_LABELS[group], sec_s), "", "", "", "", "", "", ""]
table_rows.append(sec_row)
n = row_idx
row_styles += [
("SPAN", (0,n), (-1,n)),
("BACKGROUND", (0,n), (-1,n), GROUP_BG[group]),
("TOPPADDING", (0,n), (-1,n), 4),
("BOTTOMPADDING", (0,n), (-1,n), 4),
("LEFTPADDING", (0,n), (-1,n), 5),
]
row_idx += 1
for (item_no, topic, curr_status, gap, adjustment, where_to_add, priority, outcome) in group_items:
st_para, st_bg = status_para(curr_status)
pr_para, pr_bg = priority_para(priority)
data_row = [
Paragraph(f"<b>{item_no}</b>", PS("in", fontName="Helvetica-Bold",
fontSize=8, textColor=MID_BLUE, alignment=TA_CENTER, leading=10)),
Paragraph(f"<b>{topic}</b>", bold_s),
st_para,
Paragraph(gap, cell_s),
Paragraph(adjustment, cell_s),
Paragraph(where_to_add, sm_s),
pr_para,
Paragraph(outcome, PS("out", fontName="Helvetica-Bold", fontSize=6.5,
textColor=GREEN_MET, alignment=TA_CENTER, leading=9)),
]
table_rows.append(data_row)
bg = HexColor("#FAFAFA") if row_idx % 2 == 0 else WHITE
row_styles += [
("BACKGROUND", (0, row_idx), (-1, row_idx), bg),
("BACKGROUND", (2, row_idx), (2, row_idx), st_bg),
("BACKGROUND", (6, row_idx), (6, row_idx), pr_bg),
("TOPPADDING", (0, row_idx), (-1, row_idx), 3),
("BOTTOMPADDING", (0, row_idx), (-1, row_idx), 4),
("LEFTPADDING", (0, row_idx), (-1, row_idx), 3),
("RIGHTPADDING", (0, row_idx), (-1, row_idx), 3),
("VALIGN", (0, row_idx), (-1, row_idx), "TOP"),
]
row_idx += 1
row_styles += [
("BOX", (0,0), (-1,-1), 0.6, MID_BLUE),
("INNERGRID", (0,1), (-1,-1), 0.3, MID_GREY),
("ALIGN", (0,1), (-1,-1), "LEFT"),
("ALIGN", (2,1), (2,-1), "CENTER"),
("ALIGN", (6,1), (6,-1), "CENTER"),
("ALIGN", (7,1), (7,-1), "CENTER"),
("ALIGN", (0,1), (0,-1), "CENTER"),
]
main_table = Table(table_rows, colWidths=COL_W, repeatRows=1)
main_table.setStyle(TableStyle(row_styles))
elements.append(main_table)
elements.append(Spacer(1, 5*mm))
# ── Action roadmap ─────────────────────────────────────────────────────────────
roadmap_txt = (
"<b>RECOMMENDED REVISION ROADMAP</b><br/><br/>"
"<b>Step 1 — HIGH priority (required before resubmission):</b><br/>"
"• Item 13: Add participant flow diagram (Figure 1). Report total sampling frame and non-response rates.<br/>"
"• Item 7: Redefine diabetes exposure using FBG ≥126 mg/dL (ADA criteria) in addition to self-report.<br/>"
"• Item 11/12: Re-run analysis using conditional logistic regression (SPSS CLOGIT). Report both CLR and "
"unconditional ORs side-by-side and discuss any differences.<br/>"
"• Item 19: Add explicit discussion of detection/surveillance bias and its likely direction on OR.<br/><br/>"
"<b>Step 2 — MEDIUM priority (strongly recommended):</b><br/>"
"• Item 6: Specify TB diagnostic criteria (bacteriologically confirmed vs. clinical).<br/>"
"• Item 8: Report glucometer QC data and interviewer blinding status.<br/>"
"• Item 16: Add crude OR column to Table 1. Justify confounder selection strategy.<br/>"
"• Item 17: Add at least one sensitivity analysis (e.g., excluding rural participants).<br/><br/>"
"<b>Step 3 — LOW priority (for best-practice reporting):</b><br/>"
"• Items 1, 2, 21, 22: Minor text additions (CIs in abstract, biological mechanism, "
"urban limitation, funder role statement)."
)
road_tbl = Table([[Paragraph(roadmap_txt, PS("road", fontName="Helvetica",
fontSize=7, textColor=DARK_GREY, leading=10))]],
colWidths=[184*mm])
road_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), PALE_BLUE),
("BOX", (0,0), (-1,-1), 0.8, MID_BLUE),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
]))
elements.append(road_tbl)
elements.append(Spacer(1, 3*mm))
elements.append(Paragraph(
"<b>Reference:</b> von Elm E et al. STROBE statement. <i>BMJ</i> 2007;335:806. | "
"Generated by Orris AI on " + datetime.date.today().strftime("%d %b %Y"),
PS("ref", fontName="Helvetica-Oblique", fontSize=6.3,
textColor=HexColor("#666666"), leading=8.5)))
# ── Footer ─────────────────────────────────────────────────────────────────────
def footer(canvas, doc):
canvas.saveState()
canvas.setFont("Helvetica", 6.5)
canvas.setFillColor(HexColor("#888888"))
w, _ = A4
canvas.drawString(13*mm, 11*mm,
"STROBE Improvement Table — Diabetes & TB (Kokiwar & Soodi Reddy, 2024)")
canvas.drawRightString(w - 13*mm, 11*mm, f"Page {doc.page}")
canvas.restoreState()
doc.build(elements, onFirstPage=footer, onLaterPages=footer)
print(f"PDF created: {OUTPUT}")
python3 /home/daytona/workspace/strobe-checklist/generate_improvement_table.py
STROBE ImprovementTable DiabetesTB
PDF Document · PDF
| Item | Gap | Fix |
|---|---|---|
| 13 | No flow diagram, no non-response data | Add CONSORT-style flow diagram as Figure 1 |
| 17 | No subgroup/sensitivity analyses | Add sex-stratified ORs, DM×tobacco interaction, rural exclusion sensitivity run |