File size: 8,199 Bytes
ea9cb88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
"""Check the compiler against landmark editing designs.

The question anyone serious asks within two minutes is "how do you know it's
right?", and "it is deterministic and well tested" is an engineering answer to
a scientific question. This is the scientific answer: run the compiler against
designs the field has already settled and show it arrives at the same verdict.

THE CLAIM THIS SUPPORTS, STATED PRECISELY
-----------------------------------------
It reproduces **known-correct decisions**. It does NOT discover new biology,
and the two must never be blurred — the first is defensible and the second
would be a lie a reviewer could take apart in one question.

TWO KINDS OF CASE, COUNTED SEPARATELY
-------------------------------------
`basis="chemistry"`   the expected verdict follows from the genetic code and
                      the two base-editor chemistries. Anyone can re-derive
                      it, so it needs no citation and counts as validation.

`basis="literature"`  the expectation is a claim about what a real programme
                      actually did. It requires a citation. Without one the
                      case is reported UNVERIFIED and does **not** count as a
                      pass — it is a named gap, which is more useful than a
                      silent absence.

That split is the honesty mechanism. "12 of 12 validated" means nothing if
half were asserted from memory; here the report always carries both numbers
and the unverified ones by name.

Pure logic: no network, no GPU, no model. It runs in CI on every commit, so a
change that breaks a landmark verdict fails the build rather than being
noticed in a demo.
"""

from __future__ import annotations

import json
import pathlib
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

from dee.core import compiler as _compiler

__all__ = ["CaseResult", "ValidationReport", "load_cases", "run_validation",
           "report_to_dict"]

CORPUS_PATH = (pathlib.Path(__file__).resolve().parent.parent
               / "data" / "landmark_designs.json")


@dataclass
class CaseResult:
    case_id: str
    name: str
    basis: str
    status: str            # "pass" | "fail" | "unverified"
    expected: Dict[str, Any] = field(default_factory=dict)
    observed: Dict[str, Any] = field(default_factory=dict)
    mismatches: List[str] = field(default_factory=list)
    why: str = ""
    citation: str = ""


@dataclass
class ValidationReport:
    results: List[CaseResult]

    @property
    def chemistry_total(self) -> int:
        return sum(1 for r in self.results if r.basis == "chemistry")

    @property
    def chemistry_passed(self) -> int:
        return sum(1 for r in self.results
                   if r.basis == "chemistry" and r.status == "pass")

    @property
    def unverified(self) -> List[str]:
        return [r.name for r in self.results if r.status == "unverified"]

    @property
    def failures(self) -> List[CaseResult]:
        return [r for r in self.results if r.status == "fail"]

    @property
    def headline(self) -> str:
        """One sentence that cannot be misread as more than it is."""
        n, tot = self.chemistry_passed, self.chemistry_total
        base = (f"{n} of {tot} landmark decisions reproduced "
                "(derivable from the genetic code and base-editor chemistry)")
        if self.unverified:
            base += (f"; {len(self.unverified)} further case(s) awaiting a "
                     "citation and not counted")
        return base + "."


def load_cases(path: Optional[pathlib.Path] = None) -> List[Dict[str, Any]]:
    p = path or CORPUS_PATH
    if not p.exists():
        return []
    data = json.loads(p.read_text())
    return [c for c in (data.get("cases") or []) if isinstance(c, dict)]


def _expand_allele(value: Any) -> str:
    """Alleles may be a literal string or {"repeat": "A", "times": 400}.

    The compact form exists because a 400-base deletion is a legitimate case
    and pasting 400 characters into JSON makes the corpus unreadable. It is
    NOT a shorthand the compiler understands — an earlier corpus wrote "A400"
    expecting it to mean 400 A's, and the compiler correctly read it as an
    allele containing a digit and refused. The harness caught that, which is
    the harness working.
    """
    if isinstance(value, dict):
        base = str(value.get("repeat") or "")
        times = int(value.get("times") or 0)
        return base * times
    return str(value or "")


def _observe(case: Dict[str, Any]) -> Dict[str, Any]:
    """Run the compiler on a case and flatten what it decided."""
    call = _compiler.classify_lesion(_expand_allele(case.get("wt_base")),
                                     _expand_allele(case.get("patient_base")))
    corr = call.correction
    return {
        "compiles": call.compiles,
        "route": call.route,
        "editor_family": corr.editor_family if corr else None,
        "strand": corr.strand if corr else None,
        "diagnostics": [d.code for d in call.diagnostics],
    }


def _compare(expected: Dict[str, Any], observed: Dict[str, Any]) -> List[str]:
    """Only the keys the case actually asserts. A case that says nothing about
    strand is not failed for the strand."""
    out: List[str] = []
    for key in ("compiles", "route", "editor_family", "strand"):
        if key in expected and observed.get(key) != expected[key]:
            out.append(f"{key}: expected {expected[key]!r}, "
                       f"got {observed.get(key)!r}")
    want_diag = expected.get("diagnostic")
    if want_diag and want_diag not in (observed.get("diagnostics") or []):
        out.append(f"diagnostic {want_diag!r} not raised "
                   f"(raised {observed.get('diagnostics')!r})")
    return out


def run_validation(cases: Optional[List[Dict[str, Any]]] = None
                   ) -> ValidationReport:
    """Every case, with literature cases short-circuited unless cited."""
    results: List[CaseResult] = []
    for case in (cases if cases is not None else load_cases()):
        basis = str(case.get("basis") or "literature")
        why = case.get("why")
        why_text = " ".join(why) if isinstance(why, list) else str(why or "")
        citation = str(case.get("citation") or "").strip()
        expected = case.get("expect") or {}

        # A literature claim without a citation is not evidence, and must not
        # be run as though it were — an uncited expectation is just a guess
        # with a filename.
        if basis == "literature" and not citation:
            results.append(CaseResult(
                case_id=str(case.get("id") or ""), name=str(case.get("name") or ""),
                basis=basis, status="unverified", expected=expected,
                why=why_text, citation=citation))
            continue

        if not expected:
            results.append(CaseResult(
                case_id=str(case.get("id") or ""), name=str(case.get("name") or ""),
                basis=basis, status="unverified", expected={},
                why=why_text or "No expectation recorded.", citation=citation))
            continue

        observed = _observe(case)
        mismatches = _compare(expected, observed)
        results.append(CaseResult(
            case_id=str(case.get("id") or ""), name=str(case.get("name") or ""),
            basis=basis, status="pass" if not mismatches else "fail",
            expected=expected, observed=observed, mismatches=mismatches,
            why=why_text, citation=citation))
    return ValidationReport(results)


def report_to_dict(report: ValidationReport) -> Dict[str, Any]:
    return {
        "headline": report.headline,
        "chemistry_passed": report.chemistry_passed,
        "chemistry_total": report.chemistry_total,
        "unverified": report.unverified,
        "all_passed": not report.failures,
        "cases": [
            {"id": r.case_id, "name": r.name, "basis": r.basis,
             "status": r.status, "expected": r.expected, "observed": r.observed,
             "mismatches": r.mismatches, "why": r.why, "citation": r.citation}
            for r in report.results
        ],
    }