@platforma-sdk/model 1.80.2 → 1.80.8
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/columns/column.cjs.map +1 -1
- package/dist/columns/column.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +4 -4
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +5 -5
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -1
- package/dist/labels/derive_distinct_labels.cjs +80 -57
- package/dist/labels/derive_distinct_labels.cjs.map +1 -1
- package/dist/labels/derive_distinct_labels.d.ts +16 -9
- package/dist/labels/derive_distinct_labels.d.ts.map +1 -1
- package/dist/labels/derive_distinct_labels.js +80 -57
- package/dist/labels/derive_distinct_labels.js.map +1 -1
- package/dist/labels/linked_column_postfix.cjs +189 -0
- package/dist/labels/linked_column_postfix.cjs.map +1 -0
- package/dist/labels/linked_column_postfix.d.ts +27 -0
- package/dist/labels/linked_column_postfix.d.ts.map +1 -0
- package/dist/labels/linked_column_postfix.js +188 -0
- package/dist/labels/linked_column_postfix.js.map +1 -0
- package/dist/package.cjs +1 -1
- package/dist/package.js +1 -1
- package/package.json +10 -10
- package/src/columns/column.ts +6 -6
- package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +110 -2
- package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +14 -18
- package/src/labels/derive_distinct_labels.test.ts +117 -72
- package/src/labels/derive_distinct_labels.ts +141 -112
- package/src/labels/linked_column_postfix.test.ts +112 -0
- package/src/labels/linked_column_postfix.ts +312 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Annotation, canonicalizeJson, getAxisId, readAnnotation } from "@milaboratories/pl-model-common";
|
|
2
|
+
import { isNil } from "es-toolkit";
|
|
3
|
+
//#region src/labels/linked_column_postfix.ts
|
|
4
|
+
/**
|
|
5
|
+
* Structural postfix derivation for linked columns (phase 2 of `deriveDistinctLabels`).
|
|
6
|
+
*
|
|
7
|
+
* A linked column is
|
|
8
|
+
* reached by a CHAIN of linkers, and we distinguish two such columns by the same ideology the main
|
|
9
|
+
* algorithm uses — "find the minimal difference, escalate until unique" — over two dimensions:
|
|
10
|
+
*
|
|
11
|
+
* - root: the single source axis of the chain, derived from `linkers[0]` (one of its axesSpec).
|
|
12
|
+
* This is "the source" — it's what we prefer to show ("difference of sources").
|
|
13
|
+
* - linkers: the linker chain itself (by `LinkLabel`), a per-step fallback used only where roots
|
|
14
|
+
* coincide.
|
|
15
|
+
*
|
|
16
|
+
* Nothing is stored redundantly: the caller passes the linker path (`linkers`) and the hit spec;
|
|
17
|
+
* root and every token are computed on the fly. The caller also supplies the `stem` (label+trace+
|
|
18
|
+
* quals from the existing single-entity machinery); this module only adds the path postfix, and
|
|
19
|
+
* only where stems collide.
|
|
20
|
+
*/
|
|
21
|
+
function defaultLinkerFormatter(parts) {
|
|
22
|
+
const chain = parts.linkers.map((l) => l.text).join(" > ");
|
|
23
|
+
const pieces = [parts.root?.text, chain || void 0].filter((p) => !isNil(p));
|
|
24
|
+
return pieces.length === 0 ? "" : `via ${pieces.join(" ")}`;
|
|
25
|
+
}
|
|
26
|
+
function extractAxisIdentity(axis) {
|
|
27
|
+
const id = getAxisId(axis);
|
|
28
|
+
return {
|
|
29
|
+
label: readAnnotation(axis, Annotation.Label)?.trim() || void 0,
|
|
30
|
+
name: id.name,
|
|
31
|
+
domain: id.domain,
|
|
32
|
+
contextDomain: id.contextDomain
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A linker is named ONLY by its human label (`LinkLabel`/`Label`) — never by its technical column
|
|
37
|
+
* name. Returns `undefined` for an unlabeled linker, which callers treat as "not renderable": such a
|
|
38
|
+
* step is skipped, and disambiguation falls to another step or the root.
|
|
39
|
+
*/
|
|
40
|
+
function extractLinkerIdentity(linker) {
|
|
41
|
+
const label = (readAnnotation(linker, Annotation.LinkLabel) ?? readAnnotation(linker, Annotation.Label))?.trim();
|
|
42
|
+
return label ? {
|
|
43
|
+
label,
|
|
44
|
+
name: label
|
|
45
|
+
} : void 0;
|
|
46
|
+
}
|
|
47
|
+
/** Domain keys where `mine` differs from at least one competitor. */
|
|
48
|
+
function differingKeys(mine, others) {
|
|
49
|
+
const my = mine ?? {};
|
|
50
|
+
const diffKeys = /* @__PURE__ */ new Set();
|
|
51
|
+
for (const o of others) {
|
|
52
|
+
const od = o ?? {};
|
|
53
|
+
for (const k of Object.keys(my)) if (my[k] !== od[k]) diffKeys.add(k);
|
|
54
|
+
for (const k of Object.keys(od)) if (my[k] !== od[k]) diffKeys.add(k);
|
|
55
|
+
}
|
|
56
|
+
return [...diffKeys];
|
|
57
|
+
}
|
|
58
|
+
function renderWithDomain(base, keys, domain) {
|
|
59
|
+
if (keys.length === 0) return base;
|
|
60
|
+
const d = domain ?? {};
|
|
61
|
+
return `${base}[${keys.map((k) => `${k}=${d[k] ?? "∅"}`).join(", ")}]`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Minimal token distinguishing `mine` from `others`: label → name → only the differing domain keys.
|
|
65
|
+
* Falls back to the bare label/name when indistinguishable at this dimension (another one covers it).
|
|
66
|
+
*/
|
|
67
|
+
function deriveToken(mine, others) {
|
|
68
|
+
const base = mine.label ?? mine.name;
|
|
69
|
+
if (mine.label !== void 0 && others.every((o) => o.label !== mine.label)) return mine.label;
|
|
70
|
+
if (others.every((o) => o.name !== mine.name)) return base;
|
|
71
|
+
const dk = differingKeys(mine.domain, others.map((o) => o.domain));
|
|
72
|
+
if (dk.length > 0) return renderWithDomain(base, dk, mine.domain);
|
|
73
|
+
const ck = differingKeys(mine.contextDomain, others.map((o) => o.contextDomain));
|
|
74
|
+
if (ck.length > 0) return renderWithDomain(base, ck, mine.contextDomain);
|
|
75
|
+
return base;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The chain's single root: the source-side axis of `linkers[0]`. A linker bridges two axis groups;
|
|
79
|
+
* the side facing the next hop (`linkers[1]`, or the hit for a single-linker chain) is the target,
|
|
80
|
+
* the other side is the source → its axis is the root. `undefined` if `linkers[0]` is not a
|
|
81
|
+
* well-formed two-group linker.
|
|
82
|
+
*/
|
|
83
|
+
function extractRoot(hit, linkers) {
|
|
84
|
+
const first = linkers[0];
|
|
85
|
+
if (first === void 0) return void 0;
|
|
86
|
+
const axes = first.axesSpec;
|
|
87
|
+
if (axes.length !== 2) return void 0;
|
|
88
|
+
const second = linkers[1] ?? hit;
|
|
89
|
+
return axes.find((a) => !second.axesSpec.some((b) => b.name === a.name));
|
|
90
|
+
}
|
|
91
|
+
const ABSENT = "__ABSENT__";
|
|
92
|
+
function deriveSlotKey(group, slot, row) {
|
|
93
|
+
if (slot.kind === "root") {
|
|
94
|
+
const r = group.roots[row];
|
|
95
|
+
return r === void 0 ? ABSENT : canonicalizeJson(getAxisId(r));
|
|
96
|
+
} else {
|
|
97
|
+
const l = group.entries[row].linkers?.[slot.i];
|
|
98
|
+
const id = l && extractLinkerIdentity(l);
|
|
99
|
+
return isNil(id) ? ABSENT : canonicalizeJson(id);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function deriveSlotToken(group, slot, row) {
|
|
103
|
+
if (slot.kind === "root") {
|
|
104
|
+
const r = group.roots[row];
|
|
105
|
+
if (r === void 0) return void 0;
|
|
106
|
+
const comp = group.roots.filter((o, j) => j !== row && !isNil(o)).map((o) => extractAxisIdentity(o));
|
|
107
|
+
return deriveToken(extractAxisIdentity(r), comp);
|
|
108
|
+
}
|
|
109
|
+
const l = group.entries[row].linkers?.[slot.i];
|
|
110
|
+
const id = l && extractLinkerIdentity(l);
|
|
111
|
+
if (isNil(id)) return void 0;
|
|
112
|
+
return deriveToken(id, group.entries.filter((_, j) => j !== row).map((e) => e.linkers?.[slot.i]).map((x) => x ? extractLinkerIdentity(x) : void 0).filter((x) => !isNil(x)));
|
|
113
|
+
}
|
|
114
|
+
function getDiscriminates(group, slot) {
|
|
115
|
+
return new Set(group.entries.map((_, r) => deriveSlotKey(group, slot, r))).size > 1;
|
|
116
|
+
}
|
|
117
|
+
/** Render one row against a chosen slot set: the distinguishing root + linker pieces, formatted. */
|
|
118
|
+
function renderRow(group, slots, row) {
|
|
119
|
+
const rootSpec = group.roots[row];
|
|
120
|
+
const rootText = slots.some((s) => s.kind === "root") ? deriveSlotToken(group, { kind: "root" }, row) : void 0;
|
|
121
|
+
const root = rootSpec !== void 0 && rootText !== void 0 ? {
|
|
122
|
+
spec: rootSpec,
|
|
123
|
+
text: rootText
|
|
124
|
+
} : void 0;
|
|
125
|
+
const linkers = slots.filter((s) => s.kind === "linker").sort((a, b) => a.i - b.i).map((s) => {
|
|
126
|
+
const spec = group.entries[row].linkers?.[s.i];
|
|
127
|
+
const text = deriveSlotToken(group, s, row);
|
|
128
|
+
return spec !== void 0 && text !== void 0 ? {
|
|
129
|
+
spec,
|
|
130
|
+
text
|
|
131
|
+
} : void 0;
|
|
132
|
+
}).filter((l) => !isNil(l));
|
|
133
|
+
if (root === void 0 && linkers.length === 0) return "";
|
|
134
|
+
return group.format({
|
|
135
|
+
root,
|
|
136
|
+
linkers
|
|
137
|
+
}, group.entries[row].hit, group.indices[row]) || "";
|
|
138
|
+
}
|
|
139
|
+
function renderAll(group, slots) {
|
|
140
|
+
return group.entries.map((_, r) => renderRow(group, slots, r));
|
|
141
|
+
}
|
|
142
|
+
function allUnique(rendered) {
|
|
143
|
+
return new Set(rendered).size === rendered.length;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Minimal slot set that makes the group unique. Escalate by priority (root, then linkers by step),
|
|
147
|
+
* then drop any redundant slot; render every row symmetrically against the result.
|
|
148
|
+
*
|
|
149
|
+
* KNOWN LIMITATION (review point): symmetric render can over-decorate a row in a mixed group (e.g.
|
|
150
|
+
* `via Sample MapperA` where `via Sample` alone is already unique for that row). Per-row trimming is
|
|
151
|
+
* a generalized `dropRedundantLinkerSuffix`; naive greedy trimming is unstable, so it's deferred.
|
|
152
|
+
*/
|
|
153
|
+
function resolveGroup(entries, indices, format) {
|
|
154
|
+
const group = {
|
|
155
|
+
entries,
|
|
156
|
+
roots: entries.map((e) => e.hit && e.linkers?.length ? extractRoot(e.hit, e.linkers) : void 0),
|
|
157
|
+
indices,
|
|
158
|
+
format
|
|
159
|
+
};
|
|
160
|
+
const maxLen = Math.max(0, ...entries.map((e) => e.linkers?.length ?? 0));
|
|
161
|
+
const escalated = [{ kind: "root" }, ...Array.from({ length: maxLen }, (_, i) => ({
|
|
162
|
+
kind: "linker",
|
|
163
|
+
i
|
|
164
|
+
}))].reduce((acc, slot) => allUnique(renderAll(group, acc)) || !getDiscriminates(group, slot) ? acc : (acc.push(slot), acc), []);
|
|
165
|
+
return renderAll(group, escalated.reduce((acc, slot) => {
|
|
166
|
+
const trial = acc.filter((s) => s !== slot);
|
|
167
|
+
return allUnique(renderAll(group, trial)) ? trial : acc;
|
|
168
|
+
}, escalated));
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Full label per entry: `stem` plus, where stems collide, a minimal postfix distinguishing the
|
|
172
|
+
* linked columns by the difference between their sources.
|
|
173
|
+
*/
|
|
174
|
+
function derivePostfixes(entries, format = defaultLinkerFormatter) {
|
|
175
|
+
const postfix = [...entries.reduce((acc, e, idx) => acc.set(e.stem, [...acc.get(e.stem) ?? [], idx]), /* @__PURE__ */ new Map()).values()].reduce((acc, idxs) => {
|
|
176
|
+
if (idxs.length < 2) return acc;
|
|
177
|
+
const resolved = resolveGroup(idxs.map((i) => entries[i]), idxs, format);
|
|
178
|
+
return idxs.reduce((m, i, k) => m.set(i, resolved[k]), acc);
|
|
179
|
+
}, /* @__PURE__ */ new Map());
|
|
180
|
+
return entries.map((e, i) => {
|
|
181
|
+
const p = postfix.get(i);
|
|
182
|
+
return p ? `${e.stem} ${p}` : e.stem;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
186
|
+
export { derivePostfixes, extractRoot };
|
|
187
|
+
|
|
188
|
+
//# sourceMappingURL=linked_column_postfix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linked_column_postfix.js","names":[],"sources":["../../src/labels/linked_column_postfix.ts"],"sourcesContent":["/**\n * Structural postfix derivation for linked columns (phase 2 of `deriveDistinctLabels`).\n *\n * A linked column is\n * reached by a CHAIN of linkers, and we distinguish two such columns by the same ideology the main\n * algorithm uses — \"find the minimal difference, escalate until unique\" — over two dimensions:\n *\n * - root: the single source axis of the chain, derived from `linkers[0]` (one of its axesSpec).\n * This is \"the source\" — it's what we prefer to show (\"difference of sources\").\n * - linkers: the linker chain itself (by `LinkLabel`), a per-step fallback used only where roots\n * coincide.\n *\n * Nothing is stored redundantly: the caller passes the linker path (`linkers`) and the hit spec;\n * root and every token are computed on the fly. The caller also supplies the `stem` (label+trace+\n * quals from the existing single-entity machinery); this module only adds the path postfix, and\n * only where stems collide.\n */\nimport {\n Annotation,\n canonicalizeJson,\n getAxisId,\n readAnnotation,\n type AxisSpec,\n type PColumnSpec,\n} from \"@milaboratories/pl-model-common\";\nimport { isNil } from \"es-toolkit\";\n\n/**\n * One entry to label: its stem plus the linker path that reached it (`linkers[0]` source-most) and\n * the hit spec used to orient the chain. Plain columns pass neither.\n */\nexport type PostfixEntry = { stem: string; hit?: PColumnSpec; linkers?: PColumnSpec[] };\n\n/**\n * One distinguishing piece of the postfix: the full source spec (axis or linker column — no info\n * lost) plus `text`, the minimal text that sets it apart from the siblings (a label, or only the\n * differing domain keys). Custom formatters may use `spec` freely; `text` is the default rendering.\n */\nexport type LinkerPart<Spec> = { spec: Spec; text: string };\n\n/** The distinguishing source of one column: its root axis (when it differs) + the linker chain. */\nexport type LinkerParts = {\n root?: LinkerPart<AxisSpec>;\n linkers: LinkerPart<PColumnSpec>[];\n};\n\n/**\n * Renders the postfix zone from the distinguishing sources. Default:\n * `via ${[root, linkers.join(\" > \")].filter(Boolean).join(\" \")}` (using each piece's `text`).\n * Returning `undefined` — or the empty string `\"\"` — suppresses the postfix entirely (the column\n * keeps just its stem); the two are treated identically.\n */\nexport type LinkerFormatter = (\n parts: LinkerParts,\n hit: PColumnSpec | undefined,\n index: number,\n) => string | undefined;\n\nfunction defaultLinkerFormatter(parts: LinkerParts): string {\n const chain = parts.linkers.map((l) => l.text).join(\" > \");\n const pieces = [parts.root?.text, chain || undefined].filter((p): p is string => !isNil(p));\n return pieces.length === 0 ? \"\" : `via ${pieces.join(\" \")}`;\n}\n\ntype AxisIdentity = { label?: string } & Pick<AxisSpec, \"name\" | \"domain\" | \"contextDomain\">;\n\nfunction extractAxisIdentity(axis: AxisSpec): AxisIdentity {\n const id = getAxisId(axis);\n return {\n label: readAnnotation(axis, Annotation.Label)?.trim() || undefined,\n name: id.name,\n domain: id.domain,\n contextDomain: id.contextDomain,\n };\n}\n\n/**\n * A linker is named ONLY by its human label (`LinkLabel`/`Label`) — never by its technical column\n * name. Returns `undefined` for an unlabeled linker, which callers treat as \"not renderable\": such a\n * step is skipped, and disambiguation falls to another step or the root.\n */\nfunction extractLinkerIdentity(linker: PColumnSpec): AxisIdentity | undefined {\n const label = (\n readAnnotation(linker, Annotation.LinkLabel) ?? readAnnotation(linker, Annotation.Label)\n )?.trim();\n return label ? { label, name: label } : undefined;\n}\n\n/** Domain keys where `mine` differs from at least one competitor. */\nfunction differingKeys(\n mine: Record<string, string> | undefined,\n others: (Record<string, string> | undefined)[],\n): string[] {\n const my = mine ?? {};\n const diffKeys = new Set<string>();\n for (const o of others) {\n const od = o ?? {};\n for (const k of Object.keys(my)) {\n if (my[k] !== od[k]) {\n diffKeys.add(k);\n }\n }\n for (const k of Object.keys(od)) {\n if (my[k] !== od[k]) {\n diffKeys.add(k);\n }\n }\n }\n return [...diffKeys];\n}\n\nfunction renderWithDomain(\n base: string,\n keys: string[],\n domain: Record<string, string> | undefined,\n): string {\n if (keys.length === 0) return base;\n const d = domain ?? {};\n const pairs = keys.map((k) => `${k}=${d[k] ?? \"∅\"}`).join(\", \");\n return `${base}[${pairs}]`;\n}\n\n/**\n * Minimal token distinguishing `mine` from `others`: label → name → only the differing domain keys.\n * Falls back to the bare label/name when indistinguishable at this dimension (another one covers it).\n */\nfunction deriveToken(mine: AxisIdentity, others: AxisIdentity[]): string {\n const base = mine.label ?? mine.name;\n if (mine.label !== undefined && others.every((o) => o.label !== mine.label)) return mine.label;\n if (others.every((o) => o.name !== mine.name)) return base;\n const dk = differingKeys(\n mine.domain,\n others.map((o) => o.domain),\n );\n if (dk.length > 0) return renderWithDomain(base, dk, mine.domain);\n const ck = differingKeys(\n mine.contextDomain,\n others.map((o) => o.contextDomain),\n );\n if (ck.length > 0) return renderWithDomain(base, ck, mine.contextDomain);\n return base;\n}\n\n/**\n * The chain's single root: the source-side axis of `linkers[0]`. A linker bridges two axis groups;\n * the side facing the next hop (`linkers[1]`, or the hit for a single-linker chain) is the target,\n * the other side is the source → its axis is the root. `undefined` if `linkers[0]` is not a\n * well-formed two-group linker.\n */\nexport function extractRoot(hit: PColumnSpec, linkers: PColumnSpec[]): AxisSpec | undefined {\n const first = linkers[0];\n if (first === undefined) return undefined;\n const axes = first.axesSpec;\n if (axes.length !== 2) return undefined;\n const second = linkers[1] ?? hit;\n return axes.find((a) => !second.axesSpec.some((b) => b.name === a.name));\n}\n\ntype Slot = { kind: \"root\" } | { kind: \"linker\"; i: number };\n\nconst ABSENT = \"__ABSENT__\"; // sentinel for \"this row has no value at this slot\"\n\n/** A colliding group: entries + their derived roots + original indices + the postfix formatter. */\ntype Group = {\n entries: PostfixEntry[];\n roots: (AxisSpec | undefined)[];\n indices: number[];\n format: LinkerFormatter;\n};\n\nfunction deriveSlotKey(group: Group, slot: Slot, row: number): string {\n if (slot.kind === \"root\") {\n const r = group.roots[row];\n return r === undefined ? ABSENT : canonicalizeJson(getAxisId(r));\n } else {\n const l = group.entries[row].linkers?.[slot.i];\n const id = l && extractLinkerIdentity(l);\n return isNil(id) ? ABSENT : canonicalizeJson(id);\n }\n}\n\nfunction deriveSlotToken(group: Group, slot: Slot, row: number): string | undefined {\n if (slot.kind === \"root\") {\n const r = group.roots[row];\n if (r === undefined) return undefined;\n const comp = group.roots\n .filter((o, j) => j !== row && !isNil(o))\n .map((o) => extractAxisIdentity(o!));\n return deriveToken(extractAxisIdentity(r), comp);\n }\n const l = group.entries[row].linkers?.[slot.i];\n const id = l && extractLinkerIdentity(l);\n if (isNil(id)) return undefined; // unlabeled / absent linker → not renderable, skip\n const comp = group.entries\n .filter((_, j) => j !== row)\n .map((e) => e.linkers?.[slot.i])\n .map((x) => (x ? extractLinkerIdentity(x) : undefined))\n .filter((x): x is AxisIdentity => !isNil(x));\n return deriveToken(id, comp);\n}\n\nfunction getDiscriminates(group: Group, slot: Slot): boolean {\n return new Set(group.entries.map((_, r) => deriveSlotKey(group, slot, r))).size > 1;\n}\n\n/** Render one row against a chosen slot set: the distinguishing root + linker pieces, formatted. */\nfunction renderRow(group: Group, slots: Slot[], row: number): string {\n const rootSpec = group.roots[row];\n const rootText = slots.some((s) => s.kind === \"root\")\n ? deriveSlotToken(group, { kind: \"root\" }, row)\n : undefined;\n const root: LinkerPart<AxisSpec> | undefined =\n rootSpec !== undefined && rootText !== undefined\n ? { spec: rootSpec, text: rootText }\n : undefined;\n\n const linkers = slots\n .filter((s): s is { kind: \"linker\"; i: number } => s.kind === \"linker\")\n .sort((a, b) => a.i - b.i)\n .map((s) => {\n const spec = group.entries[row].linkers?.[s.i];\n const text = deriveSlotToken(group, s, row);\n return spec !== undefined && text !== undefined ? { spec, text } : undefined;\n })\n .filter((l): l is LinkerPart<PColumnSpec> => !isNil(l));\n\n // No distinguishing pieces for this row → no postfix (don't invoke the formatter with empties).\n if (root === undefined && linkers.length === 0) return \"\";\n // A formatter may suppress the postfix by returning `undefined` or `\"\"`; `|| \"\"` collapses both\n // to the same empty value so neither counts as a distinguishing token during slot escalation.\n return group.format({ root, linkers }, group.entries[row].hit, group.indices[row]) || \"\";\n}\n\nfunction renderAll(group: Group, slots: Slot[]): string[] {\n return group.entries.map((_, r) => renderRow(group, slots, r));\n}\n\nfunction allUnique(rendered: string[]): boolean {\n return new Set(rendered).size === rendered.length;\n}\n\n/**\n * Minimal slot set that makes the group unique. Escalate by priority (root, then linkers by step),\n * then drop any redundant slot; render every row symmetrically against the result.\n *\n * KNOWN LIMITATION (review point): symmetric render can over-decorate a row in a mixed group (e.g.\n * `via Sample MapperA` where `via Sample` alone is already unique for that row). Per-row trimming is\n * a generalized `dropRedundantLinkerSuffix`; naive greedy trimming is unstable, so it's deferred.\n */\nfunction resolveGroup(\n entries: PostfixEntry[],\n indices: number[],\n format: LinkerFormatter,\n): string[] {\n const group: Group = {\n entries,\n roots: entries.map((e) =>\n e.hit && e.linkers?.length ? extractRoot(e.hit, e.linkers) : undefined,\n ),\n indices,\n format,\n };\n const maxLen = Math.max(0, ...entries.map((e) => e.linkers?.length ?? 0));\n\n const slots: Slot[] = [\n { kind: \"root\" },\n ...Array.from({ length: maxLen }, (_, i): Slot => ({ kind: \"linker\", i })),\n ];\n\n const escalated = slots.reduce<Slot[]>(\n (acc, slot) =>\n allUnique(renderAll(group, acc)) || !getDiscriminates(group, slot)\n ? acc\n : (acc.push(slot), acc),\n [],\n );\n const chosen = escalated.reduce<Slot[]>((acc, slot) => {\n const trial = acc.filter((s) => s !== slot);\n return allUnique(renderAll(group, trial)) ? trial : acc;\n }, escalated);\n\n return renderAll(group, chosen);\n}\n\n/**\n * Full label per entry: `stem` plus, where stems collide, a minimal postfix distinguishing the\n * linked columns by the difference between their sources.\n */\nexport function derivePostfixes(\n entries: PostfixEntry[],\n format: LinkerFormatter = defaultLinkerFormatter,\n): string[] {\n const groups = entries.reduce<Map<string, number[]>>(\n (acc, e, idx) => acc.set(e.stem, [...(acc.get(e.stem) ?? []), idx]),\n new Map(),\n );\n\n const postfix = [...groups.values()].reduce<Map<number, string>>((acc, idxs) => {\n if (idxs.length < 2) return acc; // stem already unique — no postfix\n const resolved = resolveGroup(\n idxs.map((i) => entries[i]),\n idxs,\n format,\n );\n return idxs.reduce((m, i, k) => m.set(i, resolved[k]), acc);\n }, new Map());\n\n return entries.map((e, i) => {\n const p = postfix.get(i);\n return p ? `${e.stem} ${p}` : e.stem;\n });\n}\n"],"mappings":"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"}
|
package/dist/package.cjs
CHANGED
package/dist/package.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platforma-sdk/model",
|
|
3
|
-
"version": "1.80.
|
|
3
|
+
"version": "1.80.8",
|
|
4
4
|
"description": "Platforma.bio SDK / Block Model",
|
|
5
5
|
"files": [
|
|
6
6
|
"./dist/**/*",
|
|
@@ -31,23 +31,23 @@
|
|
|
31
31
|
"lru-cache": "^11.2.2",
|
|
32
32
|
"utility-types": "^3.11.0",
|
|
33
33
|
"zod": "~3.25.76",
|
|
34
|
-
"@milaboratories/helpers": "1.14.
|
|
34
|
+
"@milaboratories/helpers": "1.14.5",
|
|
35
|
+
"@milaboratories/pl-model-common": "1.47.2",
|
|
36
|
+
"@milaboratories/ptabler-expression-js": "1.2.36",
|
|
35
37
|
"@milaboratories/pl-error-like": "1.12.10",
|
|
36
|
-
"@milaboratories/pl-model-
|
|
37
|
-
"@milaboratories/ptabler-expression-js": "1.2.35",
|
|
38
|
-
"@milaboratories/pl-model-middle-layer": "1.30.13"
|
|
38
|
+
"@milaboratories/pl-model-middle-layer": "1.30.14"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@vitest/coverage-istanbul": "^4.1.3",
|
|
42
42
|
"fast-json-patch": "^3.1.1",
|
|
43
43
|
"typescript": "~5.9.3",
|
|
44
44
|
"vitest": "^4.1.3",
|
|
45
|
-
"@milaboratories/columns-collection-driver": "0.2.1",
|
|
46
45
|
"@milaboratories/build-configs": "2.0.0",
|
|
47
|
-
"@milaboratories/
|
|
48
|
-
"@milaboratories/pf-
|
|
49
|
-
"@milaboratories/
|
|
50
|
-
"@milaboratories/
|
|
46
|
+
"@milaboratories/columns-collection-driver": "0.2.2",
|
|
47
|
+
"@milaboratories/pf-driver": "1.8.3",
|
|
48
|
+
"@milaboratories/pf-spec-driver": "1.4.22",
|
|
49
|
+
"@milaboratories/ts-configs": "1.3.1",
|
|
50
|
+
"@milaboratories/ts-builder": "1.6.1"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"build": "ts-builder build --target node",
|
package/src/columns/column.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
ColumnUniversalId,
|
|
3
|
-
LeafEntry,
|
|
4
|
-
PColumn,
|
|
5
|
-
PlRef,
|
|
6
|
-
PObjectId,
|
|
1
|
+
import {
|
|
2
|
+
type ColumnUniversalId,
|
|
3
|
+
type LeafEntry,
|
|
4
|
+
type PColumn,
|
|
5
|
+
type PlRef,
|
|
6
|
+
type PObjectId,
|
|
7
7
|
} from "@milaboratories/pl-model-common";
|
|
8
8
|
import type { GlobalCfgRenderCtx } from "../render/internal";
|
|
9
9
|
import type { TreeNodeAccessor } from "../render";
|
|
@@ -227,11 +227,11 @@ type ResolvedColumns = {
|
|
|
227
227
|
function resolveInputColumns<A, U>(
|
|
228
228
|
ctx: RenderCtxBase<A, U>,
|
|
229
229
|
options: createPlDataTableOptionsV3,
|
|
230
|
-
):
|
|
230
|
+
): undefined | ResolvedColumns {
|
|
231
231
|
if ("primaryColumns" in options) {
|
|
232
232
|
const primary = options.primaryColumns;
|
|
233
233
|
const secondary = options.columns ?? [];
|
|
234
|
-
const labels = discoverLabelColumns(ctx, primary
|
|
234
|
+
const labels = discoverLabelColumns(ctx, primary);
|
|
235
235
|
// Exclude from secondary anything already present in primary: a label
|
|
236
236
|
// column the block hands in as primary can be re-discovered here as a
|
|
237
237
|
// label for that same axis, landing in the table twice with the same id.
|
|
@@ -525,3 +525,111 @@ function remapFilterColumnIds(
|
|
|
525
525
|
|
|
526
526
|
return prune(filters) as Nil | PlDataTableFilters;
|
|
527
527
|
}
|
|
528
|
+
|
|
529
|
+
const _ = [
|
|
530
|
+
{
|
|
531
|
+
type: "column",
|
|
532
|
+
id: '{"__isRef":true,"blockId":"6b2ae9f7-526d-4fd9-8a28-c0e3756c0b07","name":"clusters.assign.clusterLabel"}',
|
|
533
|
+
spec: {
|
|
534
|
+
kind: "PColumn",
|
|
535
|
+
axesSpec: [
|
|
536
|
+
{
|
|
537
|
+
name: "pl7.app/vdj/clonotypeKey",
|
|
538
|
+
type: "String",
|
|
539
|
+
domain: {
|
|
540
|
+
"pl7.app/redefined-by": "aabfc81f-0fc9-4699-84e8-6e749a3e384d",
|
|
541
|
+
"pl7.app/vdj/chain": "IGHeavy",
|
|
542
|
+
"pl7.app/vdj/clonotypeKey/structure":
|
|
543
|
+
'[["pl7.app/vdj/sequence",["pl7.app/alphabet","aminoacid"],["pl7.app/vdj/feature","VDJRegion"]]]',
|
|
544
|
+
"pl7.app/vdj/clonotypingRunId": "6cfab875-649e-4c52-bb69-fe976c2b57a0",
|
|
545
|
+
},
|
|
546
|
+
annotations: {
|
|
547
|
+
"pl7.app/label": "Clonotype ID",
|
|
548
|
+
"pl7.app/segmentedBy": '["pl7.app/vdj/clonotypingRunId"]',
|
|
549
|
+
"pl7.app/table/fontFamily": "monospace",
|
|
550
|
+
"pl7.app/table/orderPriority": "110000",
|
|
551
|
+
"pl7.app/table/visibility": "default",
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
name: "pl7.app/clusterId",
|
|
556
|
+
valueType: "String",
|
|
557
|
+
domain: {
|
|
558
|
+
"pl7.app/clustering/algorithm": "foldseek",
|
|
559
|
+
"pl7.app/clustering/blockId": "6b2ae9f7-526d-4fd9-8a28-c0e3756c0b07",
|
|
560
|
+
},
|
|
561
|
+
annotations: {
|
|
562
|
+
"pl7.app/label": "Cluster Id",
|
|
563
|
+
"pl7.app/structure/clusteringSource": "foldseek",
|
|
564
|
+
"pl7.app/trace":
|
|
565
|
+
'[{"type":"milaboratories.samples-and-data","id":"0189e23b-5e8d-4648-8c6d-70caa5c26958","importance":10,"label":"Samples & Data"},{"type":"milaboratories.samples-and-data/dataset","id":"34U3GEP5PGJF45UG3A2WZ3JO","importance":100,"label":"MB135 + Podocytes"},{"label":"MiXCR generic amplicon","type":"milaboratories.mixcr-amplicon-alignment","id":"6cfab875-649e-4c52-bb69-fe976c2b57a0","importance":20},{"type":"milaboratories.redefine-clonotypes","importance":30,"label":"Imputed VDJRegion aa"},{"type":"milaboratories.antibody-tcr-lead-selection","importance":30,"label":"Selected Leads"},{"type":"milaboratories.3d-structure-prediction","id":"d204391a-e7dd-434d-9c24-ef77b5da81e0","importance":20,"label":"Camelid (VHH/nanobody) NBB2, CDRH3 ≤ 2.5 Å"},{"type":"milaboratories.3d-structure-clustering.clustering","id":"6b2ae9f7-526d-4fd9-8a28-c0e3756c0b07","importance":30,"label":"Full Structure+AA, TM≥0.95, cov≥0.95"}]',
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
type: "column",
|
|
571
|
+
id: '{"__isDiscovered":true,"column":"{\\"__isRef\\":true,\\"blockId\\":\\"440a489a-8ae8-4982-9d1e-6297e7b8b8d0\\",\\"name\\":\\"pf.clusterLabel4\\"}","path":[{"column":"{\\"__isRef\\":true,\\"blockId\\":\\"440a489a-8ae8-4982-9d1e-6297e7b8b8d0\\",\\"name\\":\\"pf.link3\\"}","type":"linker"}]}',
|
|
572
|
+
spec: {
|
|
573
|
+
kind: "PColumn",
|
|
574
|
+
axesSpec: [
|
|
575
|
+
{
|
|
576
|
+
name: "pl7.app/clusterId",
|
|
577
|
+
type: "String",
|
|
578
|
+
domain: {
|
|
579
|
+
"pl7.app/clustering/algorithm": "mmseqs2",
|
|
580
|
+
"pl7.app/clustering/blockId": "440a489a-8ae8-4982-9d1e-6297e7b8b8d0",
|
|
581
|
+
"pl7.app/redefined-by": "aabfc81f-0fc9-4699-84e8-6e749a3e384d",
|
|
582
|
+
"pl7.app/vdj/chain": "IGHeavy",
|
|
583
|
+
"pl7.app/vdj/clonotypeKey/structure":
|
|
584
|
+
'[["pl7.app/vdj/sequence",["pl7.app/alphabet","aminoacid"],["pl7.app/vdj/feature","VDJRegion"]]]',
|
|
585
|
+
"pl7.app/vdj/clonotypingRunId": "6cfab875-649e-4c52-bb69-fe976c2b57a0",
|
|
586
|
+
},
|
|
587
|
+
annotations: {
|
|
588
|
+
"pl7.app/label": "Cluster Id",
|
|
589
|
+
"pl7.app/table/orderPriority": "990000",
|
|
590
|
+
"pl7.app/table/visibility": "default",
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
],
|
|
594
|
+
name: "pl7.app/label",
|
|
595
|
+
valueType: "String",
|
|
596
|
+
annotations: {
|
|
597
|
+
"pl7.app/label": "Cluster Id",
|
|
598
|
+
"pl7.app/table/visibility": "default",
|
|
599
|
+
"pl7.app/trace":
|
|
600
|
+
'[{"importance":10,"label":"Samples & Data","type":"milaboratories.samples-and-data","id":"0189e23b-5e8d-4648-8c6d-70caa5c26958"},{"id":"34U3GEP5PGJF45UG3A2WZ3JO","importance":100,"label":"MB135 + Podocytes","type":"milaboratories.samples-and-data/dataset"},{"label":"MiXCR generic amplicon","type":"milaboratories.mixcr-amplicon-alignment","id":"6cfab875-649e-4c52-bb69-fe976c2b57a0","importance":20},{"type":"milaboratories.redefine-clonotypes","importance":30,"label":"Imputed VDJRegion aa"},{"type":"milaboratories.clonotype-clustering.clustering","importance":30,"label":"Imputed VDJRegion aa, BLOSUM62, ident:0.95, cov:0.95"}]',
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
type: "column",
|
|
606
|
+
id: '{"__isDiscovered":true,"column":"{\\"__isRef\\":true,\\"blockId\\":\\"6b2ae9f7-526d-4fd9-8a28-c0e3756c0b07\\",\\"name\\":\\"clusters.summary.clusterLabel\\"}","path":[{"column":"{\\"__isRef\\":true,\\"blockId\\":\\"6b2ae9f7-526d-4fd9-8a28-c0e3756c0b07\\",\\"name\\":\\"clusters.link.isCentroid\\"}","type":"linker"}]}',
|
|
607
|
+
spec: {
|
|
608
|
+
kind: "PColumn",
|
|
609
|
+
axesSpec: [
|
|
610
|
+
{
|
|
611
|
+
name: "pl7.app/clusterId",
|
|
612
|
+
type: "String",
|
|
613
|
+
domain: {
|
|
614
|
+
"pl7.app/clustering/algorithm": "foldseek",
|
|
615
|
+
"pl7.app/clustering/blockId": "6b2ae9f7-526d-4fd9-8a28-c0e3756c0b07",
|
|
616
|
+
},
|
|
617
|
+
annotations: {
|
|
618
|
+
"pl7.app/label": "Cluster Id",
|
|
619
|
+
"pl7.app/table/orderPriority": "990000",
|
|
620
|
+
"pl7.app/table/visibility": "default",
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
],
|
|
624
|
+
name: "pl7.app/label",
|
|
625
|
+
valueType: "String",
|
|
626
|
+
annotations: {
|
|
627
|
+
"pl7.app/label": "Cluster Id",
|
|
628
|
+
"pl7.app/table/orderPriority": "990000",
|
|
629
|
+
"pl7.app/table/visibility": "default",
|
|
630
|
+
"pl7.app/trace":
|
|
631
|
+
'[{"type":"milaboratories.samples-and-data","id":"0189e23b-5e8d-4648-8c6d-70caa5c26958","importance":10,"label":"Samples & Data"},{"type":"milaboratories.samples-and-data/dataset","id":"34U3GEP5PGJF45UG3A2WZ3JO","importance":100,"label":"MB135 + Podocytes"},{"label":"MiXCR generic amplicon","type":"milaboratories.mixcr-amplicon-alignment","id":"6cfab875-649e-4c52-bb69-fe976c2b57a0","importance":20},{"type":"milaboratories.redefine-clonotypes","importance":30,"label":"Imputed VDJRegion aa"},{"type":"milaboratories.antibody-tcr-lead-selection","importance":30,"label":"Selected Leads"},{"type":"milaboratories.3d-structure-prediction","id":"d204391a-e7dd-434d-9c24-ef77b5da81e0","importance":20,"label":"Camelid (VHH/nanobody) NBB2, CDRH3 ≤ 2.5 Å"},{"type":"milaboratories.3d-structure-clustering.clustering","id":"6b2ae9f7-526d-4fd9-8a28-c0e3756c0b07","importance":30,"label":"Full Structure+AA, TM≥0.95, cov≥0.95"}]',
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
},
|
|
635
|
+
];
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
import { PColumnName } from "@milaboratories/pl-model-common";
|
|
8
8
|
import type { RenderCtxBase } from "../../../render";
|
|
9
9
|
import type { ColumnRecipe, ColumnsSource } from "../../../columns";
|
|
10
|
-
import { ColumnsCollection,
|
|
10
|
+
import { ColumnsCollection, isLeafColumn } from "../../../columns";
|
|
11
11
|
import type { ColumnsSelectorConfig } from "./createPlDataTableV3";
|
|
12
12
|
|
|
13
13
|
export type DiscoverTableColumnOptions = {
|
|
@@ -30,7 +30,7 @@ export type DiscoveredTableColumns = {
|
|
|
30
30
|
export function discoverTableColumns(
|
|
31
31
|
ctx: RenderCtxBase,
|
|
32
32
|
options: DiscoverTableColumnOptions,
|
|
33
|
-
):
|
|
33
|
+
): DiscoveredTableColumns {
|
|
34
34
|
const discoveredColumns = ColumnsCollection(options.sources, { ctx: ctx.ctx })
|
|
35
35
|
.discover({ ...options.selector, anchors: options.anchors })
|
|
36
36
|
.getColumns();
|
|
@@ -54,22 +54,18 @@ export function discoverTableColumns(
|
|
|
54
54
|
export function discoverLabelColumns<A, U>(
|
|
55
55
|
ctx: RenderCtxBase<A, U>,
|
|
56
56
|
primary: ColumnRecipe[],
|
|
57
|
-
columns: ColumnRecipe[],
|
|
58
57
|
): ColumnRecipe[] {
|
|
59
|
-
if (
|
|
58
|
+
if (primary.length === 0) return [];
|
|
60
59
|
|
|
61
|
-
const axes =
|
|
62
|
-
return (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
})
|
|
73
|
-
.getColumns()
|
|
74
|
-
);
|
|
60
|
+
const axes = primary.flatMap((col) => col.getSpec().axesSpec);
|
|
61
|
+
return ColumnsCollection(undefined, { ctx: ctx.ctx })
|
|
62
|
+
.discover({
|
|
63
|
+
include: axes.map((a) => ({
|
|
64
|
+
name: { type: "exact", value: PColumnName.Label },
|
|
65
|
+
axes: [{ name: { type: "exact", value: a.name } }],
|
|
66
|
+
})),
|
|
67
|
+
anchors: Object.fromEntries(primary.map((col, i) => [`anchor_${i}`, col.getSpec()])),
|
|
68
|
+
maxHops: 0,
|
|
69
|
+
})
|
|
70
|
+
.getColumns();
|
|
75
71
|
}
|