Biologic versus anatomic resectability criteria in pancreatic ductal adenocarcinoma: a systematic review and meta-analysis. Review question: In adults with potentially operable PDAC, do biologic or combined biologic-anatomic resectability criteria predict survival and surgical outcomes better than anatomy-only criteria? Is this a good, feasible meta-analysis focused topic to publish on. Do your research extensively and look out for any similar publications.
"pancreatic ductal adenocarcinoma" AND "biologic resectability" AND "anatomic"
"pancreatic cancer" AND "resectability criteria" AND "CA 19-9" AND "survival"
biologic resectability criteria pancreatic ductal adenocarcinoma systematic review meta-analysis 2024 2025 2026
"pancreatic cancer" AND "biologic" AND "borderline resectable" AND "CA 19-9" AND "neoadjuvant"
"pancreatic cancer" AND "resectability" AND "biological criteria" AND "survival"
PMID: 42252995
PMID: 38315954
PMID: 38339242
"pancreatic cancer" AND "resectability" AND "biological" AND "NCCN" AND "outcomes"
"pancreatic cancer" AND "ABC criteria" AND "resectability" AND "JSHBPS"
"pancreatic cancer" AND "biologic" AND "resectability" AND "Oba" OR "JSHBPS" AND "criteria"
"pancreatic cancer" AND "upfront resection" AND "CA 19-9" AND "futile" AND "neoadjuvant"
JSHBPS biologic resectability pancreatic cancer Oba systematic review meta-analysis 2024 2025 PubMed
"pancreatic cancer" AND "resectable" AND "futility" AND "upfront surgery" AND "CA 19-9"
"biologic resectability" OR "biological resectability" pancreatic cancer meta-analysis systematic review published 2023 2024 2025
Create a PROSPERO protocol template tailored for this SR/MA
Create a PROSPERO protocol template tailored for this SR/MA
PROSPERO registration form fields 2024 2025 systematic review template required sections
ls -la /home/daytona/workspace/ && ls /home/daytona/skills/docx/
mkdir -p /home/daytona/workspace/pdac-biologic-resectability-protocol
~/pdac-biologic-resectability-protocol/generate.js
const {
Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
HeadingLevel, AlignmentType, BorderStyle, WidthType, ShadingType,
VerticalAlign, LevelFormat, PageNumber, NumberFormat, Footer,
Header, TableOfContents
} = require('docx');
const fs = require('fs');
// ─── Colour palette ────────────────────────────────────────────────────────
const NAVY = "1F3864"; // section heading fill
const TEAL = "1F6B75"; // accent bars
const LGREY = "F2F2F2"; // table header fill
const WHITE = "FFFFFF";
const BLACK = "000000";
const DKGREY = "404040";
// ─── Helper builders ───────────────────────────────────────────────────────
function h1(text) {
return new Paragraph({
heading: HeadingLevel.HEADING_1,
spacing: { before: 320, after: 160 },
children: [new TextRun({ text, bold: true, size: 30, color: WHITE, font: "Arial" })]
});
}
function h2(text) {
return new Paragraph({
heading: HeadingLevel.HEADING_2,
spacing: { before: 240, after: 120 },
children: [new TextRun({ text, bold: true, size: 26, color: NAVY, font: "Arial" })]
});
}
function h3(text) {
return new Paragraph({
heading: HeadingLevel.HEADING_3,
spacing: { before: 180, after: 80 },
children: [new TextRun({ text, bold: true, size: 24, color: TEAL, font: "Arial" })]
});
}
function body(text, opts = {}) {
return new Paragraph({
spacing: { before: 80, after: 80, line: 276, lineRule: "auto" },
children: [new TextRun({ text, size: 22, font: "Arial", color: DKGREY, ...opts })]
});
}
function bodyBold(text) {
return body(text, { bold: true, color: BLACK });
}
function italic(text) {
return body(text, { italics: true });
}
function blank() {
return new Paragraph({ spacing: { before: 60, after: 60 } });
}
function bullet(text, level = 0) {
return new Paragraph({
numbering: { reference: "bullets", level },
spacing: { before: 60, after: 60, line: 260, lineRule: "auto" },
children: [new TextRun({ text, size: 22, font: "Arial", color: DKGREY })]
});
}
function numbered(text, level = 0) {
return new Paragraph({
numbering: { reference: "numbers", level },
spacing: { before: 60, after: 60, line: 260, lineRule: "auto" },
children: [new TextRun({ text, size: 22, font: "Arial", color: DKGREY })]
});
}
// ─── Field-entry paragraph (PROSPERO field label + content) ────────────────
function field(label, content) {
return new Paragraph({
spacing: { before: 100, after: 60, line: 276, lineRule: "auto" },
children: [
new TextRun({ text: label + ": ", bold: true, size: 22, font: "Arial", color: BLACK }),
new TextRun({ text: content, size: 22, font: "Arial", color: DKGREY })
]
});
}
// ─── Section header block with shaded banner ───────────────────────────────
function sectionBanner(text) {
return new Paragraph({
spacing: { before: 300, after: 140 },
shading: { type: ShadingType.SOLID, color: NAVY, fill: NAVY },
children: [new TextRun({ text: " " + text, bold: true, size: 28, color: WHITE, font: "Arial" })]
});
}
function subBanner(text) {
return new Paragraph({
spacing: { before: 200, after: 100 },
shading: { type: ShadingType.SOLID, color: TEAL, fill: TEAL },
children: [new TextRun({ text: " " + text, bold: true, size: 24, color: WHITE, font: "Arial" })]
});
}
// ─── Table helpers ─────────────────────────────────────────────────────────
function tCell(text, { header = false, width = null, shade = null } = {}) {
const shading = shade
? { type: ShadingType.SOLID, color: shade, fill: shade }
: header
? { type: ShadingType.SOLID, color: LGREY, fill: LGREY }
: undefined;
const cell = new TableCell({
verticalAlign: VerticalAlign.CENTER,
shading,
width: width ? { size: width, type: WidthType.DXA } : undefined,
margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({
children: [new TextRun({ text, bold: header, size: 20, font: "Arial", color: header ? BLACK : DKGREY })]
})]
});
return cell;
}
function tRow(cells) {
return new TableRow({ children: cells });
}
function tTable(rows, colWidths) {
return new Table({
width: { size: 9026, type: WidthType.DXA },
margins: { top: 0, bottom: 0, left: 0, right: 0 },
rows: rows.map(row =>
new TableRow({
children: row.map((cell, i) =>
new TableCell({
verticalAlign: VerticalAlign.CENTER,
shading: cell.header ? { type: ShadingType.SOLID, color: LGREY, fill: LGREY } : undefined,
width: colWidths ? { size: colWidths[i], type: WidthType.DXA } : undefined,
margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({
children: [new TextRun({ text: cell.text, bold: cell.header || cell.bold, size: 20, font: "Arial",
italics: cell.italic || false, color: DKGREY })]
})]
})
)
})
)
});
}
function row(cells) { return cells.map(t => ({ text: t })); }
function hrow(cells) { return cells.map(t => ({ text: t, header: true })); }
// ─── Title page ────────────────────────────────────────────────────────────
function titlePage() {
return [
blank(), blank(),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 200, after: 80 },
shading: { type: ShadingType.SOLID, color: NAVY, fill: NAVY },
children: [new TextRun({ text: "PROSPERO SYSTEMATIC REVIEW PROTOCOL", bold: true, size: 22, font: "Arial", color: WHITE })]
}),
blank(),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 120, after: 200 },
children: [new TextRun({
text: "Biologic versus Anatomic Resectability Criteria in Pancreatic Ductal Adenocarcinoma:",
bold: true, size: 36, font: "Arial", color: NAVY
})]
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 0, after: 320 },
children: [new TextRun({
text: "A Systematic Review and Meta-Analysis",
bold: true, size: 32, font: "Arial", color: TEAL
})]
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 0, after: 80 },
children: [new TextRun({ text: "Protocol Version 1.0", size: 22, font: "Arial", color: DKGREY })]
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 0, after: 80 },
children: [new TextRun({ text: "Date: June 30, 2026", size: 22, font: "Arial", color: DKGREY })]
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 0, after: 80 },
children: [new TextRun({ text: "PROSPERO Registration Status: Pending", size: 22, font: "Arial", color: DKGREY, italics: true })]
}),
blank(),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 40, after: 40 },
shading: { type: ShadingType.SOLID, color: LGREY, fill: LGREY },
children: [new TextRun({ text: "Registration Number: [To be assigned upon PROSPERO submission]", size: 20, font: "Arial", color: DKGREY, italics: true })]
}),
blank(), blank(),
// Review team placeholder table
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 80, after: 80 },
children: [new TextRun({ text: "REVIEW TEAM", bold: true, size: 24, font: "Arial", color: NAVY })]
}),
tTable([
hrow(["Role", "Name", "Affiliation", "ORCID"]),
row(["Principal Investigator / Guarantor", "[Author 1]", "[Institution, Department, Country]", "[0000-0000-0000-0000]"]),
row(["Co-Investigator", "[Author 2]", "[Institution, Department, Country]", "[0000-0000-0000-0000]"]),
row(["Co-Investigator", "[Author 3]", "[Institution, Department, Country]", "[0000-0000-0000-0000]"]),
row(["Biostatistician", "[Author 4]", "[Institution, Department, Country]", "[0000-0000-0000-0000]"]),
row(["Information Specialist / Librarian", "[Author 5]", "[Institution, Department, Country]", "[0000-0000-0000-0000]"])
], [2200, 2000, 3000, 1826]),
blank(), blank(),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 80, after: 80 },
children: [new TextRun({ text: "Conflicts of Interest: [All authors declare no conflicts of interest / specify if any]", size: 20, font: "Arial", color: DKGREY, italics: true })]
}),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 80, after: 80 },
children: [new TextRun({ text: "Funding: [State funding source or 'No external funding']", size: 20, font: "Arial", color: DKGREY, italics: true })]
}),
blank(),
// Page break
new Paragraph({ children: [], pageBreakBefore: true })
];
}
// ─── SECTION 1: Title & Administrative ─────────────────────────────────────
function section1() {
return [
sectionBanner("SECTION 1: TITLE AND ADMINISTRATIVE DETAILS"),
blank(),
h2("1.1 Review Title"),
body("Biologic versus anatomic resectability criteria in pancreatic ductal adenocarcinoma: a systematic review and meta-analysis of survival and surgical outcomes."),
blank(),
h2("1.2 PROSPERO Field: Title (as submitted to registry)"),
body("Biologic versus anatomic resectability criteria in pancreatic ductal adenocarcinoma: a systematic review and meta-analysis"),
blank(),
h2("1.3 Keywords"),
body("Pancreatic ductal adenocarcinoma; PDAC; resectability criteria; biologic resectability; anatomic resectability; borderline resectable; CA 19-9; neoadjuvant therapy; overall survival; R0 resection; ABC criteria; JSHBPS; futile resection"),
blank(),
h2("1.4 Anticipated Start and Completion Dates"),
tTable([
hrow(["Milestone", "Anticipated Date"]),
row(["Protocol finalised and PROSPERO submitted", "July 2026"]),
row(["Database searches completed", "August 2026"]),
row(["Title/abstract screening completed", "September 2026"]),
row(["Full-text review completed", "October 2026"]),
row(["Data extraction completed", "November 2026"]),
row(["Risk of bias assessment completed", "November 2026"]),
row(["Statistical analysis completed", "December 2026"]),
row(["Manuscript submitted", "February 2027"])
], [5000, 4026]),
blank(),
h2("1.5 Stage of Review at Time of Registration"),
body("Preliminary searches have been conducted to confirm the absence of an existing systematic review or meta-analysis addressing this precise research question. Formal database searches have not yet commenced. No screening or data extraction has begun."),
blank(),
];
}
// ─── SECTION 2: Background & Rationale ─────────────────────────────────────
function section2() {
return [
sectionBanner("SECTION 2: BACKGROUND AND RATIONALE"),
blank(),
h2("2.1 Background"),
body("Pancreatic ductal adenocarcinoma (PDAC) remains one of the most lethal solid malignancies, with a 5-year overall survival of approximately 12% across all stages. Surgical resection combined with systemic chemotherapy is the only potentially curative treatment modality; however, fewer than 20% of patients present with disease amenable to upfront surgery."),
blank(),
body("Historically, resectability in PDAC has been defined exclusively by anatomic criteria - specifically the relationship between the primary tumour and the coeliac axis (CA), superior mesenteric artery (SMA), superior mesenteric vein (SMV), and portal vein (PV). The NCCN, ISGPS, AHPBA/SSO/SSAT, and MD Anderson classification systems stratify patients into resectable (R), borderline resectable (BR), and locally advanced (LA) categories based on computed tomography (CT)-defined vascular contact. These systems have formed the backbone of multidisciplinary tumour board decisions for over a decade."),
blank(),
body("A fundamental limitation of anatomy-only criteria is that they do not capture tumour biology or systemic burden of disease. Approximately 30-40% of patients with anatomically resectable PDAC experience early recurrence (within 6 months of surgery), suggesting occult systemic micrometastatic disease at the time of 'curative' resection - rendering such operations oncologically futile. Conversely, patients with anatomically locally advanced disease but favourable tumour biology may derive meaningful survival benefit from aggressive multimodal treatment including complex vascular resection."),
blank(),
body("This recognition has driven the emergence of biologic resectability criteria. The Japanese Society of Hepato-Biliary-Pancreatic Surgery (JSHBPS) published a position paper in 2022 (Oba et al.) formally introducing 'biological borderline resectable' (BR-B) status, defined by: (i) serum CA 19-9 >500 U/mL, (ii) regional lymph node metastases suspected but not histologically confirmed, or (iii) PET-CT findings suspicious but not confirmed for distant metastases. The 'ABC' staging framework - incorporating Anatomy, Biology (CA 19-9), and Conditional (performance status) factors - was validated in a large 1,835-patient multicentre cohort (Dekker et al., TAPS Consortium, JCO 2024), demonstrating that each ABC factor was an independent prognostic variable for overall survival (OS) in patients with localised PDAC treated with (modified) FOLFIRINOX. A landmark publication by Crippa et al. (JAMA Surgery, 2024) demonstrated the futility of upfront resection in a subset of anatomically resectable patients, supporting biologic restratification."),
blank(),
body("Despite this growing body of evidence, no systematic review or meta-analysis has directly compared biologic or combined biologic-anatomic resectability criteria against anatomy-only criteria in terms of their ability to predict survival and surgical outcomes. Narrative reviews (Rompen et al., Cancers 2024; Lee et al., Korean J Radiol 2026) and guideline statements have explicitly identified this as a priority research gap. A formal synthesis is urgently needed to inform treatment decision-making, guideline development, and the design of future prospective trials."),
blank(),
h2("2.2 Rationale for a Systematic Review and Meta-Analysis"),
body("A systematic review is the appropriate study design to comprehensively identify, critically appraise, and synthesise the available evidence on this topic. A meta-analysis will be conducted where studies are sufficiently homogeneous in population, intervention/comparator, and outcome to permit quantitative pooling. The results will provide clinicians, guideline developers, and researchers with a rigorous, up-to-date evidence synthesis to support clinical decision-making and define the research agenda."),
blank(),
h2("2.3 Confirmation of Absence of Existing Similar Reviews"),
body("A comprehensive preliminary search was conducted on June 30, 2026, across MEDLINE (PubMed), PROSPERO registry, and the Cochrane Database of Systematic Reviews. Search terms included: 'pancreatic ductal adenocarcinoma', 'biologic resectability', 'anatomic resectability', 'CA 19-9', 'borderline resectable', 'ABC criteria', 'systematic review', and 'meta-analysis'. No published or registered systematic review or meta-analysis directly comparing biologic versus anatomic resectability criteria in PDAC with respect to survival and surgical outcomes was identified. The search identified multiple narrative reviews (Rompen 2024, Lee 2026), a large multicenter retrospective cohort study (Dekker 2024), and a JSHBPS position paper (Oba 2022), none of which constitute a systematic review with meta-analysis. This review will therefore address a genuine and important evidence gap."),
blank(),
];
}
// ─── SECTION 3: Review Question & PICO ─────────────────────────────────────
function section3() {
return [
sectionBanner("SECTION 3: RESEARCH QUESTION AND PICO FRAMEWORK"),
blank(),
h2("3.1 Primary Review Question"),
body("In adults with potentially operable, non-metastatic pancreatic ductal adenocarcinoma, do biologic or combined biologic-anatomic resectability criteria predict survival and surgical outcomes more accurately than anatomy-only criteria?"),
blank(),
h2("3.2 Secondary Review Questions"),
bullet("What is the prognostic impact of individual biologic factors (CA 19-9, PET findings, clinical lymph node status, performance status) on overall survival in patients classified as anatomically resectable?"),
bullet("Do biologic criteria identify a subgroup of anatomically resectable patients who derive no survival benefit from upfront surgery (i.e., biologically futile resections)?"),
bullet("Does reclassification using combined biologic-anatomic criteria improve discriminatory accuracy for R0 resection rate compared with anatomy-only criteria?"),
bullet("What CA 19-9 threshold best predicts poor OS in anatomically resectable or borderline resectable PDAC?"),
bullet("Are combined biologic-anatomic criteria (e.g., ABC scoring, JSHBPS BR-B) validated across different patient populations, treatment eras, and chemotherapy regimens?"),
blank(),
h2("3.3 PICO Framework"),
blank(),
tTable([
hrow(["PICO Component", "Definition", "Specific Details"]),
[
{ text: "P - Population", bold: true },
{ text: "Adults with potentially operable PDAC" },
{ text: "Histologically or cytologically confirmed PDAC; age ≥18 years; non-metastatic disease at diagnosis; considered for surgical resection at MDT; includes anatomically resectable, borderline resectable, and locally advanced subgroups provided study reports biologic criteria" }
],
[
{ text: "I - Intervention", bold: true },
{ text: "Biologic or combined biologic-anatomic resectability criteria" },
{ text: "Any system incorporating ≥1 biologic factor: (1) CA 19-9 level (any threshold); (2) PET-CT metabolic activity; (3) suspicious regional or extra-regional lymphadenopathy (radiologic or clinical); (4) performance status (ECOG/WHO); (5) tumour phenotype on imaging (e.g., hypovascular); (6) liquid biopsy / ctDNA; (7) combined ABC criteria; (8) JSHBPS BR-B criteria; (9) other validated biologic markers" }
],
[
{ text: "C - Comparator", bold: true },
{ text: "Anatomy-only resectability criteria" },
{ text: "Standard CT-based anatomic criteria: NCCN (any version), ISGPS 2017 International Consensus, AHPBA/SSO/SSAT, MD Anderson, or institutional anatomy-only criteria classifying patients as R, BR, or LA" }
],
[
{ text: "O - Outcomes", bold: true },
{ text: "Survival and surgical outcomes" },
{ text: "See Section 5 for full outcome hierarchy and definitions" }
],
[
{ text: "S - Study Design", bold: true },
{ text: "Comparative observational and interventional studies" },
{ text: "Randomised controlled trials; prospective cohort studies; retrospective cohort studies with ≥2 comparison groups or reporting outcomes stratified by biologic criteria" }
]
], [1800, 2400, 4826]),
blank(),
];
}
// ─── SECTION 4: Eligibility Criteria ───────────────────────────────────────
function section4() {
return [
sectionBanner("SECTION 4: ELIGIBILITY CRITERIA"),
blank(),
h2("4.1 Inclusion Criteria"),
blank(),
subBanner("4.1.1 Population"),
bullet("Adults aged ≥18 years with a confirmed diagnosis of pancreatic ductal adenocarcinoma (PDAC) - histological or cytological confirmation required"),
bullet("Non-metastatic disease at baseline (AJCC stage I-III, or equivalent) OR studies that separately report outcomes in non-metastatic patients"),
bullet("Patients considered potentially operable or evaluated for surgical resectability at multidisciplinary team (MDT) review"),
bullet("Any treatment pathway: upfront surgery, neoadjuvant therapy followed by surgery, or systemic therapy alone (provided resectability reclassification data are reported)"),
blank(),
subBanner("4.1.2 Intervention / Exposure"),
bullet("Studies applying ≥1 biologic or conditional factor to resectability classification in addition to, or instead of, standard anatomic criteria"),
bullet("Acceptable biologic factors include but are not limited to: serum CA 19-9 (any threshold), CEA, PET-CT SUVmax/metabolic tumour volume, clinical or radiologic lymph node status, ECOG/WHO performance status, tumour morphology/phenotype on cross-sectional imaging, circulating tumour DNA, KRAS mutation in plasma, combined ABC staging score, JSHBPS BR-B classification"),
blank(),
subBanner("4.1.3 Comparator"),
bullet("Studies reporting outcomes under anatomy-only criteria (NCCN, ISGPS, AHPBA/SSO/SSAT, MD Anderson, or institutional CT-based systems without biologic factors) either as a separate cohort, time period, or as the reference group within the same study"),
bullet("Studies comparing outcomes across strata defined by biologic criteria within an anatomically classified cohort are eligible (biologic factors as effect modifiers)"),
blank(),
subBanner("4.1.4 Outcomes"),
bullet("Must report ≥1 pre-specified primary or secondary outcome (see Section 5)"),
blank(),
subBanner("4.1.5 Study Design"),
bullet("Randomised controlled trials (RCTs)"),
bullet("Prospective cohort studies (single or multicentre)"),
bullet("Retrospective cohort studies with a comparator group OR reporting survival outcomes stratified by biologic resectability factors, with n ≥30 patients per group"),
bullet("Multivariate analyses reporting hazard ratios (HR) or odds ratios (OR) for biologic factors with 95% confidence intervals are preferred for meta-analysis"),
blank(),
subBanner("4.1.6 Language and Publication Type"),
bullet("Full-text publications in peer-reviewed journals"),
bullet("English language (primary); non-English publications will be included if translation can be obtained and assessed for eligibility"),
bullet("Published from January 2000 onwards (when contemporary anatomic classification systems were established) to present"),
blank(),
h2("4.2 Exclusion Criteria"),
blank(),
tTable([
hrow(["Exclusion Category", "Criterion"]),
row(["Histology", "Non-PDAC pancreatic tumours: pancreatic neuroendocrine tumours (PNETs), acinar cell carcinoma, mucinous cystic neoplasm-derived adenocarcinoma (unless PDAC subset data extractable), ampullary carcinoma, distal cholangiocarcinoma"]),
row(["Disease stage", "Studies exclusively reporting outcomes in patients with confirmed distant metastatic disease at baseline (stage IV); studies of oligometastatic PDAC are excluded unless a non-metastatic sub-analysis is separately reported"]),
row(["Intervention", "Studies applying only anatomy-only criteria with no biologic factor reported - ineligible as the intervention arm"]),
row(["Study design", "Case reports; case series with n<30 total; editorials; letters; conference abstracts without full data; review articles (narrative, scoping, systematic); animal or in vitro studies"]),
row(["Data availability", "Studies not reporting any pre-specified primary or secondary outcome with extractable data; studies where biologic factor data cannot be disaggregated from combined cohort outcomes"]),
row(["Duplication", "Duplicate publications of the same cohort - the most complete or recent publication will be retained; partial overlapping cohorts will be handled as per Section 9.4"])
], [2400, 6626]),
blank(),
];
}
// ─── SECTION 5: Outcomes ────────────────────────────────────────────────────
function section5() {
return [
sectionBanner("SECTION 5: OUTCOMES"),
blank(),
h2("5.1 Primary Outcomes"),
blank(),
tTable([
hrow(["Outcome", "Definition", "Measure / Data Required", "Priority"]),
[
{ text: "Overall Survival (OS)", bold: true },
{ text: "Time from date of diagnosis or treatment initiation to death from any cause" },
{ text: "Median OS (months); HR with 95% CI; Kaplan-Meier curve data where individual patient data (IPD) are available" },
{ text: "PRIMARY 1", bold: true }
],
[
{ text: "R0 Resection Rate", bold: true },
{ text: "Proportion of resected patients achieving a margin-negative (R0) resection, defined per reporting standard of each study; note will record whether R0 is defined as ≥1 mm (Leeds) or tumour clearance (R0)" },
{ text: "Proportion (%) with 95% CI; OR for biologic vs. anatomy-only groups" },
{ text: "PRIMARY 2", bold: true }
]
], [2000, 3000, 2526, 1500]),
blank(),
h2("5.2 Secondary Outcomes"),
blank(),
tTable([
hrow(["Outcome", "Definition", "Data Required"]),
row(["Disease-Free / Recurrence-Free Survival (DFS/RFS)", "Time from surgery to disease recurrence (locoregional or distant) or death from any cause, whichever occurs first", "Median DFS/RFS (months); HR with 95% CI"]),
row(["Rate of Futile Resection", "Resection followed by recurrence or death within 6 months of surgery; OR any pre-specified 'early recurrence' definition used in the source study", "Proportion (%); OR with 95% CI"]),
row(["Completion of Intended Treatment", "Proportion of patients completing planned adjuvant or neoadjuvant chemotherapy; rate of aborted surgery (explored but not resected)", "Proportion (%)"]),
row(["30-day and 90-day Postoperative Mortality", "All-cause mortality within 30 and 90 days of pancreatic resection", "Proportion (%)"]),
row(["Major Postoperative Morbidity", "Clavien-Dindo grade ≥III complications; post-pancreatectomy haemorrhage (PPH) grade B/C; post-operative pancreatic fistula (POPF) grade B/C", "Proportion (%); OR with 95% CI"]),
row(["Resection Rate / Conversion Rate", "Proportion of patients ultimately undergoing surgical resection following neoadjuvant therapy (conversion rate for anatomically BR or LA at baseline)", "Proportion (%)"]),
row(["Prognostic Accuracy of Biologic Criteria", "C-statistic / concordance index for OS prediction; sensitivity and specificity of biologic thresholds (particularly CA 19-9 cutoffs) for predicting OS or futile resection", "C-statistic; AUC-ROC with 95% CI (where reported)"]),
row(["Lymph Node Yield and Lymph Node Ratio", "Number of lymph nodes retrieved; ratio of positive to total nodes (LNR)", "Median or mean; correlation with biologic criteria"])
], [2800, 3700, 2526]),
blank(),
h2("5.3 Outcome Hierarchy and Meta-Analysis Eligibility"),
body("For meta-analysis, outcomes will be prioritised in the order listed above. Quantitative pooling will be performed for OS (HR), R0 rate (OR), and DFS (HR) as primary meta-analysis endpoints. Other outcomes will be pooled if ≥3 studies report extractable data. Where ≥3 studies report a C-statistic, a meta-analysis of prognostic accuracy will be performed separately."),
blank(),
h2("5.4 Time Points"),
body("OS and DFS will be extracted at: median follow-up, and at 1, 2, 3, and 5 years where reported. Landmark survival will be noted."),
blank(),
];
}
// ─── SECTION 6: Search Strategy ────────────────────────────────────────────
function section6() {
return [
sectionBanner("SECTION 6: INFORMATION SOURCES AND SEARCH STRATEGY"),
blank(),
h2("6.1 Electronic Databases"),
body("The following databases will be searched from January 2000 to the date of search:"),
blank(),
tTable([
hrow(["Database", "Platform / Provider", "Search Limit"]),
row(["MEDLINE", "PubMed (NLM)", "January 2000 - present"]),
row(["Embase", "Elsevier / Ovid", "January 2000 - present"]),
row(["Cochrane Central Register of Controlled Trials (CENTRAL)", "Cochrane Library (Wiley)", "January 2000 - present"]),
row(["Web of Science Core Collection", "Clarivate Analytics", "January 2000 - present"]),
row(["Scopus", "Elsevier", "January 2000 - present"]),
row(["CINAHL", "EBSCO", "January 2000 - present"])
], [2800, 3000, 3226]),
blank(),
h2("6.2 Additional Sources"),
bullet("ClinicalTrials.gov and the WHO ICTRP: for registered trials reporting biologic criteria data"),
bullet("ISRCTN registry: for European trials"),
bullet("Grey literature: conference proceedings of ASCO, ESMO, HPB Association, APA, ISGPS (2018-present) via conference abstract databases"),
bullet("Reference lists of all included studies and relevant narrative reviews (backward citation search)"),
bullet("Forward citation search of key papers (Oba 2022, Dekker/TAPS 2024, Crippa 2024) using Web of Science and Scopus"),
bullet("Contact with corresponding authors of included studies for unpublished data where primary outcomes are incompletely reported"),
blank(),
h2("6.3 Sample Search Strategy (MEDLINE/PubMed)"),
body("The following is the draft MEDLINE search strategy. This will be adapted for each database in consultation with an information specialist. Final search strings will be appended to the published manuscript as a supplement."),
blank(),
new Paragraph({
spacing: { before: 80, after: 80 },
shading: { type: ShadingType.SOLID, color: "F5F5F5", fill: "F5F5F5" },
children: [new TextRun({
text: [
"#1 \"Pancreatic Neoplasms\"[MeSH] OR \"pancreatic cancer\"[tiab] OR \"pancreatic ductal adenocarcinoma\"[tiab] OR \"PDAC\"[tiab] OR \"pancreatic adenocarcinoma\"[tiab] OR \"pancreas cancer\"[tiab]",
"#2 \"Carcinoma, Pancreatic Ductal\"[MeSH]",
"#3 #1 OR #2",
"#4 \"resectability\"[tiab] OR \"resectable\"[tiab] OR \"borderline resectable\"[tiab] OR \"locally advanced\"[tiab] OR \"unresectable\"[tiab] OR \"resection criteria\"[tiab]",
"#5 \"biologic\"[tiab] OR \"biological\"[tiab] OR \"biology\"[tiab] OR \"biomarker\"[tiab] OR \"biologic criteria\"[tiab] OR \"biological criteria\"[tiab] OR \"biologic resectability\"[tiab]",
"#6 \"CA 19-9\"[tiab] OR \"CA19-9\"[tiab] OR \"carbohydrate antigen 19-9\"[tiab] OR \"CEA\"[tiab] OR \"carcinoembryonic antigen\"[tiab]",
"#7 \"ABC criteria\"[tiab] OR \"ABC staging\"[tiab] OR \"ABC factors\"[tiab]",
"#8 \"JSHBPS\"[tiab] OR \"Japanese Society of Hepato-Biliary-Pancreatic Surgery\"[tiab]",
"#9 \"PET\"[tiab] OR \"positron emission tomography\"[tiab] OR \"PET-CT\"[tiab] OR \"FDG-PET\"[tiab]",
"#10 \"lymph node\"[tiab] OR \"lymphadenopathy\"[tiab] OR \"nodal status\"[tiab]",
"#11 \"performance status\"[tiab] OR \"ECOG\"[tiab] OR \"Eastern Cooperative Oncology Group\"[tiab]",
"#12 \"ctDNA\"[tiab] OR \"circulating tumour DNA\"[tiab] OR \"circulating tumor DNA\"[tiab] OR \"liquid biopsy\"[tiab]",
"#13 #5 OR #6 OR #7 OR #8 OR #9 OR #10 OR #11 OR #12",
"#14 \"NCCN\"[tiab] OR \"National Comprehensive Cancer Network\"[tiab] OR \"ISGPS\"[tiab] OR \"anatomic criteria\"[tiab] OR \"anatomical criteria\"[tiab] OR \"vascular involvement\"[tiab] OR \"superior mesenteric artery\"[tiab] OR \"celiac axis\"[tiab]",
"#15 \"overall survival\"[tiab] OR \"disease-free survival\"[tiab] OR \"recurrence-free survival\"[tiab] OR \"R0 resection\"[tiab] OR \"margin-negative\"[tiab] OR \"futile resection\"[tiab] OR \"prognosis\"[tiab] OR \"prognostic\"[tiab]",
"#16 \"Survival Analysis\"[MeSH] OR \"Prognosis\"[MeSH] OR \"Treatment Outcome\"[MeSH]",
"#17 #15 OR #16",
"#18 #3 AND #4 AND #13 AND #17",
"#19 Limit: January 2000 - present; Humans"
].join("\n"),
size: 18, font: "Courier New", color: "333333"
})]
}),
blank(),
h2("6.4 Search Restrictions"),
bullet("Date: January 2000 to present (contemporary anatomic classification systems emerged ~2001-2006; biologic criteria from ~2018 onwards, but early CA 19-9 studies from 2000 will be captured)"),
bullet("Language: English primary; no formal language restriction applied at database level - non-English records will be flagged for translation"),
bullet("No study design filter will be applied at the search stage (filters will be applied during screening)"),
bullet("No publication type filter will be applied at the search stage"),
blank(),
];
}
// ─── SECTION 7: Screening & Selection ──────────────────────────────────────
function section7() {
return [
sectionBanner("SECTION 7: STUDY SELECTION PROCESS"),
blank(),
h2("7.1 Record Management"),
body("All records identified from database searches will be exported to Endnote or Zotero reference management software and subsequently imported into a dedicated systematic review management platform (Rayyan, Covidence, or equivalent). Duplicate records will be identified using automated deduplication followed by manual review."),
blank(),
h2("7.2 Screening Process"),
blank(),
subBanner("Phase 1: Title and Abstract Screening"),
body("Two reviewers (Author 1 and Author 2) will independently screen all titles and abstracts against the pre-specified eligibility criteria. Disagreements will be resolved by discussion; if consensus is not reached, a third reviewer (Author 3) will arbitrate. A random 10% sample will be double-screened prior to commencing full independent screening to calibrate reviewers, with a target inter-rater agreement of kappa ≥0.80. Records will be classified as: Include, Exclude, or Uncertain. All uncertain records will be advanced to full-text review."),
blank(),
subBanner("Phase 2: Full-Text Review"),
body("Full-text articles will be retrieved for all records passing title/abstract screening. Two reviewers will independently assess each full-text against the eligibility criteria using a standardised, piloted eligibility form. Reasons for exclusion will be recorded for all excluded full-text articles and reported in the PRISMA 2020 flow diagram. Disagreements will be resolved by discussion or third-reviewer arbitration. Inter-rater agreement (Cohen's kappa) will be calculated and reported."),
blank(),
h2("7.3 PRISMA 2020 Flow Diagram"),
body("A PRISMA 2020-compliant flow diagram will be generated reporting: records identified by database search; records identified through additional sources; records removed as duplicates; records screened at title/abstract; records excluded at title/abstract (with reasons); full-texts retrieved; full-texts excluded (with reasons); and studies included in the systematic review and meta-analysis."),
blank(),
];
}
// ─── SECTION 8: Data Extraction ─────────────────────────────────────────────
function section8() {
return [
sectionBanner("SECTION 8: DATA EXTRACTION"),
blank(),
h2("8.1 Data Extraction Process"),
body("Data will be extracted by two independent reviewers using a standardised, piloted data extraction form developed in REDCap, Microsoft Excel, or Covidence. The form will be piloted on three to five representative studies before formal extraction begins. Discrepancies will be resolved by discussion or third-reviewer adjudication."),
blank(),
h2("8.2 Data Items to be Extracted"),
blank(),
subBanner("Study Identification and Design"),
tTable([
hrow(["Data Item", "Details"]),
row(["Study identifier", "First author, year, journal, country of origin"]),
row(["Study design", "RCT; prospective cohort; retrospective cohort; registry-based"]),
row(["Setting", "Single centre / multicentre; country/region; academic vs. community"]),
row(["Treatment era", "Year range of patient enrolment"]),
row(["Funding source", "Industry / government / institutional / none stated"]),
row(["Ethical approval / consent", "Yes / No / Not stated"])
], [3500, 5526]),
blank(),
subBanner("Population Characteristics"),
tTable([
hrow(["Data Item", "Details"]),
row(["Sample size", "Total N; n per group (biologic criteria group vs. anatomy-only group)"]),
row(["Age", "Mean or median (SD or IQR)"]),
row(["Sex distribution", "% male"]),
row(["Tumour location", "Head / body-tail split (%)"]),
row(["Anatomic classification at diagnosis", "% R / % BR / % LA (with criteria system used)"]),
row(["AJCC/TNM stage", "If reported"]),
row(["Baseline CA 19-9", "Median (IQR); % above threshold of interest"]),
row(["Baseline ECOG/performance status", "Distribution"]),
row(["Chemotherapy regimen", "Gemcitabine-based; FOLFIRINOX; mFOLFIRINOX; gemcitabine + nab-paclitaxel; other"]),
row(["Neoadjuvant vs. upfront surgery", "% receiving NAT; % upfront surgery"])
], [3500, 5526]),
blank(),
subBanner("Biologic Criteria Applied"),
tTable([
hrow(["Data Item", "Details"]),
row(["Biologic factors used", "List all biologic/conditional factors applied"]),
row(["CA 19-9 threshold", "Exact cut-off value(s) used; basis for threshold (data-driven, JSHBPS, institutional)"]),
row(["Classification system", "JSHBPS BR-B; ABC staging; institutional; other - specify"]),
row(["Timing of assessment", "At diagnosis; after NAT; at MDT; other"]),
row(["Definition of 'biologically resectable' vs. 'biologically non-resectable'", "Exact operational definition as stated in source study"]),
row(["CA 19-9 non-secretor handling", "Reported / not reported; exclusion or imputation method"])
], [3500, 5526]),
blank(),
subBanner("Outcomes Data"),
tTable([
hrow(["Outcome", "Data Extracted"]),
row(["Overall survival", "Median OS (months); HR (95% CI); p-value; 1-/2-/3-/5-year OS rates; Kaplan-Meier data"]),
row(["R0 resection rate", "n/N; %; OR or RR (95% CI)"]),
row(["Disease-free / recurrence-free survival", "Median DFS (months); HR (95% CI)"]),
row(["Futile resection rate", "n/N; % per definition used"]),
row(["Postoperative mortality", "30-day and 90-day mortality rates"]),
row(["Major morbidity", "Clavien-Dindo ≥III rate; POPF B/C rate; PPH B/C rate"]),
row(["Conversion rate", "% of BR/LA patients converting to resectable after NAT"]),
row(["C-statistic / AUC", "C-statistic value (95% CI) for biologic vs. anatomy-only model"])
], [3500, 5526]),
blank(),
h2("8.3 Handling of Missing Data"),
bullet("Corresponding authors will be contacted by email for missing outcome data, unreported confidence intervals, or cohort overlap clarification; up to two contact attempts will be made over four weeks"),
bullet("For meta-analysis, if standard errors are not reported, they will be estimated from p-values or confidence intervals using standard formulae"),
bullet("Studies with insufficient data for quantitative synthesis will be included in the narrative synthesis only"),
bullet("Multiple imputation or sensitivity analyses addressing missing data will be performed if missingness is judged to be non-negligible (>10% of studies missing a primary outcome)"),
blank(),
];
}
// ─── SECTION 9: Risk of Bias & Quality ─────────────────────────────────────
function section9() {
return [
sectionBanner("SECTION 9: RISK OF BIAS AND METHODOLOGICAL QUALITY ASSESSMENT"),
blank(),
h2("9.1 Tools for Risk of Bias Assessment"),
blank(),
tTable([
hrow(["Study Design", "Risk of Bias Tool", "Domains Assessed"]),
row(["Randomised Controlled Trials", "Cochrane RoB 2.0", "Randomisation; deviations from intended interventions; missing outcome data; measurement of the outcome; selection of the reported result"]),
row(["Prospective and Retrospective Cohort Studies", "ROBINS-I (Risk of Bias in Non-randomised Studies of Interventions)", "Confounding; selection of participants; classification of interventions; deviations from intended interventions; missing data; measurement of outcomes; selection of the reported result"]),
row(["Studies used for prognostic accuracy (C-statistic)", "PROBAST (Prediction model Risk Of Bias ASsessment Tool)", "Participants; predictors; outcome; analysis"]),
row(["General methodological quality", "Newcastle-Ottawa Scale (NOS) for observational studies - as supplementary assessment"]
)
], [2400, 2200, 4426]),
blank(),
h2("9.2 Risk of Bias Assessment Process"),
bullet("Two reviewers will independently complete risk of bias assessments for each included study"),
bullet("Disagreements will be resolved by discussion; third-reviewer adjudication if consensus is not reached"),
bullet("Overall risk of bias judgements will be categorised as: Low risk; Some concerns; High risk (for RoB 2.0) or Low / Moderate / Serious / Critical (for ROBINS-I)"),
bullet("Risk of bias assessments will be presented in a summary table and traffic-light plot"),
bullet("Risk of bias will not be used as an exclusion criterion; however, sensitivity analyses restricted to low-risk-of-bias studies will be performed for all primary outcomes"),
blank(),
h2("9.3 Assessment of Reporting Quality"),
body("Reporting quality of observational studies will be assessed using the STROBE checklist. Reporting quality of prognostic factor studies will be assessed using REMARK (REporting recommendations for tumour MARKer prognostic studies) as a supplementary tool."),
blank(),
h2("9.4 Handling Overlapping Cohorts"),
body("Where multiple publications report data from the same or overlapping patient cohorts, the following hierarchy will be applied: (i) the publication with the largest sample size will be prioritised; (ii) if sample sizes are equal, the most recent publication will be retained; (iii) if cohorts are partially overlapping, this will be reported transparently, and sensitivity analyses excluding potentially overlapping studies will be performed."),
blank(),
];
}
// ─── SECTION 10: Data Synthesis ─────────────────────────────────────────────
function section10() {
return [
sectionBanner("SECTION 10: DATA SYNTHESIS AND STATISTICAL ANALYSIS"),
blank(),
h2("10.1 Narrative Synthesis"),
body("A structured narrative synthesis will be conducted for all included studies, regardless of whether quantitative pooling is feasible. This will follow the SWiM (Synthesis Without Meta-analysis) guidance. Studies will be grouped by: (i) biologic criteria applied; (ii) anatomic classification system; (iii) treatment pathway (upfront surgery vs. NAT); (iv) chemotherapy regimen era."),
blank(),
h2("10.2 Meta-Analysis"),
blank(),
subBanner("10.2.1 Pooling Conditions"),
body("Quantitative meta-analysis will be conducted when ≥3 studies report the same outcome with sufficient methodological homogeneity. If <3 studies are available for a given outcome, results will be presented in narrative form only."),
blank(),
subBanner("10.2.2 Effect Measures"),
tTable([
hrow(["Outcome", "Effect Measure", "Pooling Method"]),
row(["Overall survival", "Hazard Ratio (HR) with 95% CI", "Generic inverse variance method"]),
row(["R0 resection rate", "Odds Ratio (OR) with 95% CI", "Mantel-Haenszel method"]),
row(["Disease-free survival", "Hazard Ratio (HR) with 95% CI", "Generic inverse variance method"]),
row(["Futile resection rate", "Odds Ratio (OR) with 95% CI", "Mantel-Haenszel method"]),
row(["Postoperative mortality/morbidity", "Odds Ratio (OR) with 95% CI", "Mantel-Haenszel method"]),
row(["C-statistic", "C-statistic (SE)", "Generic inverse variance (logit transformation)"])
], [3000, 2500, 3526]),
blank(),
subBanner("10.2.3 Statistical Model"),
body("A random-effects model (DerSimonian and Laird method) will be used as the primary pooling model, given anticipated between-study heterogeneity in patient populations, biologic criteria definitions, CA 19-9 thresholds, and treatment regimens. Fixed-effect models will be reported as sensitivity analyses."),
blank(),
subBanner("10.2.4 Heterogeneity Assessment"),
bullet("Cochran Q test (chi-squared) for statistical heterogeneity (significance threshold: p <0.10)"),
bullet("I² statistic to quantify the proportion of total variation attributable to between-study heterogeneity: <25% low; 25-50% moderate; 51-75% substantial; >75% considerable"),
bullet("Tau² (between-study variance) to quantify the magnitude of heterogeneity"),
bullet("Sources of heterogeneity will be explored through pre-specified subgroup analyses and meta-regression (see Section 10.3)"),
blank(),
subBanner("10.2.5 Publication Bias"),
bullet("Funnel plots will be generated for outcomes with ≥10 studies contributing to a meta-analysis"),
bullet("Statistical tests: Egger's test (for continuous outcomes) and Begg's rank test (for dichotomous outcomes), with a significance threshold of p <0.10"),
bullet("Trim-and-fill method will be applied if publication bias is detected to estimate the adjusted pooled effect"),
blank(),
h2("10.3 Subgroup Analyses (Pre-specified)"),
body("The following subgroup analyses are pre-specified to explore sources of heterogeneity:"),
blank(),
tTable([
hrow(["Subgroup Factor", "Categories", "Rationale"]),
row(["Biologic criteria system", "JSHBPS BR-B; ABC staging (TAPS); CA 19-9 alone; composite biologic score; other", "Different systems may have different discriminatory ability"]),
row(["CA 19-9 threshold", "≤37 / 38-199 / 200-499 / ≥500 U/mL", "Threshold heterogeneity is a primary driver of between-study variation; clinically important for guideline recommendations"]),
row(["Anatomic resectability category", "Resectable; Borderline resectable; Locally advanced; Mixed", "Biologic criteria may add more discriminatory value in anatomically resectable vs. borderline patients"]),
row(["Treatment pathway", "Upfront surgery; Neoadjuvant therapy + surgery; Systemic therapy only", "Biologic criteria are most directly relevant to NAT vs. upfront surgery decision"]),
row(["Chemotherapy regimen era", "Pre-FOLFIRINOX (<2011); FOLFIRINOX/mFOLFIRINOX era (2011-present); GnP era", "Survival benchmarks differ substantially by treatment era"]),
row(["Study design", "RCT; prospective cohort; retrospective cohort", "To assess whether study design influences effect estimates"]),
row(["Risk of bias", "Low risk only vs. all studies (ROBINS-I / RoB 2.0)", "To assess influence of study quality on pooled estimates"]),
row(["Tumour location", "Pancreatic head vs. body-tail", "Anatomy and biology may differ by tumour location"])
], [2600, 3000, 3426]),
blank(),
h2("10.4 Sensitivity Analyses (Pre-specified)"),
bullet("Exclusion of studies with high/critical risk of bias (ROBINS-I): to assess robustness of primary estimates"),
bullet("Leave-one-out analysis: iteratively excluding each study to assess influence on pooled estimates"),
bullet("Fixed-effect model vs. random-effects model: to assess model dependency"),
bullet("Exclusion of studies not providing multivariate-adjusted HRs (unadjusted estimates only): to assess confounding"),
bullet("Restriction to studies with CA 19-9 >500 U/mL as the biologic threshold (JSHBPS-aligned): to assess consistency with the most widely cited biologic BR definition"),
bullet("Exclusion of retrospective studies: if a sufficient number of prospective studies exist"),
blank(),
h2("10.5 Dose-Response / Threshold Analysis"),
body("A meta-regression of the HR for OS against CA 19-9 threshold value will be performed to identify the optimal CA 19-9 cutoff for predicting OS in anatomically resectable or borderline resectable PDAC. A minimum of 10 studies will be required for meaningful meta-regression."),
blank(),
h2("10.6 Software"),
body("All statistical analyses will be performed using R (version ≥4.3, packages: meta, metafor, metamisc, dmetar) and/or Review Manager 5.4 (RevMan, Cochrane Collaboration). Forest plots will be produced using the forestplot or ggforest package in R. GRADE evidence tables will be generated using GRADEpro GDT."),
blank(),
];
}
// ─── SECTION 11: GRADE & Certainty ──────────────────────────────────────────
function section11() {
return [
sectionBanner("SECTION 11: CERTAINTY OF EVIDENCE (GRADE)"),
blank(),
h2("11.1 GRADE Framework"),
body("The certainty of evidence for each primary and key secondary outcome will be assessed using the GRADE (Grading of Recommendations Assessment, Development and Evaluation) framework. Evidence will be rated as High, Moderate, Low, or Very Low certainty based on assessment of the following five domains:"),
blank(),
tTable([
hrow(["GRADE Domain", "Assessment Approach"]),
row(["Risk of bias", "Based on ROBINS-I / RoB 2.0 results for included studies"]),
row(["Inconsistency", "Degree of heterogeneity (I² and Q-test); visual inspection of forest plots"]),
row(["Indirectness", "Population (non-PDAC histology, metastatic disease included); outcome definitions (R0 definitions, OS from surgery vs. diagnosis); intervention (differing biologic criteria systems)"]),
row(["Imprecision", "Width of 95% CIs; sample size; whether CIs cross null or clinically important threshold"]),
row(["Publication bias", "Based on funnel plot analysis and Egger's test"])
], [3000, 6026]),
blank(),
body("Starting certainty for randomised studies = High; for observational studies = Low. Certainty will be upgraded if: large effect size (OR >5 or <0.2); dose-response; or all plausible confounders would reduce the estimate."),
blank(),
h2("11.2 Summary of Findings Tables"),
body("GRADE Summary of Findings (SoF) tables will be generated for each primary outcome using GRADEpro GDT. Tables will present: the number of studies and participants; risk of bias, inconsistency, indirectness, and imprecision ratings; pooled effect estimate; and overall certainty rating. SoF tables will be included in the main manuscript."),
blank(),
];
}
// ─── SECTION 12: Reporting ──────────────────────────────────────────────────
function section12() {
return [
sectionBanner("SECTION 12: REPORTING"),
blank(),
h2("12.1 Reporting Guidelines"),
body("This systematic review and meta-analysis will be reported in accordance with:"),
bullet("PRISMA 2020 (Preferred Reporting Items for Systematic Reviews and Meta-Analyses) - Page et al., BMJ 2021"),
bullet("PRISMA-P 2015 (Protocol reporting) - Shamseer et al., BMJ 2015 - for this protocol"),
bullet("MOOSE (Meta-analysis Of Observational Studies in Epidemiology) - Stroup et al., JAMA 2000 - supplementary, given predominance of observational studies"),
bullet("GRADE handbook for certainty of evidence assessments"),
bullet("SWiM guidance for narrative synthesis sections"),
blank(),
h2("12.2 Protocol Amendments"),
body("Any amendments to this protocol after PROSPERO registration will be documented with rationale and date in the PROSPERO record and in the published manuscript. A change log will be maintained by the principal investigator."),
blank(),
h2("12.3 Dissemination Plan"),
body("The completed systematic review and meta-analysis will be submitted for peer review to a high-impact, internationally recognised journal in the field of hepatopancreatobiliary surgery or oncology. Target journals include: Annals of Surgery; JAMA Surgery; Gut; HPB; Annals of Surgical Oncology; Journal of Clinical Oncology; British Journal of Surgery. Results will also be presented at relevant international surgical conferences (ISGPS, IHPBA, SSAT, ASCO, ESMO). The protocol will be made publicly available via the PROSPERO registry."),
blank(),
];
}
// ─── SECTION 13: PROSPERO Administrative Fields ─────────────────────────────
function section13() {
return [
sectionBanner("SECTION 13: PROSPERO ADMINISTRATIVE FIELDS (REGISTRY COMPLETION GUIDE)"),
blank(),
body("The following section maps the review content to PROSPERO's 22 required registry fields. Copy-paste the content provided into the corresponding PROSPERO field when completing online registration at crd.york.ac.uk/PROSPERO."),
blank(),
tTable([
hrow(["PROSPERO Field #", "Field Name", "Content / Entry"]),
row(["1", "Review title", "Biologic versus anatomic resectability criteria in pancreatic ductal adenocarcinoma: a systematic review and meta-analysis"]),
row(["2", "Original language title", "[Leave blank if English]"]),
row(["3", "Keywords", "Pancreatic ductal adenocarcinoma; PDAC; biologic resectability; anatomic resectability; borderline resectable; CA 19-9; neoadjuvant therapy; overall survival; R0 resection; ABC criteria; JSHBPS"]),
row(["4", "Anticipated or actual start date", "01/07/2026"]),
row(["5", "Anticipated completion date", "28/02/2027"]),
row(["6", "Stage of review at time of registration", "Preliminary searches done. Formal searches not yet started."]),
row(["7", "Named contact (Guarantor)", "[Author 1 Name, email, institution]"]),
row(["8", "Review team members", "[List all authors with institutions and ORCID IDs]"]),
row(["9", "Organisational affiliation", "[Lead institution name, department, country]"]),
row(["10", "Funding sources / sponsors", "[State funding source or 'No external funding for this review']"]),
row(["11", "Conflicts of interest", "[State 'None declared' or specify]"]),
row(["12", "Condition or domain being studied", "Pancreatic ductal adenocarcinoma (PDAC) - resectability assessment and surgical management"]),
row(["13", "Participants / population", "Adults (≥18 years) with non-metastatic, histologically confirmed PDAC deemed potentially operable at MDT review, including anatomically resectable, borderline resectable, and locally advanced subgroups"]),
row(["14", "Intervention(s), exposure(s)", "Biologic or combined biologic-anatomic resectability criteria incorporating ≥1 biologic factor: CA 19-9, PET findings, lymph node status, performance status, ctDNA, ABC staging, JSHBPS BR-B criteria, or other validated biologic markers"]),
row(["15", "Comparator(s) / control", "Anatomy-only resectability criteria (NCCN, ISGPS, AHPBA/SSO/SSAT, MD Anderson or institutional CT-based anatomic criteria)"]),
row(["16", "Types of study to be included", "Randomised controlled trials; prospective cohort studies; retrospective cohort studies with ≥2 comparison groups. Minimum sample size: n≥30 per group for retrospective studies."]),
row(["17", "Context", "Any healthcare setting where pancreatic cancer surgery and/or neoadjuvant therapy is performed; all countries; all treatment eras from January 2000 to present"]),
row(["18", "Primary outcome(s)", "1. Overall survival (OS): median OS (months); HR with 95% CI. 2. R0 resection rate: proportion (%) achieving margin-negative resection; OR with 95% CI. Time points: at study follow-up, and at 1, 2, 3, 5 years where available."]),
row(["19", "Secondary outcome(s)", "Disease-free survival (DFS/RFS); rate of futile resection (early recurrence <6 months); completion of intended systemic therapy; 30-day and 90-day postoperative mortality; major morbidity (Clavien-Dindo ≥III); conversion/resection rate; prognostic accuracy (C-statistic) of biologic vs. anatomy-only criteria"]),
row(["20", "Data extraction (selection and coding)", "Dual independent extraction using a standardised, piloted data extraction form (REDCap/Excel/Covidence). Corresponding authors contacted for missing data. Disagreements resolved by consensus or third-reviewer adjudication."]),
row(["21", "Risk of bias (quality) assessment", "RCTs: Cochrane RoB 2.0. Observational studies: ROBINS-I. Prognostic accuracy studies: PROBAST. Supplementary: Newcastle-Ottawa Scale. Dual independent assessment. Sensitivity analyses restricted to low-risk-of-bias studies."]),
row(["22", "Strategy for data synthesis", "Random-effects meta-analysis (DerSimonian-Laird) for OS (HR), R0 rate (OR), DFS (HR) where ≥3 studies allow pooling. Heterogeneity: Q-test, I², Tau². Pre-specified subgroup analyses (biologic criteria type, CA 19-9 threshold, anatomic stage, treatment era, study design, risk of bias). Meta-regression for CA 19-9 threshold dose-response. Funnel plots and Egger's test for publication bias. Narrative SWiM synthesis for all included studies. GRADE evidence certainty assessment. Software: R (meta, metafor, dmetar) and/or RevMan 5.4."]),
row(["23 (optional)", "Analysis of subgroups or subsets", "Pre-specified: biologic criteria system; CA 19-9 threshold; anatomic resectability category (R/BR/LA); treatment pathway; chemotherapy era; study design; risk of bias; tumour location"]),
row(["24 (optional)", "Type and method of review", "Systematic Review AND Meta-Analysis"]),
row(["25 (optional)", "Language", "English (primary); other languages included if translation available"]),
row(["26 (optional)", "Country", "[Country of registration institution]"]),
row(["27 (optional)", "Dissemination plans", "Submission to peer-reviewed international HPB surgery or oncology journal (target: Annals of Surgery, JAMA Surgery, HPB, Ann Surg Oncol, Gut). Conference presentations at ISGPS, IHPBA, ASCO, ESMO. Protocol publicly available via PROSPERO."]),
row(["28 (optional)", "Other registration details", "No prior registration exists for this specific review question. Preliminary searches completed June 30, 2026. No competing review identified on PROSPERO, Cochrane Library, or PubMed."])
], [1200, 2300, 5526]),
blank(),
];
}
// ─── SECTION 14: References ──────────────────────────────────────────────────
function section14() {
return [
sectionBanner("SECTION 14: KEY REFERENCES FOR INTRODUCTION AND PROTOCOL JUSTIFICATION"),
blank(),
body("The following references should be cited in the systematic review manuscript and are the primary evidence base justifying this review:"),
blank(),
numbered("Oba A, Del Chiaro M, Satoi S, Kim SW, Takahashi H, Yu J, et al. New criteria of resectability for pancreatic cancer: a position paper by the Japanese Society of Hepato-Biliary-Pancreatic Surgery (JSHBPS). J Hepatobiliary Pancreat Sci. 2022;29(7):725-731. doi:10.1002/jhbp.1112. PMID: 34581016."),
numbered("Dekker EN, van Dam JL, Janssen QP, Besselink MG, DeSilva A, Doppenberg D, et al. Improved clinical staging system for localized pancreatic cancer using the ABC factors: a TAPS Consortium study. J Clin Oncol. 2024;42(12):1357-1367. doi:10.1200/JCO.23.01311. PMID: 38315954."),
numbered("Crippa S, Malleo G, Mazzaferro V, Langella S, Ricci C, Casciani F, et al. Futility of up-front resection for anatomically resectable pancreatic cancer. JAMA Surg. 2024;159(10):1139-1147. doi:10.1001/jamasurg.2024.2485."),
numbered("Rompen IF, Habib JR, Wolfgang CL, Javed AA. Anatomical and biological considerations to determine resectability in pancreatic cancer. Cancers (Basel). 2024;16(3):489. doi:10.3390/cancers16030489. PMID: 38339242."),
numbered("Lee SS, Kim DW, Lee W, Kim KP. Updates on imaging assessment of pancreatic cancer for determining anatomic and biologic resectability. Korean J Radiol. 2026;27(7):634-651. doi:10.3348/kjr.2026.0341. PMID: 42252995."),
numbered("Isaji S, Mizuno S, Windsor JA, Bassi C, Fernandez-Del Castillo C, Hackert T, et al. International consensus on definition and criteria of borderline resectable pancreatic ductal adenocarcinoma 2017. Pancreatology. 2018;18(1):2-11."),
numbered("Stoop TF, Javed AA, Oba A, Koerkamp BG, Seufferlein T, Wilmink JW, et al. Pancreatic cancer. Lancet. 2025;405(10487):1182-1202."),
numbered("Jain AJ, Maxwell JE, Katz MHG, Snyder RA. Surgical considerations for neoadjuvant therapy for pancreatic adenocarcinoma. Cancers (Basel). 2023;15(16):4174. PMID: 37627202."),
numbered("Miyahara S, Takahashi H, Akita H, et al. Prognostic significance of biologic factors in patients with a modest radiologic response to neoadjuvant treatment for resectable and borderline resectable pancreatic cancers. Ann Surg Oncol. 2024;31:1200-1210. PMID: 38368291."),
numbered("Page MJ, McKenzie JE, Bossuyt PM, Boutron I, Hoffmann TC, Mulrow CD, et al. The PRISMA 2020 statement: an updated guideline for reporting systematic reviews. BMJ. 2021;372:n71."),
numbered("Shamseer L, Moher D, Clarke M, Ghersi D, Liberati A, Petticrew M, et al. Preferred reporting items for systematic review and meta-analysis protocols (PRISMA-P) 2015: elaboration and explanation. BMJ. 2015;350:g7647."),
numbered("Higgins JPT, Thomas J, Chandler J, Cumpston M, Li T, Page MJ, et al. (editors). Cochrane Handbook for Systematic Reviews of Interventions version 6.4. Cochrane; 2023. www.training.cochrane.org/handbook."),
numbered("Sterne JAC, Hernan MA, McAleenan A, Reeves BC, Higgins JPT. Chapter 25: Assessing risk of bias in a non-randomized study of interventions. In: Cochrane Handbook version 6.4. 2023."),
numbered("D'Ambra V, Ricci C, Ingaldi C, et al. Predictive factors for long-term survival in pancreatic ductal adenocarcinoma that underwent surgery: a systematic review and meta-analysis. Updates Surg. 2026;78:509-518."),
blank(),
];
}
// ─── SECTION 15: Appendix / PRISMA-P Checklist ─────────────────────────────
function section15() {
return [
sectionBanner("SECTION 15: PRISMA-P 2015 CHECKLIST MAPPING"),
blank(),
body("The following table maps each PRISMA-P 2015 item to the corresponding section of this protocol. Complete this table when submitting the protocol as a standalone publication."),
blank(),
tTable([
hrow(["PRISMA-P Item #", "Item Description", "Protocol Section"]),
row(["1a", "Title: Identify document as systematic review protocol", "Title Page"]),
row(["1b", "Registration: PROSPERO registration number", "Title Page"]),
row(["2a", "Contact for correspondence", "Section 1.2 / Team Table"]),
row(["2b", "Contributions of each author", "Title Page - Review Team Table"]),
row(["2c", "Amendments to protocol", "Section 12.2"]),
row(["2d", "Funding / support", "Title Page / Section 13 Field 10"]),
row(["2e", "Role of funders", "Section 13 Field 10"]),
row(["2f", "Conflicts of interest", "Title Page / Section 13 Field 11"]),
row(["3", "Rationale: describe the rationale for the review", "Section 2"]),
row(["4", "Objectives: explicit PICO statement", "Section 3"]),
row(["5a", "Eligibility criteria: PICO components", "Sections 3 and 4"]),
row(["5b", "Eligibility criteria: report characteristics", "Section 4"]),
row(["5c", "Eligibility criteria: study design", "Section 4.1.5"]),
row(["6a", "Information sources: databases", "Section 6.1"]),
row(["6b", "Information sources: search strategy example", "Section 6.3"]),
row(["7", "Study records: screening and selection process", "Section 7"]),
row(["8", "Data items: all variables sought", "Section 8.2"]),
row(["9", "Outcomes and prioritisation", "Section 5"]),
row(["10a", "Risk of bias: item-level assessment at study level", "Section 9.1"]),
row(["10b", "Risk of bias: planned use in evidence synthesis", "Section 9.2 / 10.4"]),
row(["11a", "Data synthesis: criteria for quantitative synthesis", "Section 10.2.1"]),
row(["11b", "Data synthesis: measures of effect/pooling", "Section 10.2.2"]),
row(["11c", "Data synthesis: heterogeneity assessment", "Section 10.2.4"]),
row(["11d", "Data synthesis: if meta-analysis done, model specification", "Section 10.2.3"]),
row(["11e", "Data synthesis: subgroup analyses", "Section 10.3"]),
row(["11f", "Data synthesis: sensitivity analyses", "Section 10.4"]),
row(["12", "Meta-bias: publication bias", "Section 10.2.5"]),
row(["13", "Confidence in cumulative evidence (GRADE)", "Section 11"])
], [1400, 4000, 3626]),
blank(),
blank(),
new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 200, after: 200 },
shading: { type: ShadingType.SOLID, color: LGREY, fill: LGREY },
children: [new TextRun({
text: "END OF PROTOCOL — Version 1.0 | Date: June 30, 2026",
bold: true, size: 20, font: "Arial", color: NAVY, italics: true
})]
})
];
}
// ─── Assemble document ──────────────────────────────────────────────────────
const doc = new Document({
styles: {
default: {
document: { run: { font: "Arial", size: 22, color: DKGREY } }
},
paragraphStyles: [
{
id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 30, bold: true, font: "Arial", color: WHITE },
paragraph: { spacing: { before: 300, after: 160 }, outlineLevel: 0,
shading: { type: ShadingType.SOLID, color: NAVY, fill: NAVY } }
},
{
id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 26, bold: true, font: "Arial", color: NAVY },
paragraph: { spacing: { before: 240, after: 120 }, outlineLevel: 1 }
},
{
id: "Heading3", name: "Heading 3", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 24, bold: true, font: "Arial", color: TEAL },
paragraph: { spacing: { before: 180, after: 80 }, outlineLevel: 2 }
}
]
},
numbering: {
config: [
{
reference: "bullets",
levels: [
{ level: 0, format: LevelFormat.BULLET, text: "\u2022", alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } } },
{ level: 1, format: LevelFormat.BULLET, text: "\u25E6", alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 1080, hanging: 360 } } } }
]
},
{
reference: "numbers",
levels: [
{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }
]
}
]
},
sections: [
{
properties: {
page: {
size: { width: 12240, height: 15840 },
margin: { top: 1080, bottom: 1080, left: 1080, right: 1080 }
}
},
headers: {
default: new Header({
children: [
new Paragraph({
alignment: AlignmentType.RIGHT,
spacing: { before: 0, after: 0 },
children: [
new TextRun({ text: "PDAC Biologic vs. Anatomic Resectability | PROSPERO Protocol v1.0 ", size: 16, font: "Arial", color: "888888" })
]
})
]
})
},
footers: {
default: new Footer({
children: [
new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new TextRun({ text: "Page ", size: 16, font: "Arial", color: "888888" }),
new TextRun({ children: [new PageNumber()], size: 16, font: "Arial", color: "888888" })
]
})
]
})
},
children: [
...titlePage(),
...section1(),
...section2(),
...section3(),
...section4(),
...section5(),
...section6(),
...section7(),
...section8(),
...section9(),
...section10(),
...section11(),
...section12(),
...section13(),
...section14(),
...section15()
]
}
]
});
Packer.toBuffer(doc).then(buffer => {
fs.writeFileSync("/home/daytona/workspace/pdac-biologic-resectability-protocol/PDAC_Biologic_vs_Anatomic_Resectability_PROSPERO_Protocol_v1.0.docx", buffer);
console.log("SUCCESS: Document written.");
}).catch(err => {
console.error("ERROR:", err);
process.exit(1);
});
cd /home/daytona/workspace/pdac-biologic-resectability-protocol && node generate.js 2>&1
~/pdac-biologic-resectability-protocol/generate.js
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/pdac-biologic-resectability-protocol/generate.js'
cd /home/daytona/workspace/pdac-biologic-resectability-protocol && node generate.js 2>&1
python3 /home/daytona/skills/docx/scripts/office/validate.py "/home/daytona/workspace/pdac-biologic-resectability-protocol/PDAC_Biologic_vs_Anatomic_Resectability_PROSPERO_Protocol_v1.0.docx" 2>&1 && ls -lh /home/daytona/workspace/pdac-biologic-resectability-protocol/
ls -lh /home/daytona/workspace/pdac-biologic-resectability-protocol/ && python3 -c "import zipfile; z=zipfile.ZipFile('/home/daytona/workspace/pdac-biologic-resectability-protocol/PDAC_Biologic_vs_Anatomic_Resectability_PROSPERO_Protocol_v1.0.docx'); print('Valid ZIP/DOCX. Files:', z.namelist()[:8])"
PDAC Biologic vs Anatomic Resectability PROSPERO Protocol v1.0
Word Document · DOCX
| Section | Content |
|---|---|
| Title Page | Full title, version, date, 5-author review team table with ORCID fields, COI and funding placeholders |
| 1 - Administrative | PROSPERO-ready title, keywords, milestone timeline table, stage of review at registration |
| 2 - Background | Clinical rationale; anatomy-only limitations; emergence of biologic criteria (JSHBPS BR-B, ABC/TAPS, Crippa futility data); gap justification; confirmation of no prior SR/MA |
| 3 - PICO | Primary + 5 secondary review questions; full PICO table with precise definitions for P, I, C, O, and Study design |
| 4 - Eligibility | Detailed inclusion (population, intervention, comparator, outcomes, design, language) and exclusion criteria table |
| 5 - Outcomes | Primary outcomes (OS and R0 rate) and 8 secondary outcomes in structured tables with data definitions |
| 6 - Search Strategy | 6 databases; additional sources (ClinicalTrials.gov, grey literature, citation searches); full 19-step MEDLINE/PubMed search string |
| 7 - Screening | Phase 1 (title/abstract) and Phase 2 (full-text) dual-reviewer process; kappa calibration; PRISMA flow diagram plan |
| 8 - Data Extraction | Standardised extraction items across 4 domains (study ID, population, biologic criteria, outcomes); missing data handling |
| 9 - Risk of Bias | RoB 2.0 (RCTs); ROBINS-I (observational); PROBAST (prognostic accuracy); NOS supplementary; overlapping cohort policy |
| 10 - Statistical Analysis | Random-effects model; effect measures by outcome type; heterogeneity (I², Q, Tau²); 8 pre-specified subgroup analyses; 6 sensitivity analyses; meta-regression for CA 19-9 threshold dose-response; publication bias; R software |
| 11 - GRADE | Certainty of evidence framework; upgrading/downgrading criteria; Summary of Findings table plan |
| 12 - Reporting | PRISMA 2020, PRISMA-P, MOOSE, GRADE, SWiM; amendment policy; target journal list |
| 13 - PROSPERO Fields | Copy-paste guide for all 28 PROSPERO registry fields - ready to enter online |
| 14 - References | 14 key references with full citations including PMIDs and DOIs |
| 15 - PRISMA-P Checklist | All 27 PRISMA-P 2015 items mapped to protocol sections |
Generate a pilot data extraction form (Excel/REDCap template)
.../pdac-biologic-resectability-protocol/build_extraction_form.py
"""
Pilot Data Extraction Form - Excel Workbook
SR/MA: Biologic vs Anatomic Resectability Criteria in PDAC
Generated: June 30, 2026
"""
import openpyxl
from openpyxl import Workbook
from openpyxl.styles import (
Font, PatternFill, Alignment, Border, Side,
GradientFill
)
from openpyxl.styles.numbers import FORMAT_NUMBER_COMMA_SEPARATED1
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.datavalidation import DataValidation
from openpyxl.formatting.rule import ColorScaleRule, CellIsRule, FormulaRule
from openpyxl.worksheet.table import Table, TableStyleInfo
from openpyxl.comments import Comment
from openpyxl.chart import BarChart, Reference
# ─── Colour palette ───────────────────────────────────────────────────────────
NAVY = "1F3864"
TEAL = "1F6B75"
TEAL_LT = "D6EEF1"
NAVY_LT = "D9E2F3"
GOLD = "BF8F00"
GOLD_LT = "FFF2CC"
GREEN_LT = "E2EFDA"
RED_LT = "FFDCE1"
GREY_LT = "F2F2F2"
GREY_MED = "D9D9D9"
WHITE = "FFFFFF"
BLACK = "000000"
ORANGE_LT = "FCE4D6"
PURPLE_LT = "EAD1DC"
# ─── Style helpers ────────────────────────────────────────────────────────────
def hdr_font(size=11, bold=True, color=WHITE, italic=False):
return Font(name="Arial", size=size, bold=bold, color=color, italic=italic)
def body_font(size=10, bold=False, color=BLACK, italic=False):
return Font(name="Arial", size=size, bold=bold, color=color, italic=italic)
def fill(hex_color):
return PatternFill("solid", fgColor=hex_color)
def border_thin():
s = Side(style="thin", color="BFBFBF")
return Border(left=s, right=s, top=s, bottom=s)
def border_med():
s = Side(style="medium", color="595959")
return Border(left=s, right=s, top=s, bottom=s)
def wrap_align(h="left", v="top"):
return Alignment(horizontal=h, vertical=v, wrap_text=True)
def center_align(v="center"):
return Alignment(horizontal="center", vertical=v, wrap_text=True)
def apply_cell(ws, row, col, value, fnt=None, fll=None, bdr=None, aln=None, num_fmt=None):
c = ws.cell(row=row, column=col, value=value)
if fnt: c.font = fnt
if fll: c.fill = fll
if bdr: c.border = bdr
if aln: c.alignment = aln
if num_fmt: c.number_format = num_fmt
return c
def add_comment(ws, row, col, text, author="SR Team"):
c = ws.cell(row=row, column=col)
comment = Comment(text, author)
comment.width = 280
comment.height = 120
c.comment = comment
# ─── Sheet banner ─────────────────────────────────────────────────────────────
def write_banner(ws, title, subtitle, col_span=20):
ws.row_dimensions[1].height = 36
ws.row_dimensions[2].height = 22
ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=col_span)
c = ws.cell(row=1, column=1, value=title)
c.font = Font(name="Arial", size=16, bold=True, color=WHITE)
c.fill = fill(NAVY)
c.alignment = center_align()
ws.merge_cells(start_row=2, start_column=1, end_row=2, end_column=col_span)
c2 = ws.cell(row=2, column=1, value=subtitle)
c2.font = Font(name="Arial", size=10, bold=False, color=WHITE, italic=True)
c2.fill = fill(TEAL)
c2.alignment = center_align()
# ─── Section header row ────────────────────────────────────────────────────────
def section_row(ws, row, label, col_span, color=NAVY_LT, text_color=NAVY):
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=col_span)
c = ws.cell(row=row, column=1, value=label)
c.font = Font(name="Arial", size=10, bold=True, color=text_color)
c.fill = fill(color)
c.alignment = Alignment(horizontal="left", vertical="center", indent=1)
c.border = border_thin()
ws.row_dimensions[row].height = 18
# ─── Column header row ────────────────────────────────────────────────────────
def col_headers(ws, row, headers, colors=None, widths=None):
ws.row_dimensions[row].height = 32
for i, h in enumerate(headers, 1):
bg = colors[i-1] if colors and i <= len(colors) else TEAL
c = ws.cell(row=row, column=i, value=h)
c.font = hdr_font(size=9, color=WHITE)
c.fill = fill(bg)
c.alignment = wrap_align("center", "center")
c.border = border_thin()
if widths and i <= len(widths):
ws.column_dimensions[get_column_letter(i)].width = widths[i-1]
# ─── Data row ─────────────────────────────────────────────────────────────────
def data_row(ws, row, values, bg=WHITE, bold_first=False):
ws.row_dimensions[row].height = 20
for i, v in enumerate(values, 1):
bf = (bold_first and i == 1)
c = ws.cell(row=row, column=i, value=v)
c.font = body_font(bold=bf)
c.fill = fill(bg)
c.alignment = wrap_align()
c.border = border_thin()
# ─── Dropdown validation ──────────────────────────────────────────────────────
def add_dropdown(ws, sqref, options, prompt="Select a value"):
formula = '"' + ",".join(options) + '"'
dv = DataValidation(type="list", formula1=formula, allow_blank=True,
showDropDown=False, showErrorMessage=True,
error="Invalid entry. Use the dropdown list.",
errorTitle="Invalid Value",
showInputMessage=True, promptTitle="Instruction",
prompt=prompt)
ws.add_data_validation(dv)
dv.add(sqref)
# ─── Number validation ────────────────────────────────────────────────────────
def add_num_validation(ws, sqref, min_val, max_val, prompt="Enter a number"):
dv = DataValidation(type="decimal", operator="between",
formula1=str(min_val), formula2=str(max_val),
allow_blank=True, showErrorMessage=True,
error=f"Value must be between {min_val} and {max_val}.",
errorTitle="Invalid Number",
showInputMessage=True, promptTitle="Instruction",
prompt=prompt)
ws.add_data_validation(dv)
dv.add(sqref)
# ─── Freeze panes ─────────────────────────────────────────────────────────────
def freeze(ws, cell="A5"):
ws.freeze_panes = cell
###############################################################################
# SHEET 1: INSTRUCTIONS
###############################################################################
def build_instructions(wb):
ws = wb.create_sheet("INSTRUCTIONS", 0)
ws.sheet_view.showGridLines = False
ws.column_dimensions["A"].width = 22
ws.column_dimensions["B"].width = 90
write_banner(ws,
"DATA EXTRACTION FORM — INSTRUCTIONS AND CODEBOOK",
"SR/MA: Biologic vs Anatomic Resectability Criteria in PDAC | Version 1.0 | June 2026",
col_span=2)
ws.row_dimensions[3].height = 10
content = [
("PURPOSE", ""),
("Review title", "Biologic versus anatomic resectability criteria in pancreatic ductal adenocarcinoma: a systematic review and meta-analysis"),
("PROSPERO ID", "[To be added after registration]"),
("Form version", "1.0 — Pilot"),
("Date created", "30 June 2026"),
("", ""),
("HOW TO USE THIS FORM", ""),
("Step 1 — Pilot testing", "Before formal extraction begins, complete this form for 3–5 representative studies. Discuss and resolve discrepancies with your co-extractor at a consensus meeting. Document any modifications to field definitions in the AMENDMENTS tab."),
("Step 2 — Dual extraction", "Two reviewers extract each study independently using separate copies of this workbook (named Extractor_A.xlsx and Extractor_B.xlsx). Do NOT share your extraction with your co-extractor until both are complete."),
("Step 3 — Discrepancy check", "Compare extractions using the DISCREPANCY_LOG tab. For each disagreement, discuss and record the resolved value. If consensus cannot be reached, a third reviewer adjudicates."),
("Step 4 — Final database", "Transfer agreed values into the MASTER_DATABASE tab. Maintain one row per study (or per subgroup if a study reports multiple eligible cohorts separately)."),
("", ""),
("SHEET OVERVIEW", ""),
("INSTRUCTIONS", "This sheet — guidance, codebook, and field definitions"),
("A_STUDY_ID", "Section A: Study identification and administrative details"),
("B_POPULATION", "Section B: Patient population characteristics"),
("C_BIOLOGIC_CRITERIA", "Section C: Biologic resectability criteria applied"),
("D_ANATOMIC_CRITERIA", "Section D: Anatomic resectability criteria applied"),
("E_TREATMENT", "Section E: Treatment details (NAT vs. upfront surgery)"),
("F_OUTCOMES_SURVIVAL", "Section F: Survival outcomes (OS, DFS)"),
("G_OUTCOMES_SURGICAL", "Section G: Surgical outcomes (R0, morbidity, mortality)"),
("H_ROB", "Section H: Risk of bias assessment (ROBINS-I / RoB 2.0 / PROBAST)"),
("MASTER_DATABASE", "Consolidated one-row-per-study database for meta-analysis"),
("DISCREPANCY_LOG", "Log of disagreements between Extractor A and Extractor B"),
("AMENDMENTS", "Protocol deviations and field definition updates"),
("LISTS", "Hidden dropdown option lists (do not edit)"),
("", ""),
("GENERAL RULES", ""),
("Missing data", "If a value is not reported in the paper, enter NR (Not Reported). Do NOT leave cells blank — blank cells cannot be distinguished from data not yet extracted."),
("Not applicable", "Enter NA if the field genuinely does not apply to this study design."),
("Unclear data", "If data appear to be present but cannot be reliably extracted, enter UC (Unclear) and add a note in the adjacent NOTES column."),
("Estimates from graphs", "If numerical data are estimated from a Kaplan-Meier curve or figure (e.g., using WebPlotDigitizer), record the value with an asterisk (*) and note 'Estimated from figure' in the NOTES column."),
("Multiple cohorts", "If a single publication reports two or more separate, extractable patient cohorts (e.g., biologic-R vs. anatomy-only-R), create one row per cohort in MASTER_DATABASE, suffixed _A, _B, etc. Record in the NOTES column."),
("Units", "Always record units explicitly (e.g., months, U/mL, %). Do not convert units between studies."),
("Multivariate vs. unadjusted", "Always prefer multivariate-adjusted estimates (HR, OR) over unadjusted. Record BOTH if available. Flag the preferred estimate in the META-ANALYSIS USE? column."),
("", ""),
("ABBREVIATIONS", ""),
("BR", "Borderline resectable"),
("BR-B","Biological borderline resectable (JSHBPS 2022)"),
("CA 19-9", "Carbohydrate antigen 19-9"),
("CI", "Confidence interval"),
("DFS", "Disease-free survival"),
("ECOG","Eastern Cooperative Oncology Group performance status"),
("HR", "Hazard ratio"),
("IQR", "Interquartile range"),
("LA", "Locally advanced"),
("LN", "Lymph node"),
("mFOLFIRINOX", "Modified FOLFIRINOX regimen"),
("NAT", "Neoadjuvant therapy"),
("NCCN","National Comprehensive Cancer Network"),
("NR", "Not reported"),
("NA", "Not applicable"),
("OR", "Odds ratio"),
("OS", "Overall survival"),
("PD", "Progressive disease"),
("PDAC","Pancreatic ductal adenocarcinoma"),
("PET", "Positron emission tomography"),
("R", "Resectable (anatomically)"),
("R0", "Margin-negative resection"),
("R1", "Microscopic margin-positive resection"),
("RFS", "Recurrence-free survival"),
("RoB", "Risk of bias"),
("SD", "Standard deviation"),
("SMV", "Superior mesenteric vein"),
("SMA", "Superior mesenteric artery"),
("UC", "Unclear"),
]
row = 4
for label, desc in content:
ws.row_dimensions[row].height = 15
if label in ("PURPOSE","HOW TO USE THIS FORM","SHEET OVERVIEW","GENERAL RULES","ABBREVIATIONS"):
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=2)
c = ws.cell(row=row, column=1, value=" " + label)
c.font = Font(name="Arial", size=10, bold=True, color=WHITE)
c.fill = fill(TEAL)
c.alignment = Alignment(horizontal="left", vertical="center")
elif label == "":
ws.row_dimensions[row].height = 6
else:
c1 = ws.cell(row=row, column=1, value=label)
c1.font = Font(name="Arial", size=9, bold=True, color=NAVY)
c1.fill = fill(GREY_LT)
c1.alignment = wrap_align()
c1.border = border_thin()
c2 = ws.cell(row=row, column=2, value=desc)
c2.font = body_font(size=9)
c2.alignment = wrap_align()
c2.border = border_thin()
ws.row_dimensions[row].height = max(15, min(60, len(desc)//6 * 15 // 10 * 10))
row += 1
ws.sheet_tab_color = NAVY
###############################################################################
# SECTION A: STUDY IDENTIFICATION
###############################################################################
def build_section_A(wb):
ws = wb.create_sheet("A_STUDY_ID")
ws.sheet_view.showGridLines = False
COL_WIDTHS = [6, 20, 16, 12, 12, 14, 18, 14, 14, 18, 18, 18, 14, 30]
HEADERS = [
"Study\nID", "First Author\n(Year)", "Full Citation\n(Author Year Journal)",
"Publication\nYear", "Country\n(1st author)", "Study\nDesign",
"Setting\n(single/multi)", "Enrolment\nStart Year", "Enrolment\nEnd Year",
"Total N\n(all patients)", "N Biologic\nCriteria Group", "N Anatomy-Only\nGroup",
"Funding\nSource", "Notes / Comments"
]
write_banner(ws,
"SECTION A — STUDY IDENTIFICATION & ADMINISTRATIVE DETAILS",
"SR/MA: Biologic vs Anatomic Resectability in PDAC | Complete one row per study",
col_span=len(HEADERS))
ws.row_dimensions[3].height = 6
col_headers(ws, 4, HEADERS,
colors=[NAVY]*len(HEADERS),
widths=COL_WIDTHS)
freeze(ws, "A5")
# Sample / pilot rows
pilot_data = [
["S001", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["S002", "", "", "", "", "", "", "", "", "", "", "", "", ""],
["S003", "", "", "", "", "", "", "", "", "", "", "", "", ""],
]
for i, row_vals in enumerate(pilot_data):
bg = GREY_LT if i % 2 == 0 else WHITE
data_row(ws, 5+i, row_vals, bg=bg)
# Dropdowns
add_dropdown(ws, "F5:F54",
["RCT", "Prospective cohort", "Retrospective cohort",
"Registry-based cohort", "Case-control", "Other"],
"Select study design")
add_dropdown(ws, "G5:G54",
["Single-centre", "Multicentre (2-5)", "Multicentre (>5)", "National registry", "International registry"],
"Select setting")
add_dropdown(ws, "M5:M54",
["Industry", "Government / NIH / NHMRC / MRC", "Institutional / hospital",
"Charity / foundation", "No funding stated", "No external funding", "Multiple sources", "NR"],
"Select funding source")
# Comments
add_comment(ws, 4, 1, "Use format S001, S002... Enter same ID across all Section sheets.")
add_comment(ws, 4, 6, "RCT = randomised controlled trial. Use most specific design.")
add_comment(ws, 4, 11, "N in the group where biologic criteria were APPLIED or reported.")
add_comment(ws, 4, 12, "N in anatomy-only control/comparator group. Enter NR if not separable.")
# Conditional formatting: highlight rows with NR/UC
cf_range = f"A5:N54"
ws.conditional_formatting.add(cf_range,
FormulaRule(formula=['ISNUMBER(SEARCH("UC",A5))'],
fill=PatternFill("solid", fgColor=GOLD_LT)))
ws.sheet_tab_color = NAVY
return ws
###############################################################################
# SECTION B: POPULATION
###############################################################################
def build_section_B(wb):
ws = wb.create_sheet("B_POPULATION")
ws.sheet_view.showGridLines = False
HEADERS = [
"Study\nID", "Total N", "Age Metric\n(mean/median)", "Age Value\n(years)",
"Age SD/IQR", "Male\n(%)", "Head\nLocation (%)",
"Body/Tail\n(%)", "AJCC Stage\nI-II (%)", "AJCC Stage\nIII (%)",
"Anatomic\nClassification System", "% Resectable\n(R)", "% Borderline\nResectable (BR)",
"% Locally\nAdvanced (LA)", "Baseline CA 19-9\nMedian (U/mL)", "CA 19-9 IQR\nor range",
"% CA 19-9\n>500 U/mL", "ECOG 0\n(%)", "ECOG ≥1\n(%)",
"Prior Chemo\nat enrolment (%)", "Notes"
]
WIDTHS = [6,7,10,8,10,7,10,10,10,10,20,9,10,9,14,14,11,8,8,12,30]
write_banner(ws,
"SECTION B — PATIENT POPULATION CHARACTERISTICS",
"SR/MA: Biologic vs Anatomic Resectability in PDAC | Complete one row per study",
col_span=len(HEADERS))
ws.row_dimensions[3].height = 6
col_headers(ws, 4, HEADERS, colors=[NAVY]*len(HEADERS), widths=WIDTHS)
freeze(ws, "A5")
for i in range(10):
bg = GREY_LT if i % 2 == 0 else WHITE
data_row(ws, 5+i, [""]*len(HEADERS), bg=bg)
add_dropdown(ws, "C5:C54", ["Mean", "Median", "NR"], "Report whether age given as mean or median")
add_dropdown(ws, "K5:K54",
["NCCN (2017 or later)", "NCCN (pre-2017)", "ISGPS 2017 International Consensus",
"AHPBA/SSO/SSAT", "MD Anderson", "Institutional - anatomy only",
"Combined anatomic + biologic (specify in Notes)", "NR", "Other (specify in Notes)"],
"Select anatomic classification system used")
add_comment(ws, 4, 5, "Enter SD if mean used, IQR if median used. Format: SD=X or IQR=X–Y")
add_comment(ws, 4, 15, "Baseline = pre-treatment serum CA 19-9 in U/mL. Note assay if specified.")
add_comment(ws, 4, 17, "Percentage of patients with CA 19-9 >500 U/mL at baseline. Enter NR if threshold not reported.")
add_comment(ws, 4, 20, "Enter % who received prior chemotherapy before enrolment / index classification point. Usually 0 for upfront surgery studies.")
ws.sheet_tab_color = TEAL
###############################################################################
# SECTION C: BIOLOGIC CRITERIA
###############################################################################
def build_section_C(wb):
ws = wb.create_sheet("C_BIOLOGIC_CRITERIA")
ws.sheet_view.showGridLines = False
HEADERS = [
"Study\nID",
"Biologic Criteria\nSystem / Name",
"CA 19-9 Used\n(Yes/No)",
"CA 19-9\nThreshold (U/mL)",
"CA 19-9 Threshold\nBasis",
"PET-CT Used\n(Yes/No)",
"PET Criterion\n(description)",
"Clinical LN\nStatus Used?",
"LN Criterion\n(description)",
"Performance\nStatus Used?",
"ECOG Threshold\n(e.g. ≥1, ≥2)",
"ctDNA / Liquid\nBiopsy Used?",
"Imaging\nPhenotype Used?",
"Other Biologic\nFactors (specify)",
"No. of Biologic\nFactors Combined",
"Biologic BR (BR-B)\nDefined? (Y/N)",
"Biologic BR\nDefinition (verbatim)",
"Timing of\nBiologic Assessment",
"CA 19-9 Non-Secretors\nHandled? (Y/N/NR)",
"Non-Secretor\nHandling Method",
"Notes"
]
WIDTHS = [6,20,9,10,16,9,18,10,18,12,10,10,12,18,10,9,30,16,12,18,30]
write_banner(ws,
"SECTION C — BIOLOGIC RESECTABILITY CRITERIA APPLIED",
"SR/MA: Biologic vs Anatomic Resectability in PDAC | Complete one row per study",
col_span=len(HEADERS))
ws.row_dimensions[3].height = 6
col_headers(ws, 4, HEADERS,
colors=[TEAL if i in [1,2] else NAVY for i in range(len(HEADERS))],
widths=WIDTHS)
freeze(ws, "A5")
for i in range(10):
bg = TEAL_LT if i % 2 == 0 else WHITE
data_row(ws, 5+i, [""]*len(HEADERS), bg=bg)
add_dropdown(ws, "B5:B54",
["JSHBPS BR-B (Oba 2022)", "ABC staging (TAPS / Dekker 2024)",
"CA 19-9 alone (institutional)", "CA 19-9 + PET",
"CA 19-9 + LN status", "CA 19-9 + ECOG",
"Multi-biologic composite (specify in Notes)",
"Imaging phenotype only", "ctDNA / liquid biopsy",
"Other (specify in Notes)", "NR"],
"Select biologic criteria system")
add_dropdown(ws, "C5:C54", ["Yes","No","NR"], "Was CA 19-9 used as a biologic criterion?")
add_dropdown(ws, "E5:E54",
["JSHBPS (500 U/mL)", "Data-driven (ROC/Youden)", "Institutional protocol",
"Prior publication cited", "Other (specify in Notes)", "NR"],
"Basis for the CA 19-9 threshold value chosen")
add_dropdown(ws, "F5:F54", ["Yes","No","NR"], "Was PET-CT used as a biologic criterion?")
add_dropdown(ws, "H5:H54", ["Yes","No","NR"], "Was clinical/radiologic lymph node status used?")
add_dropdown(ws, "J5:J54", ["Yes","No","NR"], "Was performance status used as a biologic/conditional criterion?")
add_dropdown(ws, "L5:L54", ["Yes","No","NR"], "Was ctDNA / liquid biopsy used?")
add_dropdown(ws, "M5:M54", ["Yes","No","NR"], "Was imaging tumour phenotype (e.g., hypovascular pattern) used?")
add_dropdown(ws, "P5:P54", ["Yes","No","NR"], "Did the study define a 'biological borderline resectable' category?")
add_dropdown(ws, "R5:R54",
["At diagnosis (pre-treatment)", "After neoadjuvant therapy (post-treatment)",
"At MDT review", "At surgery", "Multiple time points", "NR"],
"When was the biologic assessment performed?")
add_dropdown(ws, "S5:S54", ["Yes", "No", "NR"], "Were Lewis antigen-negative (CA 19-9 non-secretor) patients addressed?")
add_dropdown(ws, "T5:T54",
["Excluded from analysis", "Included as CA 19-9=0", "Imputed / substituted (CA125 or CEA)",
"Subgroup analysed separately", "Not addressed", "NR"],
"How were CA 19-9 non-secretors handled?")
add_comment(ws, 4, 4, "Record EXACT threshold used (e.g., >500, ≥500, >37). If multiple thresholds tested, record all separated by semicolons and note primary threshold.")
add_comment(ws, 4, 7, "Describe PET criterion (e.g., FDG avid LN >1cm, indeterminate distant uptake). Quote source paper where possible.")
add_comment(ws, 4, 17, "Copy the verbatim definition of biologic BR from the source paper (maximum 200 characters). Use quotation marks.")
add_comment(ws, 4, 15, "Count of distinct biologic factors combined (e.g., CA 19-9 + ECOG = 2). Enter 1 if only CA 19-9.")
ws.sheet_tab_color = TEAL
###############################################################################
# SECTION D: ANATOMIC CRITERIA
###############################################################################
def build_section_D(wb):
ws = wb.create_sheet("D_ANATOMIC_CRITERIA")
ws.sheet_view.showGridLines = False
HEADERS = [
"Study\nID", "Anatomic Criteria\nSystem Used",
"Anatomic Criteria\nVersion / Year",
"SMA Contact\nThreshold for BR",
"SMV/PV Contact\nThreshold for BR",
"CA Contact\nThreshold for BR",
"Radiologic\nModality",
"CT Protocol\n(pancreatic-phase?)",
"% Resectable\n(R) by Anatomy",
"% Borderline\nResectable (BR)",
"% Locally\nAdvanced (LA)",
"Anatomy + Biology\nCombined? (Y/N)",
"Combined\nClassification Name",
"Notes"
]
WIDTHS = [6, 22, 14, 14, 14, 14, 14, 14, 10, 10, 10, 12, 20, 30]
write_banner(ws,
"SECTION D — ANATOMIC RESECTABILITY CRITERIA APPLIED",
"SR/MA: Biologic vs Anatomic Resectability in PDAC | Complete one row per study",
col_span=len(HEADERS))
ws.row_dimensions[3].height = 6
col_headers(ws, 4, HEADERS, colors=[NAVY]*len(HEADERS), widths=WIDTHS)
freeze(ws, "A5")
for i in range(10):
bg = NAVY_LT if i % 2 == 0 else WHITE
data_row(ws, 5+i, [""]*len(HEADERS), bg=bg)
add_dropdown(ws, "B5:B54",
["NCCN", "ISGPS 2017 International Consensus", "AHPBA/SSO/SSAT",
"MD Anderson", "Institutional (anatomy only)", "Multiple systems reported",
"Not clearly stated", "Other (specify in Notes)"],
"Select primary anatomic criteria system")
add_dropdown(ws, "G5:G54",
["CT (pancreatic protocol)", "CT (standard)", "MRI", "CT + MRI",
"CT + PET-CT", "EUS", "NR"],
"Select imaging modality for anatomic assessment")
add_dropdown(ws, "H5:H54",
["Yes — dedicated pancreatic protocol (arterial + venous phases)",
"Partial — venous phase only", "No — standard CT", "NR"],
"Was a dedicated pancreatic CT protocol used?")
add_dropdown(ws, "L5:L54", ["Yes", "No", "NR"],
"Did the study combine anatomic + biologic criteria into a single classification?")
add_comment(ws, 4, 4, "Record degree of contact threshold (e.g., ≤180° contact = BR, >180° = LA). Quote from paper or cite NCCN/ISGPS version.")
add_comment(ws, 4, 13, "If combined criteria used, record name (e.g., 'ABC staging', 'JSHBPS BR-AB').")
ws.sheet_tab_color = NAVY
###############################################################################
# SECTION E: TREATMENT
###############################################################################
def build_section_E(wb):
ws = wb.create_sheet("E_TREATMENT")
ws.sheet_view.showGridLines = False
HEADERS = [
"Study\nID", "Primary Treatment\nPathway",
"% Upfront\nSurgery", "% Neoadjuvant\nTherapy (NAT)",
"NAT Regimen\n(primary)", "NAT Regimen\n(secondary/other)",
"Median NAT\nCycles (n)",
"% Completing\nPlanned NAT",
"Chemotherapy\nEra",
"% Achieving\nResection",
"% Anatomic-R\nReceiving NAT",
"% Anatomic-BR\nReceiving NAT",
"% Anatomic-LA\nReceiving NAT",
"Adjuvant\nChemo Used?",
"Adjuvant\nChemo Regimen",
"% Completing\nAdjuvant Chemo",
"Notes"
]
WIDTHS = [6, 20, 9, 9, 20, 20, 10, 10, 14, 10, 11, 11, 11, 10, 18, 10, 30]
write_banner(ws,
"SECTION E — TREATMENT DETAILS",
"SR/MA: Biologic vs Anatomic Resectability in PDAC | Complete one row per study",
col_span=len(HEADERS))
ws.row_dimensions[3].height = 6
col_headers(ws, 4, HEADERS, colors=[NAVY]*len(HEADERS), widths=WIDTHS)
freeze(ws, "A5")
for i in range(10):
bg = GREY_LT if i % 2 == 0 else WHITE
data_row(ws, 5+i, [""]*len(HEADERS), bg=bg)
add_dropdown(ws, "B5:B54",
["Upfront surgery (majority)", "Neoadjuvant therapy (majority)",
"Mixed (upfront surgery and NAT)", "Systemic therapy / no surgery",
"NAT for BR/LA; upfront surgery for R", "NR"],
"Select primary treatment pathway")
add_dropdown(ws, "E5:E54",
["FOLFIRINOX", "mFOLFIRINOX", "Gemcitabine + nab-paclitaxel (GnP)",
"Gemcitabine monotherapy", "Gemcitabine + erlotinib",
"FOLFIRINOX + radiotherapy", "GnP + radiotherapy",
"SBRT / SABR alone", "Other (specify in Notes)", "NR"],
"Select primary NAT chemotherapy regimen")
add_dropdown(ws, "F5:F54",
["FOLFIRINOX", "mFOLFIRINOX", "Gemcitabine + nab-paclitaxel (GnP)",
"Gemcitabine monotherapy", "Other (specify)", "NR", "NA"],
"Select secondary NAT regimen if multiple used")
add_dropdown(ws, "I5:I54",
["Pre-FOLFIRINOX era (<2011)", "Gemcitabine era (2011–2016)",
"FOLFIRINOX era (2011–2018)", "Modern era (2016–present, FOLFIRINOX/GnP)",
"Mixed / cross-era", "NR"],
"Select chemotherapy era for this cohort")
add_dropdown(ws, "N5:N54", ["Yes", "No", "Variable (per protocol)", "NR"],
"Was adjuvant chemotherapy systematically used?")
add_comment(ws, 4, 11, "% of anatomically Resectable patients who received NAT (key for biologic criteria impact analysis).")
add_comment(ws, 4, 10, "% of all eligible patients ultimately undergoing pancreatic resection.")
ws.sheet_tab_color = GOLD
###############################################################################
# SECTION F: OUTCOMES — SURVIVAL
###############################################################################
def build_section_F(wb):
ws = wb.create_sheet("F_OUTCOMES_SURVIVAL")
ws.sheet_view.showGridLines = False
HEADERS = [
"Study\nID",
# OS — Overall cohort
"OS: Median\n(months) — ALL",
"OS: HR\n(biologic vs anatomy)",
"OS: HR 95%CI\nLower",
"OS: HR 95%CI\nUpper",
"OS: HR\nAdjusted? (Y/N)",
"OS: Covariates\nin adjustment",
"OS: 1-yr rate\n(%)",
"OS: 2-yr rate\n(%)",
"OS: 3-yr rate\n(%)",
"OS: 5-yr rate\n(%)",
# OS by anatomic subgroup
"OS: Median\n(months) — R group",
"OS: Median\n(months) — BR group",
"OS: Median\n(months) — LA group",
"OS: Median (months)\n— Biologic-R subgroup",
"OS: Median (months)\n— Biologic-BR subgroup",
# DFS
"DFS/RFS: Median\n(months) — ALL",
"DFS: HR\n(biologic vs anatomy)",
"DFS: HR 95%CI\nLower",
"DFS: HR 95%CI\nUpper",
"DFS: HR\nAdjusted? (Y/N)",
"DFS: 1-yr rate\n(%)",
"DFS: 3-yr rate\n(%)",
# Futile resection
"Futile Resection\nRate (%)",
"Futile Resection\nDefinition Used",
"Futile Resection OR\n(biologic vs anatomy)",
"Futile Resection OR\n95%CI Lower",
"Futile Resection OR\n95%CI Upper",
# Meta-analysis flags
"OS HR — Use in\nMeta-Analysis? (Y/N)",
"DFS HR — Use in\nMeta-Analysis? (Y/N)",
"Notes"
]
WIDTHS = [6,10,8,8,8,10,20,8,8,8,8,10,10,10,14,14,10,8,8,8,10,8,8,10,20,8,8,8,10,10,30]
write_banner(ws,
"SECTION F — SURVIVAL OUTCOMES",
"OS = Overall Survival | DFS = Disease-Free Survival | HR = Hazard Ratio | Enter NR if not reported",
col_span=len(HEADERS))
ws.row_dimensions[3].height = 6
col_headers(ws, 4, HEADERS,
colors=[NAVY if i < 17 else TEAL if i < 24 else GOLD for i in range(len(HEADERS))],
widths=WIDTHS)
freeze(ws, "A5")
# Sub-category labels row
cats = [
(1, 1, "Study ID"),
(2, 11, "OVERALL SURVIVAL — FULL COHORT"),
(12, 16, "OS BY ANATOMIC/BIOLOGIC SUBGROUP"),
(17, 23, "DFS / RECURRENCE-FREE SURVIVAL"),
(24, 28, "FUTILE RESECTION"),
(29, 30, "META-ANALYSIS FLAGS"),
(31, 31, "Notes")
]
# Insert a sub-header row
ws.insert_rows(4)
ws.row_dimensions[4].height = 14
for start, end, label in cats:
if start == end:
c = ws.cell(row=4, column=start, value=label)
else:
ws.merge_cells(start_row=4, start_column=start, end_row=4, end_column=end)
c = ws.cell(row=4, column=start, value=label)
c.font = Font(name="Arial", size=8, bold=True, color=WHITE)
c.fill = fill(NAVY if start < 17 else TEAL if start < 24 else GOLD if start < 29 else TEAL)
c.alignment = center_align()
c.border = border_thin()
for i in range(10):
bg = GREY_LT if i % 2 == 0 else WHITE
data_row(ws, 6+i, [""]*len(HEADERS), bg=bg)
add_dropdown(ws, "F6:F55", ["Yes - multivariable", "Yes - univariable", "No - unadjusted", "NR"],
"Was the OS HR from a multivariable or univariable analysis?")
add_dropdown(ws, "U6:U55", ["Yes - multivariable", "Yes - univariable", "No - unadjusted", "NR"],
"Was the DFS HR adjusted?")
add_dropdown(ws, "AD6:AD55", ["Yes", "No - preferred estimate missing", "No - unadjusted only"],
"Flag whether this OS HR should be used in the primary meta-analysis")
add_dropdown(ws, "AE6:AE55", ["Yes", "No - preferred estimate missing", "No - unadjusted only"],
"Flag whether this DFS HR should be used in the primary meta-analysis")
add_dropdown(ws, "Y6:Y55",
["Death or recurrence within 6 months of surgery",
"Recurrence within 3 months", "Recurrence within 12 months",
"No adjuvant therapy completion", "Other (specify in Notes)", "NR"],
"Select the futile resection definition used in the source paper")
add_comment(ws, 5, 3, "Record HR for biologic criteria group vs. anatomy-only group. HR>1 = worse OS in biologic group. Note direction carefully.")
add_comment(ws, 5, 24, "% of resected patients meeting the 'futile resection' definition (see column Y for definition used).")
add_comment(ws, 5, 31, "Use 'Estimated from KM curve*' if value taken from figure.")
ws.sheet_tab_color = TEAL
###############################################################################
# SECTION G: SURGICAL OUTCOMES
###############################################################################
def build_section_G(wb):
ws = wb.create_sheet("G_OUTCOMES_SURGICAL")
ws.sheet_view.showGridLines = False
HEADERS = [
"Study\nID",
# R0 / resection
"Total Resection\nRate (%)",
"R0 Rate\n(% of resected)",
"R0 Definition\nUsed",
"R0 OR\n(biologic vs anatomy)",
"R0 OR\n95%CI Lower",
"R0 OR\n95%CI Upper",
"R0 OR\nAdjusted? (Y/N)",
"R1 Rate\n(%)",
"R2 Rate\n(%)",
# Morbidity/mortality
"30-day\nMortality (%)",
"90-day\nMortality (%)",
"Major Morbidity\n(CD ≥III) (%)",
"POPF B/C\nRate (%)",
"PPH B/C\nRate (%)",
"DGE B/C\nRate (%)",
"Hospital LOS\nMedian (days)",
# Conversion
"Conversion Rate\nBR/LA to R (%)",
"Explored-not-resected\nRate (%)",
# Lymph node
"Median LN\nYield (n)",
"Median LN\nRatio (positive/total)",
# Vascular resection
"Vascular Resection\nRate (%)",
"Vascular Resection\nType (SMV/PV/artery)",
# C-statistic
"C-statistic\n(biologic model)",
"C-statistic\n95%CI Lower",
"C-statistic\n95%CI Upper",
"C-statistic\n(anatomy-only model)",
# Meta-analysis
"R0 OR — Use in\nMeta-Analysis? (Y/N)",
"Notes"
]
WIDTHS = [6,9,8,20,8,8,8,10,8,8,9,9,12,10,10,10,10,12,12,10,12,10,18,10,10,10,10,10,30]
write_banner(ws,
"SECTION G — SURGICAL OUTCOMES",
"R0 = Margin-negative resection | CD = Clavien-Dindo | POPF = Post-op Pancreatic Fistula | Enter NR if not reported",
col_span=len(HEADERS))
ws.row_dimensions[3].height = 6
col_headers(ws, 4, HEADERS,
colors=[NAVY]*10 + [TEAL]*7 + [GOLD]*2 + [NAVY]*2 + [TEAL]*2 + [NAVY]*4 + [TEAL],
widths=WIDTHS)
freeze(ws, "A5")
for i in range(10):
bg = GREY_LT if i % 2 == 0 else WHITE
data_row(ws, 5+i, [""]*len(HEADERS), bg=bg)
add_dropdown(ws, "D5:D54",
["Leeds (≥1mm tumour-free margin)", "Standard (no tumour cells at inked margin)",
"UICC R0 (>0mm)", "Institutional (specify in Notes)", "NR"],
"Select R0 definition used in the source paper")
add_dropdown(ws, "H5:H54", ["Yes - multivariable", "Yes - univariable", "No - unadjusted", "NR"],
"Was the R0 OR from a multivariable analysis?")
add_dropdown(ws, "AB5:AB54", ["Yes", "No - data not available", "No - not preferred estimate"],
"Flag for use in R0 meta-analysis")
add_comment(ws, 4, 3, "R0 rate as % of all resected patients. Use Leeds definition if specified. Note which definition was used in column D.")
add_comment(ws, 4, 18, "% of patients who were anatomically BR or LA at baseline and ultimately underwent resection after NAT.")
add_comment(ws, 4, 24, "C-statistic (AUC) for the biologic-inclusive prediction model for OS. Enter NR if not reported.")
add_comment(ws, 4, 27, "C-statistic for anatomy-only model (comparator). Enter NR if not reported.")
ws.sheet_tab_color = NAVY
###############################################################################
# SECTION H: RISK OF BIAS
###############################################################################
def build_section_H(wb):
ws = wb.create_sheet("H_ROB")
ws.sheet_view.showGridLines = False
HEADERS = [
"Study\nID", "Study\nDesign",
"RoB Tool\nApplied",
# ROBINS-I
"D1: Confounding\n(ROBINS-I)",
"D2: Selection\n(ROBINS-I)",
"D3: Intervention\nClassification",
"D4: Deviations\n(ROBINS-I)",
"D5: Missing\nData (ROBINS-I)",
"D6: Outcome\nMeasurement",
"D7: Selective\nReporting",
"ROBINS-I\nOverall",
# RoB 2 (for RCTs)
"D1: Randomisation\n(RoB 2)",
"D2: Deviations\n(RoB 2)",
"D3: Missing\nOutcomes (RoB 2)",
"D4: Outcome\nMeasurement (RoB 2)",
"D5: Selective\nReporting (RoB 2)",
"RoB 2\nOverall",
# PROBAST
"PROBAST\nParticipants",
"PROBAST\nPredictors",
"PROBAST\nOutcome",
"PROBAST\nAnalysis",
"PROBAST\nOverall",
# Reporting
"STROBE\nScore (/22)",
# Summary
"Overall RoB\nJudgement",
"Key Concerns\n(free text)",
"Notes"
]
WIDTHS = [6, 16, 14,
14,14,14,14,14,14,14,14,
14,14,14,14,14,14,
14,14,14,14,14,
10,
16,35,30]
write_banner(ws,
"SECTION H — RISK OF BIAS ASSESSMENT",
"ROBINS-I (observational) | RoB 2.0 (RCTs) | PROBAST (prognostic accuracy) | Enter domain judgements",
col_span=len(HEADERS))
ws.row_dimensions[3].height = 6
col_headers(ws, 4, HEADERS,
colors=[NAVY]*3 + [TEAL]*8 + [GOLD]*6 + ["8E44AD"]*5 + [NAVY]*3 + [TEAL],
widths=WIDTHS)
freeze(ws, "A5")
for i in range(10):
bg = GREY_LT if i % 2 == 0 else WHITE
data_row(ws, 5+i, [""]*len(HEADERS), bg=bg)
robins_options = ["Low", "Moderate", "Serious", "Critical", "NI (no information)", "NA"]
rob2_options = ["Low risk", "Some concerns", "High risk", "NA"]
probast_opt = ["Low risk", "High risk", "Unclear", "NA"]
overall_opts = ["Low", "Moderate", "Serious", "Critical", "NA"]
for col in range(4, 11): # ROBINS-I D1-D7
add_dropdown(ws, f"{get_column_letter(col)}5:{get_column_letter(col)}54",
robins_options, "ROBINS-I domain judgement")
add_dropdown(ws, "K5:K54", overall_opts, "ROBINS-I overall judgement")
for col in range(12, 17): # RoB 2 D1-D5
add_dropdown(ws, f"{get_column_letter(col)}5:{get_column_letter(col)}54",
rob2_options, "RoB 2.0 domain judgement")
add_dropdown(ws, "Q5:Q54", ["Low risk", "Some concerns", "High risk", "NA"], "RoB 2.0 overall")
for col in range(18, 22): # PROBAST
add_dropdown(ws, f"{get_column_letter(col)}5:{get_column_letter(col)}54",
probast_opt, "PROBAST domain judgement")
add_dropdown(ws, "V5:V54", ["Low risk", "High risk", "Unclear", "NA"], "PROBAST overall")
add_dropdown(ws, "B5:B54",
["RCT", "Prospective cohort", "Retrospective cohort", "Registry"],
"Confirm study design for RoB tool selection")
add_dropdown(ws, "C5:C54",
["ROBINS-I", "RoB 2.0", "PROBAST", "ROBINS-I + PROBAST", "RoB 2.0 + PROBAST"],
"Select RoB tool applied")
add_dropdown(ws, "X5:X54",
["Low", "Moderate", "Serious/High", "Critical", "Unclear"],
"Overall risk of bias judgement synthesising all domains")
# Conditional formatting: colour RoB cells by judgement
rob_range = f"D5:K54"
ws.conditional_formatting.add(rob_range,
CellIsRule(operator="equal", formula=['"Low"'],
fill=PatternFill("solid", fgColor=GREEN_LT)))
ws.conditional_formatting.add(rob_range,
CellIsRule(operator="equal", formula=['"Moderate"'],
fill=PatternFill("solid", fgColor=GOLD_LT)))
ws.conditional_formatting.add(rob_range,
CellIsRule(operator="equal", formula=['"Serious"'],
fill=PatternFill("solid", fgColor=ORANGE_LT)))
ws.conditional_formatting.add(rob_range,
CellIsRule(operator="equal", formula=['"Critical"'],
fill=PatternFill("solid", fgColor=RED_LT)))
ws.sheet_tab_color = "C0392B" # Red
###############################################################################
# MASTER DATABASE (one row per study, key fields only)
###############################################################################
def build_master(wb):
ws = wb.create_sheet("MASTER_DATABASE")
ws.sheet_view.showGridLines = False
HEADERS = [
"Study ID", "First Author (Year)", "Study Design",
"Country", "N Total", "Biologic Criteria System",
"CA 19-9 Threshold\n(U/mL)", "Anatomic Criteria System",
"% Resectable (R)", "% BR", "% LA",
"Treatment Pathway", "NAT Regimen",
"Median OS — ALL\n(months)",
"OS HR (biologic vs anatomy)",
"OS HR 95%CI Lower", "OS HR 95%CI Upper",
"OS HR Adjusted?",
"Median DFS (months)",
"DFS HR", "DFS HR 95%CI Lower", "DFS HR 95%CI Upper",
"R0 Rate (%)",
"R0 OR (biologic vs anatomy)",
"R0 OR 95%CI Lower", "R0 OR 95%CI Upper",
"Futile Resection\nRate (%)",
"90-day Mortality (%)",
"Major Morbidity\nCD≥III (%)",
"C-statistic\n(biologic)",
"C-statistic\n(anatomy-only)",
"ROBINS-I / RoB\nOverall",
"OS HR Meta-\nAnalysis? (Y/N)",
"R0 OR Meta-\nAnalysis? (Y/N)",
"Notes"
]
WIDTHS = [8,20,16,12,8,22,10,22,10,8,8,18,18,
10,10,8,8,14,10,8,8,8,8,10,8,8,10,10,12,10,10,14,10,10,30]
write_banner(ws,
"MASTER DATABASE — CONSOLIDATED EXTRACTION (ONE ROW PER STUDY)",
"Transfer agreed values here after discrepancy resolution. This sheet feeds the meta-analysis.",
col_span=len(HEADERS))
ws.row_dimensions[3].height = 8
col_headers(ws, 4, HEADERS, colors=[NAVY]*len(HEADERS), widths=WIDTHS)
freeze(ws, "A5")
for i in range(20):
bg = GREY_LT if i % 2 == 0 else WHITE
data_row(ws, 5+i, [""]*len(HEADERS), bg=bg)
# Highlight key meta-analysis columns
for r in range(5, 25):
for c in [15, 16, 17, 24, 25, 26]: # HR and OR columns
cell = ws.cell(row=r, column=c)
cell.fill = fill(TEAL_LT)
add_dropdown(ws, "AH5:AH24", ["Yes", "No"], "Include this study's OS HR in primary meta-analysis?")
add_dropdown(ws, "AI5:AI24", ["Yes", "No"], "Include this study's R0 OR in primary meta-analysis?")
# Legend
ws.cell(row=26, column=1, value="LEGEND:").font = Font(name="Arial", size=9, bold=True)
legend = [
(TEAL_LT, "Key meta-analysis input fields (HR, OR)"),
(GREY_LT, "Alternating row shading"),
(GREEN_LT, "Low risk of bias"),
(GOLD_LT, "Moderate risk of bias"),
(RED_LT, "Serious/Critical risk of bias"),
]
for j, (color, label) in enumerate(legend):
c1 = ws.cell(row=27+j, column=1, value="")
c1.fill = fill(color)
c1.border = border_thin()
c2 = ws.cell(row=27+j, column=2, value=label)
c2.font = body_font(size=9)
c2.border = border_thin()
ws.sheet_tab_color = "27AE60" # Green
###############################################################################
# DISCREPANCY LOG
###############################################################################
def build_discrepancy_log(wb):
ws = wb.create_sheet("DISCREPANCY_LOG")
ws.sheet_view.showGridLines = False
HEADERS = [
"Log\nID", "Study\nID", "Section\n& Field",
"Extractor A\nValue", "Extractor B\nValue",
"Discrepancy\nType",
"Resolution\nMethod",
"Resolved\nValue",
"Resolved\nBy",
"Date\nResolved",
"Notes"
]
WIDTHS = [7, 8, 20, 25, 25, 18, 18, 25, 14, 12, 30]
write_banner(ws,
"DISCREPANCY LOG",
"Record all disagreements between Extractor A and Extractor B here. Update after each consensus meeting.",
col_span=len(HEADERS))
ws.row_dimensions[3].height = 6
col_headers(ws, 4, HEADERS, colors=[NAVY]*len(HEADERS), widths=WIDTHS)
freeze(ws, "A5")
for i in range(15):
bg = GREY_LT if i % 2 == 0 else WHITE
data_row(ws, 5+i, [""]*len(HEADERS), bg=bg)
ws.cell(row=5+i, column=1, value=f"D{i+1:03d}").fill = fill(bg)
add_dropdown(ws, "F5:F34",
["Data value differs", "NR vs. extractable value",
"Estimation from figure vs. NR", "Unit discrepancy",
"Cohort overlap ambiguity", "Definition interpretation differs",
"Other"],
"Select type of discrepancy")
add_dropdown(ws, "G5:G34",
["Consensus (discussion)", "Third-reviewer adjudication",
"Author contact", "Conservative estimate adopted", "Other"],
"Select resolution method")
ws.sheet_tab_color = GOLD
###############################################################################
# AMENDMENTS LOG
###############################################################################
def build_amendments(wb):
ws = wb.create_sheet("AMENDMENTS")
ws.sheet_view.showGridLines = False
HEADERS = [
"Amendment\nID", "Date", "Section / Field\nAffected",
"Original Definition\n/ Instruction",
"Revised Definition\n/ Instruction",
"Reason for\nAmendment",
"Approved\nBy",
"PROSPERO\nUpdate Required?"
]
WIDTHS = [10, 12, 20, 35, 35, 30, 14, 14]
write_banner(ws,
"PROTOCOL AMENDMENTS LOG",
"Document any changes to field definitions, eligibility criteria, or extraction rules made during the pilot phase or formal extraction.",
col_span=len(HEADERS))
ws.row_dimensions[3].height = 6
col_headers(ws, 4, HEADERS, colors=[NAVY]*len(HEADERS), widths=WIDTHS)
freeze(ws, "A5")
for i in range(10):
bg = GREY_LT if i % 2 == 0 else WHITE
data_row(ws, 5+i, [""]*len(HEADERS), bg=bg)
ws.cell(row=5+i, column=1, value=f"A{i+1:03d}").fill = fill(bg)
add_dropdown(ws, "H5:H24", ["Yes — update PROSPERO record", "No — minor clarification only", "To be determined"],
"Does this amendment require a PROSPERO record update?")
ws.sheet_tab_color = "8E44AD" # Purple
###############################################################################
# LISTS (hidden dropdown source sheet)
###############################################################################
def build_lists(wb):
ws = wb.create_sheet("LISTS")
ws.sheet_view.showGridLines = False
data = {
"Study Design": ["RCT","Prospective cohort","Retrospective cohort","Registry-based","Case-control","Other"],
"Yes/No/NR": ["Yes","No","NR","NA"],
"Risk of Bias Levels": ["Low","Moderate","Serious","Critical","NI","NA"],
"NAT Regimens": ["FOLFIRINOX","mFOLFIRINOX","Gemcitabine + nab-paclitaxel (GnP)",
"Gemcitabine monotherapy","Gemcitabine + erlotinib",
"FOLFIRINOX + radiotherapy","GnP + radiotherapy","SBRT alone","Other","NR","NA"],
"Anatomic Systems": ["NCCN","ISGPS 2017","AHPBA/SSO/SSAT","MD Anderson","Institutional","Multiple","NR","Other"],
"Biologic Systems": ["JSHBPS BR-B (Oba 2022)","ABC staging (TAPS 2024)","CA 19-9 alone",
"CA 19-9 + PET","CA 19-9 + LN","CA 19-9 + ECOG","Multi-biologic composite","Other","NR"],
"CA19-9 Threshold Basis": ["JSHBPS (500 U/mL)","Data-driven ROC/Youden","Institutional protocol",
"Prior publication","Other","NR"],
"Funding Sources": ["Industry","Government/NIH/NHMRC/MRC","Institutional","Charity/Foundation",
"No funding stated","No external funding","Multiple sources","NR"],
}
ws.cell(row=1, column=1, value="DROPDOWN OPTION LISTS (Reference Only — Do Not Edit)").font = Font(name="Arial", size=10, bold=True, color=NAVY)
col = 1
for header, items in data.items():
ws.cell(row=2, column=col, value=header).font = hdr_font(size=9, color=WHITE)
ws.cell(row=2, column=col).fill = fill(NAVY)
ws.cell(row=2, column=col).alignment = wrap_align("center")
ws.column_dimensions[get_column_letter(col)].width = 28
for r, item in enumerate(items, 3):
c = ws.cell(row=r, column=col, value=item)
c.font = body_font(size=9)
c.border = border_thin()
col += 1
ws.sheet_state = "hidden"
###############################################################################
# BUILD WORKBOOK
###############################################################################
wb = Workbook()
# Remove default sheet
if "Sheet" in wb.sheetnames:
del wb["Sheet"]
build_instructions(wb)
build_section_A(wb)
build_section_B(wb)
build_section_C(wb)
build_section_D(wb)
build_section_E(wb)
build_section_F(wb)
build_section_G(wb)
build_section_H(wb)
build_master(wb)
build_discrepancy_log(wb)
build_amendments(wb)
build_lists(wb)
OUTPUT = "/home/daytona/workspace/pdac-biologic-resectability-protocol/PDAC_Biologic_Resectability_DataExtraction_Form_v1.0.xlsx"
wb.save(OUTPUT)
print(f"SUCCESS: Workbook saved to {OUTPUT}")
cd /home/daytona/workspace/pdac-biologic-resectability-protocol && python3 build_extraction_form.py 2>&1
.../pdac-biologic-resectability-protocol/build_extraction_form.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/pdac-biologic-resectability-protocol/build_extraction_form.py'
cd /home/daytona/workspace/pdac-biologic-resectability-protocol && python3 build_extraction_form.py 2>&1