github-actions[bot] commited on
Commit
ea9cb88
·
1 Parent(s): 3f7edda

Deploy 53117c1

Browse files

Validation corpus, a specificity handoff, and the design record

Source: https://github.com/WINTER4000/turingDNA/commit/53117c103469b28cb1ec444c32e955243a998aa8

dee/core/compiler.py CHANGED
@@ -430,6 +430,32 @@ CAPABILITY_NOTES = {
430
  "GUIDE-seq, CIRCLE-seq or an equivalent empirical assay."),
431
  }
432
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  SCOPE = {
434
  "application": "Somatic therapeutic design only. Germline and embryo "
435
  "editing are out of scope and refused.",
@@ -563,8 +589,19 @@ def compile_report(wt_allele: str, patient_allele: str, *,
563
  # ── specificity ─────────────────────────────────────────────────────
564
  # Always carries its caveat, even when it runs: a pass that reports "ok"
565
  # on a coding-sequence-only index would read as a clean bill of health.
 
 
 
 
 
 
 
 
 
 
 
566
  add("specificity", "warn" if can_check_specificity else "unavailable",
567
- CAPABILITY_NOTES["specificity"])
568
 
569
  # ── emit ────────────────────────────────────────────────────────────
570
  add("emit", "ok",
@@ -887,3 +924,145 @@ def plan_base_edit_strategies(window: str, target_offset: int,
887
  "choosing — a silent or intronic bystander in a regulatory "
888
  "element is exactly the case nothing else checks."))
889
  return out[:max_results], diags
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  "GUIDE-seq, CIRCLE-seq or an equivalent empirical assay."),
431
  }
432
 
433
+ # What the specificity pass hands over when it cannot clear a guide itself.
434
+ # A refusal that just stops is a shrug; a refusal that says exactly what to run
435
+ # next is a handoff. These are the searches this deployment is NOT doing, named
436
+ # precisely enough to be executed by someone who has the tools.
437
+ SPECIFICITY_HANDOFF = {
438
+ "in_silico": [
439
+ ("Cas-OFFinder / CRISPRme", "genome-wide, mismatch- and bulge-tolerant "
440
+ "search over the WHOLE assembly, not only coding sequence. CRISPRme "
441
+ "additionally accounts for common variants, which matters when the "
442
+ "patient's own genome differs from the reference at an off-target."),
443
+ ],
444
+ "empirical": [
445
+ ("GUIDE-seq", "unbiased, cell-based detection of double-strand-break "
446
+ "capture sites."),
447
+ ("CIRCLE-seq / CHANGE-seq", "in vitro, high-sensitivity nomination of "
448
+ "candidate off-targets from purified genomic DNA."),
449
+ ("Targeted amplicon sequencing", "deep sequencing of the nominated "
450
+ "sites in the actual therapeutic cell product."),
451
+ ],
452
+ "note": (
453
+ "For a base editor the relevant off-target question is not only where "
454
+ "the nuclease cuts. Cas-independent deamination is not detected by a "
455
+ "DSB-capture assay at all, so a clean GUIDE-seq result does not, on "
456
+ "its own, clear a base editor."),
457
+ }
458
+
459
  SCOPE = {
460
  "application": "Somatic therapeutic design only. Germline and embryo "
461
  "editing are out of scope and refused.",
 
589
  # ── specificity ─────────────────────────────────────────────────────
590
  # Always carries its caveat, even when it runs: a pass that reports "ok"
591
  # on a coding-sequence-only index would read as a clean bill of health.
592
+ #
593
+ # And it now HANDS OFF. A refusal that just stops is a shrug; one that
594
+ # names the exact searches this deployment is not doing is a work order
595
+ # somebody can act on. That difference is most of the value of admitting
596
+ # the gap in the first place.
597
+ spec_detail = CAPABILITY_NOTES["specificity"] + " Not cleared here — run: "
598
+ spec_detail += "; ".join(
599
+ f"{name} ({why.split('.')[0].lower()})"
600
+ for name, why in (SPECIFICITY_HANDOFF["in_silico"]
601
+ + SPECIFICITY_HANDOFF["empirical"]))
602
+ spec_detail += ". " + SPECIFICITY_HANDOFF["note"]
603
  add("specificity", "warn" if can_check_specificity else "unavailable",
604
+ spec_detail)
605
 
606
  # ── emit ────────────────────────────────────────────────────────────
607
  add("emit", "ok",
 
924
  "choosing — a silent or intronic bystander in a regulatory "
925
  "element is exactly the case nothing else checks."))
926
  return out[:max_results], diags
927
+
928
+
929
+ # ═══════════════════════════════════════════════════════════════════════
930
+ # The design record
931
+ # ═══════════════════════════════════════════════════════════════════════
932
+ # In a platform-IND world the deliverable is not a therapy — it is a design
933
+ # and its complete justification, in a form a reviewer can audit and a third
934
+ # party can reproduce. Today that document is assembled by hand, per patient,
935
+ # by scientists. This emits it.
936
+ #
937
+ # Deterministic by construction: same inputs, byte-identical output. Nothing
938
+ # here reads a clock or a random source, because a record that changes between
939
+ # runs cannot be diffed, and a record that cannot be diffed cannot be audited.
940
+ # The caller stamps time and provenance if it wants them.
941
+
942
+ RECORD_VERSION = "1"
943
+
944
+
945
+ def design_record(report: "CompileReport", *, variant: str = "",
946
+ resolved: Optional[Dict[str, object]] = None,
947
+ provenance: Optional[Dict[str, str]] = None) -> str:
948
+ """Render a compile report as a plain-text design record.
949
+
950
+ Plain text on purpose: it diffs, it pastes into an email or a lab
951
+ notebook, it survives every tool in the chain, and nothing about it can
952
+ silently re-render differently later.
953
+ """
954
+ L: List[str] = []
955
+ add = L.append
956
+
957
+ add("TURINGDNA THERAPEUTIC DESIGN RECORD")
958
+ add(f"record-version {RECORD_VERSION}")
959
+ add("=" * 72)
960
+ add("")
961
+ add("SCOPE")
962
+ for k in ("application", "status", "silent_on"):
963
+ add(f" {k:<12} {report.scope.get(k, '')}")
964
+ add("")
965
+
966
+ if variant or resolved:
967
+ add("VARIANT")
968
+ if variant:
969
+ add(f" notation {variant}")
970
+ for key, label in (("gene", "gene"), ("transcript", "transcript"),
971
+ ("chrom", "chromosome"), ("position", "position"),
972
+ ("assembly", "assembly"), ("consequence", "consequence"),
973
+ ("orientation", "allele orientation"),
974
+ ("source", "resolved by")):
975
+ val = (resolved or {}).get(key)
976
+ if val:
977
+ add(f" {label:<12} {val}")
978
+ if resolved:
979
+ add(f" {'alleles':<12} wild-type {resolved.get('wt_base')} -> "
980
+ f"patient {resolved.get('patient_base')} (forward strand)")
981
+ add("")
982
+
983
+ corr = report.lesion.correction if report.lesion else None
984
+ if report.lesion:
985
+ add("LESION")
986
+ add(f" kind {report.lesion.kind}")
987
+ add(f" size {report.lesion.size}")
988
+ add(f" route {report.lesion.route}")
989
+ if corr:
990
+ add(f" correction {corr.sense_change} on the sense strand")
991
+ add(f" chemistry {corr.editor_family} writes {corr.editor_change} "
992
+ f"on the {corr.strand} strand")
993
+ add("")
994
+
995
+ add("PASSES")
996
+ for p in report.passes:
997
+ add(f" [{p.status:<11}] {p.title}")
998
+ if p.detail:
999
+ for line in _wrap(p.detail, 68):
1000
+ add(f" {line}")
1001
+ for d in p.diagnostics:
1002
+ add(f" - {d.level.upper()} {d.code}: {d.message}")
1003
+ if d.remedy:
1004
+ for line in _wrap(f"remedy: {d.remedy}", 62):
1005
+ add(f" {line}")
1006
+ add("")
1007
+
1008
+ if report.strategies:
1009
+ add("STRATEGIES")
1010
+ for s in report.strategies:
1011
+ add(f" #{s.rank} {s.editor_id} on the "
1012
+ f"{'sense' if s.strand == '+' else 'antisense'} strand")
1013
+ add(f" spacer {s.spacer}")
1014
+ add(f" PAM {s.pam}")
1015
+ add(f" forward position {s.position}")
1016
+ add(f" target at spacer {s.target_spacer_pos} "
1017
+ f"(editor activity {s.target_activity})")
1018
+ add(f" on-target score {s.on_target_score}")
1019
+ if not s.bystanders:
1020
+ add(" bystanders none in this guide's window")
1021
+ else:
1022
+ add(f" bystanders {len(s.bystanders)}")
1023
+ for b in s.bystanders:
1024
+ d = ("not scored" if b.delta_ll is None
1025
+ else f"delta log-likelihood {b.delta_ll:+.4f}")
1026
+ add(f" {b.label or (b.from_base + '>' + b.to_base)}"
1027
+ f" spacer pos {b.spacer_pos} {d}")
1028
+ add("")
1029
+
1030
+ gaps = report.incomplete_because
1031
+ add("COMPLETENESS")
1032
+ add(f" compiled {'yes' if report.compiled else 'NO'}")
1033
+ if gaps:
1034
+ add(" NOT ESTABLISHED by this record:")
1035
+ for g in gaps:
1036
+ add(f" - {g}")
1037
+ else:
1038
+ add(" every pass produced a result")
1039
+ add("")
1040
+
1041
+ if provenance:
1042
+ add("PROVENANCE")
1043
+ for k in sorted(provenance):
1044
+ add(f" {k:<12} {provenance[k]}")
1045
+ add("")
1046
+
1047
+ add("-" * 72)
1048
+ add("This is a design record, not a clinical decision and not an approval.")
1049
+ add("Predicted specificity is not measured specificity. Model-derived")
1050
+ add("judgements are zero-shot and have no validated relationship to")
1051
+ add("clinical outcome. Nothing here substitutes for the preclinical")
1052
+ add("programme, and nothing here should reach a patient on its own.")
1053
+ return "\n".join(L)
1054
+
1055
+
1056
+ def _wrap(text: str, width: int) -> List[str]:
1057
+ """Tiny greedy wrapper — textwrap would do, but this keeps the record's
1058
+ formatting identical across Python versions."""
1059
+ words, line, out = str(text).split(), "", []
1060
+ for w in words:
1061
+ if line and len(line) + 1 + len(w) > width:
1062
+ out.append(line)
1063
+ line = w
1064
+ else:
1065
+ line = f"{line} {w}".strip()
1066
+ if line:
1067
+ out.append(line)
1068
+ return out
dee/core/compiler_validation.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Check the compiler against landmark editing designs.
2
+
3
+ The question anyone serious asks within two minutes is "how do you know it's
4
+ right?", and "it is deterministic and well tested" is an engineering answer to
5
+ a scientific question. This is the scientific answer: run the compiler against
6
+ designs the field has already settled and show it arrives at the same verdict.
7
+
8
+ THE CLAIM THIS SUPPORTS, STATED PRECISELY
9
+ -----------------------------------------
10
+ It reproduces **known-correct decisions**. It does NOT discover new biology,
11
+ and the two must never be blurred — the first is defensible and the second
12
+ would be a lie a reviewer could take apart in one question.
13
+
14
+ TWO KINDS OF CASE, COUNTED SEPARATELY
15
+ -------------------------------------
16
+ `basis="chemistry"` the expected verdict follows from the genetic code and
17
+ the two base-editor chemistries. Anyone can re-derive
18
+ it, so it needs no citation and counts as validation.
19
+
20
+ `basis="literature"` the expectation is a claim about what a real programme
21
+ actually did. It requires a citation. Without one the
22
+ case is reported UNVERIFIED and does **not** count as a
23
+ pass — it is a named gap, which is more useful than a
24
+ silent absence.
25
+
26
+ That split is the honesty mechanism. "12 of 12 validated" means nothing if
27
+ half were asserted from memory; here the report always carries both numbers
28
+ and the unverified ones by name.
29
+
30
+ Pure logic: no network, no GPU, no model. It runs in CI on every commit, so a
31
+ change that breaks a landmark verdict fails the build rather than being
32
+ noticed in a demo.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import json
38
+ import pathlib
39
+ from dataclasses import dataclass, field
40
+ from typing import Any, Dict, List, Optional
41
+
42
+ from dee.core import compiler as _compiler
43
+
44
+ __all__ = ["CaseResult", "ValidationReport", "load_cases", "run_validation",
45
+ "report_to_dict"]
46
+
47
+ CORPUS_PATH = (pathlib.Path(__file__).resolve().parent.parent
48
+ / "data" / "landmark_designs.json")
49
+
50
+
51
+ @dataclass
52
+ class CaseResult:
53
+ case_id: str
54
+ name: str
55
+ basis: str
56
+ status: str # "pass" | "fail" | "unverified"
57
+ expected: Dict[str, Any] = field(default_factory=dict)
58
+ observed: Dict[str, Any] = field(default_factory=dict)
59
+ mismatches: List[str] = field(default_factory=list)
60
+ why: str = ""
61
+ citation: str = ""
62
+
63
+
64
+ @dataclass
65
+ class ValidationReport:
66
+ results: List[CaseResult]
67
+
68
+ @property
69
+ def chemistry_total(self) -> int:
70
+ return sum(1 for r in self.results if r.basis == "chemistry")
71
+
72
+ @property
73
+ def chemistry_passed(self) -> int:
74
+ return sum(1 for r in self.results
75
+ if r.basis == "chemistry" and r.status == "pass")
76
+
77
+ @property
78
+ def unverified(self) -> List[str]:
79
+ return [r.name for r in self.results if r.status == "unverified"]
80
+
81
+ @property
82
+ def failures(self) -> List[CaseResult]:
83
+ return [r for r in self.results if r.status == "fail"]
84
+
85
+ @property
86
+ def headline(self) -> str:
87
+ """One sentence that cannot be misread as more than it is."""
88
+ n, tot = self.chemistry_passed, self.chemistry_total
89
+ base = (f"{n} of {tot} landmark decisions reproduced "
90
+ "(derivable from the genetic code and base-editor chemistry)")
91
+ if self.unverified:
92
+ base += (f"; {len(self.unverified)} further case(s) awaiting a "
93
+ "citation and not counted")
94
+ return base + "."
95
+
96
+
97
+ def load_cases(path: Optional[pathlib.Path] = None) -> List[Dict[str, Any]]:
98
+ p = path or CORPUS_PATH
99
+ if not p.exists():
100
+ return []
101
+ data = json.loads(p.read_text())
102
+ return [c for c in (data.get("cases") or []) if isinstance(c, dict)]
103
+
104
+
105
+ def _expand_allele(value: Any) -> str:
106
+ """Alleles may be a literal string or {"repeat": "A", "times": 400}.
107
+
108
+ The compact form exists because a 400-base deletion is a legitimate case
109
+ and pasting 400 characters into JSON makes the corpus unreadable. It is
110
+ NOT a shorthand the compiler understands — an earlier corpus wrote "A400"
111
+ expecting it to mean 400 A's, and the compiler correctly read it as an
112
+ allele containing a digit and refused. The harness caught that, which is
113
+ the harness working.
114
+ """
115
+ if isinstance(value, dict):
116
+ base = str(value.get("repeat") or "")
117
+ times = int(value.get("times") or 0)
118
+ return base * times
119
+ return str(value or "")
120
+
121
+
122
+ def _observe(case: Dict[str, Any]) -> Dict[str, Any]:
123
+ """Run the compiler on a case and flatten what it decided."""
124
+ call = _compiler.classify_lesion(_expand_allele(case.get("wt_base")),
125
+ _expand_allele(case.get("patient_base")))
126
+ corr = call.correction
127
+ return {
128
+ "compiles": call.compiles,
129
+ "route": call.route,
130
+ "editor_family": corr.editor_family if corr else None,
131
+ "strand": corr.strand if corr else None,
132
+ "diagnostics": [d.code for d in call.diagnostics],
133
+ }
134
+
135
+
136
+ def _compare(expected: Dict[str, Any], observed: Dict[str, Any]) -> List[str]:
137
+ """Only the keys the case actually asserts. A case that says nothing about
138
+ strand is not failed for the strand."""
139
+ out: List[str] = []
140
+ for key in ("compiles", "route", "editor_family", "strand"):
141
+ if key in expected and observed.get(key) != expected[key]:
142
+ out.append(f"{key}: expected {expected[key]!r}, "
143
+ f"got {observed.get(key)!r}")
144
+ want_diag = expected.get("diagnostic")
145
+ if want_diag and want_diag not in (observed.get("diagnostics") or []):
146
+ out.append(f"diagnostic {want_diag!r} not raised "
147
+ f"(raised {observed.get('diagnostics')!r})")
148
+ return out
149
+
150
+
151
+ def run_validation(cases: Optional[List[Dict[str, Any]]] = None
152
+ ) -> ValidationReport:
153
+ """Every case, with literature cases short-circuited unless cited."""
154
+ results: List[CaseResult] = []
155
+ for case in (cases if cases is not None else load_cases()):
156
+ basis = str(case.get("basis") or "literature")
157
+ why = case.get("why")
158
+ why_text = " ".join(why) if isinstance(why, list) else str(why or "")
159
+ citation = str(case.get("citation") or "").strip()
160
+ expected = case.get("expect") or {}
161
+
162
+ # A literature claim without a citation is not evidence, and must not
163
+ # be run as though it were — an uncited expectation is just a guess
164
+ # with a filename.
165
+ if basis == "literature" and not citation:
166
+ results.append(CaseResult(
167
+ case_id=str(case.get("id") or ""), name=str(case.get("name") or ""),
168
+ basis=basis, status="unverified", expected=expected,
169
+ why=why_text, citation=citation))
170
+ continue
171
+
172
+ if not expected:
173
+ results.append(CaseResult(
174
+ case_id=str(case.get("id") or ""), name=str(case.get("name") or ""),
175
+ basis=basis, status="unverified", expected={},
176
+ why=why_text or "No expectation recorded.", citation=citation))
177
+ continue
178
+
179
+ observed = _observe(case)
180
+ mismatches = _compare(expected, observed)
181
+ results.append(CaseResult(
182
+ case_id=str(case.get("id") or ""), name=str(case.get("name") or ""),
183
+ basis=basis, status="pass" if not mismatches else "fail",
184
+ expected=expected, observed=observed, mismatches=mismatches,
185
+ why=why_text, citation=citation))
186
+ return ValidationReport(results)
187
+
188
+
189
+ def report_to_dict(report: ValidationReport) -> Dict[str, Any]:
190
+ return {
191
+ "headline": report.headline,
192
+ "chemistry_passed": report.chemistry_passed,
193
+ "chemistry_total": report.chemistry_total,
194
+ "unverified": report.unverified,
195
+ "all_passed": not report.failures,
196
+ "cases": [
197
+ {"id": r.case_id, "name": r.name, "basis": r.basis,
198
+ "status": r.status, "expected": r.expected, "observed": r.observed,
199
+ "mismatches": r.mismatches, "why": r.why, "citation": r.citation}
200
+ for r in report.results
201
+ ],
202
+ }
dee/data/landmark_designs.json ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_readme": [
3
+ "Landmark editing designs the compiler is checked against.",
4
+ "",
5
+ "TWO KINDS OF ENTRY, and the difference is the whole point:",
6
+ "",
7
+ " basis = 'chemistry' the expected verdict follows from the genetic code",
8
+ " and the two base-editor chemistries. It can be",
9
+ " asserted without citing anyone, because anyone can",
10
+ " re-derive it. These COUNT as validation.",
11
+ "",
12
+ " basis = 'literature' the expected verdict is a claim about what a real",
13
+ " published or approved programme actually did. It",
14
+ " needs a citation. Until `citation` is filled in,",
15
+ " the runner reports the case as UNVERIFIED and it",
16
+ " does NOT count as a pass.",
17
+ "",
18
+ "An unverified entry is a to-do with a name, not a failure. The report",
19
+ "shows both numbers separately so 'validated' can never quietly include",
20
+ "cases nobody checked.",
21
+ "",
22
+ "Adding a literature case: fill `citation` with something a reader can",
23
+ "look up (DOI, PMID, or the trial/product name), and set the expectation",
24
+ "from the paper — never from memory."
25
+ ],
26
+
27
+ "cases": [
28
+ {
29
+ "id": "hbs-sickle-hbb",
30
+ "name": "HbS (sickle cell) — HBB Glu6Val",
31
+ "variant": "NM_000518.5:c.20A>T",
32
+ "locus": "GRCh38 chr11:5227002",
33
+ "wt_base": "T",
34
+ "patient_base": "A",
35
+ "basis": "chemistry",
36
+ "expect": {
37
+ "compiles": false,
38
+ "route": "prime_editing",
39
+ "diagnostic": "transversion_no_base_editor"
40
+ },
41
+ "why": [
42
+ "The HGVS is written on the transcript (minus) strand as A>T. On the",
43
+ "forward strand the reference base is T and the patient carries A.",
44
+ "T>A is a TRANSVERSION, and neither ABE (A>G) nor CBE (C>T) produces a",
45
+ "transversion on either strand — so no base editor can correct HbS.",
46
+ "The compiler must refuse and route to prime editing.",
47
+ "",
48
+ "This is also why the approved sickle-cell editing therapy targets the",
49
+ "BCL11A erythroid enhancer to re-induce fetal haemoglobin rather than",
50
+ "correcting the HBB mutation directly. That clinical fact is context,",
51
+ "not the assertion: the assertion here is purely the chemistry, which",
52
+ "anyone can re-derive from the reference sequence."
53
+ ],
54
+ "verified_note": "Coordinate and forward-strand alleles confirmed against Ensembl GRCh38 and the reference sequence at that position on 2026-08-12.",
55
+ "citation": ""
56
+ },
57
+ {
58
+ "id": "transition-abe-sense",
59
+ "name": "Generic G>A transition, sense strand",
60
+ "wt_base": "G",
61
+ "patient_base": "A",
62
+ "basis": "chemistry",
63
+ "expect": {
64
+ "compiles": true,
65
+ "route": "base_editing",
66
+ "editor_family": "ABE",
67
+ "strand": "sense"
68
+ },
69
+ "why": [
70
+ "The patient carries A where wild-type is G. Restoring G means writing",
71
+ "A>G, which is exactly what an adenine base editor does, on the sense",
72
+ "strand. No other chemistry applies."
73
+ ],
74
+ "citation": ""
75
+ },
76
+ {
77
+ "id": "transition-cbe-antisense",
78
+ "name": "Generic A>G transition, antisense strand",
79
+ "wt_base": "A",
80
+ "patient_base": "G",
81
+ "basis": "chemistry",
82
+ "expect": {
83
+ "compiles": true,
84
+ "route": "base_editing",
85
+ "editor_family": "CBE",
86
+ "strand": "antisense"
87
+ },
88
+ "why": [
89
+ "Restoring A from G is G>A on the sense strand. On the antisense strand",
90
+ "that position reads C and must become T — a cytosine base editor,",
91
+ "engaging the opposite strand. Getting this backwards designs a guide",
92
+ "against the wrong strand."
93
+ ],
94
+ "citation": ""
95
+ },
96
+ {
97
+ "id": "large-deletion-not-editable",
98
+ "name": "Multi-kilobase deletion",
99
+ "wt_base": { "repeat": "A", "times": 400 },
100
+ "patient_base": "",
101
+ "basis": "chemistry",
102
+ "expect": {
103
+ "compiles": false,
104
+ "route": "none",
105
+ "diagnostic": "lesion_too_large"
106
+ },
107
+ "why": [
108
+ "Base editors rewrite one base chemically and prime editing has a",
109
+ "practical size ceiling. A lesion of this size needs a different",
110
+ "modality entirely — integrase or recombinase-mediated insertion, or",
111
+ "gene addition — and the compiler must say so rather than returning a",
112
+ "guide."
113
+ ],
114
+ "citation": ""
115
+ },
116
+ {
117
+ "id": "small-insertion-prime-editing",
118
+ "name": "Three-base insertion",
119
+ "wt_base": "",
120
+ "patient_base": "ATG",
121
+ "basis": "chemistry",
122
+ "expect": {
123
+ "compiles": false,
124
+ "route": "prime_editing",
125
+ "diagnostic": "indel_not_base_editable"
126
+ },
127
+ "why": [
128
+ "Base editors do not add or remove bases. An insertion routes to prime",
129
+ "editing, which this compiler does not yet design — so it must refuse",
130
+ "rather than offer a base-editing guide that cannot install the change."
131
+ ],
132
+ "citation": ""
133
+ },
134
+
135
+ {
136
+ "id": "kj-cps1-personalised-base-edit",
137
+ "name": "First personalised in-vivo base-editing therapy (CPS1 deficiency)",
138
+ "basis": "literature",
139
+ "expect": {},
140
+ "why": [
141
+ "PENDING. The bespoke n-of-1 base-editing therapy dosed in an infant",
142
+ "with CPS1 deficiency is the single most important case to reproduce,",
143
+ "because it is the template for the whole personalised-editing model.",
144
+ "",
145
+ "Deliberately left unfilled: encoding the variant, the editor and the",
146
+ "guide from memory would be exactly the fabrication this corpus exists",
147
+ "to prevent. Fill `wt_base`/`patient_base`/`expect` from the primary",
148
+ "publication and add the DOI or PMID to `citation`."
149
+ ],
150
+ "citation": ""
151
+ },
152
+ {
153
+ "id": "bcl11a-enhancer-hbf-reinduction",
154
+ "name": "BCL11A erythroid enhancer disruption (fetal haemoglobin re-induction)",
155
+ "basis": "literature",
156
+ "expect": {},
157
+ "why": [
158
+ "PENDING. The approved sickle-cell / beta-thalassaemia editing therapy",
159
+ "does not correct a coding variant — it disrupts an enhancer. Worth",
160
+ "encoding because it exercises a different question entirely (does the",
161
+ "compiler reason about a REGULATORY target?) and because it is the",
162
+ "clinical counterpart to the HbS refusal above.",
163
+ "",
164
+ "Needs the enhancer coordinates and the guide from the primary source."
165
+ ],
166
+ "citation": ""
167
+ }
168
+ ]
169
+ }
dee/server.py CHANGED
@@ -3598,8 +3598,23 @@ def create_app() -> Flask:
3598
 
3599
  out = _compiler.report_to_dict(report)
3600
  out["resolved"] = resolved
 
 
 
 
 
 
3601
  return jsonify(out)
3602
 
 
 
 
 
 
 
 
 
 
3603
  @app.get("/api/benchmarks")
3604
  def benchmarks() -> Response:
3605
  """The receipts — how well the engine's zero-shot ranking predicts
@@ -4318,6 +4333,7 @@ from dee.core import scoring as _scoring
4318
  # DNA-level tiers (Evo 2, via Modal) — same "/api/models reports what's
4319
  # actually runnable" contract, separate id-space (see the models() route).
4320
  from dee.core import compiler as _compiler
 
4321
  from dee.core import variant_resolve as _variant_resolve
4322
  from dee.core import dna_scoring as _dna_scoring
4323
 
 
3598
 
3599
  out = _compiler.report_to_dict(report)
3600
  out["resolved"] = resolved
3601
+ # The deliverable: a design and its complete justification, in a form
3602
+ # that diffs and pastes. Deterministic — same inputs, same bytes.
3603
+ out["record"] = _compiler.design_record(
3604
+ report, variant=notation, resolved=resolved,
3605
+ provenance={"engine": "TuringDNA compiler",
3606
+ "record": _compiler.RECORD_VERSION})
3607
  return jsonify(out)
3608
 
3609
+ @app.get("/api/compiler/validation")
3610
+ def compiler_validation() -> Response:
3611
+ """Landmark designs the compiler is checked against. Public: the whole
3612
+ value is that anyone can look at it, including the gaps."""
3613
+ report = _compiler_validation.run_validation()
3614
+ resp = jsonify(_compiler_validation.report_to_dict(report))
3615
+ resp.headers["Cache-Control"] = "no-store, max-age=0"
3616
+ return resp
3617
+
3618
  @app.get("/api/benchmarks")
3619
  def benchmarks() -> Response:
3620
  """The receipts — how well the engine's zero-shot ranking predicts
 
4333
  # DNA-level tiers (Evo 2, via Modal) — same "/api/models reports what's
4334
  # actually runnable" contract, separate id-space (see the models() route).
4335
  from dee.core import compiler as _compiler
4336
+ from dee.core import compiler_validation as _compiler_validation
4337
  from dee.core import variant_resolve as _variant_resolve
4338
  from dee.core import dna_scoring as _dna_scoring
4339
 
tests/test_compiler.py CHANGED
@@ -512,3 +512,68 @@ def test_scores_attach_by_label():
512
  assert hit and all(b.delta_ll == -3.75 for b in hit)
513
  return
514
  pytest.skip("fixture produced no bystanders")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
  assert hit and all(b.delta_ll == -3.75 for b in hit)
513
  return
514
  pytest.skip("fixture produced no bystanders")
515
+
516
+
517
+ # ═══════════════════════════════════════════════════════════════════════
518
+ # The design record — the actual deliverable
519
+ # ═══════════════════════════════════════════════════════════════════════
520
+ def test_the_record_is_deterministic():
521
+ """A record that changes between runs cannot be diffed, and one that
522
+ cannot be diffed cannot be audited."""
523
+ r = C.compile_report("G", "A", window=WINDOW, offset=0)
524
+ a = C.design_record(r, variant="NM_1:c.1G>A")
525
+ b = C.design_record(r, variant="NM_1:c.1G>A")
526
+ assert a == b and len(a) > 400
527
+
528
+
529
+ def test_the_record_carries_the_scope_limits():
530
+ r = C.compile_report("G", "A", window=WINDOW, offset=0)
531
+ rec = C.design_record(r)
532
+ assert "Somatic" in rec and "Not IND-ready" in rec
533
+ assert "not a clinical decision" in rec
534
+ assert "Predicted specificity is not measured specificity" in rec
535
+
536
+
537
+ def test_the_record_names_what_it_did_not_establish():
538
+ """The section that makes it a record rather than a certificate."""
539
+ r = C.compile_report("G", "A", window=WINDOW, offset=0)
540
+ rec = C.design_record(r)
541
+ assert "NOT ESTABLISHED by this record" in rec
542
+ assert "Assess specificity" in rec
543
+
544
+
545
+ def test_a_refusal_produces_a_record_too():
546
+ """A refused design still deserves documentation — often more so."""
547
+ r = C.compile_report("A", "C") # transversion
548
+ rec = C.design_record(r, variant="NM_1:c.1C>A")
549
+ assert "compiled NO" in rec
550
+ assert "transversion_no_base_editor" in rec
551
+ assert "remedy:" in rec
552
+
553
+
554
+ def test_bystander_scores_appear_in_the_record_and_unscored_says_so():
555
+ corr = C.classify_lesion("G", "A").correction
556
+ for off in range(len(HBB)):
557
+ if HBB[off] != "G":
558
+ continue
559
+ st, dg = C.plan_base_edit_strategies(HBB, off, corr)
560
+ if not any(s.bystanders for s in st):
561
+ continue
562
+ C.label_bystanders(HBB, st)
563
+ r = C.compile_report("G", "A", window=HBB, offset=off,
564
+ strategies=st, enumerate_diags=dg)
565
+ rec = C.design_record(r)
566
+ assert "STRATEGIES" in rec and "bystanders" in rec
567
+ assert "not scored" in rec, "unscored must be stated, not omitted"
568
+ return
569
+ pytest.skip("fixture produced no bystanders")
570
+
571
+
572
+ def test_the_specificity_pass_hands_off_instead_of_shrugging():
573
+ r = C.compile_report("G", "A", window=WINDOW, offset=0)
574
+ sp = _by_name(r)["specificity"]
575
+ assert "Cas-OFFinder" in sp.detail or "CRISPRme" in sp.detail
576
+ assert "GUIDE-seq" in sp.detail
577
+ assert "Cas-independent deamination" in sp.detail, (
578
+ "a base editor is not cleared by a DSB-capture assay, and the handoff "
579
+ "must say so")
tests/test_compiler_validation.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Landmark-design validation. This file guards the honesty mechanism.
2
+
3
+ The number that matters is not "how many passed" but "how many passed that
4
+ were checkable without trusting anyone's memory". A harness that lets an
5
+ uncited claim count as validation is worse than no harness, because it
6
+ launders a guess into a credential.
7
+ """
8
+ import pytest
9
+
10
+ from dee.core import compiler_validation as V
11
+
12
+
13
+ def test_the_bundled_corpus_loads():
14
+ cases = V.load_cases()
15
+ assert cases, "the corpus must ship with the package"
16
+ assert all("id" in c and "basis" in c for c in cases)
17
+
18
+
19
+ def test_every_chemistry_case_reproduces():
20
+ """If this fails, the compiler changed its verdict on a settled design."""
21
+ report = V.run_validation()
22
+ assert not report.failures, [
23
+ (f.name, f.mismatches) for f in report.failures]
24
+ assert report.chemistry_passed == report.chemistry_total
25
+ assert report.chemistry_total >= 5, "corpus should not silently shrink"
26
+
27
+
28
+ def test_the_sickle_cell_case_is_a_refusal_and_stays_one():
29
+ """The anchor case. HbS is T>A on the forward strand — a transversion —
30
+ so no base editor can correct it. If this ever 'passes' as compilable,
31
+ something has broken very badly."""
32
+ report = V.run_validation()
33
+ hbs = next(r for r in report.results if r.case_id == "hbs-sickle-hbb")
34
+ assert hbs.status == "pass"
35
+ assert hbs.observed["compiles"] is False
36
+ assert hbs.observed["route"] == "prime_editing"
37
+ assert "transversion_no_base_editor" in hbs.observed["diagnostics"]
38
+
39
+
40
+ def test_an_uncited_literature_case_is_unverified_not_passed():
41
+ """The mechanism. An expectation nobody can look up must never count."""
42
+ cases = [{
43
+ "id": "made-up", "name": "Uncited claim", "basis": "literature",
44
+ "wt_base": "G", "patient_base": "A",
45
+ "expect": {"compiles": True, "route": "base_editing"},
46
+ "citation": "",
47
+ }]
48
+ report = V.run_validation(cases)
49
+ assert report.results[0].status == "unverified"
50
+ assert report.chemistry_passed == 0
51
+ assert "Uncited claim" in report.unverified
52
+
53
+
54
+ def test_a_cited_literature_case_is_actually_run():
55
+ cases = [{
56
+ "id": "cited", "name": "Cited claim", "basis": "literature",
57
+ "wt_base": "G", "patient_base": "A",
58
+ "expect": {"compiles": True, "route": "base_editing",
59
+ "editor_family": "ABE"},
60
+ "citation": "PMID:00000000",
61
+ }]
62
+ assert V.run_validation(cases).results[0].status == "pass"
63
+
64
+
65
+ def test_a_wrong_expectation_fails_loudly_with_the_delta():
66
+ cases = [{
67
+ "id": "wrong", "name": "Wrong", "basis": "chemistry",
68
+ "wt_base": "G", "patient_base": "A",
69
+ "expect": {"editor_family": "CBE"}, # it is ABE
70
+ }]
71
+ r = V.run_validation(cases).results[0]
72
+ assert r.status == "fail"
73
+ assert any("editor_family" in m and "ABE" in m for m in r.mismatches)
74
+
75
+
76
+ def test_a_case_is_not_failed_for_something_it_never_asserted():
77
+ """Partial expectations are legitimate; only stated keys are checked."""
78
+ cases = [{
79
+ "id": "partial", "name": "Partial", "basis": "chemistry",
80
+ "wt_base": "G", "patient_base": "A",
81
+ "expect": {"compiles": True}, # says nothing about strand
82
+ }]
83
+ assert V.run_validation(cases).results[0].status == "pass"
84
+
85
+
86
+ def test_the_headline_never_reads_as_more_than_it_is():
87
+ report = V.run_validation()
88
+ h = report.headline
89
+ assert "derivable from the genetic code" in h
90
+ if report.unverified:
91
+ assert "awaiting a citation and not counted" in h
92
+
93
+
94
+ def test_unverified_cases_are_named_not_merely_counted():
95
+ """A gap with a name is a to-do; a gap as a number is a footnote."""
96
+ report = V.run_validation()
97
+ assert all(isinstance(n, str) and n for n in report.unverified)