shrut27 commited on
Commit
c00ce2f
·
verified ·
1 Parent(s): d875b22

Fix all sidebar filters and add live Rescue score reweighting

Browse files
Files changed (4) hide show
  1. assets/app.js +80 -18
  2. assets/style.css +13 -0
  3. docs/WORKFLOW.md +1 -0
  4. index.html +25 -0
assets/app.js CHANGED
@@ -34,6 +34,10 @@ const STAGES = [
34
 
35
  let DATA = {};
36
  let activeTab = 0;
 
 
 
 
37
  const layoutBase = {
38
  font: { color: "#111827", family: "Inter, Arial, sans-serif", size: 12 },
39
  paper_bgcolor: "white",
@@ -91,6 +95,42 @@ function filteredVariants() {
91
  );
92
  }
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  function plot(id, traces, extra = {}) {
95
  Plotly.react(id, traces, { ...layoutBase, ...extra }, { responsive: true, displayModeBar: false });
96
  }
@@ -100,16 +140,16 @@ function render() {
100
  const variants = DATA.variants;
101
  const vf = filteredVariants();
102
  const varRow = variants.find((r) => r.hgvs_p === v) || variants[0];
103
- const rankSel = DATA.rankings.filter((r) => r.variant === v).sort((a, b) => num(a.rank) - num(b.rank));
104
  const top1 = rankSel[0];
105
  const structSel = DATA.structures.filter((r) => r.variant === v);
106
  const pocketSel = DATA.pockets.filter((r) => r.variant === v);
107
  const content = document.getElementById("content");
108
 
109
  const views = [
110
- () => overview(variants, varRow, top1, v),
111
  () => qcTab(variants, vf),
112
- () => routingTab(variants),
113
  () => structureTab(varRow, structSel, v),
114
  () => pocketTab(pocketSel, v),
115
  () => libraryTab(v),
@@ -126,6 +166,9 @@ function render() {
126
  }
127
 
128
  function overview(variants, varRow, top1, v) {
 
 
 
129
  return `
130
  <h2 class="section">Project snapshot — lung cancer TP53 panel</h2>
131
  <div class="metrics">
@@ -133,8 +176,8 @@ function overview(variants, varRow, top1, v) {
133
  ${metric("Unique HGVS", uniq(variants, "hgvs_p").length)}
134
  ${metric("QC flags", variants.filter((r) => r.qc_status === "Flagged").length)}
135
  ${metric("Library size", DATA.compounds.length.toLocaleString())}
136
- ${metric("Docked pairs", DATA.docking.length.toLocaleString())}
137
- ${metric("MD replicas", DATA.md.length.toLocaleString())}
138
  </div>
139
  <h2 class="section">Focus variant</h2>
140
  <div class="metrics">
@@ -451,30 +494,30 @@ function counts(rows, key) {
451
 
452
  function drawPlots(tab, v, varRow, rankSel, top1, structSel, pocketSel, vf) {
453
  if (tab === 0) {
454
- const exons = uniq(DATA.variants, "exon");
455
  plot("p_sun", exons.map((e, i) => {
456
- const sub = DATA.variants.filter((r) => r.exon === e);
457
  const types = counts(sub, "type_inferred");
458
  return { type: "bar", name: e, x: Object.keys(types), y: Object.values(types), marker: { color: PALETTE[i % PALETTE.length] } };
459
  }), { barmode: "stack", title: "Observations by exon and inferred class" });
460
- const ordered = [...DATA.variants].sort((a, b) => num(b.priority_score) - num(a.priority_score));
461
  plot("p_pri", [{ type: "bar", x: ordered.map((r) => r.hgvs_p), y: ordered.map((r) => num(r.priority_score)), marker: { color: "#0E7C7B" } }], { title: "Variant priority score", xaxis: { tickangle: -45 } });
462
  }
463
  if (tab === 1) {
464
- const types = uniq(DATA.variants, "type_inferred");
465
  plot("p_af", types.map((t, i) => ({
466
- type: "histogram", name: t, x: DATA.variants.filter((r) => r.type_inferred === t).map((r) => num(r.allele_frequency)), marker: { color: PALETTE[i] },
467
  })), { barmode: "overlay", title: "Allele frequency by inferred class" });
468
  plot("p_map", [{
469
  type: "scatter", mode: "markers",
470
- x: DATA.variants.map((r) => num(r.position)),
471
- y: DATA.variants.map((r) => num(r.allele_frequency)),
472
- text: DATA.variants.map((r) => r.hgvs_p),
473
- marker: { size: DATA.variants.map((r) => 8 + 18 * num(r.priority_score)), color: DATA.variants.map((r) => r.qc_status === "Flagged" ? "#C45C26" : "#0E7C7B") },
474
  }], { title: "Residue map — size = priority, orange = QC flag", xaxis: { title: "Residue" }, yaxis: { title: "Allele frequency" } });
475
  }
476
  if (tab === 2) {
477
- const c = counts(DATA.variants, "route");
478
  plot("p_route", [{ type: "bar", x: Object.values(c), y: Object.keys(c), orientation: "h", marker: { color: "#1B365D" } }], { title: "Observations per route" });
479
  }
480
  if (tab === 3) {
@@ -599,7 +642,7 @@ function drawPlots(tab, v, varRow, rankSel, top1, structSel, pocketSel, vf) {
599
  }], { title: "Selectivity × ADMET × Rescue", scene: { xaxis: { title: "Sel" }, yaxis: { title: "ADMET" }, zaxis: { title: "Score" } } });
600
  const top15 = rankSel.slice(0, 15);
601
  plot("p_rankbar", [{ type: "bar", x: top15.map((r) => r.name), y: top15.map((r) => num(r.rescue_score)), marker: { color: top15.map((r) => recColor[r.recommendation] || "#1B365D") } }], { title: "Ranked Rescue / Opportunity Score", xaxis: { tickangle: -40 } });
602
- const w = { Bmut: 0.22, Sselectivity: 0.18, MDstability: 0.16, Frescue: 0.14, ADMET: 0.12, Evidence: 0.10, Risk: 0.08 };
603
  const terms = [
604
  ["Bmut", w.Bmut * num(top1.Bmut)],
605
  ["Selectivity", w.Sselectivity * num(top1.Sselectivity)],
@@ -661,8 +704,27 @@ async function init() {
661
  fillSelect("exonSelect", ["All", ...uniq(DATA.variants, "exon").sort()]);
662
  fillSelect("typeSelect", ["All", ...uniq(DATA.variants, "type_inferred")]);
663
  fillSelect("variantSelect", uniq(DATA.variants, "hgvs_p"), "p.Y220C");
664
- ["variantSelect", "exonSelect", "typeSelect"].forEach((id) => {
665
- document.getElementById(id).addEventListener("change", render);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  });
667
  render();
668
  }
 
34
 
35
  let DATA = {};
36
  let activeTab = 0;
37
+ const DEFAULT_WEIGHTS = {
38
+ Bmut: 0.22, Sselectivity: 0.18, MDstability: 0.16,
39
+ Frescue: 0.14, ADMET: 0.12, Evidence: 0.10, Risk: 0.08,
40
+ };
41
  const layoutBase = {
42
  font: { color: "#111827", family: "Inter, Arial, sans-serif", size: 12 },
43
  paper_bgcolor: "white",
 
95
  );
96
  }
97
 
98
+ function weights() {
99
+ const out = {};
100
+ Object.keys(DEFAULT_WEIGHTS).forEach((key) => {
101
+ out[key] = num(document.getElementById(`w_${key}`).value);
102
+ });
103
+ return out;
104
+ }
105
+
106
+ function rescore(rows) {
107
+ const w = weights();
108
+ const rescored = rows.map((r) => {
109
+ const score = Math.max(0, Math.min(1,
110
+ w.Bmut * num(r.Bmut)
111
+ + w.Sselectivity * num(r.Sselectivity)
112
+ + w.MDstability * num(r.MDstability)
113
+ + w.Frescue * num(r.Frescue)
114
+ + w.ADMET * num(r.ADMET)
115
+ + w.Evidence * num(r.Evidence)
116
+ - w.Risk * num(r.Risk)
117
+ ));
118
+ const recommendation = r.admet_flag === "Severe liability"
119
+ ? "No-go"
120
+ : (score >= 0.74 && num(r.confidence) >= 0.68 ? "Go" : (score >= 0.58 ? "Hold" : "No-go"));
121
+ return { ...r, rescue_score: +score.toFixed(3), recommendation };
122
+ });
123
+ rescored.sort((a, b) => b.rescue_score - a.rescue_score);
124
+ return rescored.map((r, i) => ({ ...r, rank: i + 1 }));
125
+ }
126
+
127
+ function syncVariantOptions() {
128
+ const allowed = uniq(filteredVariants(), "hgvs_p");
129
+ const current = currentVariant();
130
+ fillSelect("variantSelect", allowed, allowed.includes("p.Y220C") ? "p.Y220C" : allowed[0]);
131
+ if (allowed.includes(current)) document.getElementById("variantSelect").value = current;
132
+ }
133
+
134
  function plot(id, traces, extra = {}) {
135
  Plotly.react(id, traces, { ...layoutBase, ...extra }, { responsive: true, displayModeBar: false });
136
  }
 
140
  const variants = DATA.variants;
141
  const vf = filteredVariants();
142
  const varRow = variants.find((r) => r.hgvs_p === v) || variants[0];
143
+ const rankSel = rescore(DATA.rankings.filter((r) => r.variant === v));
144
  const top1 = rankSel[0];
145
  const structSel = DATA.structures.filter((r) => r.variant === v);
146
  const pocketSel = DATA.pockets.filter((r) => r.variant === v);
147
  const content = document.getElementById("content");
148
 
149
  const views = [
150
+ () => overview(vf, varRow, top1, v),
151
  () => qcTab(variants, vf),
152
+ () => routingTab(vf),
153
  () => structureTab(varRow, structSel, v),
154
  () => pocketTab(pocketSel, v),
155
  () => libraryTab(v),
 
166
  }
167
 
168
  function overview(variants, varRow, top1, v) {
169
+ const allowed = new Set(variants.map((r) => r.hgvs_p));
170
+ const docked = DATA.docking.filter((r) => allowed.has(r.variant));
171
+ const mdRows = DATA.md.filter((r) => allowed.has(r.variant));
172
  return `
173
  <h2 class="section">Project snapshot — lung cancer TP53 panel</h2>
174
  <div class="metrics">
 
176
  ${metric("Unique HGVS", uniq(variants, "hgvs_p").length)}
177
  ${metric("QC flags", variants.filter((r) => r.qc_status === "Flagged").length)}
178
  ${metric("Library size", DATA.compounds.length.toLocaleString())}
179
+ ${metric("Docked pairs", docked.length.toLocaleString())}
180
+ ${metric("MD replicas", mdRows.length.toLocaleString())}
181
  </div>
182
  <h2 class="section">Focus variant</h2>
183
  <div class="metrics">
 
494
 
495
  function drawPlots(tab, v, varRow, rankSel, top1, structSel, pocketSel, vf) {
496
  if (tab === 0) {
497
+ const exons = uniq(vf, "exon");
498
  plot("p_sun", exons.map((e, i) => {
499
+ const sub = vf.filter((r) => r.exon === e);
500
  const types = counts(sub, "type_inferred");
501
  return { type: "bar", name: e, x: Object.keys(types), y: Object.values(types), marker: { color: PALETTE[i % PALETTE.length] } };
502
  }), { barmode: "stack", title: "Observations by exon and inferred class" });
503
+ const ordered = [...vf].sort((a, b) => num(b.priority_score) - num(a.priority_score));
504
  plot("p_pri", [{ type: "bar", x: ordered.map((r) => r.hgvs_p), y: ordered.map((r) => num(r.priority_score)), marker: { color: "#0E7C7B" } }], { title: "Variant priority score", xaxis: { tickangle: -45 } });
505
  }
506
  if (tab === 1) {
507
+ const types = uniq(vf, "type_inferred");
508
  plot("p_af", types.map((t, i) => ({
509
+ type: "histogram", name: t, x: vf.filter((r) => r.type_inferred === t).map((r) => num(r.allele_frequency)), marker: { color: PALETTE[i] },
510
  })), { barmode: "overlay", title: "Allele frequency by inferred class" });
511
  plot("p_map", [{
512
  type: "scatter", mode: "markers",
513
+ x: vf.map((r) => num(r.position)),
514
+ y: vf.map((r) => num(r.allele_frequency)),
515
+ text: vf.map((r) => r.hgvs_p),
516
+ marker: { size: vf.map((r) => 8 + 18 * num(r.priority_score)), color: vf.map((r) => r.qc_status === "Flagged" ? "#C45C26" : "#0E7C7B") },
517
  }], { title: "Residue map — size = priority, orange = QC flag", xaxis: { title: "Residue" }, yaxis: { title: "Allele frequency" } });
518
  }
519
  if (tab === 2) {
520
+ const c = counts(vf, "route");
521
  plot("p_route", [{ type: "bar", x: Object.values(c), y: Object.keys(c), orientation: "h", marker: { color: "#1B365D" } }], { title: "Observations per route" });
522
  }
523
  if (tab === 3) {
 
642
  }], { title: "Selectivity × ADMET × Rescue", scene: { xaxis: { title: "Sel" }, yaxis: { title: "ADMET" }, zaxis: { title: "Score" } } });
643
  const top15 = rankSel.slice(0, 15);
644
  plot("p_rankbar", [{ type: "bar", x: top15.map((r) => r.name), y: top15.map((r) => num(r.rescue_score)), marker: { color: top15.map((r) => recColor[r.recommendation] || "#1B365D") } }], { title: "Ranked Rescue / Opportunity Score", xaxis: { tickangle: -40 } });
645
+ const w = weights();
646
  const terms = [
647
  ["Bmut", w.Bmut * num(top1.Bmut)],
648
  ["Selectivity", w.Sselectivity * num(top1.Sselectivity)],
 
704
  fillSelect("exonSelect", ["All", ...uniq(DATA.variants, "exon").sort()]);
705
  fillSelect("typeSelect", ["All", ...uniq(DATA.variants, "type_inferred")]);
706
  fillSelect("variantSelect", uniq(DATA.variants, "hgvs_p"), "p.Y220C");
707
+ document.getElementById("variantSelect").addEventListener("change", render);
708
+ ["exonSelect", "typeSelect"].forEach((id) => {
709
+ document.getElementById(id).addEventListener("change", () => {
710
+ syncVariantOptions();
711
+ render();
712
+ });
713
+ });
714
+ Object.keys(DEFAULT_WEIGHTS).forEach((key) => {
715
+ const input = document.getElementById(`w_${key}`);
716
+ const output = document.getElementById(`out_${key}`);
717
+ input.addEventListener("input", () => {
718
+ output.value = num(input.value).toFixed(2);
719
+ render();
720
+ });
721
+ });
722
+ document.getElementById("resetWeights").addEventListener("click", () => {
723
+ Object.entries(DEFAULT_WEIGHTS).forEach(([key, value]) => {
724
+ document.getElementById(`w_${key}`).value = value;
725
+ document.getElementById(`out_${key}`).value = value.toFixed(2);
726
+ });
727
+ render();
728
  });
729
  render();
730
  }
assets/style.css CHANGED
@@ -31,6 +31,19 @@ body {
31
  border-radius: 6px;
32
  border: none;
33
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  .muted { color: #c5d0e3; font-size: 13px; }
35
  .small { font-size: 12px; line-height: 1.45; }
36
  main { flex: 1; padding: 18px 22px 40px; overflow-x: hidden; }
 
31
  border-radius: 6px;
32
  border: none;
33
  }
34
+ .weight-panel {
35
+ margin-top: 18px;
36
+ padding-top: 14px;
37
+ border-top: 1px solid rgba(255,255,255,0.22);
38
+ }
39
+ .weight-panel h3 { margin: 0 0 10px; font-size: 14px; }
40
+ .weight-panel label { margin: 9px 0; }
41
+ .weight-panel output { float: right; font-variant-numeric: tabular-nums; }
42
+ .weight-panel input[type="range"] { width: 100%; margin-top: 5px; accent-color: #35b8b5; }
43
+ .weight-panel button {
44
+ width: 100%; margin-top: 8px; padding: 7px; border: 1px solid #9fb2cf;
45
+ border-radius: 6px; color: white; background: transparent; cursor: pointer;
46
+ }
47
  .muted { color: #c5d0e3; font-size: 13px; }
48
  .small { font-size: 12px; line-height: 1.45; }
49
  main { flex: 1; padding: 18px 22px 40px; overflow-x: hidden; }
docs/WORKFLOW.md CHANGED
@@ -19,3 +19,4 @@
19
 
20
  - 2026-08-13: Created `tp53-discovery-platform` covering variant QC, routing, structures, pockets, AI screen, docking, MD, ranking, validation, architecture and reports.
21
  - 2026-08-14: Physics/literature calculation engine (NP_000537.3 QC, published ΔΔG, ΔG=RT ln Kd, Lipinski/Veber/Egan/QED, consensus R(c,m)). Variant and docking CSV ingest for real-world evaluation. Methods tab and experimental top-10 plan.
 
 
19
 
20
  - 2026-08-13: Created `tp53-discovery-platform` covering variant QC, routing, structures, pockets, AI screen, docking, MD, ranking, validation, architecture and reports.
21
  - 2026-08-14: Physics/literature calculation engine (NP_000537.3 QC, published ΔΔG, ΔG=RT ln Kd, Lipinski/Veber/Egan/QED, consensus R(c,m)). Variant and docking CSV ingest for real-world evaluation. Methods tab and experimental top-10 plan.
22
+ - 2026-08-14: Fixed hosted sidebar reactivity. Exon/class filters now cascade into focus variants, KPIs, plots, routing, docking/MD counts, and tables. Added seven live Rescue/Opportunity weight sliders with immediate re-ranking and recommendation updates.
index.html CHANGED
@@ -21,6 +21,31 @@
21
  <label>Inferred class
22
  <select id="typeSelect"><option>All</option></select>
23
  </label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  <p class="muted small">Rescue / Opportunity Score uses the documented multi-objective weights. Computational estimates only.</p>
25
  </aside>
26
  <main>
 
21
  <label>Inferred class
22
  <select id="typeSelect"><option>All</option></select>
23
  </label>
24
+ <div class="weight-panel">
25
+ <h3>Rescue score weights</h3>
26
+ <label>Mutant binding <output id="out_Bmut">0.22</output>
27
+ <input id="w_Bmut" type="range" min="0" max="0.40" step="0.01" value="0.22" />
28
+ </label>
29
+ <label>Mutant selectivity <output id="out_Sselectivity">0.18</output>
30
+ <input id="w_Sselectivity" type="range" min="0" max="0.40" step="0.01" value="0.18" />
31
+ </label>
32
+ <label>MD stability <output id="out_MDstability">0.16</output>
33
+ <input id="w_MDstability" type="range" min="0" max="0.40" step="0.01" value="0.16" />
34
+ </label>
35
+ <label>Rescue relevance <output id="out_Frescue">0.14</output>
36
+ <input id="w_Frescue" type="range" min="0" max="0.40" step="0.01" value="0.14" />
37
+ </label>
38
+ <label>ADMET / exposure <output id="out_ADMET">0.12</output>
39
+ <input id="w_ADMET" type="range" min="0" max="0.40" step="0.01" value="0.12" />
40
+ </label>
41
+ <label>Evidence <output id="out_Evidence">0.10</output>
42
+ <input id="w_Evidence" type="range" min="0" max="0.40" step="0.01" value="0.10" />
43
+ </label>
44
+ <label>Risk penalty <output id="out_Risk">0.08</output>
45
+ <input id="w_Risk" type="range" min="0" max="0.30" step="0.01" value="0.08" />
46
+ </label>
47
+ <button id="resetWeights" type="button">Reset weights</button>
48
+ </div>
49
  <p class="muted small">Rescue / Opportunity Score uses the documented multi-objective weights. Computational estimates only.</p>
50
  </aside>
51
  <main>