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,90 @@
1
+ 3Dmol.js incorporates code from GLmol, Three.js, and jQuery and
2
+ is licensed under a BSD-3-Clause license.
3
+
4
+ * 3Dmol.js
5
+ Copyright (c) 2014, University of Pittsburgh and contributors
6
+ All rights reserved.
7
+
8
+ Redistribution and use in source and binary forms, with or without
9
+ modification, are permitted provided that the following conditions are met:
10
+
11
+ 1. Redistributions of source code must retain the above copyright
12
+ notice, this list of conditions and the following disclaimer.
13
+
14
+ 2. Redistributions in binary form must reproduce the above copyright
15
+ notice, this list of conditions and the following disclaimer in the
16
+ documentation and/or other materials provided with the distribution.
17
+
18
+ 3. Neither the name of the copyright holder nor the names of its
19
+ contributors may be used to endorse or promote products derived from
20
+ this software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
28
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
+
34
+ * GLmol
35
+ GLmol - Molecular Viewer on WebGL/Javascript (0.47)
36
+ (C) Copyright 2011-2012, biochem_fan
37
+ License: dual license of MIT or LGPL3
38
+
39
+ Contributors:
40
+ Robert Hanson for parseXYZ, deferred instantiation
41
+
42
+
43
+
44
+ * Three.js
45
+ https://github.com/mrdoob/three.js
46
+
47
+ Copyright (c) 2010-2012 three.js Authors. All rights reserved.
48
+
49
+ Permission is hereby granted, free of charge, to any person obtaining a copy
50
+ of this software and associated documentation files (the "Software"), to deal
51
+ in the Software without restriction, including without limitation the rights
52
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
53
+ copies of the Software, and to permit persons to whom the Software is
54
+ furnished to do so, subject to the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be included in
57
+ all copies or substantial portions of the Software.
58
+
59
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
60
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
61
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
62
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
63
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
64
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
65
+ THE SOFTWARE.
66
+
67
+ * jQuery
68
+ http://jquery.org/
69
+
70
+ Copyright (c) 2011 John Resig
71
+
72
+ Permission is hereby granted, free of charge, to any person obtaining
73
+ a copy of this software and associated documentation files (the
74
+ "Software"), to deal in the Software without restriction, including
75
+ without limitation the rights to use, copy, modify, merge, publish,
76
+ distribute, sublicense, and/or sell copies of the Software, and to
77
+ permit persons to whom the Software is furnished to do so, subject to
78
+ the following conditions:
79
+
80
+ The above copyright notice and this permission notice shall be
81
+ included in all copies or substantial portions of the Software.
82
+
83
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
84
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
85
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
86
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
87
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
88
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
89
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
90
+
@@ -0,0 +1,370 @@
1
+ // SPDX-FileCopyrightText: 2026 Mikhaeel Atef Rizk Wahba
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ //
4
+ // Check a counts matrix and a sample design table -- entirely in the browser.
5
+ //
6
+ // Authored once and loaded by two surfaces: `bindsight ui` (templates/
7
+ // your_data.html.j2) and the published documentation site (docs/try-your-data.md).
8
+ // Neither owns it, because two copies of one checker diverge and the published
9
+ // one would be the copy that silently stopped matching what the pipeline does.
10
+ //
11
+ // Nothing here uploads anything. There is no fetch, no XHR, no form action: the
12
+ // files are read with File.slice().text() and never leave the machine. That is
13
+ // a stronger promise on a public website than it was on localhost, and it is
14
+ // the reason this file may never grow a network call --
15
+ // tests/test_web_ui.py asserts exactly that.
16
+ //
17
+ // Every value this page shows comes from the reader's own files: the filename,
18
+ // the column headers, the cell values it reads as contrast levels. Those are
19
+ // composed as DOM nodes with textContent, never concatenated into markup. This
20
+ // file did concatenate them, and was then published on a public site, which
21
+ // turned "a filename is whatever the user typed" into script execution in the
22
+ // documentation's origin -- from a `.tsv` handed to someone by a stranger. The
23
+ // rule that replaced it is mechanical rather than careful: `innerHTML` is only
24
+ // ever assigned the empty string, and tests/test_web_ui.py fails any authored
25
+ // script that assigns anything else. Careful was what produced the bug.
26
+ //
27
+ // The contract with both surfaces is three element ids: `counts`, `design`
28
+ // and `checks`.
29
+
30
+ // --- nodes, not markup ------------------------------------------------------
31
+
32
+ function el(tag, className) {
33
+ const node = document.createElement(tag);
34
+ if (className) node.className = className;
35
+ return node;
36
+ }
37
+
38
+ function t(value) {
39
+ return document.createTextNode(String(value));
40
+ }
41
+
42
+ function code(value) {
43
+ const node = el("code");
44
+ node.textContent = String(value);
45
+ return node;
46
+ }
47
+
48
+ function strong(value) {
49
+ const node = el("strong");
50
+ node.textContent = String(value);
51
+ return node;
52
+ }
53
+
54
+ function br() {
55
+ return el("br");
56
+ }
57
+
58
+ // Parsed in the browser. A header and a few hundred rows are enough to tell a
59
+ // reader whether this is the file they meant, and it costs no round trip and no
60
+ // upload of data that may be sensitive.
61
+ async function head(file, n) {
62
+ const text = await file.slice(0, 256 * 1024).text();
63
+ return text.split(/\r?\n/).filter(function (l) { return l.trim(); }).slice(0, n);
64
+ }
65
+
66
+ function card(title, bodyNodes, kind) {
67
+ const glyph = kind === "ok" ? "✓" : kind === "warn" ? "!" : "✗";
68
+ const note = el("div", "note note--" + kind);
69
+
70
+ const mark = el("span", "note__glyph");
71
+ mark.setAttribute("aria-hidden", "true");
72
+ mark.textContent = glyph;
73
+
74
+ const wrap = el("div", "note__body");
75
+ const heading = el("p");
76
+ heading.style.margin = "0";
77
+ heading.appendChild(strong(title));
78
+
79
+ const body = el("div", "small");
80
+ bodyNodes.forEach(function (node) {
81
+ if (node) body.appendChild(node);
82
+ });
83
+
84
+ wrap.appendChild(heading);
85
+ wrap.appendChild(body);
86
+ note.appendChild(mark);
87
+ note.appendChild(wrap);
88
+ return note;
89
+ }
90
+
91
+ function splitRow(line) {
92
+ return line.indexOf("\t") >= 0 ? line.split("\t") : line.split(/\s*,\s*/);
93
+ }
94
+
95
+ // Which run is allowed to render. Choosing both files fires two `change`
96
+ // events, so two runs are in flight at once and each awaits a file read. The
97
+ // older one must not write: while this built one string and assigned it at the
98
+ // end, a slow first run would overwrite the fresh verdict with a stale one;
99
+ // appending nodes instead, it would append a second copy of its cards. Neither
100
+ // is what the reader asked for, so the stale run returns without drawing.
101
+ var latestRun = 0;
102
+
103
+ async function check() {
104
+ const thisRun = ++latestRun;
105
+ // Reset every run: a fixed file must clear the previous verdict.
106
+ var designBlocked = false;
107
+ const counts = document.getElementById("counts").files[0];
108
+ const design = document.getElementById("design").files[0];
109
+ const out = document.getElementById("checks");
110
+ if (!counts && !design) {
111
+ out.innerHTML = "";
112
+ return;
113
+ }
114
+
115
+ const cards = [];
116
+ let countsSamples = null;
117
+
118
+ if (counts) {
119
+ if (counts.name.endsWith(".gz")) {
120
+ cards.push(
121
+ card("Counts matrix", [t(counts.name + " — compressed; checked when the run starts.")], "ok")
122
+ );
123
+ } else {
124
+ const rows = await head(counts, 5);
125
+ const header = rows.length ? splitRow(rows[0]) : [];
126
+ countsSamples = header.slice(1);
127
+ const thin = header.length < 3;
128
+ const body = [
129
+ t(counts.name),
130
+ br(),
131
+ t(
132
+ header.length + " columns, " + (header.length - 1) + " samples, " +
133
+ (rows.length - 1) + "+ genes read."
134
+ ),
135
+ ];
136
+ if (thin) {
137
+ body.push(t(" "), strong("That is too few columns"), t(" — is the file tab-separated?"));
138
+ }
139
+ cards.push(card("Counts matrix", body, thin ? "warn" : "ok"));
140
+ }
141
+ }
142
+
143
+ if (design) {
144
+ const rows = await head(design, 500);
145
+ if (rows.length) {
146
+ const header = splitRow(rows[0]);
147
+ const body = rows.slice(1).map(splitRow);
148
+
149
+ // Every two-level column, not just the winner. This used to keep only
150
+ // the first match (preferring a name containing "cond") and report it as
151
+ // "Two-level factor: x" with a tick. A design table whose `condition`
152
+ // column has one level and whose `batch` column has two was therefore
153
+ // reported ready to run -- against `batch` -- and the run it recommends
154
+ // would contrast a technical covariate and return plausible genes
155
+ // answering a question nobody asked.
156
+ const candidates = [];
157
+ const conditionish = [];
158
+ header.forEach(function (name, i) {
159
+ const values = Array.from(new Set(body.map(function (r) { return r[i]; }).filter(Boolean)));
160
+ const named = name.toLowerCase().indexOf("cond") >= 0 || name.toLowerCase() === "group";
161
+ if (named) { conditionish.push({ name: name, count: values.length }); }
162
+ if (values.length === 2) {
163
+ candidates.push({ name: name, levels: values, named: named });
164
+ }
165
+ });
166
+ const named = candidates.filter(function (c) { return c.named; });
167
+ const chosen = named.length ? named[0] : candidates[0] || null;
168
+ const factor = chosen ? chosen.name : null;
169
+ const levels = chosen ? chosen.levels : null;
170
+ // Not a count of alternatives. The question is whether the page had any
171
+ // basis for its pick beyond "this column happens to hold two values". A
172
+ // name that reads as a condition is a basis; nothing else is, however few
173
+ // columns were in the running. The case that prompted this had exactly
174
+ // one candidate -- a `batch` column, in a file whose `condition` column
175
+ // held a single level -- and reported it with a tick.
176
+ const guessed = !!chosen && !chosen.named;
177
+
178
+ const parts = [
179
+ t(design.name),
180
+ br(),
181
+ t(body.length + " samples, " + header.length + " columns."),
182
+ ];
183
+
184
+ if (factor) {
185
+ parts.push(br(), t("Two-level factor: "), code(factor), t(" — "));
186
+ levels.forEach(function (l, i) {
187
+ if (i) parts.push(t(" against "));
188
+ parts.push(code(l));
189
+ });
190
+
191
+ const field = el("div", "field");
192
+ field.style.marginTop = ".75rem";
193
+ const label = el("label");
194
+ label.setAttribute("for", "num");
195
+ label.textContent = "Contrast";
196
+ const select = el("select");
197
+ select.id = "num";
198
+ levels.forEach(function (l) {
199
+ const option = el("option");
200
+ // Set as properties. The value a reader's file supplied is data, and
201
+ // an attribute built by concatenation is the one place a level like
202
+ // `" onmouseover="…` escapes its quotes without ever using an angle
203
+ // bracket -- which is why escaping-on-the-way-in was not the fix.
204
+ option.value = String(l);
205
+ option.textContent = String(l);
206
+ select.appendChild(option);
207
+ });
208
+ field.appendChild(label);
209
+ field.appendChild(select);
210
+ parts.push(field);
211
+
212
+ if (candidates.length > 1) {
213
+ parts.push(br(), t("Columns with exactly two values: "));
214
+ candidates.forEach(function (c, i) {
215
+ if (i) parts.push(t(", "));
216
+ parts.push(code(c.name));
217
+ });
218
+ parts.push(t("."));
219
+ }
220
+ if (guessed) {
221
+ // The user's own factor, named, because that is what they have to fix.
222
+ const wrongLevels = conditionish.filter(function (c) { return c.count !== 2; });
223
+ parts.push(
224
+ br(),
225
+ strong("This column was chosen because it has two values, not because it is your factor."),
226
+ t(" ")
227
+ );
228
+ if (wrongLevels.length) {
229
+ parts.push(
230
+ t("The column you probably meant, "),
231
+ code(wrongLevels[0].name),
232
+ t(
233
+ ", holds " + wrongLevels[0].count +
234
+ (wrongLevels[0].count === 1 ? " level" : " levels") +
235
+ ", and a contrast needs exactly two. "
236
+ )
237
+ );
238
+ } else {
239
+ parts.push(t("No column here is named like a condition. "));
240
+ }
241
+ parts.push(
242
+ t(
243
+ "The pipeline contrasts whichever column your config names — check " +
244
+ "that it is the comparison you mean."
245
+ )
246
+ );
247
+ }
248
+ } else {
249
+ parts.push(
250
+ br(),
251
+ strong("No two-level column found."),
252
+ t(" The contrast needs a column with exactly two distinct values.")
253
+ );
254
+ }
255
+
256
+ // Three states, not two. The two files usually come from different
257
+ // exports, so a name mismatch is the ordinary way this goes wrong.
258
+ var overlap = null;
259
+ if (countsSamples && countsSamples.length) {
260
+ const designSamples = body.map(function (r) { return r[0]; }).filter(Boolean);
261
+ const shared = designSamples.filter(function (s) { return countsSamples.indexOf(s) >= 0; });
262
+ overlap = { shared: shared.length, total: designSamples.length };
263
+ parts.push(
264
+ br(),
265
+ t("Sample names shared with the counts matrix: "),
266
+ strong(shared.length),
267
+ t(" of " + designSamples.length)
268
+ );
269
+ if (shared.length === 0) {
270
+ parts.push(
271
+ t(" — "),
272
+ strong("none match"),
273
+ t(
274
+ ", so the contrast cannot be built. The two files are probably from" +
275
+ " different exports; the design's first column has to hold the" +
276
+ " counts matrix's column headers."
277
+ )
278
+ );
279
+ } else if (shared.length < designSamples.length) {
280
+ parts.push(
281
+ t(
282
+ " — the other " + (designSamples.length - shared.length) +
283
+ " are not columns of the counts matrix and will be dropped."
284
+ )
285
+ );
286
+ }
287
+ }
288
+
289
+ // The severity follows the finding. Marking this OK beside "the contrast
290
+ // cannot be built" is the failure this page exists to prevent, one step
291
+ // earlier in the pipeline.
292
+ var blocked = !factor || (overlap && overlap.shared === 0);
293
+ // A guessed factor is not a clean bill of health: the page chose by
294
+ // column order, and only the reader knows whether that is the biology.
295
+ var partial =
296
+ guessed || (overlap && overlap.shared > 0 && overlap.shared < overlap.total);
297
+ cards.push(card("Design table", parts, blocked ? "err" : partial ? "warn" : "ok"));
298
+ designBlocked = blocked;
299
+ }
300
+ }
301
+
302
+ if (counts && design) {
303
+ const privacy =
304
+ "Nothing on this page was sent anywhere — the checks above ran in this " +
305
+ "browser, on the first 256 kB of each file, so a counts matrix of " +
306
+ "patient data never left the machine. ";
307
+ if (designBlocked) {
308
+ cards.push(
309
+ card(
310
+ "Not ready to run",
311
+ [
312
+ t(
313
+ privacy +
314
+ "The pipeline would fail on these two files for the reason above, so " +
315
+ "there is no command to give yet. Fix the design table and re-check."
316
+ ),
317
+ ],
318
+ "err"
319
+ )
320
+ );
321
+ } else {
322
+ const pre = el("pre");
323
+ pre.appendChild(code("bindsight discover my.yaml --out runs/mine"));
324
+ cards.push(
325
+ card(
326
+ "Running it",
327
+ [
328
+ t(
329
+ privacy +
330
+ "To run the pipeline on it, point the CLI at a config naming these two files:"
331
+ ),
332
+ pre,
333
+ t("then "),
334
+ code("bindsight ui"),
335
+ t(" to read the result here."),
336
+ ],
337
+ "ok"
338
+ )
339
+ );
340
+ }
341
+ }
342
+
343
+ // Read after every await above: a newer run may have started while this one
344
+ // was reading a file, and it owns the output now.
345
+ if (thisRun !== latestRun) return;
346
+ out.innerHTML = "";
347
+ cards.forEach(function (node) {
348
+ out.appendChild(node);
349
+ });
350
+ }
351
+
352
+ function boot() {
353
+ var counts = document.getElementById("counts");
354
+ var design = document.getElementById("design");
355
+ if (!counts || !design) return;
356
+ // mkdocs-material's instant navigation swaps page content without a reload,
357
+ // so this runs again on a page that may already be wired up.
358
+ if (counts.dataset.checkReady === "1") return;
359
+ counts.dataset.checkReady = "1";
360
+ counts.addEventListener("change", check);
361
+ design.addEventListener("change", check);
362
+ }
363
+
364
+ if (window.document$ && typeof window.document$.subscribe === "function") {
365
+ window.document$.subscribe(boot);
366
+ } else if (document.readyState !== "loading") {
367
+ boot();
368
+ } else {
369
+ document.addEventListener("DOMContentLoaded", boot);
370
+ }
@@ -0,0 +1,48 @@
1
+ {#- SPDX-FileCopyrightText: 2026 Mikhaeel Atef Rizk Wahba
2
+ SPDX-License-Identifier: AGPL-3.0-or-later -#}
3
+ <!doctype html>
4
+ <html lang="en">
5
+ <head>
6
+ <meta charset="utf-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <title>{% block title %}bindsight{% endblock %}</title>
9
+ <link rel="stylesheet" href="/static/bindsight.css">
10
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><circle cx='8' cy='8' r='7' fill='none' stroke='%230b5394' stroke-width='2'/><circle cx='8' cy='8' r='2' fill='%230f7d73'/></svg>">
11
+ </head>
12
+ <body>
13
+ <div class="shell">
14
+ <nav class="sidebar" aria-label="Sections">
15
+ <a class="brand" href="/">
16
+ <span class="brand__name">bindsight</span>
17
+ <span class="brand__tag">Expression → Binder</span>
18
+ </a>
19
+
20
+ <div class="nav">
21
+ {% set sections = [
22
+ ('overview', '/', 'Overview'),
23
+ ('evidence', '/evidence', 'Evidence'),
24
+ ('try', '/try', 'Try it'),
25
+ ('your_data', '/your-data', 'Your data'),
26
+ ('runs', '/runs', 'Runs'),
27
+ ] %}
28
+ {% for key, href, label in sections %}
29
+ <a href="{{ href }}"{% if nav == key %} aria-current="page"{% endif %}>{{ label }}</a>
30
+ {% endfor %}
31
+ </div>
32
+
33
+ <div class="sidebar__foot">
34
+ v{{ version }} · AGPL-3.0<br>
35
+ <a href="{{ github }}">Source</a>
36
+ </div>
37
+ </nav>
38
+
39
+ <main class="main">
40
+ <div class="wrap">
41
+ {% block content %}{% endblock %}
42
+ </div>
43
+ </main>
44
+ </div>
45
+ <script src="/static/charts.js"></script>
46
+ {% block scripts %}{% endblock %}
47
+ </body>
48
+ </html>