syntheogenesis / dee /core /crispr_methods.py
github-actions[bot]
Deploy 2565f24
7b284c7
Raw
History Blame Contribute Delete
12.8 kB
"""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."
),
}