@complat/react-spectra-editor 1.7.0 → 1.8.0

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 (111) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -662
  3. package/README.md +5 -0
  4. package/dist/__tests__/fixtures/lc_ms_jcamp.js +2329 -0
  5. package/dist/__tests__/fixtures/lc_ms_jcamp_2.js +2874 -0
  6. package/dist/__tests__/fixtures/lc_ms_jcamp_mz_chemstation.js +3242 -0
  7. package/dist/__tests__/fixtures/lc_ms_jcamp_tic_chemstation.js +854 -0
  8. package/dist/__tests__/fixtures/lc_ms_jcamp_tic_neg.js +1233 -0
  9. package/dist/__tests__/fixtures/lc_ms_jcamp_tic_pos.js +1233 -0
  10. package/dist/__tests__/fixtures/lc_ms_jcamp_uvvis.js +48908 -0
  11. package/dist/__tests__/fixtures/lc_ms_jcamp_uvvis_chemstation.js +15424 -0
  12. package/dist/__tests__/units/components/cmd_bar/r05_submit_btn.test.js +158 -3
  13. package/dist/__tests__/units/components/d3_line_rect.test.js +85 -0
  14. package/dist/__tests__/units/components/d3_multi/multi_focus.test.js +43 -0
  15. package/dist/__tests__/units/components/hplc_viewer.test.js +123 -0
  16. package/dist/__tests__/units/components/panel/info.test.js +55 -15
  17. package/dist/__tests__/units/features/lc-ms/parsing/lcmsCategory.test.js +73 -0
  18. package/dist/__tests__/units/features/lc-ms/submit/peaks.test.js +310 -0
  19. package/dist/actions/curve.js +5 -2
  20. package/dist/actions/hplc_ms.js +66 -0
  21. package/dist/actions/integration.js +17 -2
  22. package/dist/actions/ui.js +39 -11
  23. package/dist/app.js +21 -8
  24. package/dist/components/cmd_bar/01_viewer.js +15 -11
  25. package/dist/components/cmd_bar/03_peak.js +25 -16
  26. package/dist/components/cmd_bar/04_integration.js +116 -17
  27. package/dist/components/cmd_bar/08_peak_group.js +83 -0
  28. package/dist/components/cmd_bar/collapsible_toolbar_group.js +101 -0
  29. package/dist/components/cmd_bar/common.js +4 -3
  30. package/dist/components/cmd_bar/index.js +76 -18
  31. package/dist/components/cmd_bar/r01_layout.js +6 -0
  32. package/dist/components/cmd_bar/r02_scan.js +15 -11
  33. package/dist/components/cmd_bar/r03_threshold.js +19 -3
  34. package/dist/components/cmd_bar/r04_submit.js +61 -6
  35. package/dist/components/cmd_bar/r05_submit_btn.js +60 -13
  36. package/dist/components/cmd_bar/r06_predict_btn.js +4 -4
  37. package/dist/components/cmd_bar/r07_wavelength_btn.js +10 -9
  38. package/dist/components/cmd_bar/r09_detector.js +2 -2
  39. package/dist/components/cmd_bar/tri_btn.js +4 -1
  40. package/dist/components/common/draw.js +6 -5
  41. package/dist/components/d3_line/index.js +70 -15
  42. package/dist/components/d3_line/line_focus.js +104 -67
  43. package/dist/components/d3_line_rect/index.js +836 -0
  44. package/dist/components/d3_line_rect/line_focus.js +526 -0
  45. package/dist/components/d3_line_rect/multi_focus.js +433 -0
  46. package/dist/components/d3_line_rect/rect_focus.js +247 -0
  47. package/dist/components/d3_multi/index.js +154 -163
  48. package/dist/components/d3_multi/multi_focus.js +117 -68
  49. package/dist/components/d3_rect/index.js +6 -0
  50. package/dist/components/d3_rect/rect_focus.js +17 -5
  51. package/dist/components/hplc_viewer.js +169 -0
  52. package/dist/components/lc_ms_uv_tools_bar.js +175 -0
  53. package/dist/components/multi_jcamps_viewer.js +17 -5
  54. package/dist/components/panel/compare.js +84 -62
  55. package/dist/components/panel/cyclic_voltamery_data.js +15 -14
  56. package/dist/components/panel/graph_selection.js +32 -30
  57. package/dist/components/panel/index.js +28 -22
  58. package/dist/components/panel/info.js +90 -31
  59. package/dist/constants/action_type.js +23 -2
  60. package/dist/constants/list_graph.js +16 -0
  61. package/dist/constants/list_layout.js +2 -1
  62. package/dist/constants/list_ui.js +5 -2
  63. package/dist/features/lc-ms/entities/extractEntityLCMS.js +187 -0
  64. package/dist/features/lc-ms/parsing/chemstation.js +108 -0
  65. package/dist/features/lc-ms/parsing/index.js +46 -0
  66. package/dist/features/lc-ms/parsing/lcmsCategory.js +44 -0
  67. package/dist/features/lc-ms/parsing/lcmsJcampInfo.js +18 -0
  68. package/dist/features/lc-ms/parsing/lcmsMsPage.js +241 -0
  69. package/dist/features/lc-ms/parsing/pageValue.js +27 -0
  70. package/dist/features/lc-ms/submit/index.js +24 -0
  71. package/dist/features/lc-ms/submit/peaks.js +66 -0
  72. package/dist/features/lc-ms/ui/wavelengthSelect.js +54 -0
  73. package/dist/fn.js +4 -1
  74. package/dist/helpers/brush.js +74 -19
  75. package/dist/helpers/calc.js +4 -2
  76. package/dist/helpers/cfg.js +5 -4
  77. package/dist/helpers/chem.js +449 -71
  78. package/dist/helpers/compass.js +181 -17
  79. package/dist/helpers/extractEntityLCMS.js +24 -0
  80. package/dist/helpers/extractParams.js +118 -33
  81. package/dist/helpers/extractPeaksEdit.js +29 -9
  82. package/dist/helpers/format.js +189 -4
  83. package/dist/helpers/init.js +25 -5
  84. package/dist/helpers/integration.js +130 -17
  85. package/dist/helpers/integration_draft.js +43 -0
  86. package/dist/helpers/integration_focus.js +155 -0
  87. package/dist/helpers/integration_jcamp.js +68 -0
  88. package/dist/helpers/integration_split.js +202 -0
  89. package/dist/helpers/mount.js +8 -1
  90. package/dist/helpers/sweep.js +39 -0
  91. package/dist/index.js +284 -26
  92. package/dist/layer_content.js +17 -3
  93. package/dist/layer_init.js +124 -14
  94. package/dist/layer_prism.js +31 -5
  95. package/dist/reducers/index.js +3 -1
  96. package/dist/reducers/reducer_curve.js +54 -36
  97. package/dist/reducers/reducer_hplc_ms/hydrate.js +289 -0
  98. package/dist/reducers/reducer_hplc_ms/index.js +122 -0
  99. package/dist/reducers/reducer_hplc_ms/persistence.js +74 -0
  100. package/dist/reducers/reducer_hplc_ms/tic.js +70 -0
  101. package/dist/reducers/reducer_hplc_ms/utils.js +70 -0
  102. package/dist/reducers/reducer_hplc_ms/uvvis.js +377 -0
  103. package/dist/reducers/reducer_integration.js +265 -7
  104. package/dist/reducers/reducer_layout.js +1 -1
  105. package/dist/reducers/reducer_submit.js +2 -1
  106. package/dist/reducers/reducer_ui.js +149 -13
  107. package/dist/reducers/undo_redo_config.js +2 -2
  108. package/dist/sagas/saga_lcms_ui.js +85 -0
  109. package/dist/sagas/saga_ui.js +177 -74
  110. package/dist/setupTests.js +5 -5
  111. package/package.json +10 -7
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ exports.getLcMsInfo = getLcMsInfo;
8
+ exports.isLcMsGroup = void 0;
9
+ exports.splitAndReindexEntities = splitAndReindexEntities;
10
+ const collectCategories = entity => {
11
+ const categories = [];
12
+ if (Array.isArray(entity.features)) {
13
+ entity.features.forEach(feature => {
14
+ if (feature?.csCategory) categories.push(...[].concat(feature.csCategory));
15
+ });
16
+ }
17
+ if (entity.feature?.csCategory) {
18
+ categories.push(...[].concat(entity.feature.csCategory));
19
+ }
20
+ if (entity.csCategory) {
21
+ categories.push(...[].concat(entity.csCategory));
22
+ }
23
+ return categories;
24
+ };
25
+ const getEntityValue = (entity, path, fallback = '') => {
26
+ const parts = path.split('.');
27
+ let current = entity;
28
+ for (let i = 0; i < parts.length; i += 1) {
29
+ if (!current) return fallback;
30
+ current = current[parts[i]];
31
+ }
32
+ return current ?? fallback;
33
+ };
34
+ const normalizeUnit = value => String(value || '').toUpperCase().replace(/\s+/g, '');
35
+ const normalizeText = value => String(value || '').toUpperCase();
36
+ const parseNumeric = value => {
37
+ if (typeof value === 'number') return Number.isFinite(value) ? value : null;
38
+ const text = String(value || '').trim();
39
+ if (!text) return null;
40
+ const match = text.match(/[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?/);
41
+ if (!match) return null;
42
+ const parsed = Number(match[0]);
43
+ return Number.isFinite(parsed) ? parsed : null;
44
+ };
45
+ const firstSpectrum = entity => entity?.spectra?.[0] || entity?.feature || entity?.features?.[0] || null;
46
+ const getFirstTicX = entity => {
47
+ const spectrum = firstSpectrum(entity);
48
+ const x0 = spectrum?.data?.[0]?.x?.[0];
49
+ return parseNumeric(x0);
50
+ };
51
+ const getMzPage = entity => {
52
+ const spectrum = firstSpectrum(entity);
53
+ return parseNumeric(spectrum?.pageValue ?? spectrum?.page ?? spectrum?.pageSymbol);
54
+ };
55
+ function getLcMsInfo(entity = {}) {
56
+ if (entity.lcmsKind) {
57
+ return {
58
+ kind: entity.lcmsKind,
59
+ polarity: entity.lcmsPolarity || 'neutral'
60
+ };
61
+ }
62
+ const categories = collectCategories(entity);
63
+ const normalizedCategories = categories.map(String).map(category => category.toUpperCase());
64
+ const scanMode = normalizeText(entity.scanMode || entity.SCAN_MODE || entity.SCANMODE || getEntityValue(entity, 'info.SCAN_MODE') || getEntityValue(entity, 'info.SCANMODE') || getEntityValue(entity, 'spectra.0.scanMode') || getEntityValue(entity, 'spectra.0.SCAN_MODE') || getEntityValue(entity, 'spectra.0.SCANMODE') || getEntityValue(entity, 'feature.scanMode') || getEntityValue(entity, 'feature.SCAN_MODE') || getEntityValue(entity, 'feature.SCANMODE') || getEntityValue(entity, 'features.0.scanMode') || getEntityValue(entity, 'features.0.SCAN_MODE') || getEntityValue(entity, 'features.0.SCANMODE'));
65
+ const hasNeg = normalizedCategories.some(category => category.includes('NEGATIVE') || category.startsWith('NEGATIV')) || scanMode.includes('NEGATIVE') || scanMode.includes('NEGATIV');
66
+ const hasPos = normalizedCategories.some(category => category.includes('POSITIVE') || category.startsWith('POSITIV')) || scanMode.includes('POSITIVE') || scanMode.includes('POSITIV');
67
+ let polarity = 'neutral';
68
+ if (hasNeg) {
69
+ polarity = 'negative';
70
+ } else if (hasPos) {
71
+ polarity = 'positive';
72
+ }
73
+ let kind = null;
74
+ if (normalizedCategories.some(category => category.includes('TIC'))) kind = 'tic';
75
+ if (!kind && normalizedCategories.some(category => category.includes('MZ'))) kind = 'mz';
76
+ if (!kind && normalizedCategories.some(category => category.includes('UVVIS'))) kind = 'uvvis';
77
+ const dataType = String(entity.dataType || entity.DATATYPE || getEntityValue(entity, 'info.DATATYPE') || getEntityValue(entity, 'spectra.0.dataType') || getEntityValue(entity, 'spectra.0.DATATYPE') || getEntityValue(entity, 'feature.dataType') || getEntityValue(entity, 'feature.DATATYPE') || getEntityValue(entity, 'features.0.dataType') || getEntityValue(entity, 'features.0.DATATYPE')).toUpperCase();
78
+ const type = normalizeText(entity.type || entity.TYPE || getEntityValue(entity, 'info.TYPE') || getEntityValue(entity, 'spectra.0.type') || getEntityValue(entity, 'spectra.0.TYPE') || getEntityValue(entity, 'feature.type') || getEntityValue(entity, 'feature.TYPE') || getEntityValue(entity, 'features.0.type') || getEntityValue(entity, 'features.0.TYPE'));
79
+ if (!kind && dataType.includes('MASS TIC')) kind = 'tic';
80
+ if (!kind && dataType.includes('MASS SPECTRUM')) kind = 'mz';
81
+ if (!kind && (dataType.includes('UV') || dataType.includes('UV-VIS'))) kind = 'uvvis';
82
+ if (!kind && type.includes('CHROMATOGRAM')) kind = 'tic';
83
+ if (!kind && type.includes('SPECTRUM') && dataType.includes('MASS')) kind = 'mz';
84
+ const xUnit = normalizeUnit(entity.xUnit || getEntityValue(entity, 'spectra.0.xUnit') || getEntityValue(entity, 'feature.xUnit') || getEntityValue(entity, 'features.0.xUnit'));
85
+ const yUnit = normalizeUnit(entity.yUnit || getEntityValue(entity, 'spectra.0.yUnit') || getEntityValue(entity, 'feature.yUnit') || getEntityValue(entity, 'features.0.yUnit'));
86
+ if (!kind && xUnit.includes('M/Z')) kind = 'mz';
87
+ if (!kind && (xUnit.includes('TIME') || xUnit.includes('MINUTE')) && (yUnit.includes('INTENSITY') || yUnit.includes('COUNT'))) {
88
+ kind = 'tic';
89
+ }
90
+ if (entity.lcmsPolarity) {
91
+ polarity = entity.lcmsPolarity;
92
+ }
93
+ return {
94
+ kind: kind || 'unknown',
95
+ polarity
96
+ };
97
+ }
98
+ const alignMzPolarityWithTic = (ticEntities, mzEntities) => {
99
+ const ticByPolarity = {};
100
+ ticEntities.forEach(tic => {
101
+ const info = getLcMsInfo(tic);
102
+ const x = getFirstTicX(tic);
103
+ if (info.polarity && x != null) {
104
+ ticByPolarity[info.polarity] = x;
105
+ }
106
+ });
107
+ const ticPolarityKeys = Object.keys(ticByPolarity);
108
+ if (ticPolarityKeys.length < 2) return;
109
+ for (let i = 0; i < mzEntities.length; i += 1) {
110
+ const entity = mzEntities[i];
111
+ const mzPage = getMzPage(entity);
112
+ if (mzPage != null) {
113
+ let nearestPolarity = null;
114
+ let nearestDistance = Infinity;
115
+ ticPolarityKeys.forEach(polarity => {
116
+ const ticX = ticByPolarity[polarity];
117
+ const distance = Math.abs(mzPage - ticX);
118
+ if (distance < nearestDistance) {
119
+ nearestDistance = distance;
120
+ nearestPolarity = polarity;
121
+ }
122
+ });
123
+ if (nearestPolarity) {
124
+ entity.lcmsPolarity = nearestPolarity;
125
+ }
126
+ }
127
+ }
128
+ };
129
+ function splitAndReindexEntities(entities = []) {
130
+ const tic = [];
131
+ const mz = [];
132
+ const uvvis = [];
133
+ const unknown = [];
134
+ const normalizedEntities = entities.map(entity => ({
135
+ ...entity
136
+ }));
137
+ normalizedEntities.forEach(e => {
138
+ const info = getLcMsInfo(e);
139
+ e.lcmsKind = info.kind;
140
+ e.lcmsPolarity = info.polarity;
141
+ if (info.kind === 'tic') tic.push(e);else if (info.kind === 'mz') mz.push(e);else if (info.kind === 'uvvis') uvvis.push(e);else unknown.push(e);
142
+ });
143
+ alignMzPolarityWithTic(tic, mz);
144
+ const polarityRank = {
145
+ positive: 0,
146
+ negative: 1,
147
+ neutral: 2
148
+ };
149
+ const byPolarity = (a, b) => {
150
+ const aInfo = getLcMsInfo(a);
151
+ const bInfo = getLcMsInfo(b);
152
+ return (polarityRank[aInfo.polarity] ?? 99) - (polarityRank[bInfo.polarity] ?? 99);
153
+ };
154
+ tic.sort(byPolarity).forEach((e, i) => {
155
+ e.curveIdx = i;
156
+ });
157
+ mz.sort(byPolarity).forEach((e, i) => {
158
+ e.curveIdx = i;
159
+ });
160
+ return {
161
+ ticEntities: tic,
162
+ mzEntities: mz,
163
+ uvvisEntities: uvvis,
164
+ unknownEntities: unknown,
165
+ dataEntities: [...mz, ...uvvis, ...unknown],
166
+ allEntities: normalizedEntities
167
+ };
168
+ }
169
+ const isLcMsGroup = (entities = []) => {
170
+ if (!entities || !Array.isArray(entities) || entities.length === 0) {
171
+ return false;
172
+ }
173
+ const counts = {
174
+ tic: 0,
175
+ mz: 0,
176
+ uvvis: 0
177
+ };
178
+ entities.forEach(e => {
179
+ const {
180
+ kind
181
+ } = getLcMsInfo(e);
182
+ if (counts[kind] !== undefined) counts[kind] += 1;
183
+ });
184
+ return counts.uvvis > 0 && (counts.tic > 0 || counts.mz > 0);
185
+ };
186
+ exports.isLcMsGroup = isLcMsGroup;
187
+ var _default = exports.default = splitAndReindexEntities;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.parseChemstationPages = exports.isChemstationLcms = void 0;
7
+ var _lcmsMsPage = require("./lcmsMsPage");
8
+ const parseChemstationPages = (source, jcamp) => {
9
+ if (typeof source !== 'string') return [];
10
+ const parts = source.split(/##PAGE=/);
11
+ if (parts.length <= 1) return [];
12
+ const info = jcamp?.info || {};
13
+ const baseSpectrum = Array.isArray(jcamp?.spectra) ? jcamp.spectra[0] : null;
14
+ const dataType = baseSpectrum?.dataType || jcamp?.dataType || 'LC/MS';
15
+ const xUnit = info.XUNITS || baseSpectrum?.xUnit || jcamp?.xUnit || '';
16
+ const yUnit = info.YUNITS || baseSpectrum?.yUnit || jcamp?.yUnit || '';
17
+ const spectra = [];
18
+ for (let i = 1; i < parts.length; i += 1) {
19
+ const block = parts[i];
20
+ const lines = block.split(/\r?\n/);
21
+ const pageLine = (lines[0] || '').trim();
22
+ const pageValue = (0, _lcmsMsPage.parsePageValue)(pageLine);
23
+ const dataStart = lines.findIndex(line => line.startsWith('##DATA TABLE') || line.startsWith('##XYDATA'));
24
+ if (dataStart >= 0) {
25
+ const x = [];
26
+ const y = [];
27
+ for (let j = dataStart + 1; j < lines.length; j += 1) {
28
+ const rawLine = lines[j].trim();
29
+ if (rawLine) {
30
+ if (rawLine.startsWith('##')) break;
31
+ const partsLine = rawLine.split(/[,\s]+/).filter(Boolean);
32
+ if (partsLine.length >= 2) {
33
+ const xVal = Number(partsLine[0]);
34
+ const yVal = Number(partsLine[1]);
35
+ if (Number.isFinite(xVal) && Number.isFinite(yVal)) {
36
+ x.push(xVal);
37
+ y.push(yVal);
38
+ }
39
+ }
40
+ }
41
+ }
42
+ if (x.length > 0) {
43
+ const pageSymbol = pageLine || pageValue;
44
+ spectra.push({
45
+ dataType,
46
+ xUnit,
47
+ yUnit,
48
+ pageValue,
49
+ page: pageLine || pageValue,
50
+ pageSymbol,
51
+ data: [{
52
+ x,
53
+ y
54
+ }]
55
+ });
56
+ }
57
+ }
58
+ }
59
+ return spectra;
60
+ };
61
+ exports.parseChemstationPages = parseChemstationPages;
62
+ const isChemstationLcms = (source, jcamp) => {
63
+ if (typeof source !== 'string') return false;
64
+ const dt = String(jcamp?.dataType || jcamp?.info?.DATATYPE || '').toUpperCase();
65
+ if (dt.includes('LC/MS') || dt.includes('MASS TIC')) return true;
66
+ const spectra = Array.isArray(jcamp?.spectra) ? jcamp.spectra : [];
67
+ const info = jcamp?.info || {};
68
+ const scanMode = String(info.SCAN_MODE || info.SCANMODE || '').toUpperCase();
69
+ const type = String(info.TYPE || '').toUpperCase();
70
+ const software = String(info.SOFTWARE || '').toUpperCase();
71
+ const csCategory = jcamp?.info?.$CSCATEGORY;
72
+ const categories = Array.isArray(csCategory) ? csCategory.map(c => String(c).toUpperCase()) : [];
73
+ const hasPolarityCategory = categories.some(c => c.includes('POSITIVE') || c.includes('NEGATIVE') || c.includes('NEUTRAL'));
74
+ const hasTicOrUvvisCategory = categories.some(c => c.includes('TIC') || c.includes('UVVIS'));
75
+ const hasHplcUvvisSpectrumDataType = spectra.some(s => {
76
+ const sdt = String(s?.dataType || '').toUpperCase();
77
+ return sdt.includes('HPLC UV-VIS') || sdt.includes('UVVIS');
78
+ });
79
+ const hasMassTicSpectrumDataType = spectra.some(s => {
80
+ const sdt = String(s?.dataType || '').toUpperCase();
81
+ return sdt.includes('MASS TIC') || sdt.includes('TIC');
82
+ });
83
+ const hasMassSpectrumDataType = spectra.some(s => {
84
+ const sdt = String(s?.dataType || '').toUpperCase();
85
+ return sdt.includes('MASS SPECTRUM');
86
+ });
87
+ const hasMassSpectrumRootDataType = dt.includes('MASS SPECTRUM');
88
+ const hasScanModeHint = scanMode.includes('POSITIVE') || scanMode.includes('NEGATIVE') || scanMode.includes('POSITIV') || scanMode.includes('NEGATIV');
89
+ const hasTypeHint = type.includes('MS SPECTRUM') || type.includes('MS CHROMATOGRAM');
90
+ const hasSoftwareHint = software.includes('OPENLAB');
91
+ const hasMultipleSpectra = spectra.length > 1;
92
+ const hasPageMetadata = spectra.some(s => s?.page != null || s?.pageValue != null);
93
+ const hasNtuplesPageHeader = /##NTUPLES_PAGE_HEADER\s*=/.test(source);
94
+ if (hasNtuplesPageHeader && (hasTicOrUvvisCategory || hasHplcUvvisSpectrumDataType || hasMassTicSpectrumDataType || hasMassSpectrumDataType && hasPolarityCategory)) {
95
+ return true;
96
+ }
97
+ if (hasMultipleSpectra && hasPageMetadata && (hasTicOrUvvisCategory || hasHplcUvvisSpectrumDataType || hasMassTicSpectrumDataType || hasMassSpectrumDataType && hasPolarityCategory)) {
98
+ return true;
99
+ }
100
+ if (hasMassSpectrumRootDataType && (hasMassSpectrumDataType || hasScanModeHint || hasTypeHint || hasSoftwareHint)) {
101
+ return true;
102
+ }
103
+ if (hasMassTicSpectrumDataType && (hasTypeHint || hasSoftwareHint || hasScanModeHint || spectra.length > 0)) {
104
+ return true;
105
+ }
106
+ return false;
107
+ };
108
+ exports.isChemstationLcms = isChemstationLcms;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "buildLcmsMsPageJcamp", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _lcmsMsPage.buildLcmsMsPageJcamp;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "inferLcMsCategory", {
13
+ enumerable: true,
14
+ get: function get() {
15
+ return _lcmsCategory.inferLcMsCategory;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "isChemstationLcms", {
19
+ enumerable: true,
20
+ get: function get() {
21
+ return _chemstation.isChemstationLcms;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "parseChemstationPages", {
25
+ enumerable: true,
26
+ get: function get() {
27
+ return _chemstation.parseChemstationPages;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "parseFeaturePageValue", {
31
+ enumerable: true,
32
+ get: function get() {
33
+ return _pageValue.parseFeaturePageValue;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "readLcmsMzPageFromJcampInfo", {
37
+ enumerable: true,
38
+ get: function get() {
39
+ return _lcmsJcampInfo.readLcmsMzPageFromJcampInfo;
40
+ }
41
+ });
42
+ var _lcmsCategory = require("./lcmsCategory");
43
+ var _lcmsMsPage = require("./lcmsMsPage");
44
+ var _lcmsJcampInfo = require("./lcmsJcampInfo");
45
+ var _chemstation = require("./chemstation");
46
+ var _pageValue = require("./pageValue");
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.normalizeLcMsMode = exports.inferLcMsKind = exports.inferLcMsCategory = void 0;
7
+ const normalizeLcMsMode = value => {
8
+ const mode = String(value || '').toUpperCase();
9
+ if (!mode) return 'NEUTRAL';
10
+ if (mode.includes('NEGATIVE') || mode.includes('NEGATIV')) return 'NEGATIVE';
11
+ if (mode.includes('POSITIVE') || mode.includes('POSITIV')) return 'POSITIVE';
12
+ if (mode.includes('NEUTRAL')) return 'NEUTRAL';
13
+ return 'NEUTRAL';
14
+ };
15
+ exports.normalizeLcMsMode = normalizeLcMsMode;
16
+ const inferLcMsKind = (spectrum, jcamp) => {
17
+ const spectrumDataType = String(spectrum?.dataType || '').toUpperCase();
18
+ const jcampDataType = String(jcamp?.dataType || '').toUpperCase();
19
+ const type = String(jcamp?.info?.TYPE || '').toUpperCase();
20
+ const xUnit = String(spectrum?.xUnit || jcamp?.info?.XUNITS || jcamp?.xUnit || '').toUpperCase();
21
+ const yUnit = String(spectrum?.yUnit || jcamp?.info?.YUNITS || jcamp?.yUnit || '').toUpperCase();
22
+ if (spectrumDataType.includes('MASS TIC') || jcampDataType.includes('MASS TIC') || type.includes('CHROMATOGRAM')) {
23
+ return 'TIC';
24
+ }
25
+ if (spectrumDataType.includes('MASS SPECTRUM') || jcampDataType.includes('MASS SPECTRUM')) {
26
+ return 'MZ';
27
+ }
28
+ if (xUnit.includes('TIME') || xUnit.includes('MINUTE')) {
29
+ if (yUnit.includes('INTENSITY') || yUnit.includes('COUNT')) return 'TIC';
30
+ }
31
+ if (xUnit.includes('M/Z') || xUnit.includes('MZ')) return 'MZ';
32
+ return 'SPECTRUM';
33
+ };
34
+ exports.inferLcMsKind = inferLcMsKind;
35
+ const inferLcMsCategory = (spectrum, jcamp) => {
36
+ const existing = spectrum?.csCategory;
37
+ if (existing) return existing;
38
+ const category = inferLcMsKind(spectrum, jcamp);
39
+ const mode = normalizeLcMsMode(jcamp?.info?.SCAN_MODE || jcamp?.info?.SCANMODE);
40
+ if (category === 'UVVIS') return 'UVVIS PEAK TABLE';
41
+ if (category === 'TIC' || category === 'MZ') return `${category} ${mode} SPECTRUM`;
42
+ return `${category} SPECTRUM`;
43
+ };
44
+ exports.inferLcMsCategory = inferLcMsCategory;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.readLcmsMzPageFromJcampInfo = void 0;
7
+ var _lcmsMsPage = require("./lcmsMsPage");
8
+ /* eslint-disable import/prefer-default-export */
9
+
10
+ const readLcmsMzPageFromJcampInfo = info => {
11
+ if (!info || info.$CSLCMSMZPAGE == null || info.$CSLCMSMZPAGE === '') return null;
12
+ const raw = info.$CSLCMSMZPAGE;
13
+ const scalar = Array.isArray(raw) ? raw[0] : raw;
14
+ if (scalar == null || scalar === '') return null;
15
+ const parsed = typeof scalar === 'number' && Number.isFinite(scalar) ? scalar : (0, _lcmsMsPage.parsePageValue)(String(scalar).trim());
16
+ return Number.isFinite(parsed) ? parsed : null;
17
+ };
18
+ exports.readLcmsMzPageFromJcampInfo = readLcmsMzPageFromJcampInfo;
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.selectLcmsMsPage = exports.parsePageValue = exports.buildLcmsMsPageJcamp = void 0;
7
+ var _extractEntityLCMS = require("../entities/extractEntityLCMS");
8
+ const parsePageValue = raw => {
9
+ if (!raw) return null;
10
+ const match = String(raw).match(/[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?/);
11
+ return match ? parseFloat(match[0]) : null;
12
+ };
13
+ exports.parsePageValue = parsePageValue;
14
+ const normalizeLcmsRequestPolarity = value => {
15
+ if (value == null || value === '') return null;
16
+ const text = String(value).trim().toLowerCase();
17
+ if (text === 'positive' || text === 'pos' || text === '+') return 'positive';
18
+ if (text === 'negative' || text === 'neg' || text === '-') return 'negative';
19
+ if (text === 'neutral' || text === 'neu' || text === '0') return 'neutral';
20
+ return text || null;
21
+ };
22
+ const stringifyLcmsPageValue = value => {
23
+ if (value == null) return '';
24
+ return String(value).trim();
25
+ };
26
+ const getLcmsPageCandidates = (entity = {}) => {
27
+ const spectra = Array.isArray(entity?.spectra) ? entity.spectra : [];
28
+ if (spectra.length > 0) return spectra;
29
+ const features = entity?.features;
30
+ if (Array.isArray(features)) return features;
31
+ if (features && typeof features === 'object') return Object.values(features);
32
+ if (entity?.feature) return [entity.feature];
33
+ return [];
34
+ };
35
+ const getSpectrumDataPairs = (spectrum = {}) => {
36
+ const data = spectrum?.data?.[0];
37
+ const x = Array.isArray(data?.x) ? data.x : [];
38
+ const y = Array.isArray(data?.y) ? data.y : [];
39
+ const size = Math.min(x.length, y.length);
40
+ return {
41
+ x: x.slice(0, size),
42
+ y: y.slice(0, size)
43
+ };
44
+ };
45
+ const describeLcmsMsSource = (entity = {}, entityIndex = -1) => {
46
+ const info = (0, _extractEntityLCMS.getLcMsInfo)(entity);
47
+ const title = entity?.title || entity?.info?.TITLE || entity?.info?.$TITLE || 'untitled';
48
+ const scanMode = entity?.scanMode || entity?.SCAN_MODE || entity?.info?.SCAN_MODE || entity?.info?.SCANMODE || '';
49
+ return {
50
+ index: entityIndex,
51
+ kind: info.kind,
52
+ polarity: info.polarity,
53
+ title,
54
+ scanMode,
55
+ spectraCount: getLcmsPageCandidates(entity).length
56
+ };
57
+ };
58
+ const selectLcmsMsPage = (entities = [], request = {}) => {
59
+ const retentionTimeRaw = stringifyLcmsPageValue(request?.retentionTime);
60
+ const retentionTime = parsePageValue(retentionTimeRaw);
61
+ const polarity = normalizeLcmsRequestPolarity(request?.polarity);
62
+ const tolerance = 1e-5;
63
+ const split = (0, _extractEntityLCMS.splitAndReindexEntities)(entities);
64
+ const mzEntities = split?.mzEntities || [];
65
+ if (mzEntities.length === 0) {
66
+ return {
67
+ retentionTimeRaw,
68
+ retentionTime,
69
+ polarity,
70
+ entity: null,
71
+ entityIndex: -1,
72
+ spectrum: null,
73
+ sourceFallback: false,
74
+ pageFallback: false
75
+ };
76
+ }
77
+ let candidateEntries = mzEntities.map((entity, index) => ({
78
+ entity,
79
+ index
80
+ }));
81
+ let sourceFallback = false;
82
+ if (candidateEntries.length === 1) {
83
+ const onlyPolarity = (0, _extractEntityLCMS.getLcMsInfo)(candidateEntries[0].entity).polarity;
84
+ sourceFallback = polarity != null && polarity !== onlyPolarity;
85
+ } else if (polarity != null) {
86
+ const matchingEntries = candidateEntries.filter(({
87
+ entity
88
+ }) => (0, _extractEntityLCMS.getLcMsInfo)(entity).polarity === polarity);
89
+ if (matchingEntries.length > 0) {
90
+ candidateEntries = matchingEntries;
91
+ } else {
92
+ sourceFallback = true;
93
+ }
94
+ }
95
+ let best = null;
96
+ candidateEntries.forEach(({
97
+ entity,
98
+ index
99
+ }) => {
100
+ getLcmsPageCandidates(entity).forEach((spectrum, spectrumIndex) => {
101
+ const pageValue = parsePageValue(spectrum?.pageValue ?? spectrum?.page ?? spectrum?.pageSymbol);
102
+ const distance = Number.isFinite(retentionTime) && Number.isFinite(pageValue) ? Math.abs(pageValue - retentionTime) : Number.POSITIVE_INFINITY;
103
+ const hasExactMatch = Number.isFinite(distance) && distance <= tolerance;
104
+ if (!best) {
105
+ best = {
106
+ entity,
107
+ entityIndex: index,
108
+ spectrum,
109
+ spectrumIndex,
110
+ pageValue,
111
+ distance,
112
+ hasExactMatch
113
+ };
114
+ return;
115
+ }
116
+ if (hasExactMatch && !best.hasExactMatch) {
117
+ best = {
118
+ entity,
119
+ entityIndex: index,
120
+ spectrum,
121
+ spectrumIndex,
122
+ pageValue,
123
+ distance,
124
+ hasExactMatch
125
+ };
126
+ return;
127
+ }
128
+ if (hasExactMatch === best.hasExactMatch && distance < best.distance) {
129
+ best = {
130
+ entity,
131
+ entityIndex: index,
132
+ spectrum,
133
+ spectrumIndex,
134
+ pageValue,
135
+ distance,
136
+ hasExactMatch
137
+ };
138
+ }
139
+ });
140
+ });
141
+ if (!best) {
142
+ const fallbackEntity = candidateEntries[0]?.entity || mzEntities[0];
143
+ const fallbackEntityIndex = candidateEntries[0]?.index ?? 0;
144
+ const fallbackSpectrum = getLcmsPageCandidates(fallbackEntity)[0] || null;
145
+ return {
146
+ retentionTimeRaw,
147
+ retentionTime,
148
+ polarity,
149
+ entity: fallbackEntity,
150
+ entityIndex: fallbackEntityIndex,
151
+ spectrum: fallbackSpectrum,
152
+ sourceFallback,
153
+ pageFallback: Number.isFinite(retentionTime)
154
+ };
155
+ }
156
+ return {
157
+ retentionTimeRaw,
158
+ retentionTime,
159
+ polarity,
160
+ entity: best.entity,
161
+ entityIndex: best.entityIndex,
162
+ spectrum: best.spectrum,
163
+ sourceFallback,
164
+ pageFallback: Number.isFinite(retentionTime) && !best.hasExactMatch
165
+ };
166
+ };
167
+ exports.selectLcmsMsPage = selectLcmsMsPage;
168
+ const buildLcmsMsPageJcamp = (entities = [], request = {}) => {
169
+ const selection = selectLcmsMsPage(entities, request);
170
+ const {
171
+ retentionTimeRaw,
172
+ retentionTime,
173
+ polarity,
174
+ entity,
175
+ entityIndex,
176
+ spectrum,
177
+ sourceFallback,
178
+ pageFallback
179
+ } = selection;
180
+ const pageHeader = retentionTimeRaw || stringifyLcmsPageValue(spectrum?.pageValue ?? spectrum?.page ?? spectrum?.pageSymbol);
181
+ if (!entity || !spectrum) {
182
+ const emptyResult = {
183
+ jcamp: '',
184
+ selection: {
185
+ retentionTime: retentionTimeRaw,
186
+ polarity,
187
+ msSourceChosen: null,
188
+ pageChosen: null,
189
+ hasPageHeader: false,
190
+ fallbackApplied: sourceFallback || pageFallback
191
+ }
192
+ };
193
+ return emptyResult;
194
+ }
195
+ const {
196
+ x,
197
+ y
198
+ } = getSpectrumDataPairs(spectrum);
199
+ const points = x.map((xValue, index) => `${xValue}, ${y[index]}`);
200
+ const minX = x.length > 0 ? Math.min(...x) : null;
201
+ const maxX = x.length > 0 ? Math.max(...x) : null;
202
+ const minY = y.length > 0 ? Math.min(...y) : null;
203
+ const maxY = y.length > 0 ? Math.max(...y) : null;
204
+ const xUnit = spectrum?.xUnit || entity?.xUnit || entity?.info?.XUNITS || 'm/z';
205
+ const yUnit = spectrum?.yUnit || entity?.yUnit || entity?.info?.YUNITS || 'Intensity';
206
+ const scanMode = spectrum?.scanMode || entity?.scanMode || entity?.info?.SCAN_MODE || entity?.info?.SCANMODE || '';
207
+ const title = spectrum?.title || entity?.title || entity?.info?.TITLE || 'Spectrum';
208
+ const pageChosen = stringifyLcmsPageValue(spectrum?.pageValue ?? spectrum?.page ?? spectrum?.pageSymbol);
209
+ const lines = [`##TITLE=${title}`, '##JCAMP-DX=5.00', '##DATA TYPE=MASS SPECTRUM', '##DATA CLASS=XYPOINTS', '##ORIGIN=Chemspectra', '##OWNER=', `##XUNITS=${xUnit}`, `##YUNITS=${yUnit}`];
210
+ if (scanMode) lines.push(`##SCAN_MODE=${scanMode}`);
211
+ if (pageHeader) lines.push(`##PAGE=${pageHeader}`);
212
+ lines.push(`##NPOINTS=${points.length}`);
213
+ if (minX != null) lines.push(`##FIRSTX=${x[0]}`);
214
+ if (maxX != null) lines.push(`##LASTX=${x[x.length - 1]}`);
215
+ if (minX != null) lines.push(`##MINX=${minX}`);
216
+ if (maxX != null) lines.push(`##MAXX=${maxX}`);
217
+ if (minY != null) lines.push(`##MINY=${minY}`);
218
+ if (maxY != null) lines.push(`##MAXY=${maxY}`);
219
+ if (y.length > 0) lines.push(`##FIRSTY=${y[0]}`);
220
+ lines.push('##XYDATA=(XY..XY)');
221
+ lines.push(...points);
222
+ lines.push('##END=');
223
+ const jcamp = `${lines.join('\n')}\n`;
224
+ const selectionLog = {
225
+ retentionTime: retentionTimeRaw,
226
+ polarity,
227
+ msSourceChosen: describeLcmsMsSource(entity, entityIndex),
228
+ pageChosen: {
229
+ requested: retentionTimeRaw,
230
+ selected: pageChosen,
231
+ numericRequested: Number.isFinite(retentionTime) ? retentionTime : null
232
+ },
233
+ hasPageHeader: pageHeader ? jcamp.includes(`##PAGE=${pageHeader}`) : false,
234
+ fallbackApplied: sourceFallback || pageFallback
235
+ };
236
+ return {
237
+ jcamp,
238
+ selection: selectionLog
239
+ };
240
+ };
241
+ exports.buildLcmsMsPageJcamp = buildLcmsMsPageJcamp;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.parseFeaturePageValue = void 0;
7
+ /* eslint-disable import/prefer-default-export */
8
+ const parseFeaturePageValue = feature => {
9
+ const candidates = [feature?.pageValue, feature?.page, feature?.pageSymbol];
10
+ for (let i = 0; i < candidates.length; i += 1) {
11
+ const raw = candidates[i];
12
+ if (raw != null) {
13
+ if (typeof raw === 'number') {
14
+ if (Number.isFinite(raw)) return raw;
15
+ } else {
16
+ const text = String(raw).split('\n')[0].trim();
17
+ const match = text.match(/[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?/);
18
+ if (match) {
19
+ const value = Number(match[0]);
20
+ if (Number.isFinite(value)) return value;
21
+ }
22
+ }
23
+ }
24
+ }
25
+ return null;
26
+ };
27
+ exports.parseFeaturePageValue = parseFeaturePageValue;