@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.
Files changed (49) hide show
  1. package/dist/__tests__/components/SmartGroupFieldRecipe.groups.test.d.ts +1 -0
  2. package/dist/__tests__/components/SmartGroupManual.cohorts.test.d.ts +1 -0
  3. package/dist/components/AutoGroupModal.adapter.d.ts +42 -0
  4. package/dist/components/AutoGroupModal.vue.d.ts +1 -105
  5. package/dist/components/SmartGroup.types.d.ts +71 -0
  6. package/dist/components/SmartGroupFieldRecipe.groups.d.ts +53 -0
  7. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +37 -0
  8. package/dist/components/SmartGroupManual.cohorts.d.ts +52 -0
  9. package/dist/components/SmartGroupManual.vue.d.ts +17 -0
  10. package/dist/components/SmartGroupModal.vue.d.ts +27 -0
  11. package/dist/components/index.d.ts +3 -0
  12. package/dist/components/index.js +2 -2
  13. package/dist/{components-BGVwavdd.js → components-DjfatNJv.js} +2519 -2335
  14. package/dist/components-DjfatNJv.js.map +1 -0
  15. package/dist/composables/index.js +2 -2
  16. package/dist/{composables-C_hPF0Gn.js → composables-CpBhNKHf.js} +7 -147
  17. package/dist/composables-CpBhNKHf.js.map +1 -0
  18. package/dist/index.js +4 -4
  19. package/dist/install.js +1 -1
  20. package/dist/styles.css +12711 -12222
  21. package/dist/{useProtocolTemplates-BbvlHoPD.js → useProtocolTemplates-C8-YlHj1.js} +206 -66
  22. package/dist/useProtocolTemplates-C8-YlHj1.js.map +1 -0
  23. package/package.json +1 -1
  24. package/src/__tests__/components/AutoGroupModal.adapter.test.ts +164 -0
  25. package/src/__tests__/components/SampleSelector.test.ts +176 -16
  26. package/src/__tests__/components/SmartGroupFieldRecipe.groups.test.ts +96 -0
  27. package/src/__tests__/components/SmartGroupManual.cohorts.test.ts +97 -0
  28. package/src/components/AutoGroupModal.adapter.ts +147 -0
  29. package/src/components/AutoGroupModal.vue +176 -1321
  30. package/src/components/SampleSelector.vue +8 -23
  31. package/src/components/SmartGroup.types.ts +93 -0
  32. package/src/components/SmartGroupFieldRecipe.groups.ts +105 -0
  33. package/src/components/SmartGroupFieldRecipe.story.vue +58 -0
  34. package/src/components/SmartGroupFieldRecipe.vue +427 -0
  35. package/src/components/SmartGroupManual.cohorts.ts +112 -0
  36. package/src/components/SmartGroupManual.story.vue +55 -0
  37. package/src/components/SmartGroupManual.vue +398 -0
  38. package/src/components/SmartGroupModal.story.vue +61 -0
  39. package/src/components/SmartGroupModal.vue +61 -0
  40. package/src/components/index.ts +3 -0
  41. package/src/styles/components/sample-selector.css +1 -5
  42. package/src/styles/components/smart-group.css +708 -0
  43. package/src/styles/index.css +1 -1
  44. package/dist/components-BGVwavdd.js.map +0 -1
  45. package/dist/composables-C_hPF0Gn.js.map +0 -1
  46. package/dist/useProtocolTemplates-BbvlHoPD.js.map +0 -1
  47. package/src/__tests__/components/AutoGroupModal.preview.test.ts +0 -46
  48. package/src/styles/components/auto-group-modal.css +0 -1336
  49. /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
+ }