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,67 @@
|
|
|
1
|
+
/* SPDX-FileCopyrightText: 2026 Mikhaeel Atef Rizk Wahba
|
|
2
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* The report's own layer, on top of the shared design system.
|
|
5
|
+
*
|
|
6
|
+
* Everything a reader recognises -- statistics, badges, notes, tables, the
|
|
7
|
+
* absence dash -- comes from bindsight.css, so the report and the served
|
|
8
|
+
* interface are the same product. What is left here is what only a single
|
|
9
|
+
* self-contained file needs: a masthead, an embedded plot, and the row
|
|
10
|
+
* highlight that marks a candidate inside the top N.
|
|
11
|
+
*
|
|
12
|
+
* This file used to be 212 lines and carried a second copy of the tokens, a
|
|
13
|
+
* `.badge` that differed from the app's, and a `.kpi` component with no
|
|
14
|
+
* denominator slot -- which exempted the report's four headline numbers from
|
|
15
|
+
* the rule the rest of the project enforces.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
main {
|
|
19
|
+
max-width: var(--measure-wide, 72rem);
|
|
20
|
+
margin: 0 auto;
|
|
21
|
+
padding: var(--s-8) var(--s-6) var(--s-16);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Masthead. The app has a navigation rail here; a file opened from an email
|
|
25
|
+
has nowhere to navigate to, so it gets the run's identity instead. */
|
|
26
|
+
header {
|
|
27
|
+
background: var(--navy-deep);
|
|
28
|
+
color: #eaf1fa;
|
|
29
|
+
padding: var(--s-8) var(--s-6);
|
|
30
|
+
}
|
|
31
|
+
header .inner { max-width: var(--measure-wide, 72rem); margin: 0 auto; }
|
|
32
|
+
header h1 { color: #fff; margin-bottom: var(--s-2); }
|
|
33
|
+
header .subtitle { color: #c7d8ee; margin: 0 0 var(--s-3); font-size: var(--t-sm); }
|
|
34
|
+
header .meta { color: #a8c0dd; font-size: var(--t-xs); }
|
|
35
|
+
header .meta code { background: rgba(255, 255, 255, 0.12); color: #eaf1fa; }
|
|
36
|
+
header a { color: #cfe0f5; }
|
|
37
|
+
|
|
38
|
+
section { margin-bottom: var(--s-12); }
|
|
39
|
+
|
|
40
|
+
/* The volcano plot is embedded as a data URI, so it must not be able to push
|
|
41
|
+
the page wider than the measure. */
|
|
42
|
+
img.plot {
|
|
43
|
+
display: block;
|
|
44
|
+
width: 100%;
|
|
45
|
+
height: auto;
|
|
46
|
+
border: 1px solid var(--rule);
|
|
47
|
+
border-radius: var(--radius);
|
|
48
|
+
background: var(--surface);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* A candidate inside the top N. Backed by a badge in the same row, so the
|
|
52
|
+
tint is a second signal rather than the only one. */
|
|
53
|
+
tbody tr.top { background: #fffbe6; }
|
|
54
|
+
|
|
55
|
+
footer {
|
|
56
|
+
border-top: 1px solid var(--rule);
|
|
57
|
+
padding: var(--s-6);
|
|
58
|
+
color: var(--ink-soft);
|
|
59
|
+
font-size: var(--t-xs);
|
|
60
|
+
text-align: center;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@media print {
|
|
64
|
+
header { background: none; color: var(--ink); padding: 0 0 var(--s-4); }
|
|
65
|
+
header h1, header .subtitle, header .meta { color: var(--ink); }
|
|
66
|
+
main { padding: 0; }
|
|
67
|
+
}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>bindsight report — {{ run_name }}</title>
|
|
6
|
+
<style>{{ css|safe }}</style>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<header>
|
|
10
|
+
<div class="inner">
|
|
11
|
+
<h1>bindsight report</h1>
|
|
12
|
+
<p class="subtitle">RNA-seq → de novo protein binder candidates, with end-to-end provenance.</p>
|
|
13
|
+
<div class="meta">
|
|
14
|
+
run: <strong>{{ run_name }}</strong> ·
|
|
15
|
+
id: <code>{{ run_id }}</code> ·
|
|
16
|
+
created: {{ created_at }}
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</header>
|
|
20
|
+
|
|
21
|
+
<main>
|
|
22
|
+
|
|
23
|
+
{% if failed_stages %}
|
|
24
|
+
<section id="incomplete">
|
|
25
|
+
<h2>Incomplete run — a stage failed</h2>
|
|
26
|
+
<div class="note note--err"><span class="note__glyph" aria-hidden="true">✗</span><div class="note__body"><p style="margin:0">
|
|
27
|
+
<strong>These results are incomplete: a pipeline stage failed, so this is not a
|
|
28
|
+
real negative result.</strong>
|
|
29
|
+
The run stopped at
|
|
30
|
+
{% for f in failed_stages %}<code>{{ f.name }}</code>{% if not loop.last %}, {% endif %}{% endfor %},
|
|
31
|
+
which means the counts and tables below record a crash rather than a
|
|
32
|
+
measurement. Nothing here supports a conclusion — not even a negative one
|
|
33
|
+
— until the failure is fixed and the run repeated.
|
|
34
|
+
</p></div></div>
|
|
35
|
+
<div class="table-wrap">
|
|
36
|
+
<table>
|
|
37
|
+
<thead><tr><th>failed stage</th><th>error</th></tr></thead>
|
|
38
|
+
<tbody>
|
|
39
|
+
{% for f in failed_stages %}
|
|
40
|
+
<tr>
|
|
41
|
+
<td><strong>{{ f.name }}</strong></td>
|
|
42
|
+
<td><code>{{ f.error or 'no error detail recorded' }}</code></td>
|
|
43
|
+
</tr>
|
|
44
|
+
{% endfor %}
|
|
45
|
+
</tbody>
|
|
46
|
+
</table>
|
|
47
|
+
</div>
|
|
48
|
+
</section>
|
|
49
|
+
{% endif %}
|
|
50
|
+
|
|
51
|
+
<section>
|
|
52
|
+
<h2>Summary</h2>
|
|
53
|
+
<!-- `.stat`, not a private `.kpi`: the component carries the rule that a
|
|
54
|
+
figure appears with its denominator. Each of these is a survival count,
|
|
55
|
+
and the denominator is the stage before it — which is the shape of the
|
|
56
|
+
funnel and was already on disk. -->
|
|
57
|
+
<div class="row">
|
|
58
|
+
<div class="stat{% if not n_deg %} stat--absent{% endif %}">
|
|
59
|
+
<span class="stat__label">Genes tested</span>
|
|
60
|
+
<span class="stat__value">{{ n_deg or '—'|safe }}</span>
|
|
61
|
+
<span class="stat__of">rows in the differential-expression table</span>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="stat{% if n_significant is none %} stat--absent{% endif %}">
|
|
64
|
+
<span class="stat__label">Significant DEGs</span>
|
|
65
|
+
<span class="stat__value">{{ n_significant }}</span>
|
|
66
|
+
<span class="stat__of">of {{ n_deg or '—'|safe }} tested</span>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="stat{% if n_candidates is none %} stat--absent{% endif %}">
|
|
69
|
+
<span class="stat__label">Candidate targets</span>
|
|
70
|
+
<span class="stat__value">{{ n_candidates }}</span>
|
|
71
|
+
<span class="stat__of">of {{ n_significant or '—'|safe }} significant, after the surfaceome filter</span>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="stat{% if n_epitopes is none %} stat--absent{% endif %}">
|
|
74
|
+
<span class="stat__label">Epitopes</span>
|
|
75
|
+
<span class="stat__value">{{ n_epitopes }}</span>
|
|
76
|
+
<span class="stat__of">of {{ n_candidates or '—'|safe }} candidates, top-N only</span>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
{% if failed_stages %}
|
|
80
|
+
<div class="note note--err"><span class="note__glyph" aria-hidden="true">✗</span><div class="note__body"><p style="margin:0">
|
|
81
|
+
The counts above are whatever the crashed run left on disk — they are not
|
|
82
|
+
measurements. See <a href="#incomplete">Incomplete run</a>.
|
|
83
|
+
</p></div></div>
|
|
84
|
+
{% endif %}
|
|
85
|
+
<div class="note note--info"><span class="note__glyph" aria-hidden="true">i</span><div class="note__body"><p style="margin:0">
|
|
86
|
+
Every number above is one click from its evidence in the
|
|
87
|
+
<a href="#provenance">provenance</a> section. The artifacts on disk
|
|
88
|
+
(Parquet + JSON-LD) are the canonical record — this HTML is a
|
|
89
|
+
rendered view.
|
|
90
|
+
</p></div></div>
|
|
91
|
+
</section>
|
|
92
|
+
|
|
93
|
+
{% if volcano_b64 %}
|
|
94
|
+
<section>
|
|
95
|
+
<h2>Differential expression</h2>
|
|
96
|
+
<p>
|
|
97
|
+
Volcano plot of the differential expression analysis.
|
|
98
|
+
{% if deg_fdr is not none and deg_log2fc is not none %}
|
|
99
|
+
Significant genes (padj < {{ deg_fdr }} and
|
|
100
|
+
|log2FC| ≥ {{ deg_log2fc }}) are highlighted; the dashed lines
|
|
101
|
+
mark those cutoffs.
|
|
102
|
+
{% else %}
|
|
103
|
+
Significant genes are highlighted. This run did not record the cutoffs it
|
|
104
|
+
applied, so no threshold lines are drawn.
|
|
105
|
+
{% endif %}
|
|
106
|
+
</p>
|
|
107
|
+
<img class="plot" src="data:image/png;base64,{{ volcano_b64 }}" alt="Volcano plot">
|
|
108
|
+
</section>
|
|
109
|
+
{% endif %}
|
|
110
|
+
|
|
111
|
+
<section>
|
|
112
|
+
<h2>Candidate targets</h2>
|
|
113
|
+
<p>
|
|
114
|
+
Genes that survived all filters: significantly differentially expressed,
|
|
115
|
+
surface-exposed (SURFY), antibody-tractable (Open Targets, when enabled),
|
|
116
|
+
and below the safety-event threshold. Top-N rows highlighted.
|
|
117
|
+
</p>
|
|
118
|
+
{% if candidates_table %}
|
|
119
|
+
<div class="table-wrap">
|
|
120
|
+
<table>
|
|
121
|
+
<thead>
|
|
122
|
+
<tr>
|
|
123
|
+
<th>rank</th><th>symbol</th><th>UniProt</th>
|
|
124
|
+
<th>log2FC</th><th>padj</th>
|
|
125
|
+
<th>tractable modalities</th><th>safety events</th>
|
|
126
|
+
<th>has structure</th><th>top-N</th>
|
|
127
|
+
</tr>
|
|
128
|
+
</thead>
|
|
129
|
+
<tbody>
|
|
130
|
+
{% for r in candidates_table %}
|
|
131
|
+
<tr {% if r.rank_in_top_n %}class="top"{% endif %}>
|
|
132
|
+
<td class="num">{{ r.rank }}</td>
|
|
133
|
+
<td><strong>{{ r.symbol or '-' }}</strong></td>
|
|
134
|
+
<td><code>{{ r.uniprot_id or '-' }}</code></td>
|
|
135
|
+
<td class="num">{{ r.log2fc }}</td>
|
|
136
|
+
<td class="num">{{ r.padj }}</td>
|
|
137
|
+
<td>{{ r.tractable_modalities or '-' }}</td>
|
|
138
|
+
<td class="num">{{ r.n_safety_events }}</td>
|
|
139
|
+
<td>
|
|
140
|
+
{% if r.has_alphafold_structure %}<span class="badge badge--ok"><span class="badge__glyph" aria-hidden="true">✓</span>yes</span>
|
|
141
|
+
{% else %}<span class="badge badge--warn"><span class="badge__glyph" aria-hidden="true">!</span>no</span>{% endif %}
|
|
142
|
+
</td>
|
|
143
|
+
<td>
|
|
144
|
+
{% if r.rank_in_top_n %}<span class="badge badge--ok"><span class="badge__glyph" aria-hidden="true">✓</span>top</span>{% endif %}
|
|
145
|
+
</td>
|
|
146
|
+
</tr>
|
|
147
|
+
{% endfor %}
|
|
148
|
+
</tbody>
|
|
149
|
+
</table>
|
|
150
|
+
</div>
|
|
151
|
+
{% elif failed_stages %}
|
|
152
|
+
<div class="note note--err"><span class="note__glyph" aria-hidden="true">✗</span><div class="note__body"><p style="margin:0">
|
|
153
|
+
No candidate table exists because the run did not finish (see
|
|
154
|
+
<a href="#incomplete">above</a>). The filters were never reached, so loosening
|
|
155
|
+
them would be answering a question that was never asked.
|
|
156
|
+
</p></div></div>
|
|
157
|
+
{% elif candidates_unreadable %}
|
|
158
|
+
<div class="note note--err"><span class="note__glyph" aria-hidden="true">✗</span><div class="note__body"><p style="margin:0">
|
|
159
|
+
The candidate table could not be read, so how many candidates survived is
|
|
160
|
+
unknown. This is not the same as none surviving: nothing was measured.
|
|
161
|
+
</p></div></div>
|
|
162
|
+
{% else %}
|
|
163
|
+
<div class="note"><span class="note__glyph" aria-hidden="true">—</span><div class="note__body"><p style="margin:0">No candidates survived the filters. Loosen thresholds in the config and re-run.</p></div></div>
|
|
164
|
+
{% endif %}
|
|
165
|
+
</section>
|
|
166
|
+
|
|
167
|
+
<section>
|
|
168
|
+
<h2>Epitopes (top-N targets)</h2>
|
|
169
|
+
<p>
|
|
170
|
+
For each top-N target, the AlphaFoldDB structure path and SURFACE-Bind
|
|
171
|
+
epitope status. <code>surface_bind_site</code> = a vendored targetable site
|
|
172
|
+
(focused design); <code>no_surface_bind_site</code> /
|
|
173
|
+
<code>surface_bind_not_configured</code> = whole-surface design (the
|
|
174
|
+
SURFACE-Bind data tree had no site for this protein, or wasn't vendored
|
|
175
|
+
— see <code>data/surface_bind/</code> to populate it).
|
|
176
|
+
<code>surface_bind_lookup_failed</code> is different from both: the lookup
|
|
177
|
+
<em>errored</em>, so nothing is known about this protein either way. It is
|
|
178
|
+
not a measured absence of a site.
|
|
179
|
+
</p>
|
|
180
|
+
{% if epitopes_table %}
|
|
181
|
+
<div class="table-wrap">
|
|
182
|
+
<table>
|
|
183
|
+
<thead>
|
|
184
|
+
<tr><th>symbol</th><th>UniProt</th><th>structure</th><th>site id</th><th>status</th></tr>
|
|
185
|
+
</thead>
|
|
186
|
+
<tbody>
|
|
187
|
+
{% for e in epitopes_table %}
|
|
188
|
+
<tr>
|
|
189
|
+
<td><strong>{{ e.symbol or '-' }}</strong></td>
|
|
190
|
+
<td><code>{{ e.uniprot_id or '-' }}</code></td>
|
|
191
|
+
<td><code>{{ e.structure_path or '-' }}</code></td>
|
|
192
|
+
<td>{{ e.site_id or '-' }}</td>
|
|
193
|
+
<td>
|
|
194
|
+
{% if e.epitope_status == 'surface_bind_site' %}
|
|
195
|
+
<span class="badge badge--ok"><span class="badge__glyph" aria-hidden="true">✓</span>{{ e.epitope_status }}</span>
|
|
196
|
+
{% else %}
|
|
197
|
+
<span class="badge badge--warn"><span class="badge__glyph" aria-hidden="true">!</span>{{ e.epitope_status }}</span>
|
|
198
|
+
{% endif %}
|
|
199
|
+
</td>
|
|
200
|
+
</tr>
|
|
201
|
+
{% endfor %}
|
|
202
|
+
</tbody>
|
|
203
|
+
</table>
|
|
204
|
+
</div>
|
|
205
|
+
{% else %}
|
|
206
|
+
<div class="note"><span class="note__glyph" aria-hidden="true">—</span><div class="note__body"><p style="margin:0">No top-N epitopes to display.</p></div></div>
|
|
207
|
+
{% endif %}
|
|
208
|
+
</section>
|
|
209
|
+
|
|
210
|
+
<section>
|
|
211
|
+
<h2>Designed binders</h2>
|
|
212
|
+
<p>
|
|
213
|
+
{% if n_binders > binders_table|length %}The {{ binders_table|length }}
|
|
214
|
+
best-scoring of the {{ n_binders }} designs the run produced{% else %}Every
|
|
215
|
+
design the run produced{% endif %}, ranked by the composite score. <code>ipTM</code>
|
|
216
|
+
is the validator's interface confidence (higher is better) and
|
|
217
|
+
<code>PAE-int</code> the predicted interface error in Ångström
|
|
218
|
+
(lower is better). <code>passes</code> records each design against the
|
|
219
|
+
configured quality bars; nothing is dropped on their account unless
|
|
220
|
+
<code>apply_thresholds</code> is set.
|
|
221
|
+
</p>
|
|
222
|
+
{% if binders_table %}
|
|
223
|
+
<div class="table-wrap">
|
|
224
|
+
<table>
|
|
225
|
+
<thead>
|
|
226
|
+
<tr>
|
|
227
|
+
<th>rank</th><th>binder</th><th>target</th><th>ipTM</th>
|
|
228
|
+
<th>PAE-int</th><th>score</th><th>passes</th>
|
|
229
|
+
{% if include_binders %}<th>sequence</th>{% endif %}
|
|
230
|
+
</tr>
|
|
231
|
+
</thead>
|
|
232
|
+
<tbody>
|
|
233
|
+
{% for b in binders_table %}
|
|
234
|
+
<tr>
|
|
235
|
+
<td>{{ b.rank or '-' }}</td>
|
|
236
|
+
<td><code>{{ b.binder_id or '-' }}</code></td>
|
|
237
|
+
<td><strong>{{ b.symbol or b.target_uniprot or '-' }}</strong></td>
|
|
238
|
+
<td>{{ b.iptm or '-' }}</td>
|
|
239
|
+
<td>{{ b.pae_interaction or '-' }}</td>
|
|
240
|
+
<td>{{ b.score or '-' }}</td>
|
|
241
|
+
<td>
|
|
242
|
+
{% if b.passes_thresholds == 'pass' %}
|
|
243
|
+
<span class="badge badge--ok"><span class="badge__glyph" aria-hidden="true">✓</span>pass</span>
|
|
244
|
+
{% elif b.passes_thresholds == 'fail' %}
|
|
245
|
+
<span class="badge badge--warn"><span class="badge__glyph" aria-hidden="true">!</span>fail</span>
|
|
246
|
+
{% else %}
|
|
247
|
+
<span class="badge badge--info">{{ b.passes_thresholds or '-' }}</span>
|
|
248
|
+
{% endif %}
|
|
249
|
+
</td>
|
|
250
|
+
{% if include_binders %}<td><code>{{ b.sequence or '-' }}</code></td>{% endif %}
|
|
251
|
+
</tr>
|
|
252
|
+
{% endfor %}
|
|
253
|
+
</tbody>
|
|
254
|
+
</table>
|
|
255
|
+
</div>
|
|
256
|
+
{% if structure_ids %}
|
|
257
|
+
<h3>The complexes themselves</h3>
|
|
258
|
+
<p class="small muted">
|
|
259
|
+
Drawn from the mmCIF carried inside this file — no server, no network
|
|
260
|
+
request, nothing to install.
|
|
261
|
+
{% if structures_omitted %}
|
|
262
|
+
The {{ structure_ids|length }} that fit are embedded, best-ranked first;
|
|
263
|
+
{{ structures_omitted }} more {{ 'are' if structures_omitted != 1 else 'is' }}
|
|
264
|
+
in <code>design/_targets/</code>, left out so this stays a file you can send.
|
|
265
|
+
{% endif %}
|
|
266
|
+
</p>
|
|
267
|
+
<div class="field viewer-picker" style="max-width:32rem">
|
|
268
|
+
<label for="binder-pick">Design</label>
|
|
269
|
+
<select id="binder-pick" data-binder-picker>
|
|
270
|
+
{% for bid in structure_ids %}
|
|
271
|
+
<option value="{{ bid }}">{{ bid }}</option>
|
|
272
|
+
{% endfor %}
|
|
273
|
+
</select>
|
|
274
|
+
</div>
|
|
275
|
+
<div id="viewer" class="viewer" role="img" data-binder-viewer
|
|
276
|
+
aria-label="Three-dimensional view of the predicted binder-target complex">
|
|
277
|
+
<p class="viewer__fallback small muted" role="alert">
|
|
278
|
+
The structure viewer needs JavaScript. The complexes are in this file
|
|
279
|
+
either way, as mmCIF.
|
|
280
|
+
</p>
|
|
281
|
+
</div>
|
|
282
|
+
<div class="viewer-legend">
|
|
283
|
+
<span><span class="swatch" style="background:#9aa5b1"></span>
|
|
284
|
+
chain <strong>T</strong> — the target</span>
|
|
285
|
+
<span><span class="swatch" style="background:#0f7d73"></span>
|
|
286
|
+
chain <strong>B</strong> — the designed binder</span>
|
|
287
|
+
</div>
|
|
288
|
+
{% elif structures_omitted %}
|
|
289
|
+
<h3>The complexes themselves</h3>
|
|
290
|
+
<p class="small muted">
|
|
291
|
+
None are embedded here. {{ structures_omitted }} predicted complex{{ 'es' if structures_omitted != 1 else '' }}
|
|
292
|
+
would have pushed this file past the size it will carry, so
|
|
293
|
+
{{ 'they are' if structures_omitted != 1 else 'it is' }} in
|
|
294
|
+
<code>design/_targets/</code> instead. Open
|
|
295
|
+
{{ 'them' if structures_omitted != 1 else 'it' }} in any structure viewer,
|
|
296
|
+
or re-render with a larger budget. Saying nothing here would have left this
|
|
297
|
+
section silently missing.
|
|
298
|
+
</p>
|
|
299
|
+
{% endif %}
|
|
300
|
+
{% if not include_binders %}
|
|
301
|
+
<div class="note"><span class="note__glyph" aria-hidden="true">—</span><div class="note__body"><p style="margin:0">
|
|
302
|
+
Re-render with <code>--include-binders</code> to embed each designed sequence.
|
|
303
|
+
</p></div></div>
|
|
304
|
+
{% endif %}
|
|
305
|
+
{% else %}
|
|
306
|
+
<div class="note"><span class="note__glyph" aria-hidden="true">—</span><div class="note__body"><p style="margin:0">
|
|
307
|
+
No designed binders in this run. Run <code>bindsight design</code> and
|
|
308
|
+
<code>bindsight rank</code> to produce them.
|
|
309
|
+
</p></div></div>
|
|
310
|
+
{% endif %}
|
|
311
|
+
</section>
|
|
312
|
+
|
|
313
|
+
{% if taxonomy_counts %}
|
|
314
|
+
<section>
|
|
315
|
+
<h2>Why candidates dropped (negative-result taxonomy)</h2>
|
|
316
|
+
<p>
|
|
317
|
+
Each of the {{ n_taxonomy }} differentially-expressed genes gets exactly one
|
|
318
|
+
disposition explaining why it did or didn't become a surfaced candidate —
|
|
319
|
+
so the failure modes are auditable rather than discarded. The counts sum to the
|
|
320
|
+
gene total.
|
|
321
|
+
</p>
|
|
322
|
+
<div class="table-wrap">
|
|
323
|
+
<table>
|
|
324
|
+
<thead><tr><th>disposition</th><th>genes</th><th>share</th></tr></thead>
|
|
325
|
+
<tbody>
|
|
326
|
+
{% for d in taxonomy_counts %}
|
|
327
|
+
<tr {% if d.disposition == 'surfaced' %}class="top"{% endif %}>
|
|
328
|
+
<td>
|
|
329
|
+
{% if d.disposition == 'surfaced' %}<span class="badge badge--ok"><span class="badge__glyph" aria-hidden="true">✓</span>{{ d.disposition }}</span>
|
|
330
|
+
{% else %}<code>{{ d.disposition }}</code>{% endif %}
|
|
331
|
+
</td>
|
|
332
|
+
<td class="num">{{ d.count }}</td>
|
|
333
|
+
<td class="num">{{ d.pct }}</td>
|
|
334
|
+
</tr>
|
|
335
|
+
{% endfor %}
|
|
336
|
+
</tbody>
|
|
337
|
+
</table>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="note"><span class="note__glyph" aria-hidden="true">—</span><div class="note__body"><p style="margin:0">Full per-gene table: <code>taxonomy/failure_taxonomy.parquet</code>.</p></div></div>
|
|
340
|
+
</section>
|
|
341
|
+
{% endif %}
|
|
342
|
+
|
|
343
|
+
<section id="provenance">
|
|
344
|
+
<h2>Provenance</h2>
|
|
345
|
+
<p>
|
|
346
|
+
Every stage's tool, version, license, inputs (with SHA-256), and outputs
|
|
347
|
+
(with SHA-256). The full PROV-O JSON-LD is in
|
|
348
|
+
<code>run_manifest.jsonld</code> at the run root.
|
|
349
|
+
</p>
|
|
350
|
+
{% if stages %}
|
|
351
|
+
<div class="table-wrap">
|
|
352
|
+
<table>
|
|
353
|
+
<thead>
|
|
354
|
+
<tr><th>stage</th><th>tool</th><th>version</th><th>license</th><th>status</th><th>notes</th></tr>
|
|
355
|
+
</thead>
|
|
356
|
+
<tbody>
|
|
357
|
+
{% for s in stages %}
|
|
358
|
+
<tr>
|
|
359
|
+
<td><strong>{{ s.name }}</strong></td>
|
|
360
|
+
<td><code>{{ s.tool.name }}</code></td>
|
|
361
|
+
<td>{{ s.tool.version }}</td>
|
|
362
|
+
<td>{{ s.tool.license }}</td>
|
|
363
|
+
<td>
|
|
364
|
+
{% if s.status == 'completed' %}<span class="badge badge--ok"><span class="badge__glyph" aria-hidden="true">✓</span>{{ s.status }}</span>
|
|
365
|
+
{% elif s.status == 'failed' %}<span class="badge badge--err">{{ s.status }}</span>
|
|
366
|
+
{% else %}<span class="badge badge--info">{{ s.status }}</span>{% endif %}
|
|
367
|
+
</td>
|
|
368
|
+
<td>{{ s.notes or '' }}</td>
|
|
369
|
+
</tr>
|
|
370
|
+
{% endfor %}
|
|
371
|
+
</tbody>
|
|
372
|
+
</table>
|
|
373
|
+
</div>
|
|
374
|
+
{% else %}
|
|
375
|
+
<div class="note"><span class="note__glyph" aria-hidden="true">—</span><div class="note__body"><p style="margin:0">No manifest stages found.</p></div></div>
|
|
376
|
+
{% endif %}
|
|
377
|
+
</section>
|
|
378
|
+
|
|
379
|
+
<section id="limitations">
|
|
380
|
+
<h2>Limitations</h2>
|
|
381
|
+
<p>The honest scope of expression-based discovery — read before trusting a candidate:</p>
|
|
382
|
+
<ul>
|
|
383
|
+
{% for lim in limitations %}
|
|
384
|
+
<li><strong>{{ lim.title }}.</strong> {{ lim.body }}</li>
|
|
385
|
+
{% endfor %}
|
|
386
|
+
</ul>
|
|
387
|
+
</section>
|
|
388
|
+
|
|
389
|
+
<section>
|
|
390
|
+
<h2>How to read this report</h2>
|
|
391
|
+
<ul>
|
|
392
|
+
<li><strong>Candidate targets</strong> are ranked by the combined
|
|
393
|
+
differential-expression score π = log2FC × −log10(padj).
|
|
394
|
+
Structure availability does not affect that rank: it selects which of
|
|
395
|
+
the top-ranked candidates can be carried into the GPU half, because a
|
|
396
|
+
design needs a structure to design against.</li>
|
|
397
|
+
<li><strong>Epitopes</strong> become the input to <code>bindsight design</code>
|
|
398
|
+
once SURFACE-Bind data is populated.</li>
|
|
399
|
+
<li><strong>Provenance</strong> shows what ran. Every entry's
|
|
400
|
+
SHA-256 hashes are in <code>run_manifest.jsonld</code>.</li>
|
|
401
|
+
<li><strong>Limitations</strong> states what expression-based discovery can and
|
|
402
|
+
cannot establish — candidates are hypotheses, not validated targets.</li>
|
|
403
|
+
</ul>
|
|
404
|
+
</section>
|
|
405
|
+
|
|
406
|
+
<footer>
|
|
407
|
+
Generated by <a href="https://github.com/mikhaeelatefrizk/bindsight"><code>bindsight</code></a>.
|
|
408
|
+
Cite the upstream tools you used (see <code>LICENSING.md</code>).
|
|
409
|
+
</footer>
|
|
410
|
+
|
|
411
|
+
</main>
|
|
412
|
+
{% if structure_ids %}
|
|
413
|
+
{# Inlined, not linked: this file has to work with no server and no siblings.
|
|
414
|
+
The viewer is the same one bindsight ui and the documentation site load. #}
|
|
415
|
+
<script>{{ viewer_lib|safe }}</script>
|
|
416
|
+
<script>window.BINDSIGHT_STRUCTURES = {{ structures_json|safe }};</script>
|
|
417
|
+
<script>{{ viewer_js|safe }}</script>
|
|
418
|
+
{% endif %}
|
|
419
|
+
</body>
|
|
420
|
+
</html>
|