"""Provenance for every scored column the CRISPR table shows — the glass-box disclosure layer. dee/core/crispr.py has always been scrupulously honest in its own comments about what each number is (and is not): the on-target score is Doench-INSPIRED rather than Rule Set 2, the off-target CFD is input-only rather than genome-wide, the indel model is a heuristic standing in for inDelphi. None of that reached the user, so a scientist reviewing the tool had to ask for the composite's weights and flag the missing cell-type caveat — both of which the code already knew. This module lifts those facts out of the comments and makes them a first-class, testable, servable artifact (GET /api/crispr/methods) so the UI can show, per column, exactly how the number was made and where it stops being trustworthy. Rule for editing this file: it must describe what dee/core/crispr.py ACTUALLY does. If the algorithm changes, this changes in the same commit. Never describe an aspiration here. """ from typing import Any, Dict, List # Keys match the CRISPR result-table column labels the user sees. METHODS: Dict[str, Dict[str, Any]] = { "composite": { "label": "Composite", "what": "The column the table sorts by — one number balancing " "how well a guide should cut against how uniquely it targets.", # Kept in sync with dee.core.crispr.COMPOSITE_OFFTARGET_WEIGHT by # test_composite_weight_is_disclosed_with_its_real_value, because this # entry previously claimed a formula the code did not use. "formula": "composite = on_target × (1 − 0.6 × self_off)", "basis": "An efficacy estimate scaled down by the specificity " "penalty. The 0.6 is the only tuning constant in the whole " "CRISPR ranking and it is our judgement call, not a " "published value: it means a guide whose spacer is " "duplicated exactly elsewhere in your input keeps 40% of " "its on-target score instead of dropping to zero, because a " "repeat inside a pasted region is often a second legitimate " "copy of your target rather than a disqualifying " "off-target. Every guide carries the weight in its own row " "(composite_offtarget_weight), so the arithmetic is " "checkable per guide.", "limits": "The 0.6 is uncalibrated — no measured cleavage dataset was " "used to fit it, and a different weight would reorder guides " "whose self-off scores differ. Sort by On-target and Self-off " "separately if you want to apply your own trade-off. It also " "inherits every limit of its two inputs — in particular the " "specificity term only sees the sequence you pasted.", "citations": [], }, "on_target": { "label": "On-target", "what": "Predicted cleavage efficiency from the spacer sequence alone.", "formula": "Doench-2016 per-position nucleotide + dinucleotide weights, " "summed and clamped, mapped to [0, 1].", "basis": "Doench-INSPIRED, not Doench Rule Set 2 itself. We implement " "the strongest sequence-only features from the 2016 paper but " "skip its gradient-boosted ML layer. The column is labelled " "“TuringDNA on-target” precisely so we never claim to be RS2.", "limits": "Correlates roughly 0.55–0.65 with measured cleavage, versus " "about 0.70 for the full RS2 model — within ~80% of " "state-of-the-art, and sequence-only (it knows nothing about " "chromatin, expression, or your delivery method).", "citations": ["Doench et al. 2016, Nat Biotechnol (Rule Set 2)"], }, "self_off": { "label": "Self-off", "what": "Worst-case similarity between this guide and any OTHER " "candidate site, as a specificity warning.", "formula": "Maximum CFD score against every other PAM-adjacent site in " "the submitted sequence (0 = unique here, 1 = exact repeat).", "basis": "The exact 20×4 mismatch-penalty matrix from Doench 2016 " "Supplementary Table 19 — the industry standard for " "off-target ranking.", "limits": "SCOPE: this column searches only the sequence you pasted. " "For a real off-target check, pick an organism — the engine " "then screens your top guides against the genome and fills " "the “Genome off” column (see below).", "citations": ["Doench et al. 2016, Nat Biotechnol, Suppl. Table 19 (CFD)"], }, "genome_off": { "label": "Genome off", "what": "Real off-target search against the organism's genome — the " "column that tells you whether a guide cuts somewhere it " "shouldn't. Runs when you choose an organism.", "formula": "Seed-indexed search (8 nt PAM-proximal seed, ≤1 seed and " "≤4 total mismatches), each candidate CFD-scored; hits " "above CFD 0.05 are kept and ranked.", "basis": "Same Doench 2016 CFD matrix used for Self-off, applied " "across the indexed genome rather than just your input. Your " "guide never leaves the engine — only the public reference " "genome is downloaded, and it is cached and reused.", "limits": "Two scope limits. (1) COVERAGE: E. coli, S. cerevisiae, " "C. elegans and D. melanogaster are indexed over the " "COMPLETE genome, so an off-target anywhere is found. Human " "(GRCh38) and mouse (GRCm39) are indexed over CODING " "SEQUENCE ONLY — off-targets in introns and intergenic DNA " "are not seen there, and a dedicated whole-genome tool is " "still the right call for that work. The reason is memory, " "not preference: the seed index costs on the order of " "100 bytes per site (measured), and a whole human genome " "carries roughly 390 million PAM sites — tens of gigabytes, " "well beyond the container. (2) DEPTH: only the top-ranked guides are " "screened (the ones you would realistically order), not " "every candidate; the run tells you how many were checked.", "citations": [ "Doench et al. 2016, Nat Biotechnol, Suppl. Table 19 (CFD)", "Ensembl release 112 (GRCh38 / GRCm39 CDS); NCBI NC_000913.3 (E. coli K-12)", ], }, "ko_score": { "label": "KO score", "what": "Probability the cut produces a true loss-of-function knockout.", "formula": "ko ≈ 0.67 × cut-position bias (linear 1.0 → 0.3 across the input)", "basis": "Two empirical factors: a ~67% base rate that Cas9 indels are " "non-multiple-of-3 (true frameshifts), and a position term " "favouring earlier cuts, which truncate more of the protein.", "limits": "Approximate: without CDS exon coordinates it cannot tell a " "constitutive exon from a skipped one, nor account for " "nonsense-mediated decay escape.", "citations": ["Allen et al. 2018, Nat Biotechnol"], }, "indels": { "label": "Top indel · FS % · Dominance", "what": "The most likely repair outcomes at this cut: the single most " "probable indel, the share of outcomes that frameshift, and how " "dominant that top outcome is (40%+ = clean, <25% = messy).", "formula": "MMEJ deletions weighted MH_length × exp(−Δ/10), combined " "with a ~32% templated +1 insertion class; FS % = share of " "predicted outcomes whose length is not a multiple of 3.", "basis": "Microhomology-mediated end-joining modelled per Bae 2014, plus " "the templated single-base insertion class Cas9 is known to " "produce. These ARE per-guide, sequence-driven predictions — " "not a fixed average.", # The repair context these numbers assume, stated as a first-class # field so the UI can print it next to FS % rather than leaving the # reader to infer it. Everything here is read off the algorithm: # crispr._predict_indels models MMEJ deletions + a fixed 32% templated # +1 insertion class and has no donor, cell-type or organism argument. "assumes": "Template-free end-joining repair (MMEJ + classical NHEJ) " "of a blunt Cas9 double-strand break, in a cell with intact " "repair machinery and no HDR donor supplied. Supply a repair " "template, use a nickase or a base editor, or work in a " "repair-deficient background, and these outcomes do not " "apply.", "limits": "Two real limits. (1) HEURISTIC: this is not the inDelphi " "neural network — it reproduces roughly 80% of that model's " "rank-ordering, not its calibrated frequencies. (2) " "CELL-TYPE-AGNOSTIC: its parameters are fixed constants from " "published averages, so the same guide returns the same FS % " "whether you work in mESC, U2OS, HEK293 or primary cells — " "yet the MMEJ/NHEJ repair balance genuinely differs between " "them. Treat these as relative rankings between guides, not " "absolute rates for your cell line.", "citations": [ "Bae et al. 2014, Nat Methods (microhomology / MMEJ)", "van Overbeek et al. 2016, Mol Cell; Lemos et al. 2018, PNAS (+1 insertions)", "Shen et al. 2018, Nature (inDelphi — the model this approximates)", ], }, } # Order the UI should present them in (matches the table's column order). METHOD_ORDER: List[str] = ["composite", "on_target", "self_off", "genome_off", "ko_score", "indels"] def genome_scopes() -> Dict[str, Dict[str, Any]]: """What the genome off-target search actually covers, per organism. Read straight off ``offtarget.GENOME_SOURCES`` rather than restated here: the results panel prints this next to the off-target numbers, and a hand-copied list is exactly how a panel ends up claiming a whole-genome screen for an organism that is indexed over coding sequence only. Adding an organism to the registry adds it here with no second edit. """ try: from dee.core.offtarget import GENOME_SOURCES except Exception: # noqa: BLE001 — provenance must never break the route return {} out: Dict[str, Dict[str, Any]] = {} for key, src in GENOME_SOURCES.items(): scope = str(src.get("scope", "")) complete = scope == "full genome" out[key] = { "name": str(src.get("name", key)), "scope": scope, "complete": complete, # One sentence the UI can print verbatim, so the wording lives # next to the registry that decides whether it is true. "note": ( "Complete genome indexed — an off-target anywhere is found." if complete else "CODING SEQUENCE ONLY. Off-targets in introns, intergenic and " "regulatory DNA are outside this index and will not appear. " "Use a whole-genome tool if your application needs them." ), } return out def methods_payload() -> Dict[str, Any]: """Serializable provenance block for GET /api/crispr/methods.""" return { "order": list(METHOD_ORDER), "methods": {k: dict(METHODS[k]) for k in METHOD_ORDER}, "genome_scopes": genome_scopes(), "summary": ( "Every score here is computed from published, sequence-based " "methods — no black box, and exactly one tuning constant (the " "0.6 off-target weight in Composite, which is our judgement " "call). Two scope limits matter most: for human and mouse the " "genome off-target search covers coding sequence only, and indel " "predictions assume template-free end-joining repair with fixed " "constants — they are not tuned to your cell type." ), }