{
 "use_case": "csr-number-verifier",
 "title": "CSR number QC: a fictional phase 3 report with six seeded number errors, the same report clean, and the report as a PDF",
 "about": "ZEN-395, an invented phase 3 psoriasis trial of an invented drug (zenavotide) with placebo and two doses: sections 10-12 of the narrative and eleven tables (nine TLFs, two in-text tables), plus the same TLFs at an earlier (interim) data cut. Seeded: transposed digits (75.5 for 57.5), a last-digit rounding slip (71.8% for 71.9%), a wrong N (313 for 317), a percentage on the wrong denominator (2.2% for 3.7%), the other arm’s count (72 for 14) and a percentage left over from the interim cut (53.5% for 58.3%), and one stale cell in in-text Table 11-1. The run must flag each seeded number (the other-arm count through its percentage, which then does not compute), keep the clean report clean, sign a record that verifies and fails when changed, and read the same report from a PDF.",
 "licence": "Synthetic: the drug, sponsor, trial and every number are invented (decosa_api/verticals/csr/synth.py, seed 72; scripts/csr_samples.py). Part of decosa-api, AGPL-3.0-or-later.",
 "inputs": [
  {
   "file": "inputs/report.json",
   "what": "The seeded report: narrative paragraphs, tables and the interim-cut tables, as JSON."
  },
  {
   "file": "inputs/clean-report.json",
   "what": "The same report with no errors."
  },
  {
   "file": "inputs/report.pdf",
   "what": "The seeded report printed as a 12-page born-digital PDF."
  }
 ],
 "steps": [
  {
   "id": "check",
   "method": "POST",
   "path": "/csr/verify",
   "body": {
    "csr": {
     "$file": "inputs/report.json"
    },
    "synthetic": true
   },
   "timeout": 600
  },
  {
   "id": "verify",
   "method": "POST",
   "path": "/record/verify",
   "auth": false,
   "body": {
    "record": {
     "$ref": "check.record"
    }
   }
  },
  {
   "id": "tampered",
   "method": "POST",
   "path": "/record/verify",
   "auth": false,
   "body": {
    "record": {
     "$ref": "check.record"
    }
   },
   "tamper": {
    "at": "record.statement.counts.flags",
    "set": 0
   }
  },
  {
   "id": "clean",
   "method": "POST",
   "path": "/csr/verify",
   "body": {
    "csr": {
     "$file": "inputs/clean-report.json"
    },
    "synthetic": true
   },
   "timeout": 600
  },
  {
   "id": "pdf",
   "method": "POST",
   "path": "/csr/verify",
   "body": {
    "file_b64": {
     "$b64": "inputs/report.pdf"
    },
    "media_type": "application/pdf",
    "synthetic": true
   },
   "timeout": 900
  }
 ],
 "checks": [
  {
   "says": "the transposed mean age (75.5 for 57.5) is flagged",
   "path": "check.results[section=11.2,text=75.5][0].status",
   "in": [
    "mismatch",
    "untraceable",
    "derived_wrong"
   ]
  },
  {
   "says": "the rounding slip in disposition (71.8% for 71.9%) is flagged",
   "path": "check.results[section=10.1,text=71.8%][0].status",
   "in": [
    "mismatch",
    "untraceable",
    "derived_wrong"
   ]
  },
  {
   "says": "the wrong N in the analysis sets (313 for 317) is flagged",
   "path": "check.results[section=11.1,text=313][0].status",
   "in": [
    "mismatch",
    "untraceable",
    "derived_wrong"
   ]
  },
  {
   "says": "the percentage left over from the interim cut (53.5% for 58.3%) is flagged",
   "path": "check.results[section=12.2.1,text=53.5%][0].status",
   "in": [
    "mismatch",
    "untraceable",
    "derived_wrong"
   ]
  },
  {
   "says": "the percentage on the wrong denominator (2.2% for 3.7%) is flagged",
   "path": "check.results[section=12.3.1,text=2.2%][0].status",
   "in": [
    "mismatch",
    "untraceable",
    "derived_wrong"
   ]
  },
  {
   "says": "the other-arm count in 11.4.3 is caught: its percentage no longer computes",
   "path": "check.results[section=11.4.3,status=derived_wrong]",
   "count_min": 1
  },
  {
   "says": "the stale cell in in-text Table 11-1 is found against its source TLF 14.2.1",
   "path": "check.table_mismatches[table=11-1,source_table=14.2.1]",
   "count_min": 1
  },
  {
   "says": "at least 70 numbers are checked",
   "path": "check.counts.numbers_checked",
   "min": 70
  },
  {
   "says": "the report says who decides",
   "path": "check.markdown",
   "contains": "Who decides"
  },
  {
   "says": "the signed record verifies",
   "path": "verify.ok",
   "equals": true
  },
  {
   "says": "the record fails once its flag count is changed",
   "path": "tampered.ok",
   "equals": false
  },
  {
   "says": "the clean report comes back with at most one flag",
   "path": "clean.counts.flags",
   "max": 1
  },
  {
   "says": "the PDF is read into at least ten of its eleven tables",
   "path": "pdf.read.tables",
   "count_min": 10
  },
  {
   "says": "reading the PDF, at least four of the seeded numbers are flagged",
   "path": "pdf.counts.flags",
   "min": 4
  },
  {
   "says": "every model call has a signed receipt (the document reader’s own receipt, dr-..., is a different format and is checked by the reader)",
   "receipts": "signed",
   "min": 20,
   "ignore_prefix": [
    "dr-"
   ]
  }
 ]
}