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.
Files changed (106) hide show
  1. bindsight/README.md +44 -0
  2. bindsight/__init__.py +15 -0
  3. bindsight/benchmark/__init__.py +71 -0
  4. bindsight/benchmark/core.py +497 -0
  5. bindsight/benchmark/defaults.py +28 -0
  6. bindsight/benchmark/designer_bench.py +899 -0
  7. bindsight/benchmark/outcomes.py +508 -0
  8. bindsight/benchmark/panel.py +621 -0
  9. bindsight/benchmark/rediscovery.py +1153 -0
  10. bindsight/benchmark/statistics.py +622 -0
  11. bindsight/benchmark/study.py +1153 -0
  12. bindsight/benchmark/study_figures.py +164 -0
  13. bindsight/benchmark/study_report.py +607 -0
  14. bindsight/cli.py +2429 -0
  15. bindsight/config.py +451 -0
  16. bindsight/cost.py +258 -0
  17. bindsight/deg/__init__.py +7 -0
  18. bindsight/deg/pydeseq2_runner.py +285 -0
  19. bindsight/design/__init__.py +23 -0
  20. bindsight/design/_common.py +501 -0
  21. bindsight/design/bindcraft.py +78 -0
  22. bindsight/design/boltzgen.py +79 -0
  23. bindsight/design/developability.py +140 -0
  24. bindsight/design/embeddings.py +165 -0
  25. bindsight/design/prescreen.py +169 -0
  26. bindsight/design/protocol.py +111 -0
  27. bindsight/design/rfdiff_mpnn.py +93 -0
  28. bindsight/epitopes/__init__.py +7 -0
  29. bindsight/epitopes/surface_bind.py +134 -0
  30. bindsight/export/__init__.py +7 -0
  31. bindsight/export/ro_crate.py +595 -0
  32. bindsight/io/__init__.py +19 -0
  33. bindsight/io/cbioportal.py +118 -0
  34. bindsight/io/gdc.py +343 -0
  35. bindsight/io/paths.py +124 -0
  36. bindsight/pipelines/__init__.py +9 -0
  37. bindsight/pipelines/caveats.py +60 -0
  38. bindsight/pipelines/discover.py +1443 -0
  39. bindsight/pipelines/full_run.py +410 -0
  40. bindsight/plugins.py +335 -0
  41. bindsight/provenance/__init__.py +34 -0
  42. bindsight/provenance/append.py +199 -0
  43. bindsight/provenance/fragments.py +216 -0
  44. bindsight/provenance/manifest.py +506 -0
  45. bindsight/rank/__init__.py +7 -0
  46. bindsight/rank/scoring.py +355 -0
  47. bindsight/report/__init__.py +39 -0
  48. bindsight/report/format.py +100 -0
  49. bindsight/report/html.py +645 -0
  50. bindsight/report/showcase.py +653 -0
  51. bindsight/report/templates/report.css +67 -0
  52. bindsight/report/templates/report.html.j2 +420 -0
  53. bindsight/report/theme.py +231 -0
  54. bindsight/report/web/__init__.py +35 -0
  55. bindsight/report/web/app.py +624 -0
  56. bindsight/report/web/static/binder_viewer.js +245 -0
  57. bindsight/report/web/static/bindsight.css +455 -0
  58. bindsight/report/web/static/charts.js +425 -0
  59. bindsight/report/web/static/vendor/3Dmol-min.js +2 -0
  60. bindsight/report/web/static/vendor/3Dmol.LICENSE +90 -0
  61. bindsight/report/web/static/your_data_check.js +370 -0
  62. bindsight/report/web/templates/base.html.j2 +48 -0
  63. bindsight/report/web/templates/evidence.html.j2 +341 -0
  64. bindsight/report/web/templates/overview.html.j2 +140 -0
  65. bindsight/report/web/templates/runs.html.j2 +125 -0
  66. bindsight/report/web/templates/try.html.j2 +155 -0
  67. bindsight/report/web/templates/your_data.html.j2 +46 -0
  68. bindsight/runners/__init__.py +7 -0
  69. bindsight/runners/colab.py +158 -0
  70. bindsight/runners/job_exec.py +1110 -0
  71. bindsight/runners/kaggle.py +425 -0
  72. bindsight/runners/kaggle_kernel.py +489 -0
  73. bindsight/runners/local_docker.py +154 -0
  74. bindsight/runners/mock.py +167 -0
  75. bindsight/runners/modal_runner.py +199 -0
  76. bindsight/runners/notebook.py +116 -0
  77. bindsight/runners/notebook_content.py +217 -0
  78. bindsight/runners/protocol.py +81 -0
  79. bindsight/runners/source_wheel.py +104 -0
  80. bindsight/runners/tools.py +997 -0
  81. bindsight/structures/__init__.py +7 -0
  82. bindsight/structures/alphafolddb.py +93 -0
  83. bindsight/structures/plddt.py +92 -0
  84. bindsight/structures/topology.py +227 -0
  85. bindsight/surfaceome/__init__.py +42 -0
  86. bindsight/surfaceome/data/surfaceome_extended.tsv +4814 -0
  87. bindsight/surfaceome/data/surfy_offline.txt +17 -0
  88. bindsight/surfaceome/data/surfy_v1.ensembl.tsv +3380 -0
  89. bindsight/surfaceome/data/surfy_v1.uniprot.txt +2901 -0
  90. bindsight/surfaceome/surfy.py +441 -0
  91. bindsight/targets/__init__.py +7 -0
  92. bindsight/targets/ensembl_uniprot.py +56 -0
  93. bindsight/targets/gtex.py +250 -0
  94. bindsight/targets/open_targets.py +239 -0
  95. bindsight/validate/__init__.py +7 -0
  96. bindsight/validate/af2_ig.py +49 -0
  97. bindsight/validate/boltz2.py +256 -0
  98. bindsight/validate/chai1r.py +42 -0
  99. bindsight/validate/protocol.py +188 -0
  100. bindsight-0.3.5.data/data/bindsight_demo/config.yaml +54 -0
  101. bindsight-0.3.5.dist-info/METADATA +495 -0
  102. bindsight-0.3.5.dist-info/RECORD +106 -0
  103. bindsight-0.3.5.dist-info/WHEEL +4 -0
  104. bindsight-0.3.5.dist-info/entry_points.txt +20 -0
  105. bindsight-0.3.5.dist-info/licenses/COPYRIGHT +24 -0
  106. bindsight-0.3.5.dist-info/licenses/LICENSE +681 -0
@@ -0,0 +1,341 @@
1
+ {% extends "base.html.j2" %}
2
+ {% block title %}Evidence — bindsight{% endblock %}
3
+
4
+ {% block content %}
5
+ <h1>Evidence</h1>
6
+ <p class="lede">
7
+ Everything on this page is read from <code>benchmarks/</code> in the
8
+ repository — the same files the manuscripts cite. Nothing is illustrative,
9
+ recomputed on the fly, or typed by hand.
10
+ </p>
11
+
12
+ {% if not study and not designer %}
13
+ <div class="note note--warn">
14
+ <span class="note__glyph" aria-hidden="true">!</span>
15
+ <div class="note__body">
16
+ <p><strong>No evidence tree in this install.</strong> <code>benchmarks/</code>
17
+ ships with the repository rather than the wheel.</p>
18
+ <p class="small"><a href="{{ github }}/tree/main/benchmarks">Read it on GitHub →</a></p>
19
+ </div>
20
+ </div>
21
+ {% endif %}
22
+
23
+ {% if study %}
24
+ <h2 id="discovery">Does it resurface antigens we already trust?</h2>
25
+ <p class="prose">
26
+ <strong>{{ study.n_scored }} antigen-cohort pairs across {{ study.n_projects }}
27
+ real TCGA projects.</strong> Each cohort is every patient in the project who
28
+ contributed both a primary tumour and a solid-tissue normal, contrasted
29
+ pairwise on the patient. That is a subset of the project — a tumour with no
30
+ matched normal is not in it — and the subset is chosen by sample availability,
31
+ never by the antigen under test. No cohort is stratified by any biomarker.
32
+ </p>
33
+
34
+ <h3>Recall, on the pre-registered denominator</h3>
35
+ {% set cascade = (study.recall_cascade or {}).get('all', {}) %}
36
+ {% set w20 = ((cascade.get('at_k') or {}).get('recall@20') or {}).get('wilson') or {} %}
37
+ <div class="card">
38
+ <div class="row">
39
+ <div class="stat">
40
+ <span class="stat__label">Recall @ 20</span>
41
+ <span class="stat__value">{{ w20.get('numerator', ABSENT) }}/{{ w20.get('denominator', ABSENT) }}</span>
42
+ <span class="stat__ci">{{ interval(w20.get('low'), w20.get('high'), digits=2, confidence=w20.get('confidence')) }}</span>
43
+ <span class="stat__note">
44
+ Approved-agent antigens only — the tiers the study pre-registered.
45
+ </span>
46
+ </div>
47
+ </div>
48
+ {% if recall %}
49
+ <div data-chart="bars" data-spec='{{ tojson(recall) }}' style="margin-top:1rem"></div>
50
+ <p class="chart__caption">
51
+ Each bar is a cutoff; the figure beside it is the numerator over the
52
+ denominator, not a percentage on its own.
53
+ </p>
54
+ {% endif %}
55
+ </div>
56
+
57
+ {% if study.headline %}
58
+ {% set top = study.headline %}
59
+ <h3>The best-ranked antigen, and why it is shown separately</h3>
60
+ <div class="card">
61
+ <div class="row">
62
+ <div class="stat">
63
+ <span class="stat__label">{{ top.symbol }} rank</span>
64
+ <span class="stat__value">{{ top.rank }}</span>
65
+ <span class="stat__of">of {{ fmt_int(top.shortlist_size) }} candidates in
66
+ {{ top.project|replace('TCGA-','') }}</span>
67
+ </div>
68
+ <div class="stat">
69
+ <span class="stat__label">log2 fold change</span>
70
+ <span class="stat__value">{{ fmt(top.log2fc) }}</span>
71
+ <span class="stat__ci">
72
+ {% if top.get('lfc_se') is not none %}± {{ fmt(top.lfc_se) }} (SE){% else %}{{ ABSENT }}{% endif %}
73
+ </span>
74
+ </div>
75
+ </div>
76
+
77
+ <div class="note note--info" style="margin-top:1rem">
78
+ <span class="note__glyph" aria-hidden="true">i</span>
79
+ <div class="note__body">
80
+ <p class="small" style="margin:0">
81
+ <strong>{{ top.symbol }} is not in the recall figure above.</strong> Its
82
+ regulatory tier is <code>{{ top.get('tier', ABSENT) }}</code>, and the
83
+ study pre-registered
84
+ <code>{{ study.primary_tiers|join(', ') or ABSENT }}</code> as the primary
85
+ denominator. The two numbers answer different questions and are kept
86
+ apart deliberately: placing them side by side invites a reader to bind
87
+ the “1” in a recall fraction to a rank of 1 for a different antigen.
88
+ </p>
89
+ </div>
90
+ </div>
91
+
92
+ {% if top.get('note') %}
93
+ <div class="note note--warn" style="margin-top:.75rem">
94
+ <span class="note__glyph" aria-hidden="true">!</span>
95
+ <div class="note__body">
96
+ <p class="small" style="margin:0">
97
+ <strong>What qualifies this result.</strong> {{ top.note }}
98
+ </p>
99
+ </div>
100
+ </div>
101
+ {% endif %}
102
+ </div>
103
+ {% endif %}
104
+
105
+ <h3 id="nulls">Is the ranking doing anything? Two nulls, pointing different ways</h3>
106
+ <p class="prose">
107
+ Both are reported. Reporting only the flattering one would make the other the
108
+ finding.
109
+ </p>
110
+
111
+ <div class="grid grid--2">
112
+ <section class="card">
113
+ <h4 class="card__title">
114
+ <span class="badge badge--warn"><span class="badge__glyph" aria-hidden="true">–</span>negative</span>
115
+ Against abundance-matched decoys
116
+ </h4>
117
+ <div class="stat">
118
+ <span class="stat__label">Survive Benjamini-Hochberg</span>
119
+ <span class="stat__value">{{ study.decoy_surviving_correction }}</span>
120
+ <span class="stat__of">of {{ study.decoy_rows|length }} pairs
121
+ ({{ study.decoy_nominal }} nominally significant at 0.05)</span>
122
+ </div>
123
+ <p class="small" style="margin-top:.75rem">
124
+ Each antigen is compared with genes matched to it on abundance and
125
+ dispersion quintile, drawn from the same eligible surfaceome: would a gene
126
+ that merely <em>looks</em> like this antigen have ranked as well?
127
+ </p>
128
+ {% if study.smallest_attainable_decoy_bh is not none %}
129
+ <p class="small muted">
130
+ Read with the panel's resolution in mind: each pair's p is bounded below
131
+ by its own stratum size, so the smallest adjusted value this panel could
132
+ have produced is <strong>{{ fmt(study.smallest_attainable_decoy_bh, 3) }}</strong>.
133
+ Under 0.05 — a pair genuinely could have survived, but only one sitting
134
+ essentially on its floor. A measurement, not a foregone conclusion, and
135
+ not a sensitive one.
136
+ </p>
137
+ {% endif %}
138
+ </section>
139
+
140
+ <section class="card">
141
+ <h4 class="card__title">
142
+ <span class="badge badge--ok"><span class="badge__glyph" aria-hidden="true">✓</span>positive</span>
143
+ Against cohorts carrying no panel antigen, and against a permuted indication
144
+ </h4>
145
+ {% set gap = study.indication_gap %}
146
+ {% set spec = study.specificity_null %}
147
+ {% if gap.get('point') is not none %}
148
+ <div class="stat">
149
+ <span class="stat__label">Within-antigen difference</span>
150
+ <span class="stat__value">{{ fmt(gap.point, 3) }}</span>
151
+ <span class="stat__ci">
152
+ {{ interval(gap.low, gap.high, confidence=gap.get('confidence')) }}
153
+ over {{ gap.get('n_clusters', ABSENT) }} antigens — excludes zero
154
+ </span>
155
+ </div>
156
+ {% endif %}
157
+ {% if spec.get('p_value') is not none %}
158
+ {#
159
+ A second analysis, not a p-value for the difference above. The difference
160
+ is a cluster bootstrap over the antigens that appear in the calibration
161
+ cohorts; this is an exact permutation test over the antigens carrying a
162
+ single indication, because the permutation assigns one cohort per antigen.
163
+ The two denominators differ, and stating the p under the difference
164
+ without saying so invited the reader to read it as its significance.
165
+ #}
166
+ <p class="small" style="margin-top:.75rem">
167
+ <strong>A second test, on its own denominator.</strong> Against every
168
+ reassignment of cohorts to antigens, the mean standing is
169
+ <strong>{{ fmt(spec.get('observed'), 3) }}</strong>: exact permutation p =
170
+ <strong>{{ fmt_p(spec.p_value) }}</strong> over
171
+ {{ fmt_int(spec.get('n_permutations')) }} enumerated orderings of
172
+ {{ fmt_int(spec.get('n_antigens')) }} antigens{% if study.specificity_is_at_its_floor %} —
173
+ the floor for this design, so it is as extreme as an exhaustive
174
+ enumeration of this panel can show rather than vanishingly small{% endif %}.
175
+ Restricted to antigens with a single indication, because the permutation
176
+ assigns one cohort to each. This p is not the significance of the
177
+ difference above, which is reported with an interval and no p.
178
+ </p>
179
+ {% endif %}
180
+ {% if nulls %}
181
+ <div data-chart="forest" data-spec='{{ tojson(nulls) }}'></div>
182
+ {% endif %}
183
+ <p class="small muted">
184
+ Each antigen is its own control: the comparison is within antigen, not
185
+ between two groups of them.
186
+ </p>
187
+ </section>
188
+ </div>
189
+
190
+ <h3>Every pair</h3>
191
+ <div class="table-wrap">
192
+ <table>
193
+ <thead>
194
+ <tr>
195
+ <th>Antigen</th><th>Cohort</th>
196
+ <th class="num">log2FC</th><th class="num">padj</th>
197
+ <th class="num">Rank</th>
198
+ <th class="num"><abbr title="Where it would have ranked with every gate removed">Counterfactual</abbr></th>
199
+ <th>Outcome</th>
200
+ </tr>
201
+ </thead>
202
+ <tbody>
203
+ {% for p in study.pairs %}
204
+ <tr>
205
+ <td><strong>{{ p.get('symbol', ABSENT) }}</strong></td>
206
+ <td>{{ p.get('project', ABSENT)|replace('TCGA-','') }}</td>
207
+ <td class="num">{{ fmt(p.get('log2fc')) }}</td>
208
+ <td class="num">{{ fmt_p(p.get('padj')) }}</td>
209
+ <td class="num">
210
+ {% if p.get('rank') is not none %}{{ p.rank }} of {{ fmt_int(p.get('shortlist_size')) }}
211
+ {% else %}{{ ABSENT }}{% endif %}
212
+ </td>
213
+ <td class="num">{{ fmt_int(p.get('counterfactual_rank')) }}</td>
214
+ <td class="small">{{ p.get('outcome_class', ABSENT)|replace('_',' ') }}</td>
215
+ </tr>
216
+ {% endfor %}
217
+ </tbody>
218
+ </table>
219
+ </div>
220
+ <p class="chart__caption">
221
+ A rank is printed with the size of the list it sits in. The counterfactual
222
+ column is what separates “a filter excluded it” from “the ranking placed it low”.
223
+ </p>
224
+ {% endif %}
225
+
226
+ {% if designer %}
227
+ <h2 id="design">The design half, and the control that withdrew its headline</h2>
228
+ <div class="card">
229
+ <div class="row">
230
+ <div class="stat">
231
+ <span class="stat__label">Designs</span>
232
+ <span class="stat__value">{{ fmt_int(designer.n_designs) }}</span>
233
+ <span class="stat__of">from {{ designer.n_trajectories or ABSENT }} backbones</span>
234
+ </div>
235
+ <div class="stat">
236
+ <span class="stat__label">Mean ipTM</span>
237
+ <span class="stat__value">{{ fmt(designer.mean_iptm) }}</span>
238
+ <span class="stat__of">validator {{ designer.validator or ABSENT }}</span>
239
+ </div>
240
+ <div class="stat">
241
+ <span class="stat__label">Hardware</span>
242
+ <span class="stat__value" style="font-size:var(--t-md)">{{ designer.gpu or ABSENT }}</span>
243
+ <span class="stat__of">bindsight {{ designer.bindsight_version or ABSENT }}</span>
244
+ </div>
245
+ </div>
246
+
247
+ <div class="withdrawn">
248
+ <strong>success@0.65 is withdrawn as a measure of design quality.</strong>
249
+ A paired control folded every design beside a shuffle of its own sequence —
250
+ same length, same amino-acid composition, only the order destroyed. Under a
251
+ seeded validator averaging five diffusion draws the shuffles cleared the
252
+ customary ipTM 0.65 bar <em>more</em> often than the designs did. The ipTM
253
+ values are real Boltz-2 outputs; what does not follow is that the rate
254
+ measures the designs. The control's predictions were written down before its
255
+ data arrived.
256
+ </div>
257
+
258
+ {% if structures %}
259
+ <h3 class="card__title" style="margin-top:1.5rem">The complexes themselves</h3>
260
+ <p class="small muted" style="margin-top:0">
261
+ Boltz-2's predicted binder&ndash;target complex for each design, as committed
262
+ to this repository. <span class="screen-only">Rotate it.</span><span
263
+ class="print-only">Each is committed as mmCIF under
264
+ <code>benchmarks/designer_benchmark/</code>; the viewer is not printed
265
+ because a WebGL canvas prints blank.</span> The numbers above are summaries
266
+ of these files, not of anything else.
267
+ </p>
268
+ <div class="field viewer-picker" style="max-width:32rem">
269
+ <label for="binder-pick">Design</label>
270
+ <select id="binder-pick" data-binder-picker>
271
+ {% for b in structures %}
272
+ <option value="{{ b.binder_id }}">
273
+ {{ b.binder_id }} — ipTM {{ fmt(b.iptm) }}
274
+ </option>
275
+ {% endfor %}
276
+ </select>
277
+ </div>
278
+ <div id="viewer" class="viewer" role="img" data-binder-viewer
279
+ data-structure-url="/api/structure/{id}"
280
+ aria-label="Three-dimensional view of the predicted binder-target complex">
281
+ <p class="viewer__fallback small muted" role="alert">
282
+ The structure viewer needs JavaScript. Every complex is also committed as
283
+ mmCIF under <code>benchmarks/designer_benchmark/</code>, which is the file
284
+ this viewer reads.
285
+ </p>
286
+ </div>
287
+ <div class="viewer-legend">
288
+ <span><span class="swatch" style="background:#9aa5b1"></span>
289
+ chain&nbsp;<strong>T</strong> — the target</span>
290
+ <span><span class="swatch" style="background:#0f7d73"></span>
291
+ chain&nbsp;<strong>B</strong> — the designed binder</span>
292
+ </div>
293
+ <p class="chart__caption">
294
+ The chain letters are in the file, so the two are distinguishable without
295
+ the colours — on a projector, in greyscale, and to a reader who does not
296
+ separate teal from grey.
297
+ </p>
298
+ {% endif %}
299
+
300
+ {% if paired %}
301
+ <div data-chart="pairedStrip" data-spec='{{ tojson(paired) }}' style="margin-top:1rem"></div>
302
+ <p class="chart__caption">
303
+ One line per pair, so the pairing is visible rather than two clouds. A red
304
+ line is a pair whose shuffle scored higher than the design it came from.
305
+ </p>
306
+ {% endif %}
307
+ </div>
308
+ {% endif %}
309
+
310
+ <h2>Reproducing any of this</h2>
311
+ <p class="prose">
312
+ Three tiers. The first needs nothing but a clone and runs in seconds; it
313
+ regenerates the published pages from committed files and should produce no
314
+ diff at all.
315
+ </p>
316
+ <pre><code>git clone {{ github }}
317
+ cd bindsight
318
+ pip install -e ".[discover,report]"
319
+ python scripts/build_docs_results.py # rewrites the published pages; nothing should change</code></pre>
320
+ <p class="small muted">
321
+ Re-running the study needs network and a few CPU-hours; re-running the design
322
+ needs a GPU. Both chains are documented in the repository.
323
+ </p>
324
+ {% endblock %}
325
+
326
+ {% block scripts %}
327
+ {% if structures %}
328
+ {#- Vendored, not from a CDN: `bindsight ui` is meant to work on a laptop with
329
+ no connection once installed, and a page that silently needs one does not.
330
+ The literal path stays in this template rather than moving into the script:
331
+ tests/test_web_ui.py sweeps .j2, .py, .css and .js for references to
332
+ every vendored asset. .js was added to that set when the viewer's script
333
+ left this template for a file of its own; before that, a path living
334
+ only in a .js would have made the sweep go quietly blind.
335
+
336
+ The viewer itself is bindsight/report/web/static/binder_viewer.js, shared
337
+ with the published documentation site so the two cannot diverge. -#}
338
+ <script src="/static/vendor/3Dmol-min.js"></script>
339
+ <script src="/static/binder_viewer.js"></script>
340
+ {% endif %}
341
+ {% endblock %}
@@ -0,0 +1,140 @@
1
+ {% extends "base.html.j2" %}
2
+ {% block title %}bindsight — RNA-seq to de novo binder design{% endblock %}
3
+
4
+ {% block content %}
5
+ <h1>bindsight</h1>
6
+ <p class="lede">
7
+ An open-source pipeline that joins cohort RNA-seq target discovery to de novo
8
+ protein binder design in one reproducible workflow, with machine-readable
9
+ provenance from every ranked binder back to the patient samples it came from.
10
+ </p>
11
+
12
+ <p class="prose">
13
+ Genomics tooling stops at <em>“here are the interesting genes.”</em>
14
+ Protein-design tooling starts at <em>“given a target…”</em>. The bridge between
15
+ them is built ad-hoc, per project, and rarely reproducibly. bindsight ships
16
+ that bridge as one tool and carries an audit trail across the join.
17
+ </p>
18
+
19
+ <div class="btn-row">
20
+ <a class="btn btn--primary" href="/evidence">See what it has shown</a>
21
+ <a class="btn" href="/try">Run the demo</a>
22
+ <a class="btn" href="/your-data">Use your own data</a>
23
+ </div>
24
+
25
+ <h2>What it has, and has not, shown</h2>
26
+ <p class="prose muted">
27
+ Stated here rather than further in, because the parts that did not work are
28
+ the reason to trust the parts that did.
29
+ </p>
30
+
31
+ {% if not have_evidence %}
32
+ <div class="note note--warn">
33
+ <span class="note__glyph" aria-hidden="true">!</span>
34
+ <div class="note__body">
35
+ <p><strong>The evidence is not available in this install.</strong></p>
36
+ <p class="small">
37
+ <code>benchmarks/</code> ships with the repository, not the wheel. Clone
38
+ the repository to see the study, the controls and the binders, or read
39
+ them on <a href="{{ github }}/tree/main/benchmarks">GitHub</a>.
40
+ </p>
41
+ </div>
42
+ </div>
43
+ {% else %}
44
+
45
+ <div class="grid grid--2">
46
+ <section class="card">
47
+ <h3 class="card__title">Discovery — one result survived its controls</h3>
48
+ {% if study %}
49
+ {% set gap = study.indication_gap %}
50
+ {% if gap.get('point') is not none %}
51
+ <div class="stat">
52
+ <span class="stat__label">Indication-specific gap</span>
53
+ <span class="stat__value">{{ fmt(gap.point, 3) }}</span>
54
+ <span class="stat__ci">{{ interval(gap.low, gap.high, confidence=gap.get('confidence')) }}
55
+ over {{ gap.get('n_clusters', ABSENT) }} antigens — excludes zero</span>
56
+ </div>
57
+ <p class="small muted" style="margin-top:.75rem">
58
+ Known antigens rank higher in the cancer they are actually used in than
59
+ in cancers that are not theirs. This is a claim about the ordering, not
60
+ about any individual hit.
61
+ </p>
62
+ {% endif %}
63
+ <p class="small">
64
+ Against abundance-matched decoys the result is <strong>negative</strong>:
65
+ {{ study.decoy_nominal }} of {{ study.decoy_rows|length }} pairs are
66
+ nominally significant and
67
+ <strong>{{ study.decoy_surviving_correction }}</strong> survive
68
+ Benjamini-Hochberg.
69
+ </p>
70
+ {% endif %}
71
+ <p><a href="/evidence">The study, its denominators and both nulls →</a></p>
72
+ </section>
73
+
74
+ <section class="card">
75
+ <h3 class="card__title">Design — the headline was withdrawn</h3>
76
+ {% if designer %}
77
+ <div class="stat">
78
+ <span class="stat__label">Binders designed and folded</span>
79
+ <span class="stat__value">{{ fmt_int(designer.n_designs) }}</span>
80
+ <span class="stat__of">
81
+ from {{ designer.n_trajectories or ABSENT }} backbones on
82
+ {{ designer.gpu or 'a free GPU' }} — structures committed
83
+ </span>
84
+ </div>
85
+ <div class="withdrawn">
86
+ <strong>success@0.65 is withdrawn as a measure of design quality.</strong>
87
+ A paired control folded each design beside a shuffle of its own sequence;
88
+ the shuffles cleared the bar more often than the designs did. The ipTM
89
+ values are real Boltz-2 outputs — what does not follow is that the rate
90
+ measures the designs.
91
+ </div>
92
+ {% endif %}
93
+ <p><a href="/evidence#design">The control, and what it refuted →</a></p>
94
+ </section>
95
+ </div>
96
+
97
+ <div class="grid grid--2" style="margin-top:1rem">
98
+ <section class="card">
99
+ <h3 class="card__title">Scope</h3>
100
+ {% if study %}
101
+ <div class="row">
102
+ <div class="stat">
103
+ <span class="stat__label">Cohorts</span>
104
+ <span class="stat__value">{{ study.n_projects }}</span>
105
+ <span class="stat__of">whole, unstratified TCGA projects</span>
106
+ </div>
107
+ <div class="stat">
108
+ <span class="stat__label">Antigen-cohort pairs</span>
109
+ <span class="stat__value">{{ study.n_scored }}</span>
110
+ <span class="stat__of">panel fixed in git before any cohort ran</span>
111
+ </div>
112
+ <div class="stat">
113
+ <span class="stat__label">Surfaceome</span>
114
+ <span class="stat__value">{{ fmt_int(study.surfaceome_size) }}</span>
115
+ <span class="stat__of">eligible accessions</span>
116
+ </div>
117
+ </div>
118
+ {% endif %}
119
+ </section>
120
+
121
+ <section class="card">
122
+ <h3 class="card__title">What has not been done</h3>
123
+ <ul class="small">
124
+ <li>
125
+ The three-way designer comparison against BindCraft and BoltzGen is
126
+ <strong>not run</strong>: both need 24–32&nbsp;GB against anything larger
127
+ than a small domain, so those arms require paid backends.
128
+ </li>
129
+ <li>
130
+ The calibration's own power analysis puts <strong>142 paired designs</strong>
131
+ at 80% power to detect a 0.05 ipTM difference. The committed control has 20.
132
+ </li>
133
+ </ul>
134
+ </section>
135
+ </div>
136
+ {% endif %}
137
+
138
+ <h2>Citing this</h2>
139
+ <p class="prose">{{ citation() }} <a href="{{ github }}">{{ github }}</a></p>
140
+ {% endblock %}
@@ -0,0 +1,125 @@
1
+ {% extends "base.html.j2" %}
2
+ {% block title %}Runs — bindsight{% endblock %}
3
+
4
+ {% block content %}
5
+ <h1>Runs</h1>
6
+ <p class="lede">Runs found under <code>{{ root }}</code>.</p>
7
+
8
+ {% if not runs %}
9
+ <div class="note note--info">
10
+ <span class="note__glyph" aria-hidden="true">i</span>
11
+ <div class="note__body">
12
+ <p style="margin:0">Nothing here yet.</p>
13
+ <p class="small" style="margin:.5rem 0 0">
14
+ Run the demo, or point bindsight at your own cohort.
15
+ </p>
16
+ <div class="btn-row" style="margin-bottom:0">
17
+ <a class="btn btn--primary" href="/try">Run the demo</a>
18
+ <a class="btn" href="/your-data">Use your own data</a>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ {% else %}
23
+ <div class="table-wrap">
24
+ <table>
25
+ <thead>
26
+ <tr>
27
+ <th>Run</th>
28
+ <th><abbr title="The PROV-O record of what produced this run">Manifest</abbr></th>
29
+ <th>Candidates</th>
30
+ <th>Path</th>
31
+ </tr>
32
+ </thead>
33
+ <tbody>
34
+ {% for r in runs %}
35
+ <tr>
36
+ <td><strong>{{ r.name }}</strong></td>
37
+ <td>
38
+ {% if r.has_manifest %}
39
+ <span class="badge badge--ok"><span class="badge__glyph" aria-hidden="true">✓</span>recorded</span>
40
+ {% else %}
41
+ <span class="badge badge--warn"><span class="badge__glyph" aria-hidden="true">!</span>absent</span>
42
+ {% endif %}
43
+ </td>
44
+ <td>
45
+ {% if r.verdict == 'failed' %}
46
+ <span class="badge badge--err"><span class="badge__glyph" aria-hidden="true">✗</span>a stage failed</span>
47
+ {% elif r.has_candidates %}
48
+ <span class="badge badge--ok"><span class="badge__glyph" aria-hidden="true">✓</span>present</span>
49
+ {% else %}
50
+ <span class="badge badge--warn"><span class="badge__glyph" aria-hidden="true">!</span>absent</span>
51
+ {% endif %}
52
+ </td>
53
+ <td class="small muted"><code>{{ r.path }}</code></td>
54
+ </tr>
55
+ {% endfor %}
56
+ </tbody>
57
+ </table>
58
+ </div>
59
+
60
+ {% set broken = runs | selectattr('failures') | list %}
61
+ {% if broken %}
62
+ <div class="note note--err">
63
+ <span class="note__glyph" aria-hidden="true">✗</span>
64
+ <div class="note__body">
65
+ <p style="margin:0"><strong>Some runs did not complete.</strong></p>
66
+ <p class="small">
67
+ These are not runs that found nothing — a stage crashed, and the empty
68
+ tables it left behind look identical. The stage and its error:
69
+ </p>
70
+ <ul class="small">
71
+ {% for r in broken %}
72
+ {% for f in r.failures %}
73
+ <li><code>{{ r.name }}</code> — <strong>{{ f.name }}</strong>: {{ f.error }}</li>
74
+ {% endfor %}
75
+ {% endfor %}
76
+ </ul>
77
+ </div>
78
+ </div>
79
+ {% endif %}
80
+ {% set degraded = runs | selectattr('open_targets') | list %}
81
+ {% if degraded %}
82
+ <div class="note note--warn">
83
+ <span class="note__glyph" aria-hidden="true">!</span>
84
+ <div class="note__body">
85
+ <p style="margin:0"><strong>Some shortlists were not drawn from the whole cohort.</strong></p>
86
+ <p class="small">
87
+ Open Targets is the only genome-wide Ensembl&nbsp;&rarr;&nbsp;UniProt source
88
+ here. Where it did not answer, the only mapping left is a bundled table of a
89
+ few well-known genes, and every gene missing from that table was dropped
90
+ before the surfaceome filter.
91
+ </p>
92
+ <ul class="small">
93
+ {% for r in degraded %}
94
+ {% set ot = r.open_targets %}
95
+ <li>
96
+ <code>{{ r.name }}</code> &mdash;
97
+ <strong>{{ ot.degraded }} of {{ ot.total }}</strong> candidates mapped from the
98
+ bundled offline table
99
+ ({% for b in ot.breakdown %}<code>{{ b.status }}</code>&nbsp;&times;&nbsp;{{ b.count }}{% if not loop.last %}, {% endif %}{% endfor %}).
100
+ {% if ot.total_outage %}
101
+ Open Targets answered for none of them, so that table's handful of genes
102
+ was the entire mapping. <strong>This is not a genome-wide discovery</strong>:
103
+ a gene's absence from it is no evidence about that gene.
104
+ {% else %}
105
+ Open Targets answered for the rest, so the genes it failed on were dropped
106
+ unless that table happened to list them. <strong>This shortlist is
107
+ incomplete.</strong>
108
+ {% endif %}
109
+ </li>
110
+ {% endfor %}
111
+ </ul>
112
+ <p class="small" style="margin-bottom:0">
113
+ Re-run with Open Targets reachable for a full discovery.
114
+ </p>
115
+ </div>
116
+ </div>
117
+ {% endif %}
118
+ <p class="chart__caption">
119
+ A run missing its manifest or its candidate table is shown as <em>absent</em>,
120
+ not as a run that produced nothing. Those are different findings, and
121
+ reporting the second for the first is how a crashed stage reads as an empty
122
+ result.
123
+ </p>
124
+ {% endif %}
125
+ {% endblock %}