@morscherlab/mld-sdk 0.7.6 → 0.7.7

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.
@@ -100,6 +100,20 @@ export function detectOutliers(
100
100
  return outliers
101
101
  }
102
102
 
103
+ const QC_KEYWORDS = new Set([
104
+ 'eqc', 'iqc', 'qc', 'blank', 'std', 'standard', 'test',
105
+ ])
106
+
107
+ export function classifyOutlierAction(
108
+ sample: string,
109
+ delimiter: string,
110
+ ): OutlierAction {
111
+ const segments = sample.split(delimiter)
112
+ return segments.some(seg => QC_KEYWORDS.has(seg.toLowerCase()))
113
+ ? 'qc'
114
+ : 'include'
115
+ }
116
+
103
117
  export function extractColumns(
104
118
  samples: string[],
105
119
  delimiter: string,
@@ -378,18 +392,33 @@ export function useAutoGroup() {
378
392
  const analysis = analyzeDelimiter(lines)
379
393
  delimiter.value = analysis.delimiter
380
394
  dominantFieldCount.value = analysis.dominantFieldCount
381
- minFieldCount.value = analysis.minFieldCount
382
395
 
383
- outliers.value = detectOutliers(lines, analysis.delimiter, analysis.minFieldCount)
396
+ // Use dominantFieldCount as outlier threshold so QC/test samples with
397
+ // fewer fields than the majority are correctly flagged
398
+ outliers.value = detectOutliers(lines, analysis.delimiter, analysis.dominantFieldCount)
399
+
400
+ // Apply smart default actions: auto-classify QC/test samples
401
+ for (const outlier of outliers.value) {
402
+ outlier.action = classifyOutlierAction(outlier.sample, analysis.delimiter)
403
+ }
384
404
 
385
405
  const conforming = lines.filter(
386
406
  (_, i) => !outliers.value.some(o => o.index === i)
387
407
  )
388
- fields.value = extractColumns(conforming, analysis.delimiter, analysis.minFieldCount)
389
408
 
390
- // Reset field names and enable all by default
409
+ // Recompute minFieldCount from conforming samples only
410
+ const conformingFieldCounts = conforming.map(s => s.split(analysis.delimiter).length)
411
+ minFieldCount.value = conformingFieldCounts.length > 0
412
+ ? Math.min(...conformingFieldCounts)
413
+ : analysis.dominantFieldCount
414
+
415
+ fields.value = extractColumns(conforming, analysis.delimiter, minFieldCount.value)
416
+
417
+ // Reset field names; auto-disable constant columns (cardinality 1)
391
418
  fieldNames.value = {}
392
- enabledFields.value = new Set(fields.value.map(f => f.index))
419
+ enabledFields.value = new Set(
420
+ fields.value.filter(f => f.cardinality > 1).map(f => f.index)
421
+ )
393
422
  }
394
423
 
395
424
  function parseCsvInput() {
@@ -123,7 +123,7 @@
123
123
  }
124
124
 
125
125
  .mld-sidebar__sections > .mld-collapsible-card .mld-collapsible-card__content {
126
- padding: 0 0.75rem 0.75rem;
126
+ padding: 0.75rem;
127
127
  display: flex;
128
128
  flex-direction: column;
129
129
  gap: 0.75rem;
@@ -1,7 +1,10 @@
1
1
  /* AutoGroupModal - Smart grouping wizard */
2
2
 
3
3
  .mld-auto-group {
4
- min-height: 400px;
4
+ flex: 1;
5
+ display: flex;
6
+ flex-direction: column;
7
+ min-height: 0;
5
8
  }
6
9
 
7
10
  /* --- Mode toggle --- */
@@ -401,9 +404,7 @@
401
404
  .mld-auto-group__preview-groups {
402
405
  display: flex;
403
406
  flex-direction: column;
404
- gap: 0.375rem;
405
- max-height: 320px;
406
- overflow-y: auto;
407
+ gap: 0.5rem;
407
408
  }
408
409
 
409
410
  .mld-auto-group__preview-group {
@@ -22,6 +22,9 @@
22
22
  .mld-modal__container {
23
23
  position: relative;
24
24
  width: 100%;
25
+ max-height: calc(100vh - 2rem);
26
+ display: flex;
27
+ flex-direction: column;
25
28
  background-color: var(--bg-card);
26
29
  border-radius: var(--mld-radius-lg);
27
30
  box-shadow: var(--mld-shadow-lg);
@@ -90,6 +93,9 @@
90
93
  /* Modal Body */
91
94
  .mld-modal__body {
92
95
  padding: 1rem 1.5rem;
96
+ flex: 1;
97
+ min-height: 0;
98
+ overflow-y: auto;
93
99
  }
94
100
 
95
101
  /* Modal Footer */
@@ -39,6 +39,12 @@
39
39
  opacity: 0.5;
40
40
  }
41
41
 
42
+ .mld-number-input__buttons {
43
+ display: flex;
44
+ flex-shrink: 0;
45
+ border-left: 1px solid var(--border-color);
46
+ }
47
+
42
48
  .mld-number-input__button {
43
49
  display: flex;
44
50
  align-items: center;
@@ -65,10 +71,6 @@
65
71
  border-right: 1px solid var(--border-color);
66
72
  }
67
73
 
68
- .mld-number-input__button--increment {
69
- border-left: 1px solid var(--border-color);
70
- }
71
-
72
74
  .mld-number-input__button--sm {
73
75
  width: 1.75rem;
74
76
  }
@@ -89,7 +91,7 @@
89
91
  .mld-number-input__input {
90
92
  flex: 1;
91
93
  min-width: 0;
92
- text-align: center;
94
+ text-align: left;
93
95
  background-color: var(--bg-secondary);
94
96
  color: var(--text-primary);
95
97
  border: none;
@@ -3,10 +3,13 @@
3
3
  flex-direction: column;
4
4
  gap: 1.5rem;
5
5
  outline: none;
6
+ flex: 1;
7
+ min-height: 0;
6
8
  }
7
9
 
8
10
  /* Progress indicator */
9
11
  .mld-wizard__progress {
12
+ flex-shrink: 0;
10
13
  }
11
14
 
12
15
  .mld-wizard__steps-indicator {
@@ -101,6 +104,7 @@
101
104
  .mld-wizard__body {
102
105
  flex: 1;
103
106
  min-height: 0;
107
+ overflow-y: auto;
104
108
  }
105
109
 
106
110
  /* Navigation */
@@ -110,6 +114,7 @@
110
114
  gap: 0.5rem;
111
115
  padding-top: 1rem;
112
116
  border-top: 1px solid var(--border-color);
117
+ flex-shrink: 0;
113
118
  }
114
119
 
115
120
  .mld-wizard__nav-btn {