dsh-molbio-panel 0.1.9 → 0.1.10

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.
package/README.md CHANGED
@@ -12,12 +12,12 @@ DSH Web GUI 的**浏览器内 Molbio 面板**:右侧栏的一个 tab,列出
12
12
  面板必须走 **bundle 渠道**(`dsh.client` 双面包):preset 渠道挂不了客户端 UI
13
13
  (详见 `dsh-molbio-tools` 仓库的 `docs/client-pipeline-exploration.md`)。
14
14
 
15
- 而 `dsh-molbio-tools` 的宿主半边会注册 **46 个工具**——把它挂进一个共享的 Web profile,
15
+ 而 `dsh-molbio-tools` 的宿主半边会注册 **64 个工具**——把它挂进一个共享的 Web profile,
16
16
  等于给**该 profile 的每个会话**都塞进这些工具。所以面板单独成包:
17
17
 
18
18
  | 包 | 宿主半边 | 用途 |
19
19
  | --- | --- | --- |
20
- | `dsh-molbio-tools` | 46 个 molbio 工具 | 分子生物学专用 profile / preset |
20
+ | `dsh-molbio-tools` | 64 个 molbio 工具 | 分子生物学专用 profile / preset |
21
21
  | `dsh-molbio-panel` | **不注册任何工具**(空实现) | 只想在共享 profile 里加一个面板 |
22
22
 
23
23
  宿主半边刻意保持空:客户端扫描按 **Loader 条目**取包清单,条目只要能加载,浏览器半就能
package/index.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  * dsh-molbio-panel — host half.
3
3
  *
4
4
  * This package exists so the browser panel can be installed WITHOUT dragging
5
- * the 46 molbio tools into every session of a profile. It is a `dsh.client`
5
+ * the molbio toolset into every session of a profile. It is a `dsh.client`
6
6
  * dual-face package: the host half below, and the browser half built from the
7
7
  * `dsh-molbio-tools` sources into `lib/client.js`.
8
8
  *
package/lib/client.js CHANGED
@@ -4976,10 +4976,23 @@ window.__ModuleLoader__.load({
4976
4976
 
4977
4977
  /** Summary statistics over parsed entries. */
4978
4978
  function entryStats(entries, qualityMode = false) {
4979
- const lengths = entries.map((entry) => entry.sequence.length);
4979
+ // Count in a loop instead of spreading into Math.min/Math.max: a large FASTQ
4980
+ // can hold hundreds of thousands of reads (and millions of quality
4981
+ // characters), and spreading such an array overflows the engine's argument
4982
+ // limit with a RangeError. The numbers are identical; only the failure mode
4983
+ // changes.
4984
+ let totalBases = 0;
4985
+ let minLength = entries.length === 0 ? 0 : Infinity;
4986
+ let maxLength = 0;
4987
+ let longest = 0;
4980
4988
  let gc = 0;
4981
4989
  let at = 0;
4982
4990
  for (const entry of entries) {
4991
+ const length = entry.sequence.length;
4992
+ totalBases += length;
4993
+ if (length < minLength) minLength = length;
4994
+ if (length > maxLength) maxLength = length;
4995
+ if (length > longest) longest = length;
4983
4996
  for (const base of entry.sequence) {
4984
4997
  if (base === 'G' || base === 'C') gc++;
4985
4998
  else if (base === 'A' || base === 'T') at++;
@@ -4987,22 +5000,34 @@ window.__ModuleLoader__.load({
4987
5000
  }
4988
5001
  const stats = {
4989
5002
  entries: entries.length,
4990
- total_bases: lengths.reduce((sum, len) => sum + len, 0),
4991
- min_length: Math.min(...lengths),
4992
- max_length: Math.max(...lengths),
4993
- mean_length: Math.round((lengths.reduce((sum, len) => sum + len, 0) / lengths.length) * 100) / 100,
5003
+ total_bases: totalBases,
5004
+ min_length: minLength,
5005
+ max_length: maxLength,
5006
+ mean_length: entries.length === 0 ? 0 : Math.round((totalBases / entries.length) * 100) / 100,
4994
5007
  gc_percent: gc + at === 0 ? 0 : Math.round((gc / (gc + at)) * 1000) / 10,
4995
5008
  };
4996
5009
  if (qualityMode) {
4997
- const allQ = entries.flatMap((entry) => [...entry.quality].map((ch) => ch.charCodeAt(0) - 33));
4998
- if (allQ.length > 0) {
4999
- const mean = allQ.reduce((sum, q) => sum + q, 0) / allQ.length;
5000
- const low = allQ.filter((q) => q < 20).length / allQ.length;
5001
- stats.quality_mean = Math.round(mean * 100) / 100;
5002
- stats.quality_min = Math.min(...allQ);
5003
- stats.quality_max = Math.max(...allQ);
5004
- stats.low_quality_fraction = Math.round(low * 1000) / 1000;
5005
- const maxLen = Math.min(150, Math.max(...entries.map((e) => e.sequence.length)));
5010
+ let qualitySum = 0;
5011
+ let qualityCount = 0;
5012
+ let lowCount = 0;
5013
+ let qualityMin = Infinity;
5014
+ let qualityMax = -Infinity;
5015
+ for (const entry of entries) {
5016
+ for (const ch of entry.quality) {
5017
+ const q = ch.charCodeAt(0) - 33;
5018
+ qualitySum += q;
5019
+ qualityCount++;
5020
+ if (q < 20) lowCount++;
5021
+ if (q < qualityMin) qualityMin = q;
5022
+ if (q > qualityMax) qualityMax = q;
5023
+ }
5024
+ }
5025
+ if (qualityCount > 0) {
5026
+ stats.quality_mean = Math.round((qualitySum / qualityCount) * 100) / 100;
5027
+ stats.quality_min = qualityMin;
5028
+ stats.quality_max = qualityMax;
5029
+ stats.low_quality_fraction = Math.round((lowCount / qualityCount) * 1000) / 1000;
5030
+ const maxLen = Math.min(150, longest);
5006
5031
  const perPosition = [];
5007
5032
  for (let pos = 0; pos < maxLen; pos++) {
5008
5033
  const values = entries.filter((e) => e.sequence.length > pos).map((e) => e.quality.charCodeAt(pos) - 33);
@@ -7797,14 +7822,22 @@ window.__ModuleLoader__.load({
7797
7822
  }
7798
7823
  if (current !== null) peaks.push(current);
7799
7824
  const total = points.reduce((sum, point) => sum + point.hydropathy, 0);
7825
+ // Loop rather than Math.min(...points): the protein length is not capped and
7826
+ // a long input would overflow the argument limit with a RangeError.
7827
+ let minimumHydropathy = Infinity;
7828
+ let maximumHydropathy = -Infinity;
7829
+ for (const point of points) {
7830
+ if (point.hydropathy < minimumHydropathy) minimumHydropathy = point.hydropathy;
7831
+ if (point.hydropathy > maximumHydropathy) maximumHydropathy = point.hydropathy;
7832
+ }
7800
7833
  return {
7801
7834
  length: sequence.length,
7802
7835
  window,
7803
7836
  threshold,
7804
7837
  gravy: sequence.length === 0 ? 0 : round2([...sequence].reduce((sum, aa) => sum + KYTE_DOOLITTLE[aa], 0) / sequence.length),
7805
7838
  mean_profile: round3(total / points.length),
7806
- minimum_hydropathy: round3(Math.min(...points.map((point) => point.hydropathy))),
7807
- maximum_hydropathy: round3(Math.max(...points.map((point) => point.hydropathy))),
7839
+ minimum_hydropathy: round3(minimumHydropathy),
7840
+ maximum_hydropathy: round3(maximumHydropathy),
7808
7841
  peaks: peaks.map((peak) => ({ ...peak, maximum: round3(peak.maximum), length: peak.end - peak.start + 1 })),
7809
7842
  points,
7810
7843
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-molbio-panel",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Browser panel for the DeepSeek Harness Web GUI: a Molbio tab in the right sidebar that draws plasmid maps and sequence logos from workspace sequence files, using the same computation modules as the dsh-molbio-tools plugin.",
5
5
  "type": "module",
6
6
  "main": "index.mjs",