bindsight 0.3.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bindsight/README.md +44 -0
- bindsight/__init__.py +15 -0
- bindsight/benchmark/__init__.py +71 -0
- bindsight/benchmark/core.py +497 -0
- bindsight/benchmark/defaults.py +28 -0
- bindsight/benchmark/designer_bench.py +899 -0
- bindsight/benchmark/outcomes.py +508 -0
- bindsight/benchmark/panel.py +621 -0
- bindsight/benchmark/rediscovery.py +1153 -0
- bindsight/benchmark/statistics.py +622 -0
- bindsight/benchmark/study.py +1153 -0
- bindsight/benchmark/study_figures.py +164 -0
- bindsight/benchmark/study_report.py +607 -0
- bindsight/cli.py +2429 -0
- bindsight/config.py +451 -0
- bindsight/cost.py +258 -0
- bindsight/deg/__init__.py +7 -0
- bindsight/deg/pydeseq2_runner.py +285 -0
- bindsight/design/__init__.py +23 -0
- bindsight/design/_common.py +501 -0
- bindsight/design/bindcraft.py +78 -0
- bindsight/design/boltzgen.py +79 -0
- bindsight/design/developability.py +140 -0
- bindsight/design/embeddings.py +165 -0
- bindsight/design/prescreen.py +169 -0
- bindsight/design/protocol.py +111 -0
- bindsight/design/rfdiff_mpnn.py +93 -0
- bindsight/epitopes/__init__.py +7 -0
- bindsight/epitopes/surface_bind.py +134 -0
- bindsight/export/__init__.py +7 -0
- bindsight/export/ro_crate.py +595 -0
- bindsight/io/__init__.py +19 -0
- bindsight/io/cbioportal.py +118 -0
- bindsight/io/gdc.py +343 -0
- bindsight/io/paths.py +124 -0
- bindsight/pipelines/__init__.py +9 -0
- bindsight/pipelines/caveats.py +60 -0
- bindsight/pipelines/discover.py +1443 -0
- bindsight/pipelines/full_run.py +410 -0
- bindsight/plugins.py +335 -0
- bindsight/provenance/__init__.py +34 -0
- bindsight/provenance/append.py +199 -0
- bindsight/provenance/fragments.py +216 -0
- bindsight/provenance/manifest.py +506 -0
- bindsight/rank/__init__.py +7 -0
- bindsight/rank/scoring.py +355 -0
- bindsight/report/__init__.py +39 -0
- bindsight/report/format.py +100 -0
- bindsight/report/html.py +645 -0
- bindsight/report/showcase.py +653 -0
- bindsight/report/templates/report.css +67 -0
- bindsight/report/templates/report.html.j2 +420 -0
- bindsight/report/theme.py +231 -0
- bindsight/report/web/__init__.py +35 -0
- bindsight/report/web/app.py +624 -0
- bindsight/report/web/static/binder_viewer.js +245 -0
- bindsight/report/web/static/bindsight.css +455 -0
- bindsight/report/web/static/charts.js +425 -0
- bindsight/report/web/static/vendor/3Dmol-min.js +2 -0
- bindsight/report/web/static/vendor/3Dmol.LICENSE +90 -0
- bindsight/report/web/static/your_data_check.js +370 -0
- bindsight/report/web/templates/base.html.j2 +48 -0
- bindsight/report/web/templates/evidence.html.j2 +341 -0
- bindsight/report/web/templates/overview.html.j2 +140 -0
- bindsight/report/web/templates/runs.html.j2 +125 -0
- bindsight/report/web/templates/try.html.j2 +155 -0
- bindsight/report/web/templates/your_data.html.j2 +46 -0
- bindsight/runners/__init__.py +7 -0
- bindsight/runners/colab.py +158 -0
- bindsight/runners/job_exec.py +1110 -0
- bindsight/runners/kaggle.py +425 -0
- bindsight/runners/kaggle_kernel.py +489 -0
- bindsight/runners/local_docker.py +154 -0
- bindsight/runners/mock.py +167 -0
- bindsight/runners/modal_runner.py +199 -0
- bindsight/runners/notebook.py +116 -0
- bindsight/runners/notebook_content.py +217 -0
- bindsight/runners/protocol.py +81 -0
- bindsight/runners/source_wheel.py +104 -0
- bindsight/runners/tools.py +997 -0
- bindsight/structures/__init__.py +7 -0
- bindsight/structures/alphafolddb.py +93 -0
- bindsight/structures/plddt.py +92 -0
- bindsight/structures/topology.py +227 -0
- bindsight/surfaceome/__init__.py +42 -0
- bindsight/surfaceome/data/surfaceome_extended.tsv +4814 -0
- bindsight/surfaceome/data/surfy_offline.txt +17 -0
- bindsight/surfaceome/data/surfy_v1.ensembl.tsv +3380 -0
- bindsight/surfaceome/data/surfy_v1.uniprot.txt +2901 -0
- bindsight/surfaceome/surfy.py +441 -0
- bindsight/targets/__init__.py +7 -0
- bindsight/targets/ensembl_uniprot.py +56 -0
- bindsight/targets/gtex.py +250 -0
- bindsight/targets/open_targets.py +239 -0
- bindsight/validate/__init__.py +7 -0
- bindsight/validate/af2_ig.py +49 -0
- bindsight/validate/boltz2.py +256 -0
- bindsight/validate/chai1r.py +42 -0
- bindsight/validate/protocol.py +188 -0
- bindsight-0.3.5.data/data/bindsight_demo/config.yaml +54 -0
- bindsight-0.3.5.dist-info/METADATA +495 -0
- bindsight-0.3.5.dist-info/RECORD +106 -0
- bindsight-0.3.5.dist-info/WHEEL +4 -0
- bindsight-0.3.5.dist-info/entry_points.txt +20 -0
- bindsight-0.3.5.dist-info/licenses/COPYRIGHT +24 -0
- bindsight-0.3.5.dist-info/licenses/LICENSE +681 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
{% extends "base.html.j2" %}
|
|
2
|
+
{% block title %}Try it — bindsight{% endblock %}
|
|
3
|
+
|
|
4
|
+
{% block content %}
|
|
5
|
+
<h1>Try it</h1>
|
|
6
|
+
<p class="lede">
|
|
7
|
+
The discovery half, end to end, on a real cohort. No GPU, no account, nothing
|
|
8
|
+
to configure.
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<div class="card">
|
|
12
|
+
<h2 class="card__title">What this runs</h3>
|
|
13
|
+
<div class="grid grid--2">
|
|
14
|
+
{#- `.fact`, not `.stat`. A `.stat` is a measurement and must carry its
|
|
15
|
+
denominator or interval; these are descriptions, and borrowing the
|
|
16
|
+
figure component for them would make that rule unenforceable. -#}
|
|
17
|
+
{% for key, value in cohort.items() %}
|
|
18
|
+
<div class="fact">
|
|
19
|
+
<span class="fact__label">{{ key }}</span>
|
|
20
|
+
<span class="fact__value">{{ value }}</span>
|
|
21
|
+
</div>
|
|
22
|
+
{% endfor %}
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="btn-row">
|
|
26
|
+
<button class="btn btn--primary" id="start">Run the demo</button>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<!-- Announced, not just drawn. The run takes minutes on a cold cache and
|
|
30
|
+
every update happens without the focus moving, so without these a
|
|
31
|
+
screen-reader user hears nothing between pressing the button and giving
|
|
32
|
+
up. `polite` for progress, which is a running commentary; the outcome
|
|
33
|
+
node carries `role="status"` or `role="alert"` depending on which it
|
|
34
|
+
turns out to be. -->
|
|
35
|
+
<ol class="steps" id="steps" aria-live="polite" aria-atomic="false" hidden></ol>
|
|
36
|
+
<div id="outcome" aria-live="polite" aria-atomic="true"></div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="note note--info">
|
|
40
|
+
<span class="note__glyph" aria-hidden="true">i</span>
|
|
41
|
+
<div class="note__body">
|
|
42
|
+
<p class="small" style="margin:0">
|
|
43
|
+
Well-known antigens appear among the candidates when their signal is
|
|
44
|
+
present in the cohort. They are not guaranteed to: in whole unstratified
|
|
45
|
+
TCGA-BRCA, ERBB2 measures a log2 fold change below the filter, and the
|
|
46
|
+
study says so rather than quietly stratifying until it does not.
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
{% endblock %}
|
|
51
|
+
|
|
52
|
+
{% block scripts %}
|
|
53
|
+
<script>
|
|
54
|
+
// Per-stage progress. A single indeterminate spinner cannot tell a reader
|
|
55
|
+
// whether a run is slow or stuck, and on a cold cache this is minutes.
|
|
56
|
+
//
|
|
57
|
+
// Every value below comes from `/api/job/<id>`, whose `error` is `str(exc)`
|
|
58
|
+
// from a bare `except` -- so it carries whatever text the pipeline raised,
|
|
59
|
+
// including a design table's own column names. This built that into markup by
|
|
60
|
+
// string concatenation, which is the defect fixed in `your_data_check.js` and
|
|
61
|
+
// then missed here because the sweep that enforces the rule read `.js` files
|
|
62
|
+
// and this script lives in a template. Nodes and textContent, same as there.
|
|
63
|
+
const GLYPH = { pending: "\u25cb", running: "\u25d0", done: "\u2713", failed: "\u2717" };
|
|
64
|
+
|
|
65
|
+
function el(tag, className) {
|
|
66
|
+
const node = document.createElement(tag);
|
|
67
|
+
if (className) node.className = className;
|
|
68
|
+
return node;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function note(kind, glyph, bodyNodes) {
|
|
72
|
+
const wrap = el("div", "note note--" + kind);
|
|
73
|
+
const mark = el("span", "note__glyph");
|
|
74
|
+
mark.setAttribute("aria-hidden", "true");
|
|
75
|
+
mark.textContent = glyph;
|
|
76
|
+
const body = el("div", "note__body");
|
|
77
|
+
bodyNodes.forEach(function (n) { body.appendChild(n); });
|
|
78
|
+
wrap.appendChild(mark);
|
|
79
|
+
wrap.appendChild(body);
|
|
80
|
+
return wrap;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function stepItem(s) {
|
|
84
|
+
const li = el("li");
|
|
85
|
+
li.setAttribute("data-state", s.state);
|
|
86
|
+
const glyph = el("span", "steps__glyph");
|
|
87
|
+
glyph.setAttribute("aria-hidden", "true");
|
|
88
|
+
glyph.textContent = GLYPH[s.state] || "";
|
|
89
|
+
const spoken = el("span", "visually-hidden");
|
|
90
|
+
spoken.textContent = " (" + s.state + ")";
|
|
91
|
+
li.appendChild(glyph);
|
|
92
|
+
li.appendChild(document.createTextNode(s.name));
|
|
93
|
+
li.appendChild(spoken);
|
|
94
|
+
return li;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
document.getElementById("start").addEventListener("click", async function () {
|
|
98
|
+
const btn = this;
|
|
99
|
+
btn.disabled = true;
|
|
100
|
+
btn.textContent = "Running\u2026";
|
|
101
|
+
const outcome = document.getElementById("outcome");
|
|
102
|
+
outcome.innerHTML = "";
|
|
103
|
+
const steps = document.getElementById("steps");
|
|
104
|
+
steps.hidden = false;
|
|
105
|
+
|
|
106
|
+
const started = await fetch("/try/run", { method: "POST" });
|
|
107
|
+
const { job } = await started.json();
|
|
108
|
+
|
|
109
|
+
const poll = setInterval(async function () {
|
|
110
|
+
const res = await fetch("/api/job/" + job);
|
|
111
|
+
const state = await res.json();
|
|
112
|
+
|
|
113
|
+
steps.innerHTML = "";
|
|
114
|
+
state.stages.forEach(function (s) { steps.appendChild(stepItem(s)); });
|
|
115
|
+
|
|
116
|
+
if (state.state === "done" || state.state === "failed") {
|
|
117
|
+
clearInterval(poll);
|
|
118
|
+
btn.disabled = false;
|
|
119
|
+
btn.textContent = "Run it again";
|
|
120
|
+
|
|
121
|
+
let card;
|
|
122
|
+
if (state.state === "done") {
|
|
123
|
+
const line = el("p");
|
|
124
|
+
line.style.margin = "0";
|
|
125
|
+
const where = el("code");
|
|
126
|
+
where.textContent = String(state.run_dir);
|
|
127
|
+
const link = el("a");
|
|
128
|
+
link.href = "/runs";
|
|
129
|
+
link.textContent = "browse it";
|
|
130
|
+
line.appendChild(document.createTextNode("Complete. The run is in "));
|
|
131
|
+
line.appendChild(where);
|
|
132
|
+
line.appendChild(document.createTextNode(" \u2014 "));
|
|
133
|
+
line.appendChild(link);
|
|
134
|
+
line.appendChild(document.createTextNode("."));
|
|
135
|
+
card = note("ok", "\u2713", [line]);
|
|
136
|
+
card.setAttribute("role", "status");
|
|
137
|
+
} else {
|
|
138
|
+
const headline = el("p");
|
|
139
|
+
headline.style.margin = "0";
|
|
140
|
+
const strong = el("strong");
|
|
141
|
+
strong.textContent = "The demo did not finish.";
|
|
142
|
+
headline.appendChild(strong);
|
|
143
|
+
const detail = el("p", "small");
|
|
144
|
+
detail.style.margin = ".5rem 0 0";
|
|
145
|
+
// textContent: this is the one value an attacker can reach.
|
|
146
|
+
detail.textContent = String(state.error == null ? "" : state.error);
|
|
147
|
+
card = note("err", "\u2717", [headline, detail]);
|
|
148
|
+
card.setAttribute("role", "alert");
|
|
149
|
+
}
|
|
150
|
+
outcome.appendChild(card);
|
|
151
|
+
}
|
|
152
|
+
}, 700);
|
|
153
|
+
});
|
|
154
|
+
</script>
|
|
155
|
+
{% endblock %}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{% extends "base.html.j2" %}
|
|
2
|
+
{% block title %}Your data — bindsight{% endblock %}
|
|
3
|
+
|
|
4
|
+
{% block content %}
|
|
5
|
+
<h1>Your data</h1>
|
|
6
|
+
<p class="lede">
|
|
7
|
+
A counts matrix and a sample design table naming a two-level factor. Nothing
|
|
8
|
+
on this path knows about cancer — <code>tumor</code> and <code>normal</code>
|
|
9
|
+
are values you supply, not values the code expects.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<div class="card">
|
|
13
|
+
<h2 class="card__title">Files</h3>
|
|
14
|
+
<div class="field">
|
|
15
|
+
<label for="counts">Counts matrix <span class="muted">(.tsv, .txt, .gz)</span></label>
|
|
16
|
+
<input type="file" id="counts" accept=".tsv,.txt,.gz">
|
|
17
|
+
<p class="field__help">Genes as rows, samples as columns, integer counts.</p>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="field">
|
|
20
|
+
<label for="design">Sample design table <span class="muted">(.tsv, .txt, .gz)</span></label>
|
|
21
|
+
<input type="file" id="design" accept=".tsv,.txt,.gz">
|
|
22
|
+
<p class="field__help">One row per sample, with a column naming the condition.</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div id="checks" aria-live="polite" aria-atomic="false"></div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="note note--info">
|
|
29
|
+
<span class="note__glyph" aria-hidden="true">i</span>
|
|
30
|
+
<div class="note__body">
|
|
31
|
+
<p class="small" style="margin:0">
|
|
32
|
+
Files are checked the moment you choose them — shape, separator, and
|
|
33
|
+
whether the sample names in the two tables agree. The contrast levels are
|
|
34
|
+
read from your design file and offered back to you, rather than typed: a
|
|
35
|
+
mistyped level used to surface as a generic pipeline failure minutes
|
|
36
|
+
later, after the cohort had already been processed.
|
|
37
|
+
</p>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
{% endblock %}
|
|
41
|
+
|
|
42
|
+
{% block scripts %}
|
|
43
|
+
{#- The checker is bindsight/report/web/static/your_data_check.js, shared with
|
|
44
|
+
the published documentation site so the two cannot diverge. -#}
|
|
45
|
+
<script src="/static/your_data_check.js"></script>
|
|
46
|
+
{% endblock %}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Mikhaeel Atef Rizk Wahba
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
"""GPU-runner abstraction (Colab / Modal / Kaggle / local Docker / mock)."""
|
|
4
|
+
|
|
5
|
+
from bindsight.runners.protocol import CostEstimate, GPURunner, JobHandle, JobStatus
|
|
6
|
+
|
|
7
|
+
__all__ = ["CostEstimate", "GPURunner", "JobHandle", "JobStatus"]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Mikhaeel Atef Rizk Wahba
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
"""Colab GPU runner.
|
|
4
|
+
|
|
5
|
+
Colab is a free-or-cheap GPU on-ramp. bindsight doesn't have a way to *launch*
|
|
6
|
+
a Colab notebook from the CLI (Google's API doesn't permit it for free-tier
|
|
7
|
+
users), so the workflow is:
|
|
8
|
+
|
|
9
|
+
1. ``submit()`` writes a self-contained ``.ipynb`` to disk and prints the
|
|
10
|
+
command for the user to open it in Colab.
|
|
11
|
+
2. The notebook installs deps, runs the GPU step, tarballs results, writes
|
|
12
|
+
them to a known location (Google Drive folder, or anywhere the user can
|
|
13
|
+
download from).
|
|
14
|
+
3. ``poll()`` checks whether ``results.tar.gz`` has appeared in the local
|
|
15
|
+
results directory (the user downloads it from Drive after the notebook
|
|
16
|
+
finishes).
|
|
17
|
+
4. ``fetch()`` returns the local path once it's there.
|
|
18
|
+
|
|
19
|
+
This is intentionally simple. v0.2 may add Drive API auto-fetch via OAuth;
|
|
20
|
+
v0.0.x explicitly favors "user clicks two buttons in Colab" over "wire
|
|
21
|
+
OAuth and fight rate limits".
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import uuid
|
|
28
|
+
from datetime import UTC, datetime
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from bindsight.cost import estimate
|
|
33
|
+
from bindsight.runners.protocol import CostEstimate, JobHandle, JobStatus
|
|
34
|
+
|
|
35
|
+
LOG = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ColabRunner:
|
|
39
|
+
"""Local-disk Colab job orchestrator.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
notebooks_dir: where templated ``.ipynb`` files are written. Default
|
|
43
|
+
is ``./runs/<run_id>/colab_notebooks/``.
|
|
44
|
+
designer: passed through to the cost estimator.
|
|
45
|
+
n_units_per_target: trajectories per design (used for cost estimate).
|
|
46
|
+
gpu_type: GPU to request from Colab (``T4`` for free, ``A100`` for Pro+).
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
name = "colab"
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
notebooks_dir: Path | None = None,
|
|
54
|
+
*,
|
|
55
|
+
designer: str = "rfdiff_mpnn",
|
|
56
|
+
n_units_per_target: int = 50,
|
|
57
|
+
gpu_type: str = "T4",
|
|
58
|
+
) -> None:
|
|
59
|
+
self.notebooks_dir = Path(notebooks_dir) if notebooks_dir else None
|
|
60
|
+
self.designer = designer
|
|
61
|
+
self.n_units_per_target = n_units_per_target
|
|
62
|
+
self.gpu_type = gpu_type
|
|
63
|
+
|
|
64
|
+
def estimate_cost(self, spec_size: int) -> CostEstimate:
|
|
65
|
+
"""Estimate cost for ``spec_size`` design trajectories."""
|
|
66
|
+
return estimate(
|
|
67
|
+
backend=self.name,
|
|
68
|
+
stage="design",
|
|
69
|
+
plugin=self.designer,
|
|
70
|
+
n_units=spec_size,
|
|
71
|
+
gpu_type=self.gpu_type,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def submit(self, spec_path: Path, *, results_dir: Path) -> JobHandle:
|
|
75
|
+
"""Write a Colab notebook + spec to disk, return a JobHandle.
|
|
76
|
+
|
|
77
|
+
The user is responsible for opening the notebook in Colab. The
|
|
78
|
+
``handle.id`` is used as the cache key for results.
|
|
79
|
+
"""
|
|
80
|
+
results_dir.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
handle_id = str(uuid.uuid4())
|
|
82
|
+
notebook_dest = results_dir / f"{handle_id}.ipynb"
|
|
83
|
+
|
|
84
|
+
notebook = self._build_notebook(spec_path=spec_path, handle_id=handle_id)
|
|
85
|
+
notebook_dest.write_text(notebook, encoding="utf-8", newline="\n")
|
|
86
|
+
|
|
87
|
+
LOG.info(
|
|
88
|
+
"Colab notebook written to %s. Open it in Colab, run all cells, "
|
|
89
|
+
"and place the resulting %s.tar.gz in %s.",
|
|
90
|
+
notebook_dest,
|
|
91
|
+
handle_id,
|
|
92
|
+
results_dir,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return JobHandle(
|
|
96
|
+
backend=self.name,
|
|
97
|
+
id=handle_id,
|
|
98
|
+
submitted_at=datetime.now(UTC).isoformat(timespec="seconds"),
|
|
99
|
+
notebook_path=str(notebook_dest),
|
|
100
|
+
results_dir=str(results_dir),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def poll(self, handle: JobHandle) -> JobStatus:
|
|
104
|
+
"""Check whether the user has placed the results tarball in the dir."""
|
|
105
|
+
results_dir = Path((handle.model_extra or {}).get("results_dir") or ".")
|
|
106
|
+
archive = results_dir / f"{handle.id}.tar.gz"
|
|
107
|
+
if archive.exists():
|
|
108
|
+
return JobStatus(handle=handle, state="succeeded", progress=1.0)
|
|
109
|
+
return JobStatus(
|
|
110
|
+
handle=handle,
|
|
111
|
+
state="running",
|
|
112
|
+
progress=None,
|
|
113
|
+
log_tail="awaiting results.tar.gz from Colab user",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def fetch(self, handle: JobHandle) -> Path:
|
|
117
|
+
"""Return the local path to the results tarball.
|
|
118
|
+
|
|
119
|
+
Raises ``FileNotFoundError`` if the user hasn't dropped the tarball
|
|
120
|
+
in the configured results_dir yet — call :meth:`poll` first.
|
|
121
|
+
"""
|
|
122
|
+
results_dir = Path((handle.model_extra or {}).get("results_dir") or ".")
|
|
123
|
+
archive = results_dir / f"{handle.id}.tar.gz"
|
|
124
|
+
if not archive.exists():
|
|
125
|
+
raise FileNotFoundError(
|
|
126
|
+
f"results tarball not yet available: {archive}. "
|
|
127
|
+
"Run the Colab notebook and place the output here."
|
|
128
|
+
)
|
|
129
|
+
return archive
|
|
130
|
+
|
|
131
|
+
# ---------------------------------------------------------------- #
|
|
132
|
+
# Notebook construction #
|
|
133
|
+
# ---------------------------------------------------------------- #
|
|
134
|
+
def _build_notebook(self, *, spec_path: Path, handle_id: str) -> str:
|
|
135
|
+
"""Build the design + validation notebook with REAL RFdiff+MPNN+Boltz cells.
|
|
136
|
+
|
|
137
|
+
Delegates to :mod:`bindsight.runners.notebook_content` which holds the
|
|
138
|
+
canonical install + inference cell templates patterned on ColabDesign,
|
|
139
|
+
dl_binder_design, and the upstream Boltz-2 README.
|
|
140
|
+
"""
|
|
141
|
+
import json as _json # local alias keeps it out of module init time
|
|
142
|
+
|
|
143
|
+
from bindsight.runners.notebook_content import build_design_notebook
|
|
144
|
+
|
|
145
|
+
spec: dict[str, Any] = {}
|
|
146
|
+
if Path(spec_path).exists():
|
|
147
|
+
try:
|
|
148
|
+
spec = _json.loads(Path(spec_path).read_text(encoding="utf-8"))
|
|
149
|
+
except _json.JSONDecodeError:
|
|
150
|
+
spec = {}
|
|
151
|
+
|
|
152
|
+
notebook = build_design_notebook(
|
|
153
|
+
handle_id=handle_id,
|
|
154
|
+
designer=self.designer,
|
|
155
|
+
gpu_type=self.gpu_type,
|
|
156
|
+
spec=spec,
|
|
157
|
+
)
|
|
158
|
+
return _json.dumps(notebook, indent=1)
|