@morscherlab/mint-sdk 1.0.43 → 1.0.45
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/dist/__tests__/components/SmartGroupFieldRecipe.groups.test.d.ts +1 -0
- package/dist/__tests__/components/SmartGroupManual.cohorts.test.d.ts +1 -0
- package/dist/components/AutoGroupModal.adapter.d.ts +42 -0
- package/dist/components/AutoGroupModal.vue.d.ts +1 -105
- package/dist/components/SmartGroup.types.d.ts +71 -0
- package/dist/components/SmartGroupFieldRecipe.groups.d.ts +53 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +37 -0
- package/dist/components/SmartGroupManual.cohorts.d.ts +52 -0
- package/dist/components/SmartGroupManual.vue.d.ts +17 -0
- package/dist/components/SmartGroupModal.vue.d.ts +27 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +2 -2
- package/dist/{components-BGVwavdd.js → components-DjfatNJv.js} +2519 -2335
- package/dist/components-DjfatNJv.js.map +1 -0
- package/dist/composables/index.js +2 -2
- package/dist/{composables-C_hPF0Gn.js → composables-CpBhNKHf.js} +7 -147
- package/dist/composables-CpBhNKHf.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/install.js +1 -1
- package/dist/styles.css +12711 -12222
- package/dist/{useProtocolTemplates-BbvlHoPD.js → useProtocolTemplates-C8-YlHj1.js} +206 -66
- package/dist/useProtocolTemplates-C8-YlHj1.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/AutoGroupModal.adapter.test.ts +164 -0
- package/src/__tests__/components/SampleSelector.test.ts +176 -16
- package/src/__tests__/components/SmartGroupFieldRecipe.groups.test.ts +96 -0
- package/src/__tests__/components/SmartGroupManual.cohorts.test.ts +97 -0
- package/src/components/AutoGroupModal.adapter.ts +147 -0
- package/src/components/AutoGroupModal.vue +176 -1321
- package/src/components/SampleSelector.vue +8 -23
- package/src/components/SmartGroup.types.ts +93 -0
- package/src/components/SmartGroupFieldRecipe.groups.ts +105 -0
- package/src/components/SmartGroupFieldRecipe.story.vue +58 -0
- package/src/components/SmartGroupFieldRecipe.vue +427 -0
- package/src/components/SmartGroupManual.cohorts.ts +112 -0
- package/src/components/SmartGroupManual.story.vue +55 -0
- package/src/components/SmartGroupManual.vue +398 -0
- package/src/components/SmartGroupModal.story.vue +61 -0
- package/src/components/SmartGroupModal.vue +61 -0
- package/src/components/index.ts +3 -0
- package/src/styles/components/sample-selector.css +1 -5
- package/src/styles/components/smart-group.css +708 -0
- package/src/styles/index.css +1 -1
- package/dist/components-BGVwavdd.js.map +0 -1
- package/dist/composables-C_hPF0Gn.js.map +0 -1
- package/dist/useProtocolTemplates-BbvlHoPD.js.map +0 -1
- package/src/__tests__/components/AutoGroupModal.preview.test.ts +0 -46
- package/src/styles/components/auto-group-modal.css +0 -1336
- /package/dist/__tests__/components/{AutoGroupModal.preview.test.d.ts → AutoGroupModal.adapter.test.d.ts} +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure adapters mapping the `useAutoGroup` engine state to the controlled
|
|
3
|
+
* Smart Group UI (`SmartGroupFieldRecipe` / `SmartGroupManual`) and back to an
|
|
4
|
+
* `AutoGroupResult`. Framework-free so the mapping is unit-testable in isolation
|
|
5
|
+
* from `AutoGroupModal.vue`.
|
|
6
|
+
*/
|
|
7
|
+
import type { AutoGroupResult, ColumnInfo, ColumnRole } from '../types/auto-group'
|
|
8
|
+
import type { SampleGroup } from '../types'
|
|
9
|
+
import type { FieldRecipeGroup, QcRoute, SmartGroupField } from './SmartGroupFieldRecipe.groups'
|
|
10
|
+
import type { ManualSample } from './SmartGroupManual.cohorts'
|
|
11
|
+
import type { SmartGroupSeed } from './SmartGroup.types'
|
|
12
|
+
|
|
13
|
+
const ROLE_LABELS: Record<ColumnRole, string> = {
|
|
14
|
+
'constant': 'Constant',
|
|
15
|
+
'factor': 'Factor',
|
|
16
|
+
'replicate': 'Replicate',
|
|
17
|
+
'run-order': 'Run',
|
|
18
|
+
'numeric': 'Numeric',
|
|
19
|
+
'class-tag': 'Class tag',
|
|
20
|
+
'ignore': 'Ignore',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Human label for a column role; defaults to `Factor` when unset. */
|
|
24
|
+
export function roleLabel(role?: ColumnRole): string {
|
|
25
|
+
return role ? ROLE_LABELS[role] : 'Factor'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Columns worth offering as grouping toggles: factors and replicates, plus any
|
|
30
|
+
* column already enabled in `groupBy` (so a manual group-by choice still shows).
|
|
31
|
+
* Preserves the engine's column order.
|
|
32
|
+
*/
|
|
33
|
+
export function candidateColumns(columns: ColumnInfo[], enabled: ReadonlySet<number>): ColumnInfo[] {
|
|
34
|
+
return columns.filter(c => c.role === 'factor' || c.role === 'replicate' || enabled.has(c.index))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The candidate column indices in display order, so card index → engine column. */
|
|
38
|
+
export function candidateColumnIndices(columns: ColumnInfo[], enabled: ReadonlySet<number>): number[] {
|
|
39
|
+
return candidateColumns(columns, enabled).map(c => c.index)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Map engine columns to Field Recipe cards + their on/off state (same order). */
|
|
43
|
+
export function engineFieldsToCards(
|
|
44
|
+
columns: ColumnInfo[],
|
|
45
|
+
enabled: ReadonlySet<number>,
|
|
46
|
+
): { fields: SmartGroupField[]; enabled: boolean[] } {
|
|
47
|
+
const candidates = candidateColumns(columns, enabled)
|
|
48
|
+
return {
|
|
49
|
+
fields: candidates.map(c => ({
|
|
50
|
+
key: c.displayName ?? c.name,
|
|
51
|
+
role: roleLabel(c.role),
|
|
52
|
+
vals: c.uniqueValues.join(', '),
|
|
53
|
+
rep: c.role === 'replicate',
|
|
54
|
+
})),
|
|
55
|
+
enabled: candidates.map(c => enabled.has(c.index)),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Map engine sample groups to the live preview rail shape. */
|
|
60
|
+
export function engineGroupsToRail(groups: SampleGroup[]): FieldRecipeGroup[] {
|
|
61
|
+
return groups.map(g => ({ name: g.name, count: g.samples.length, color: g.color }))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ROUTE_TO_ENGINE: Record<QcRoute, 'overlay' | 'exclude' | 'mix'> = {
|
|
65
|
+
'Overlay only': 'overlay',
|
|
66
|
+
'Exclude': 'exclude',
|
|
67
|
+
'Mix into groups': 'mix',
|
|
68
|
+
}
|
|
69
|
+
const ENGINE_TO_ROUTE: Record<'overlay' | 'exclude' | 'mix', QcRoute> = {
|
|
70
|
+
overlay: 'Overlay only',
|
|
71
|
+
exclude: 'Exclude',
|
|
72
|
+
mix: 'Mix into groups',
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Map the Field Recipe QC label to the engine's class disposition routing. */
|
|
76
|
+
export function qcRouteToEngine(route: QcRoute): 'overlay' | 'exclude' | 'mix' {
|
|
77
|
+
return ROUTE_TO_ENGINE[route]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Map the engine's QC routing back to the Field Recipe label. */
|
|
81
|
+
export function engineToQcRoute(routing: 'overlay' | 'exclude' | 'mix'): QcRoute {
|
|
82
|
+
return ENGINE_TO_ROUTE[routing]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Seed the manual cohort builder from existing groups. The hierarchical group
|
|
87
|
+
* name (`group/sub/sub2`) splits into the three cohort levels; colour carries
|
|
88
|
+
* over. One seed entry per sample. Names with more than three `/`-segments keep
|
|
89
|
+
* the remainder in `sub2` (rejoined with `/`) so the round-trip is lossless —
|
|
90
|
+
* `manualSamplesToResult` re-joins on `/`.
|
|
91
|
+
*/
|
|
92
|
+
export function seedFromGroups(groups: SampleGroup[]): Record<string, SmartGroupSeed> {
|
|
93
|
+
const seed: Record<string, SmartGroupSeed> = {}
|
|
94
|
+
for (const group of groups) {
|
|
95
|
+
const parts = group.name.split('/')
|
|
96
|
+
const grp = parts[0]
|
|
97
|
+
const sub = parts[1]
|
|
98
|
+
const sub2 = parts.length > 3 ? parts.slice(2).join('/') : parts[2]
|
|
99
|
+
for (const sample of group.samples) {
|
|
100
|
+
seed[sample] = {
|
|
101
|
+
group: grp,
|
|
102
|
+
sub: sub || undefined,
|
|
103
|
+
sub2: sub2 || undefined,
|
|
104
|
+
color: group.color,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return seed
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Convert the manual cohort assignments back into an `AutoGroupResult`.
|
|
113
|
+
* `baseGroups` (the modal's incoming `groups` prop) are preserved in their
|
|
114
|
+
* original order and colour — including any that end up empty — so re-opening
|
|
115
|
+
* the modal and moving samples between cohorts is non-destructive. Newly created
|
|
116
|
+
* cohorts are appended in first-seen order.
|
|
117
|
+
*/
|
|
118
|
+
export function manualSamplesToResult(
|
|
119
|
+
samples: ManualSample[],
|
|
120
|
+
baseGroups: SampleGroup[] = [],
|
|
121
|
+
): AutoGroupResult {
|
|
122
|
+
const assigned = new Map<string, { color: string; samples: string[] }>()
|
|
123
|
+
const order: string[] = []
|
|
124
|
+
for (const sample of samples) {
|
|
125
|
+
if (!sample.group) continue
|
|
126
|
+
const name = [sample.group, sample.sub, sample.sub2].filter(Boolean).join('/')
|
|
127
|
+
if (!assigned.has(name)) {
|
|
128
|
+
assigned.set(name, { color: sample.color ?? '', samples: [] })
|
|
129
|
+
order.push(name)
|
|
130
|
+
}
|
|
131
|
+
assigned.get(name)!.samples.push(sample.name)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const groups: SampleGroup[] = []
|
|
135
|
+
const consumed = new Set<string>()
|
|
136
|
+
for (const base of baseGroups) {
|
|
137
|
+
consumed.add(base.name)
|
|
138
|
+
groups.push({ name: base.name, color: base.color, samples: assigned.get(base.name)?.samples ?? [] })
|
|
139
|
+
}
|
|
140
|
+
for (const name of order) {
|
|
141
|
+
if (consumed.has(name)) continue
|
|
142
|
+
const entry = assigned.get(name)!
|
|
143
|
+
groups.push({ name, color: entry.color, samples: entry.samples })
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { groups, experimentalGroups: groups, qcGroups: [], metadata: [], excludedSamples: [] }
|
|
147
|
+
}
|