Spaces:
Running
Running
| """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." | |
| ), | |
| } | |