Spaces:
Running
Running
github-actions[bot] commited on
Commit ·
30a9dc2
1
Parent(s): 62335fa
Deploy fab7842
Browse filesThe consequence pass now calls Evo 2, instead of claiming it did
Source: https://github.com/WINTER4000/turingDNA/commit/fab7842627a8e635689634387736e7f1fe750879
- dee/core/compiler.py +44 -12
- dee/server.py +50 -11
- dee/static/app.js +13 -1
- dee/static/index.html +15 -2
- tests/test_compiler.py +49 -5
dee/core/compiler.py
CHANGED
|
@@ -354,12 +354,17 @@ def compile_correction(wt_allele: str, patient_allele: str, *,
|
|
| 354 |
# intergenic space, so I did not clear this guide".
|
| 355 |
#
|
| 356 |
# Every pass reports one of:
|
| 357 |
-
# ok ran, nothing blocking
|
| 358 |
-
# warn ran, with a caveat the designer must read
|
| 359 |
# error ran, and refused
|
|
|
|
| 360 |
# unavailable did NOT run, because this deployment cannot — with the reason
|
| 361 |
# skipped did not run because an earlier pass already refused
|
| 362 |
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
# "unavailable" is deliberately distinct from "ok". Conflating them is how a
|
| 364 |
# tool ends up implying it checked something it never looked at.
|
| 365 |
|
|
@@ -398,7 +403,10 @@ class CompileReport:
|
|
| 398 |
|
| 399 |
@property
|
| 400 |
def incomplete_because(self) -> List[str]:
|
| 401 |
-
|
|
|
|
|
|
|
|
|
|
| 402 |
|
| 403 |
|
| 404 |
# What this deployment can and cannot do, stated once so the passes and the
|
|
@@ -435,16 +443,18 @@ def compile_report(wt_allele: str, patient_allele: str, *,
|
|
| 435 |
window: str = "", offset: int = -1,
|
| 436 |
germline: bool = False,
|
| 437 |
can_enumerate: bool = True,
|
| 438 |
-
|
| 439 |
can_check_specificity: bool = False) -> CompileReport:
|
| 440 |
"""Run the passes and report every one, including those that could not run.
|
| 441 |
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
|
|
|
|
|
|
| 448 |
"""
|
| 449 |
if germline:
|
| 450 |
raise GermlineRefused(
|
|
@@ -509,8 +519,30 @@ def compile_report(wt_allele: str, patient_allele: str, *,
|
|
| 509 |
add("enumerate", "unavailable", CAPABILITY_NOTES["enumerate"])
|
| 510 |
|
| 511 |
# ── consequence ─────────────────────────────────────────────────────
|
| 512 |
-
|
| 513 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 514 |
|
| 515 |
# ── specificity ─────────────────────────────────────────────���───────
|
| 516 |
# Always carries its caveat, even when it runs: a pass that reports "ok"
|
|
|
|
| 354 |
# intergenic space, so I did not clear this guide".
|
| 355 |
#
|
| 356 |
# Every pass reports one of:
|
| 357 |
+
# ok ran, produced a result, nothing blocking
|
| 358 |
+
# warn ran, produced a result, with a caveat the designer must read
|
| 359 |
# error ran, and refused
|
| 360 |
+
# failed was ATTEMPTED and produced no result
|
| 361 |
# unavailable did NOT run, because this deployment cannot — with the reason
|
| 362 |
# skipped did not run because an earlier pass already refused
|
| 363 |
#
|
| 364 |
+
# `failed` is separate from `warn` on purpose. Reporting a model call that
|
| 365 |
+
# errored as "passed with caveat" is a soft version of the same lie as
|
| 366 |
+
# reporting an unrun pass as ok: in both cases nothing was assessed.
|
| 367 |
+
#
|
| 368 |
# "unavailable" is deliberately distinct from "ok". Conflating them is how a
|
| 369 |
# tool ends up implying it checked something it never looked at.
|
| 370 |
|
|
|
|
| 403 |
|
| 404 |
@property
|
| 405 |
def incomplete_because(self) -> List[str]:
|
| 406 |
+
"""Passes that produced no assessment — whether they were never run
|
| 407 |
+
or were attempted and failed. Both leave the same hole in the record."""
|
| 408 |
+
return [p.title for p in self.passes
|
| 409 |
+
if p.status in ("unavailable", "failed")]
|
| 410 |
|
| 411 |
|
| 412 |
# What this deployment can and cannot do, stated once so the passes and the
|
|
|
|
| 443 |
window: str = "", offset: int = -1,
|
| 444 |
germline: bool = False,
|
| 445 |
can_enumerate: bool = True,
|
| 446 |
+
consequence: Optional[Dict[str, object]] = None,
|
| 447 |
can_check_specificity: bool = False) -> CompileReport:
|
| 448 |
"""Run the passes and report every one, including those that could not run.
|
| 449 |
|
| 450 |
+
`consequence` is the ACTUAL RESULT of scoring the variant, or None. It is
|
| 451 |
+
deliberately not a capability flag: the first version of this function
|
| 452 |
+
took `can_score_consequence: bool` and reported the pass as "ok" whenever
|
| 453 |
+
the model was merely *reachable*, so the UI printed "Assess edit
|
| 454 |
+
consequence — passed" while nothing had been assessed. That is the same
|
| 455 |
+
"configured means done" lie that dee/core/modal_client.reachable exists to
|
| 456 |
+
prevent, rebuilt one layer up. A pass reports success only when it holds
|
| 457 |
+
the output of work that happened.
|
| 458 |
"""
|
| 459 |
if germline:
|
| 460 |
raise GermlineRefused(
|
|
|
|
| 519 |
add("enumerate", "unavailable", CAPABILITY_NOTES["enumerate"])
|
| 520 |
|
| 521 |
# ── consequence ─────────────────────────────────────────────────────
|
| 522 |
+
# Reports on the RESULT, never on the ability to have produced one.
|
| 523 |
+
if consequence is None:
|
| 524 |
+
add("consequence", "unavailable", CAPABILITY_NOTES["consequence"])
|
| 525 |
+
elif not consequence.get("ok"):
|
| 526 |
+
add("consequence", "failed",
|
| 527 |
+
"Scoring was attempted and did not return a result: "
|
| 528 |
+
f"{consequence.get('error') or 'no detail'}. Nothing is reported "
|
| 529 |
+
"for this pass rather than an assumed-benign default. "
|
| 530 |
+
+ CAPABILITY_NOTES["consequence"])
|
| 531 |
+
else:
|
| 532 |
+
dl = consequence.get("delta_ll")
|
| 533 |
+
label = consequence.get("label") or "the variant"
|
| 534 |
+
# State the direction in words. A bare signed number invites the
|
| 535 |
+
# reader to supply their own convention, and half of them will
|
| 536 |
+
# supply the wrong one.
|
| 537 |
+
if isinstance(dl, (int, float)):
|
| 538 |
+
direction = ("less likely than wild-type" if dl < 0
|
| 539 |
+
else "more likely than wild-type" if dl > 0
|
| 540 |
+
else "indistinguishable from wild-type")
|
| 541 |
+
detail = (f"{label}: delta log-likelihood {dl:+.4f} — the model "
|
| 542 |
+
f"finds the patient sequence {direction}. ")
|
| 543 |
+
else:
|
| 544 |
+
detail = f"{label}: scored, no delta returned. "
|
| 545 |
+
add("consequence", "ok", detail + CAPABILITY_NOTES["consequence"])
|
| 546 |
|
| 547 |
# ── specificity ─────────────────────────────────────────────���───────
|
| 548 |
# Always carries its caveat, even when it runs: a pass that reports "ok"
|
dee/server.py
CHANGED
|
@@ -3416,6 +3416,47 @@ def create_app() -> Flask:
|
|
| 3416 |
# Lowers a pathogenic variant to an editing strategy, or refuses with a
|
| 3417 |
# diagnostic. See dee/core/compiler.py for the scope this operates in —
|
| 3418 |
# somatic design and assessment only, never a clinical decision.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3419 |
@app.post("/api/compiler/compile")
|
| 3420 |
def compiler_compile() -> Response:
|
| 3421 |
gate = _dna_signin_gate("compiler")
|
|
@@ -3434,23 +3475,21 @@ def create_app() -> Flask:
|
|
| 3434 |
if not wt and not patient:
|
| 3435 |
return jsonify({"error": "missing 'wt_allele' and 'patient_allele'"}), 400
|
| 3436 |
|
| 3437 |
-
#
|
| 3438 |
-
#
|
| 3439 |
-
#
|
| 3440 |
-
#
|
| 3441 |
-
#
|
| 3442 |
-
|
| 3443 |
-
|
| 3444 |
-
|
| 3445 |
-
app.logger.debug("DNA capability probe failed", exc_info=True)
|
| 3446 |
-
can_score = False
|
| 3447 |
|
| 3448 |
try:
|
| 3449 |
report = _compiler.compile_report(
|
| 3450 |
wt, patient, window=window, offset=offset,
|
| 3451 |
germline=bool(body.get("germline")),
|
| 3452 |
can_enumerate=True,
|
| 3453 |
-
|
| 3454 |
can_check_specificity=False,
|
| 3455 |
)
|
| 3456 |
except _compiler.GermlineRefused as exc:
|
|
|
|
| 3416 |
# Lowers a pathogenic variant to an editing strategy, or refuses with a
|
| 3417 |
# diagnostic. See dee/core/compiler.py for the scope this operates in —
|
| 3418 |
# somatic design and assessment only, never a clinical decision.
|
| 3419 |
+
def _score_variant_consequence(wt: str, patient: str, window: str, offset: int):
|
| 3420 |
+
"""Ask Evo 2 what the PATIENT allele does, in its real sequence context.
|
| 3421 |
+
|
| 3422 |
+
The variant scored is the pathogenic one against the wild-type window
|
| 3423 |
+
— `<wt><1-based pos><patient>` — so a negative delta log-likelihood
|
| 3424 |
+
means the model finds the patient's sequence less likely than
|
| 3425 |
+
wild-type. That is supporting evidence about the lesion, not proof:
|
| 3426 |
+
zero-shot, no training on this locus, no validated relationship to
|
| 3427 |
+
clinical consequence.
|
| 3428 |
+
|
| 3429 |
+
Returns None when there is nothing to score (no window, or a lesion
|
| 3430 |
+
with no single-base correction). Never raises — a scoring failure must
|
| 3431 |
+
degrade the record's completeness, not the compile.
|
| 3432 |
+
"""
|
| 3433 |
+
if not window or offset < 0 or offset >= len(window):
|
| 3434 |
+
return None
|
| 3435 |
+
if len(wt) != 1 or len(patient) != 1:
|
| 3436 |
+
return None # indels are not point substitutions
|
| 3437 |
+
if window[offset].upper() != wt.upper():
|
| 3438 |
+
return None # the verify pass will refuse this anyway
|
| 3439 |
+
|
| 3440 |
+
label = f"{wt.upper()}{offset + 1}{patient.upper()}"
|
| 3441 |
+
try:
|
| 3442 |
+
res = _dna_scoring.score_dna_variants(window, [label], tier="achilles")
|
| 3443 |
+
except Exception as exc: # noqa: BLE001
|
| 3444 |
+
app.logger.warning("consequence scoring failed: %s", exc)
|
| 3445 |
+
return {"ok": False, "label": label,
|
| 3446 |
+
"error": "the DNA model did not return a score"}
|
| 3447 |
+
|
| 3448 |
+
scores = (res or {}).get("scores") or []
|
| 3449 |
+
hit = next((s for s in scores if s.get("label") == label), None)
|
| 3450 |
+
if hit is None:
|
| 3451 |
+
skipped = (res or {}).get("skipped") or []
|
| 3452 |
+
return {"ok": False, "label": label,
|
| 3453 |
+
"error": (f"the model returned no score for {label}"
|
| 3454 |
+
+ (f" (skipped: {skipped})" if skipped else ""))}
|
| 3455 |
+
return {"ok": True, "label": label, "delta_ll": hit.get("delta_ll"),
|
| 3456 |
+
"reference_ll": (res or {}).get("reference_ll"),
|
| 3457 |
+
"checkpoint": (res or {}).get("checkpoint"),
|
| 3458 |
+
"tier": (res or {}).get("tier")}
|
| 3459 |
+
|
| 3460 |
@app.post("/api/compiler/compile")
|
| 3461 |
def compiler_compile() -> Response:
|
| 3462 |
gate = _dna_signin_gate("compiler")
|
|
|
|
| 3475 |
if not wt and not patient:
|
| 3476 |
return jsonify({"error": "missing 'wt_allele' and 'patient_allele'"}), 400
|
| 3477 |
|
| 3478 |
+
# Consequence scoring is OPT-IN. The compile itself is pure logic and
|
| 3479 |
+
# returns in milliseconds; an Evo 2 call is a rented 7B GPU and ~80s
|
| 3480 |
+
# cold, so running it on every compile would make the fast, honest
|
| 3481 |
+
# part of the tool feel broken. Off by default means the pass reports
|
| 3482 |
+
# `unavailable` — which is true — rather than quietly costing money.
|
| 3483 |
+
consequence = None
|
| 3484 |
+
if body.get("score_consequence"):
|
| 3485 |
+
consequence = _score_variant_consequence(wt, patient, window, offset)
|
|
|
|
|
|
|
| 3486 |
|
| 3487 |
try:
|
| 3488 |
report = _compiler.compile_report(
|
| 3489 |
wt, patient, window=window, offset=offset,
|
| 3490 |
germline=bool(body.get("germline")),
|
| 3491 |
can_enumerate=True,
|
| 3492 |
+
consequence=consequence,
|
| 3493 |
can_check_specificity=False,
|
| 3494 |
)
|
| 3495 |
except _compiler.GermlineRefused as exc:
|
dee/static/app.js
CHANGED
|
@@ -12310,10 +12310,14 @@ if (document.readyState === 'loading') {
|
|
| 12310 |
|
| 12311 |
const ICON = {
|
| 12312 |
ok: '✓', warn: '!', error: '✕',
|
| 12313 |
-
unavailable: '—', skipped: '·',
|
| 12314 |
};
|
|
|
|
|
|
|
|
|
|
| 12315 |
const LABEL = {
|
| 12316 |
ok: 'passed', warn: 'passed with caveat', error: 'refused',
|
|
|
|
| 12317 |
unavailable: 'not run', skipped: 'skipped',
|
| 12318 |
};
|
| 12319 |
|
|
@@ -12432,7 +12436,13 @@ if (document.readyState === 'loading') {
|
|
| 12432 |
const win = ($('tcWindow').value || '').replace(/\s+/g, '').toUpperCase();
|
| 12433 |
const offset = parseInt($('tcOffset').value, 10);
|
| 12434 |
const btn = $('tcRun');
|
|
|
|
|
|
|
| 12435 |
btn.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12436 |
try {
|
| 12437 |
const res = await fetch('/api/compiler/compile', {
|
| 12438 |
method: 'POST',
|
|
@@ -12440,6 +12450,7 @@ if (document.readyState === 'loading') {
|
|
| 12440 |
body: JSON.stringify({
|
| 12441 |
wt_allele: wt, patient_allele: patient,
|
| 12442 |
window: win, offset: isNaN(offset) ? -1 : offset,
|
|
|
|
| 12443 |
}),
|
| 12444 |
});
|
| 12445 |
const data = await res.json();
|
|
@@ -12459,6 +12470,7 @@ if (document.readyState === 'loading') {
|
|
| 12459 |
setError((err && err.message) || String(err));
|
| 12460 |
} finally {
|
| 12461 |
btn.disabled = false;
|
|
|
|
| 12462 |
}
|
| 12463 |
}
|
| 12464 |
|
|
|
|
| 12310 |
|
| 12311 |
const ICON = {
|
| 12312 |
ok: '✓', warn: '!', error: '✕',
|
| 12313 |
+
failed: '✕', unavailable: '—', skipped: '·',
|
| 12314 |
};
|
| 12315 |
+
// 'failed' is NOT 'passed with caveat'. A model call that errored
|
| 12316 |
+
// assessed nothing, and saying otherwise is the soft version of
|
| 12317 |
+
// reporting an unrun pass as ok.
|
| 12318 |
const LABEL = {
|
| 12319 |
ok: 'passed', warn: 'passed with caveat', error: 'refused',
|
| 12320 |
+
failed: 'attempted, no result',
|
| 12321 |
unavailable: 'not run', skipped: 'skipped',
|
| 12322 |
};
|
| 12323 |
|
|
|
|
| 12436 |
const win = ($('tcWindow').value || '').replace(/\s+/g, '').toUpperCase();
|
| 12437 |
const offset = parseInt($('tcOffset').value, 10);
|
| 12438 |
const btn = $('tcRun');
|
| 12439 |
+
const scoring = !!($('tcScore') || {}).checked;
|
| 12440 |
+
const origLabel = btn.textContent;
|
| 12441 |
btn.disabled = true;
|
| 12442 |
+
// Only claim a wait when there IS one. The compile alone returns in
|
| 12443 |
+
// milliseconds; saying "scoring on the GPU" for a pure-logic pass
|
| 12444 |
+
// would be theatre.
|
| 12445 |
+
if (scoring) btn.textContent = 'Compiling + scoring on the GPU…';
|
| 12446 |
try {
|
| 12447 |
const res = await fetch('/api/compiler/compile', {
|
| 12448 |
method: 'POST',
|
|
|
|
| 12450 |
body: JSON.stringify({
|
| 12451 |
wt_allele: wt, patient_allele: patient,
|
| 12452 |
window: win, offset: isNaN(offset) ? -1 : offset,
|
| 12453 |
+
score_consequence: !!($('tcScore') || {}).checked,
|
| 12454 |
}),
|
| 12455 |
});
|
| 12456 |
const data = await res.json();
|
|
|
|
| 12470 |
setError((err && err.message) || String(err));
|
| 12471 |
} finally {
|
| 12472 |
btn.disabled = false;
|
| 12473 |
+
btn.textContent = origLabel;
|
| 12474 |
}
|
| 12475 |
}
|
| 12476 |
|
dee/static/index.html
CHANGED
|
@@ -112,7 +112,7 @@
|
|
| 112 |
<!-- ?v= query bumps invalidate browser + iframe asset caches when app.css /
|
| 113 |
app.js change. Bump these numbers whenever you ship a frontend update —
|
| 114 |
without them, users keep getting the stale file for up to a week. -->
|
| 115 |
-
<link rel="stylesheet" href="/static/app.css?v=20260812-
|
| 116 |
<!-- The work catalog + the draggable rail. Kept out of app.css so two new
|
| 117 |
self-contained surfaces stay reviewable; every colour is an app.css
|
| 118 |
token, so both themes work with nothing added. -->
|
|
@@ -1326,6 +1326,19 @@
|
|
| 1326 |
<span class="field-opt">· 0-based</span></label>
|
| 1327 |
<input id="tcOffset" class="tc-num mono" type="number" min="0" value="0" />
|
| 1328 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1329 |
<div class="tc-actions">
|
| 1330 |
<button class="ghost" type="button" id="tcExampleOk"
|
| 1331 |
title="A transition — base-editable, compiles">Example that compiles</button>
|
|
@@ -2830,7 +2843,7 @@
|
|
| 2830 |
<!-- Cloning reference data must load before app.js so the Designer
|
| 2831 |
can read VECTORS / ENZYMES / CLONING_METHODS / TAGS / LINKERS. -->
|
| 2832 |
<script src="/static/cloning_db.js?v=20260530-ui-polish" defer></script>
|
| 2833 |
-
<script src="/static/app.js?v=20260812-
|
| 2834 |
<!-- The decision trace, BEFORE cockpit.js: applyEvent calls TDTrace.push
|
| 2835 |
on the very first event, and both are `defer`, so document order is
|
| 2836 |
load order. Loading it after would drop the opening events of a
|
|
|
|
| 112 |
<!-- ?v= query bumps invalidate browser + iframe asset caches when app.css /
|
| 113 |
app.js change. Bump these numbers whenever you ship a frontend update —
|
| 114 |
without them, users keep getting the stale file for up to a week. -->
|
| 115 |
+
<link rel="stylesheet" href="/static/app.css?v=20260812-tc2" />
|
| 116 |
<!-- The work catalog + the draggable rail. Kept out of app.css so two new
|
| 117 |
self-contained surfaces stay reviewable; every colour is an app.css
|
| 118 |
token, so both themes work with nothing added. -->
|
|
|
|
| 1326 |
<span class="field-opt">· 0-based</span></label>
|
| 1327 |
<input id="tcOffset" class="tc-num mono" type="number" min="0" value="0" />
|
| 1328 |
|
| 1329 |
+
<label class="primer-toggle">
|
| 1330 |
+
<input type="checkbox" id="tcScore" />
|
| 1331 |
+
<span class="primer-toggle-text">
|
| 1332 |
+
<strong>Score the variant with Evo 2</strong>
|
| 1333 |
+
<span class="field-hint">Asks the genome model what the
|
| 1334 |
+
<em>patient's</em> allele does in this exact sequence context. Off by
|
| 1335 |
+
default: the compile itself is instant logic, while this is a rented
|
| 1336 |
+
7B GPU (~25s warm, ~80s cold). Leaving it off makes the consequence
|
| 1337 |
+
pass report <strong>not run</strong> — which is the truth — rather
|
| 1338 |
+
than quietly spending money.</span>
|
| 1339 |
+
</span>
|
| 1340 |
+
</label>
|
| 1341 |
+
|
| 1342 |
<div class="tc-actions">
|
| 1343 |
<button class="ghost" type="button" id="tcExampleOk"
|
| 1344 |
title="A transition — base-editable, compiles">Example that compiles</button>
|
|
|
|
| 2843 |
<!-- Cloning reference data must load before app.js so the Designer
|
| 2844 |
can read VECTORS / ENZYMES / CLONING_METHODS / TAGS / LINKERS. -->
|
| 2845 |
<script src="/static/cloning_db.js?v=20260530-ui-polish" defer></script>
|
| 2846 |
+
<script src="/static/app.js?v=20260812-tc2" defer></script>
|
| 2847 |
<!-- The decision trace, BEFORE cockpit.js: applyEvent calls TDTrace.push
|
| 2848 |
on the very first event, and both are `defer`, so document order is
|
| 2849 |
load order. Loading it after would drop the opening events of a
|
tests/test_compiler.py
CHANGED
|
@@ -225,12 +225,56 @@ def test_specificity_still_carries_its_caveat_when_it_runs():
|
|
| 225 |
assert "GUIDE-seq" in spec.detail
|
| 226 |
|
| 227 |
|
| 228 |
-
def
|
| 229 |
r = C.compile_report("G", "A", window=WINDOW, offset=0,
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
assert
|
| 233 |
-
assert "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
|
| 235 |
|
| 236 |
def test_a_refused_lesion_skips_the_rest_rather_than_reporting_ok():
|
|
|
|
| 225 |
assert "GUIDE-seq" in spec.detail
|
| 226 |
|
| 227 |
|
| 228 |
+
def test_consequence_reports_the_number_when_scoring_actually_ran():
|
| 229 |
r = C.compile_report("G", "A", window=WINDOW, offset=0,
|
| 230 |
+
consequence={"ok": True, "label": "G1A", "delta_ll": -4.2})
|
| 231 |
+
p = _by_name(r)["consequence"]
|
| 232 |
+
assert p.status == "ok"
|
| 233 |
+
assert "-4.2000" in p.detail
|
| 234 |
+
assert "less likely than wild-type" in p.detail, "direction stated in words"
|
| 235 |
+
assert "zero-shot" in p.detail
|
| 236 |
+
assert "no validated relationship to clinical outcome" in p.detail
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
def test_a_positive_delta_is_described_in_the_other_direction():
|
| 240 |
+
r = C.compile_report("G", "A", window=WINDOW, offset=0,
|
| 241 |
+
consequence={"ok": True, "label": "G1A", "delta_ll": 1.5})
|
| 242 |
+
assert "more likely than wild-type" in _by_name(r)["consequence"].detail
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def test_reachability_alone_never_makes_the_consequence_pass_succeed():
|
| 246 |
+
"""The bug this replaced: the pass took a capability flag and reported
|
| 247 |
+
'passed' whenever the model was merely reachable, so the UI said the edit
|
| 248 |
+
had been assessed when nothing had been scored."""
|
| 249 |
+
r = C.compile_report("G", "A", window=WINDOW, offset=0)
|
| 250 |
+
assert _by_name(r)["consequence"].status == "unavailable"
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def test_a_failed_scoring_attempt_is_failed_not_passed_with_caveat():
|
| 254 |
+
"""'passed with caveat' on a model call that errored is the soft version
|
| 255 |
+
of reporting an unrun pass as ok — in both cases nothing was assessed."""
|
| 256 |
+
r = C.compile_report("G", "A", window=WINDOW, offset=0,
|
| 257 |
+
consequence={"ok": False, "label": "G1A",
|
| 258 |
+
"error": "backend refused"})
|
| 259 |
+
p = _by_name(r)["consequence"]
|
| 260 |
+
assert p.status == "failed"
|
| 261 |
+
assert p.status != "warn", "must not read as a pass"
|
| 262 |
+
assert "backend refused" in p.detail
|
| 263 |
+
assert "assumed-benign" in p.detail
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
def test_a_failed_pass_leaves_the_record_incomplete():
|
| 267 |
+
"""Never run and attempted-but-failed leave the same hole."""
|
| 268 |
+
r = C.compile_report("G", "A", window=WINDOW, offset=0,
|
| 269 |
+
consequence={"ok": False, "error": "boom"})
|
| 270 |
+
assert "Assess edit consequence" in r.incomplete_because
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def test_a_failed_consequence_does_not_block_the_compile():
|
| 274 |
+
"""The lesion still routes; only the record is incomplete."""
|
| 275 |
+
r = C.compile_report("G", "A", window=WINDOW, offset=0,
|
| 276 |
+
consequence={"ok": False, "error": "boom"})
|
| 277 |
+
assert r.compiled
|
| 278 |
|
| 279 |
|
| 280 |
def test_a_refused_lesion_skips_the_rest_rather_than_reporting_ok():
|