@platforma-sdk/model 1.80.0 → 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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const require_linked_column_postfix = require("./linked_column_postfix.cjs");
|
|
1
2
|
let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common");
|
|
2
3
|
let _milaboratories_helpers = require("@milaboratories/helpers");
|
|
3
4
|
let es_toolkit = require("es-toolkit");
|
|
@@ -5,27 +6,42 @@ let es_toolkit = require("es-toolkit");
|
|
|
5
6
|
const DISTANCE_PENALTY = .001;
|
|
6
7
|
const LABEL_TYPE = "__LABEL__";
|
|
7
8
|
const LABEL_TYPE_FULL = "__LABEL__@1";
|
|
8
|
-
const LINKER_TYPE = "__LINKER__";
|
|
9
|
-
const LINKER_TYPE_FULL = "__LINKER__@1";
|
|
10
9
|
const HIT_QUAL_TYPE = "__HIT_QUAL__";
|
|
11
10
|
const ANCHOR_QUAL_TYPE_PREFIX = "__ANCHOR_QUAL__:";
|
|
12
11
|
function isAnchorQualType(t) {
|
|
13
12
|
return t.startsWith(ANCHOR_QUAL_TYPE_PREFIX);
|
|
14
13
|
}
|
|
15
14
|
function isSyntheticType(t) {
|
|
16
|
-
return t ===
|
|
15
|
+
return t === HIT_QUAL_TYPE || isAnchorQualType(t);
|
|
17
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Distinct labels for a set of columns. Two phases:
|
|
19
|
+
* 1. {@link deriveStems} — treats each column as a single entity (native label + trace + hit/anchor
|
|
20
|
+
* qualifications) and produces the minimal distinguishing "stem".
|
|
21
|
+
* 2. {@link derivePostfixes} — for columns still colliding on their stem, appends a "via …" postfix
|
|
22
|
+
* describing the difference between their linker sources (root axis, then linker chain).
|
|
23
|
+
*/
|
|
18
24
|
function deriveDistinctLabels(values, options = {}) {
|
|
25
|
+
const stems = deriveStems(values, options);
|
|
26
|
+
return require_linked_column_postfix.derivePostfixes(values.map((v, i) => {
|
|
27
|
+
const { spec, linkerPath } = extractEntryParts(v);
|
|
28
|
+
return {
|
|
29
|
+
stem: stems[i],
|
|
30
|
+
hit: spec,
|
|
31
|
+
linkers: (linkerPath ?? []).map((s) => s.spec)
|
|
32
|
+
};
|
|
33
|
+
}), options.formatters?.linker);
|
|
34
|
+
}
|
|
35
|
+
/** Phase 1: minimal per-column stem — native label + trace + hit/anchor qualifications, no linkers. */
|
|
36
|
+
function deriveStems(values, options) {
|
|
19
37
|
const forceTraceElements = options.forceTraceElements !== void 0 && options.forceTraceElements.length > 0 ? new Set(options.forceTraceElements) : void 0;
|
|
20
38
|
const separator = options.separator ?? " / ";
|
|
21
39
|
const records = values.map((v, i) => enrichRecord(v, i, options));
|
|
22
40
|
const stats = collectTypeStats(records);
|
|
23
41
|
const hasAnySynthetic = records.some((r) => r.fullTrace.some((ft) => isSyntheticType(ft.type)));
|
|
24
42
|
const labelForced = (options.includeNativeLabel === true || hasAnySynthetic) && stats.countByType.has(LABEL_TYPE_FULL);
|
|
25
|
-
const linkerForced = stats.countByType.get(LINKER_TYPE_FULL) === values.length;
|
|
26
43
|
const forcedSet = /* @__PURE__ */ new Set();
|
|
27
44
|
if (labelForced) forcedSet.add(LABEL_TYPE_FULL);
|
|
28
|
-
if (linkerForced) forcedSet.add(LINKER_TYPE_FULL);
|
|
29
45
|
const { mainTypes, secondaryTypes } = classifyTypes(stats, values.length);
|
|
30
46
|
const build = (typeSet, force) => buildLabels(records, typeSet, forceTraceElements, separator, force);
|
|
31
47
|
if (mainTypes.length === 0) {
|
|
@@ -41,7 +57,7 @@ function deriveDistinctLabels(values, options = {}) {
|
|
|
41
57
|
const candidateResult = build(currentSet, false);
|
|
42
58
|
if (candidateResult !== void 0 && countUniqueLabels(candidateResult) === values.length) {
|
|
43
59
|
const minimized = minimizeTypeSet(currentSet, records, stats, forceTraceElements, forcedSet, separator);
|
|
44
|
-
return
|
|
60
|
+
return repairBareByPresence(build(minimized, false) ?? (0, _milaboratories_helpers.throwError)("Failed to derive unique labels"), minimized, records, stats, forcedSet, forceTraceElements, separator);
|
|
45
61
|
}
|
|
46
62
|
additionalType++;
|
|
47
63
|
if (additionalType >= mainTypes.length) {
|
|
@@ -54,7 +70,7 @@ function deriveDistinctLabels(values, options = {}) {
|
|
|
54
70
|
...mainTypes,
|
|
55
71
|
...secondaryTypes
|
|
56
72
|
]), records, stats, forceTraceElements, forcedSet, separator);
|
|
57
|
-
return
|
|
73
|
+
return repairBareByPresence(build(minimized, true) ?? (0, _milaboratories_helpers.throwError)("Failed to derive unique labels"), minimized, records, stats, forcedSet, forceTraceElements, separator);
|
|
58
74
|
}
|
|
59
75
|
function extractEntryParts(entry) {
|
|
60
76
|
if (!("spec" in entry && typeof entry.spec === "object")) return {
|
|
@@ -80,13 +96,6 @@ function formatQualification(q) {
|
|
|
80
96
|
function formatQualifications(qs) {
|
|
81
97
|
return qs.map(formatQualification).join("; ");
|
|
82
98
|
}
|
|
83
|
-
function computeStepLabel(step, stepIndex, formatters) {
|
|
84
|
-
const base = ((0, _milaboratories_pl_model_common.readAnnotation)(step.spec, _milaboratories_pl_model_common.Annotation.LinkLabel) ?? (0, _milaboratories_pl_model_common.readAnnotation)(step.spec, _milaboratories_pl_model_common.Annotation.Label))?.trim();
|
|
85
|
-
if ((0, es_toolkit.isNil)(base) || base.length === 0) return void 0;
|
|
86
|
-
if (step.qualifications === void 0 || step.qualifications.length === 0) return base;
|
|
87
|
-
const qualText = (0, es_toolkit.isFunction)(formatters?.linkerStepQualification) ? formatters.linkerStepQualification(step.qualifications, stepIndex, step.spec) : `[${formatQualifications(step.qualifications)}]`;
|
|
88
|
-
return (0, es_toolkit.isNil)(qualText) ? base : `${base} ${qualText}`;
|
|
89
|
-
}
|
|
90
99
|
function buildFullTrace(trace) {
|
|
91
100
|
const result = [];
|
|
92
101
|
const occurrences = /* @__PURE__ */ new Map();
|
|
@@ -104,7 +113,7 @@ function buildFullTrace(trace) {
|
|
|
104
113
|
return result;
|
|
105
114
|
}
|
|
106
115
|
function enrichRecord(value, index, options) {
|
|
107
|
-
const { spec, extraTrace,
|
|
116
|
+
const { spec, extraTrace, qualifications } = extractEntryParts(value);
|
|
108
117
|
const formatters = options.formatters;
|
|
109
118
|
const rawLabel = (0, _milaboratories_pl_model_common.readAnnotation)(spec, _milaboratories_pl_model_common.Annotation.Label);
|
|
110
119
|
const traceStr = (0, _milaboratories_pl_model_common.readAnnotation)(spec, _milaboratories_pl_model_common.Annotation.Trace);
|
|
@@ -128,17 +137,6 @@ function enrichRecord(value, index, options) {
|
|
|
128
137
|
else trace.splice(0, 0, labelEntry);
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
|
-
if (linkerPath !== void 0 && linkerPath.length > 0) {
|
|
132
|
-
const stepLabels = linkerPath.map((step, i) => computeStepLabel(step, i, formatters)).filter((s) => !(0, es_toolkit.isNil)(s));
|
|
133
|
-
if (stepLabels.length > 0) {
|
|
134
|
-
const linkerText = (0, es_toolkit.isFunction)(formatters?.linker) ? formatters.linker(stepLabels, spec, index) : `via ${stepLabels.join(" > ")}`;
|
|
135
|
-
if (!(0, es_toolkit.isNil)(linkerText)) trace.push({
|
|
136
|
-
type: LINKER_TYPE,
|
|
137
|
-
label: linkerText,
|
|
138
|
-
importance: -10
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
140
|
if (qualifications !== void 0 && qualifications.forQueries !== void 0) {
|
|
143
141
|
for (const [anchorId, qs] of Object.entries(qualifications.forQueries)) {
|
|
144
142
|
if (qs.length === 0) continue;
|
|
@@ -190,21 +188,18 @@ function classifyTypes(stats, totalRecords) {
|
|
|
190
188
|
function renderRecordLabel(record, includedTypes, forceTraceElements, separator) {
|
|
191
189
|
const traceParts = [];
|
|
192
190
|
const anchorParts = [];
|
|
193
|
-
let linkerLabel;
|
|
194
191
|
let hitLabel;
|
|
195
192
|
for (const ft of record.fullTrace) {
|
|
196
193
|
if (!(includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type))) continue;
|
|
197
|
-
if (ft.type ===
|
|
198
|
-
else if (ft.type === HIT_QUAL_TYPE) hitLabel = ft.label;
|
|
194
|
+
if (ft.type === HIT_QUAL_TYPE) hitLabel = ft.label;
|
|
199
195
|
else if (isAnchorQualType(ft.type)) anchorParts.push(ft.label);
|
|
200
196
|
else traceParts.push(ft.label);
|
|
201
197
|
}
|
|
202
|
-
if (traceParts.length === 0 && anchorParts.length === 0 &&
|
|
198
|
+
if (traceParts.length === 0 && anchorParts.length === 0 && hitLabel === void 0) return void 0;
|
|
203
199
|
let label = traceParts.join(separator);
|
|
204
200
|
const append = (part) => {
|
|
205
201
|
label = label.length === 0 ? part : `${label} ${part}`;
|
|
206
202
|
};
|
|
207
|
-
if (linkerLabel !== void 0) append(linkerLabel);
|
|
208
203
|
for (const a of anchorParts) append(a);
|
|
209
204
|
if (hitLabel !== void 0) append(hitLabel);
|
|
210
205
|
return label;
|
|
@@ -222,32 +217,6 @@ function buildLabels(records, includedTypes, forceTraceElements, separator, forc
|
|
|
222
217
|
}
|
|
223
218
|
return result;
|
|
224
219
|
}
|
|
225
|
-
/**
|
|
226
|
-
* Drop the "via …" linker suffix from records whose label is already unique without it.
|
|
227
|
-
*
|
|
228
|
-
* Global minimization may include `LINKER_TYPE_FULL` solely to resolve a collision between a
|
|
229
|
-
* subset of records — but `buildLabels` then renders the suffix on every record that carries a
|
|
230
|
-
* linker trace entry, including ones whose stem is already unique. We strip the suffix where it
|
|
231
|
-
* isn't load-bearing while keeping the symmetric rendering required by `linkerForced` /
|
|
232
|
-
* `forceTraceElements`.
|
|
233
|
-
*
|
|
234
|
-
* Rule: a record's linker suffix is redundant iff its stem (label rendered without LINKER) does
|
|
235
|
-
* not appear anywhere else in the set.
|
|
236
|
-
*/
|
|
237
|
-
function dropRedundantLinkerSuffix(records, globalTypeSet, forceTraceElements, forcedSet, separator, labels) {
|
|
238
|
-
if (!globalTypeSet.has(LINKER_TYPE_FULL)) return labels;
|
|
239
|
-
if (forcedSet.has(LINKER_TYPE_FULL) || forceTraceElements?.has(LINKER_TYPE)) return labels;
|
|
240
|
-
const setWithoutLinker = new Set(globalTypeSet);
|
|
241
|
-
setWithoutLinker.delete(LINKER_TYPE_FULL);
|
|
242
|
-
const stems = records.map((r) => renderRecordLabel(r, setWithoutLinker, forceTraceElements, separator));
|
|
243
|
-
const stemOccurrences = /* @__PURE__ */ new Map();
|
|
244
|
-
for (const s of stems) if (s !== void 0) stemOccurrences.set(s, (stemOccurrences.get(s) ?? 0) + 1);
|
|
245
|
-
return labels.map((label, i) => {
|
|
246
|
-
const stem = stems[i];
|
|
247
|
-
if (stem === void 0) return label;
|
|
248
|
-
return stemOccurrences.get(stem) === 1 ? stem : label;
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
220
|
function countUniqueLabels(result) {
|
|
252
221
|
if (result === void 0) return 0;
|
|
253
222
|
return new Set(result).size;
|
|
@@ -266,6 +235,60 @@ function minimizeTypeSet(typeSet, records, stats, forceTraceElements, forcedSet,
|
|
|
266
235
|
}
|
|
267
236
|
return result;
|
|
268
237
|
}
|
|
238
|
+
const ABSENT_VALUE = "\0absent";
|
|
239
|
+
/** Whether `fullType`'s rendered value differs across the group (absence counts as a value). */
|
|
240
|
+
function typeDistinguishes(records, group, fullType) {
|
|
241
|
+
const values = /* @__PURE__ */ new Set();
|
|
242
|
+
for (const i of group) {
|
|
243
|
+
const ft = records[i].fullTrace.find((e) => e.fullType === fullType);
|
|
244
|
+
values.add(ft?.label ?? ABSENT_VALUE);
|
|
245
|
+
if (values.size > 1) return true;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
/** The highest-importance trace type this row HAS (outside `typeSet`) that sets it apart from its
|
|
250
|
+
* group peers. `undefined` when the row carries nothing distinguishing of its own. */
|
|
251
|
+
function bestDistinguishingType(records, group, row, typeSet, forcedSet, forceTraceElements, stats) {
|
|
252
|
+
return records[row].fullTrace.reduce((best, ft) => {
|
|
253
|
+
if (typeSet.has(ft.fullType) || forcedSet.has(ft.fullType) || forceTraceElements?.has(ft.type)) return best;
|
|
254
|
+
if (!typeDistinguishes(records, group, ft.fullType)) return best;
|
|
255
|
+
const imp = stats.importances.get(ft.fullType) ?? 0;
|
|
256
|
+
return best === void 0 || imp > best.imp ? {
|
|
257
|
+
type: ft.fullType,
|
|
258
|
+
imp
|
|
259
|
+
} : best;
|
|
260
|
+
}, void 0)?.type;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Un-bare columns distinguished only "by absence". After minimization a column can end up with a
|
|
264
|
+
* bare trace zone (only the forced native label) while a peer sharing that same base renders extra
|
|
265
|
+
* tokens — the column reads as unique purely because it LACKS what the peer has. For each such bare
|
|
266
|
+
* column this re-renders JUST that column's label with the highest-importance trace type it actually
|
|
267
|
+
* carries that tells it apart from its peers, so every colliding column is distinguished by a token
|
|
268
|
+
* it HAS rather than by omission.
|
|
269
|
+
*
|
|
270
|
+
* Patches individual labels rather than the shared type set on purpose: the set is global, so adding
|
|
271
|
+
* a type there would also decorate unrelated columns in other groups that happen to carry it. Only
|
|
272
|
+
* bare columns' labels grow (a superset of their previous value), so uniqueness is preserved. Groups
|
|
273
|
+
* are keyed by the base = the label rendered from the forced types alone.
|
|
274
|
+
*/
|
|
275
|
+
function repairBareByPresence(labels, minimized, records, stats, forcedSet, forceTraceElements, separator) {
|
|
276
|
+
const base = records.map((r) => renderRecordLabel(r, forcedSet, forceTraceElements, separator));
|
|
277
|
+
const isBare = records.map((r, i) => renderRecordLabel(r, minimized, forceTraceElements, separator) === base[i]);
|
|
278
|
+
const groups = records.reduce((acc, _, i) => acc.set(base[i] ?? "", [...acc.get(base[i] ?? "") ?? [], i]), /* @__PURE__ */ new Map());
|
|
279
|
+
const patched = [...labels];
|
|
280
|
+
for (const group of groups.values()) {
|
|
281
|
+
if (!group.some((i) => !isBare[i])) continue;
|
|
282
|
+
for (const i of group) {
|
|
283
|
+
if (!isBare[i]) continue;
|
|
284
|
+
const chosen = bestDistinguishingType(records, group, i, minimized, forcedSet, forceTraceElements, stats);
|
|
285
|
+
if (chosen === void 0) continue;
|
|
286
|
+
const withToken = /* @__PURE__ */ new Set([...minimized, chosen]);
|
|
287
|
+
patched[i] = renderRecordLabel(records[i], withToken, forceTraceElements, separator) ?? patched[i];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return patched;
|
|
291
|
+
}
|
|
269
292
|
//#endregion
|
|
270
293
|
exports.deriveDistinctLabels = deriveDistinctLabels;
|
|
271
294
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive_distinct_labels.cjs","names":["Annotation"],"sources":["../../src/labels/derive_distinct_labels.ts"],"sourcesContent":["import {\n Annotation,\n parseJson,\n readAnnotation,\n type AxisQualification,\n type MatchQualifications,\n type PObjectId,\n type PObjectSpec,\n type StringifiedJson,\n type Trace,\n} from \"@milaboratories/pl-model-common\";\nimport { throwError } from \"@milaboratories/helpers\";\nimport { isFunction, isNil } from \"es-toolkit\";\n\nexport type { Trace, TraceEntry } from \"@milaboratories/pl-model-common\";\n\nconst DISTANCE_PENALTY = 0.001;\nconst LABEL_TYPE = \"__LABEL__\";\nconst LABEL_TYPE_FULL = \"__LABEL__@1\";\nconst LINKER_TYPE = \"__LINKER__\";\nconst LINKER_TYPE_FULL = \"__LINKER__@1\";\nconst HIT_QUAL_TYPE = \"__HIT_QUAL__\";\nconst ANCHOR_QUAL_TYPE_PREFIX = \"__ANCHOR_QUAL__:\";\n\nfunction isAnchorQualType(t: string): boolean {\n return t.startsWith(ANCHOR_QUAL_TYPE_PREFIX);\n}\n\nfunction isSyntheticType(t: string): boolean {\n return t === LINKER_TYPE || t === HIT_QUAL_TYPE || isAnchorQualType(t);\n}\n\n/** SDK-internal trace shape — adds fields used by this algorithm only, not part of the on-disk contract. */\ntype ExtendedTraceEntry = Trace[number] & {\n importance?: number;\n position?: \"prefix\" | \"suffix\";\n};\n\nexport type LinkerStep = {\n spec: PObjectSpec;\n qualifications?: AxisQualification[];\n};\n\nexport type Entry =\n | PObjectSpec\n | {\n spec: PObjectSpec;\n /** Extra trace entries merged with the base trace from annotations. */\n extraTrace?: ExtendedTraceEntry[];\n /** Linker steps traversed to discover this column; rendered as \"via …\" only when needed for uniqueness. */\n linkerPath?: LinkerStep[];\n /** Axis qualifications applied to the hit column / already-bound anchors; rendered as \"[…]\" suffixes. */\n qualifications?: MatchQualifications;\n };\n\n/**\n * Per-zone formatters. Each one receives raw inputs and returns the rendered text for that zone,\n * or `undefined` to suppress the zone entirely (no synthetic injection → no minimization, no render).\n */\nexport type DeriveLabelsFormatters = {\n /** Native column label. Default: identity. `undefined` → label entry not added (treated as if spec had no label). */\n native?: (label: string, spec: PObjectSpec, index: number) => string | undefined;\n /** Linker zone (whole \"via …\" piece). Receives step labels with step-quals already inlined.\n * Default: `via ${steps.join(\" > \")}`. */\n linker?: (linkerLabels: string[], spec: PObjectSpec, index: number) => string | undefined;\n /** Per-step linker qualifications inlined into the step base label.\n * Default: `[${formatQualifications(qs)}]`. `undefined` → step rendered without quals. */\n linkerStepQualification?: (\n qualifications: AxisQualification[],\n stepIndex: number,\n stepSpec: PObjectSpec,\n ) => string | undefined;\n /** Hit-axis qualifications block. Default: `[${formatQualifications(qs)}]`. */\n hitQualification?: (\n qualifications: AxisQualification[],\n spec: PObjectSpec,\n index: number,\n ) => string | undefined;\n /** Per-anchor qualifications block. Default: `[${anchorId}: ${formatQualifications(qs)}]`. */\n anchorQualification?: (\n anchorId: PObjectId,\n qualifications: AxisQualification[],\n spec: PObjectSpec,\n index: number,\n ) => string | undefined;\n};\n\nexport type DeriveLabelsOptions = {\n /** Separator to use between label parts (\" / \" by default). */\n separator?: string;\n /** If true, native label is appended at the end of the trace zone. By default it is prepended (label is the most important name). */\n addLabelAsSuffix?: boolean;\n /** Force inclusion of native column label even when not needed for uniqueness. */\n includeNativeLabel?: boolean;\n /** Trace types that must be included in the label. */\n forceTraceElements?: string[];\n /** Per-zone custom formatters. Returning `undefined` from any formatter suppresses the corresponding zone. */\n formatters?: DeriveLabelsFormatters;\n};\n\nexport function deriveDistinctLabels(values: Entry[], options: DeriveLabelsOptions = {}): string[] {\n const forceTraceElements =\n options.forceTraceElements !== undefined && options.forceTraceElements.length > 0\n ? new Set(options.forceTraceElements)\n : undefined;\n const separator = options.separator ?? \" / \";\n\n const records = values.map((v, i) => enrichRecord(v, i, options));\n const stats = collectTypeStats(records);\n\n const hasAnySynthetic = records.some((r) => r.fullTrace.some((ft) => isSyntheticType(ft.type)));\n const labelForced =\n (options.includeNativeLabel === true || hasAnySynthetic) &&\n stats.countByType.has(LABEL_TYPE_FULL);\n // Tied to labeled-step presence, not path presence: entries with a non-empty linkerPath\n // but no labeled steps contribute no LINKER_TYPE trace entry, so they do not count here.\n const linkerForced = stats.countByType.get(LINKER_TYPE_FULL) === values.length;\n\n const forcedSet = new Set<string>();\n if (labelForced) forcedSet.add(LABEL_TYPE_FULL);\n if (linkerForced) forcedSet.add(LINKER_TYPE_FULL);\n\n const { mainTypes, secondaryTypes } = classifyTypes(stats, values.length);\n\n const build = (typeSet: Set<string>, force: boolean) =>\n buildLabels(records, typeSet, forceTraceElements, separator, force);\n\n if (mainTypes.length === 0) {\n if (secondaryTypes.length !== 0)\n throw new Error(\"Non-empty secondary types list while main types list is empty.\");\n\n return (\n build(new Set([LABEL_TYPE_FULL]), true) ??\n throwError(\"Failed to derive labels using native column labels\")\n );\n }\n\n let includedCount = 0;\n let additionalType = -1;\n while (includedCount < mainTypes.length) {\n const currentSet = new Set<string>(forcedSet);\n for (let i = 0; i < includedCount; ++i) currentSet.add(mainTypes[i]);\n if (additionalType >= 0) currentSet.add(mainTypes[additionalType]);\n\n const candidateResult = build(currentSet, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) === values.length) {\n const minimized = minimizeTypeSet(\n currentSet,\n records,\n stats,\n forceTraceElements,\n forcedSet,\n separator,\n );\n const minimizedLabels =\n build(minimized, false) ?? throwError(\"Failed to derive unique labels\");\n return dropRedundantLinkerSuffix(\n records,\n minimized,\n forceTraceElements,\n forcedSet,\n separator,\n minimizedLabels,\n );\n }\n\n additionalType++;\n if (additionalType >= mainTypes.length) {\n includedCount++;\n additionalType = includedCount;\n }\n }\n\n const fallbackSet = new Set([...forcedSet, ...mainTypes, ...secondaryTypes]);\n const minimized = minimizeTypeSet(\n fallbackSet,\n records,\n stats,\n forceTraceElements,\n forcedSet,\n separator,\n );\n const minimizedLabels = build(minimized, true) ?? throwError(\"Failed to derive unique labels\");\n return dropRedundantLinkerSuffix(\n records,\n minimized,\n forceTraceElements,\n forcedSet,\n separator,\n minimizedLabels,\n );\n}\n\n// --- Pure helpers ---\ntype FullTraceEntry = ExtendedTraceEntry & { fullType: string; occurrenceIndex: number };\n\ntype EnrichedRecord = {\n fullTrace: FullTraceEntry[];\n};\n\nfunction extractEntryParts(entry: Entry): {\n spec: PObjectSpec;\n extraTrace: ExtendedTraceEntry[] | undefined;\n linkerPath: LinkerStep[] | undefined;\n qualifications: MatchQualifications | undefined;\n} {\n const isEnriched = \"spec\" in entry && typeof entry.spec === \"object\";\n if (!isEnriched) {\n return {\n spec: entry as PObjectSpec,\n extraTrace: undefined,\n linkerPath: undefined,\n qualifications: undefined,\n };\n }\n return {\n spec: entry.spec,\n extraTrace: entry.extraTrace,\n linkerPath: entry.linkerPath,\n qualifications: entry.qualifications,\n };\n}\n\nfunction formatQualification(q: AxisQualification): string {\n const ctx = q.contextDomain ?? {};\n const keys = Object.keys(ctx);\n if (keys.length === 0) return q.axis.name;\n const pairs = keys.map((k) => `${k}=${ctx[k]}`).join(\", \");\n return Object.prototype.hasOwnProperty.call(ctx, q.axis.name) ? pairs : `${q.axis.name} ${pairs}`;\n}\n\nfunction formatQualifications(qs: AxisQualification[]): string {\n return qs.map(formatQualification).join(\"; \");\n}\n\nfunction computeStepLabel(\n step: LinkerStep,\n stepIndex: number,\n formatters: DeriveLabelsFormatters | undefined,\n): string | undefined {\n const base = (\n readAnnotation(step.spec, Annotation.LinkLabel) ?? readAnnotation(step.spec, Annotation.Label)\n )?.trim();\n if (isNil(base) || base.length === 0) return undefined;\n if (step.qualifications === undefined || step.qualifications.length === 0) return base;\n const qualText = isFunction(formatters?.linkerStepQualification)\n ? formatters.linkerStepQualification(step.qualifications, stepIndex, step.spec)\n : `[${formatQualifications(step.qualifications)}]`;\n return isNil(qualText) ? base : `${base} ${qualText}`;\n}\n\nfunction buildFullTrace(trace: ExtendedTraceEntry[]): FullTraceEntry[] {\n const result: FullTraceEntry[] = [];\n const occurrences = new Map<string, number>();\n\n for (let i = trace.length - 1; i >= 0; --i) {\n const entry = trace[i];\n const occurrenceIndex = (occurrences.get(entry.type) ?? 0) + 1;\n occurrences.set(entry.type, occurrenceIndex);\n result.push({\n ...entry,\n fullType: `${entry.type}@${occurrenceIndex}`,\n occurrenceIndex,\n });\n }\n\n result.reverse();\n return result;\n}\n\nfunction enrichRecord(value: Entry, index: number, options: DeriveLabelsOptions): EnrichedRecord {\n const { spec, extraTrace, linkerPath, qualifications } = extractEntryParts(value);\n const formatters = options.formatters;\n\n const rawLabel = readAnnotation(spec, Annotation.Label);\n const traceStr = readAnnotation(spec, Annotation.Trace);\n const baseTrace = traceStr\n ? (parseJson(traceStr as StringifiedJson<ExtendedTraceEntry[]>) ?? [])\n : [];\n const prefixExtra = extraTrace?.filter((e) => e.position === \"prefix\") ?? [];\n const suffixExtra = extraTrace?.filter((e) => e.position !== \"prefix\") ?? [];\n const trace: ExtendedTraceEntry[] = [...prefixExtra, ...baseTrace, ...suffixExtra];\n\n if (!isNil(rawLabel)) {\n const label = isFunction(formatters?.native)\n ? formatters.native(rawLabel, spec, index)\n : rawLabel;\n if (!isNil(label)) {\n const labelEntry = { label, type: LABEL_TYPE, importance: -2 };\n if (options.addLabelAsSuffix === true) trace.push(labelEntry);\n else trace.splice(0, 0, labelEntry);\n }\n }\n\n if (linkerPath !== undefined && linkerPath.length > 0) {\n const stepLabels = linkerPath\n .map((step, i) => computeStepLabel(step, i, formatters))\n .filter((s): s is string => !isNil(s));\n if (stepLabels.length > 0) {\n const linkerText = isFunction(formatters?.linker)\n ? formatters.linker(stepLabels, spec, index)\n : `via ${stepLabels.join(\" > \")}`;\n if (!isNil(linkerText)) {\n trace.push({ type: LINKER_TYPE, label: linkerText, importance: -10 });\n }\n }\n }\n\n if (qualifications !== undefined && qualifications.forQueries !== undefined) {\n for (const [anchorId, qs] of Object.entries(qualifications.forQueries)) {\n if (qs.length === 0) continue;\n const anchorText = isFunction(formatters?.anchorQualification)\n ? formatters.anchorQualification(anchorId as PObjectId, qs, spec, index)\n : `[${anchorId}: ${formatQualifications(qs)}]`;\n if (isNil(anchorText)) continue;\n trace.push({\n type: `${ANCHOR_QUAL_TYPE_PREFIX}${anchorId}`,\n label: anchorText,\n importance: -11,\n });\n }\n if (qualifications.forHit !== undefined && qualifications.forHit.length > 0) {\n const hitText = isFunction(formatters?.hitQualification)\n ? formatters.hitQualification(qualifications.forHit, spec, index)\n : `[${formatQualifications(qualifications.forHit)}]`;\n if (!isNil(hitText)) {\n trace.push({ type: HIT_QUAL_TYPE, label: hitText, importance: -12 });\n }\n }\n }\n\n return { fullTrace: buildFullTrace(trace) };\n}\n\ntype TypeStats = {\n importances: Map<string, number>;\n countByType: Map<string, number>;\n};\n\nfunction collectTypeStats(records: EnrichedRecord[]): TypeStats {\n const importances = new Map<string, number>();\n const countByType = new Map<string, number>();\n\n for (const record of records) {\n for (let i = 0; i < record.fullTrace.length; i++) {\n const { fullType, importance: rawImportance } = record.fullTrace[i];\n const importance = rawImportance ?? 0;\n const distance = (record.fullTrace.length - i) * DISTANCE_PENALTY;\n\n countByType.set(fullType, (countByType.get(fullType) ?? 0) + 1);\n importances.set(\n fullType,\n Math.max(importances.get(fullType) ?? Number.NEGATIVE_INFINITY, importance - distance),\n );\n }\n }\n\n return { importances, countByType };\n}\n\nfunction classifyTypes(\n stats: TypeStats,\n totalRecords: number,\n): { mainTypes: string[]; secondaryTypes: string[] } {\n const sorted = [...stats.importances].sort(([, i1], [, i2]) => i2 - i1);\n\n const mainTypes: string[] = [];\n const secondaryTypes: string[] = [];\n\n for (const [typeName] of sorted) {\n if (typeName.endsWith(\"@1\") || stats.countByType.get(typeName) === totalRecords)\n mainTypes.push(typeName);\n else secondaryTypes.push(typeName);\n }\n\n return { mainTypes, secondaryTypes };\n}\n\nfunction renderRecordLabel(\n record: EnrichedRecord,\n includedTypes: Set<string>,\n forceTraceElements: Set<string> | undefined,\n separator: string,\n): string | undefined {\n const traceParts: string[] = [];\n const anchorParts: string[] = [];\n let linkerLabel: string | undefined;\n let hitLabel: string | undefined;\n\n for (const ft of record.fullTrace) {\n if (!(includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type))) continue;\n if (ft.type === LINKER_TYPE) linkerLabel = ft.label;\n else if (ft.type === HIT_QUAL_TYPE) hitLabel = ft.label;\n else if (isAnchorQualType(ft.type)) anchorParts.push(ft.label);\n else traceParts.push(ft.label);\n }\n\n const isEmpty =\n traceParts.length === 0 &&\n anchorParts.length === 0 &&\n linkerLabel === undefined &&\n hitLabel === undefined;\n\n if (isEmpty) return undefined;\n\n let label = traceParts.join(separator);\n const append = (part: string) => {\n label = label.length === 0 ? part : `${label} ${part}`;\n };\n if (linkerLabel !== undefined) append(linkerLabel);\n for (const a of anchorParts) append(a);\n if (hitLabel !== undefined) append(hitLabel);\n\n return label;\n}\n\nfunction buildLabels(\n records: EnrichedRecord[],\n includedTypes: Set<string>,\n forceTraceElements: Set<string> | undefined,\n separator: string,\n force: boolean,\n): string[] | undefined {\n const result: string[] = [];\n\n for (const r of records) {\n const rendered = renderRecordLabel(r, includedTypes, forceTraceElements, separator);\n if (rendered === undefined) {\n if (!force) return undefined;\n result.push(\"Unlabeled\");\n continue;\n }\n result.push(rendered);\n }\n\n return result;\n}\n\n/**\n * Drop the \"via …\" linker suffix from records whose label is already unique without it.\n *\n * Global minimization may include `LINKER_TYPE_FULL` solely to resolve a collision between a\n * subset of records — but `buildLabels` then renders the suffix on every record that carries a\n * linker trace entry, including ones whose stem is already unique. We strip the suffix where it\n * isn't load-bearing while keeping the symmetric rendering required by `linkerForced` /\n * `forceTraceElements`.\n *\n * Rule: a record's linker suffix is redundant iff its stem (label rendered without LINKER) does\n * not appear anywhere else in the set.\n */\nfunction dropRedundantLinkerSuffix(\n records: EnrichedRecord[],\n globalTypeSet: Set<string>,\n forceTraceElements: Set<string> | undefined,\n forcedSet: Set<string>,\n separator: string,\n labels: string[],\n): string[] {\n if (!globalTypeSet.has(LINKER_TYPE_FULL)) return labels;\n if (forcedSet.has(LINKER_TYPE_FULL) || forceTraceElements?.has(LINKER_TYPE)) return labels;\n\n const setWithoutLinker = new Set(globalTypeSet);\n setWithoutLinker.delete(LINKER_TYPE_FULL);\n\n const stems = records.map((r) =>\n renderRecordLabel(r, setWithoutLinker, forceTraceElements, separator),\n );\n\n const stemOccurrences = new Map<string, number>();\n for (const s of stems) {\n if (s !== undefined) stemOccurrences.set(s, (stemOccurrences.get(s) ?? 0) + 1);\n }\n\n return labels.map((label, i) => {\n const stem = stems[i];\n if (stem === undefined) return label;\n return stemOccurrences.get(stem) === 1 ? stem : label;\n });\n}\n\nfunction countUniqueLabels(result: string[] | undefined): number {\n if (result === undefined) return 0;\n return new Set(result).size;\n}\n\nfunction minimizeTypeSet(\n typeSet: Set<string>,\n records: EnrichedRecord[],\n stats: TypeStats,\n forceTraceElements: Set<string> | undefined,\n forcedSet: Set<string>,\n separator: string,\n): Set<string> {\n const initialResult = buildLabels(records, typeSet, forceTraceElements, separator, false);\n if (initialResult === undefined) return typeSet;\n\n const targetCardinality = countUniqueLabels(initialResult);\n const result = new Set(typeSet);\n\n const removable = [...result]\n .filter((t) => !forceTraceElements?.has(t.split(\"@\")[0]) && !forcedSet.has(t))\n .sort((a, b) => (stats.importances.get(a) ?? 0) - (stats.importances.get(b) ?? 0));\n\n for (const typeToRemove of removable) {\n const candidate = new Set(result);\n candidate.delete(typeToRemove);\n const candidateResult = buildLabels(records, candidate, forceTraceElements, separator, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) >= targetCardinality) {\n result.delete(typeToRemove);\n }\n }\n\n return result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"derive_distinct_labels.cjs","names":["derivePostfixes","Annotation"],"sources":["../../src/labels/derive_distinct_labels.ts"],"sourcesContent":["import {\n Annotation,\n parseJson,\n readAnnotation,\n type AxisQualification,\n type MatchQualifications,\n type PColumnSpec,\n type PObjectId,\n type PObjectSpec,\n type StringifiedJson,\n type Trace,\n} from \"@milaboratories/pl-model-common\";\nimport { throwError } from \"@milaboratories/helpers\";\nimport { isFunction, isNil } from \"es-toolkit\";\nimport { derivePostfixes, type LinkerFormatter } from \"./linked_column_postfix\";\n\nexport type { Trace, TraceEntry } from \"@milaboratories/pl-model-common\";\n\nconst DISTANCE_PENALTY = 0.001;\nconst LABEL_TYPE = \"__LABEL__\";\nconst LABEL_TYPE_FULL = \"__LABEL__@1\";\nconst HIT_QUAL_TYPE = \"__HIT_QUAL__\";\nconst ANCHOR_QUAL_TYPE_PREFIX = \"__ANCHOR_QUAL__:\";\n\nfunction isAnchorQualType(t: string): boolean {\n return t.startsWith(ANCHOR_QUAL_TYPE_PREFIX);\n}\n\nfunction isSyntheticType(t: string): boolean {\n return t === HIT_QUAL_TYPE || isAnchorQualType(t);\n}\n\n/** SDK-internal trace shape — adds fields used by this algorithm only, not part of the on-disk contract. */\ntype ExtendedTraceEntry = Trace[number] & {\n importance?: number;\n position?: \"prefix\" | \"suffix\";\n};\n\nexport type LinkerStep = {\n /** Linker column spec — its `axesSpec` yields the source axis (root); its `LinkLabel`/`Label` names it. */\n spec: PColumnSpec;\n /** Axis qualifications applied on this hop. Not yet consumed by the postfix (deferred). */\n qualifications?: AxisQualification[];\n};\n\nexport type Entry =\n | PObjectSpec\n | {\n spec: PObjectSpec;\n /** Extra trace entries merged with the base trace from annotations. */\n extraTrace?: ExtendedTraceEntry[];\n /** Linker steps (`[0]` source-most) traversed to reach this column; rendered as a \"via …\"\n * postfix only when needed for uniqueness — see {@link derivePostfixes}. */\n linkerPath?: LinkerStep[];\n /** Axis qualifications applied to the hit column / already-bound anchors; rendered as \"[…]\" suffixes. */\n qualifications?: MatchQualifications;\n };\n\n/**\n * Per-zone formatters. Each one receives raw inputs and returns the rendered text for that zone,\n * or `undefined` to suppress the zone entirely (no synthetic injection → no minimization, no render).\n */\nexport type DeriveLabelsFormatters = {\n /** Native column label. Default: identity. `undefined` → label entry not added (treated as if spec had no label). */\n native?: (label: string, spec: PObjectSpec, index: number) => string | undefined;\n /** Linker/source postfix zone (phase 2). Receives the distinguishing tokens (`{ root, linkers }`)\n * and returns the \"via …\" text, or `undefined` to suppress. */\n linker?: LinkerFormatter;\n /** Hit-axis qualifications block. Default: `[${formatQualifications(qs)}]`. */\n hitQualification?: (\n qualifications: AxisQualification[],\n spec: PObjectSpec,\n index: number,\n ) => string | undefined;\n /** Per-anchor qualifications block. Default: `[${anchorId}: ${formatQualifications(qs)}]`. */\n anchorQualification?: (\n anchorId: PObjectId,\n qualifications: AxisQualification[],\n spec: PObjectSpec,\n index: number,\n ) => string | undefined;\n};\n\nexport type DeriveLabelsOptions = {\n /** Separator to use between label parts (\" / \" by default). */\n separator?: string;\n /** If true, native label is appended at the end of the trace zone. By default it is prepended (label is the most important name). */\n addLabelAsSuffix?: boolean;\n /** Force inclusion of native column label even when not needed for uniqueness. */\n includeNativeLabel?: boolean;\n /** Trace types that must be included in the label. */\n forceTraceElements?: string[];\n /** Per-zone custom formatters. Returning `undefined` from any formatter suppresses the corresponding zone. */\n formatters?: DeriveLabelsFormatters;\n};\n\n/**\n * Distinct labels for a set of columns. Two phases:\n * 1. {@link deriveStems} — treats each column as a single entity (native label + trace + hit/anchor\n * qualifications) and produces the minimal distinguishing \"stem\".\n * 2. {@link derivePostfixes} — for columns still colliding on their stem, appends a \"via …\" postfix\n * describing the difference between their linker sources (root axis, then linker chain).\n */\nexport function deriveDistinctLabels(values: Entry[], options: DeriveLabelsOptions = {}): string[] {\n const stems = deriveStems(values, options);\n return derivePostfixes(\n values.map((v, i) => {\n const { spec, linkerPath } = extractEntryParts(v);\n return {\n stem: stems[i],\n // Hit columns are PColumns; the postfix reads their axesSpec to orient linkers.\n hit: spec as PColumnSpec,\n linkers: (linkerPath ?? []).map((s) => s.spec),\n };\n }),\n options.formatters?.linker,\n );\n}\n\n/** Phase 1: minimal per-column stem — native label + trace + hit/anchor qualifications, no linkers. */\nfunction deriveStems(values: Entry[], options: DeriveLabelsOptions): string[] {\n const forceTraceElements =\n options.forceTraceElements !== undefined && options.forceTraceElements.length > 0\n ? new Set(options.forceTraceElements)\n : undefined;\n const separator = options.separator ?? \" / \";\n\n const records = values.map((v, i) => enrichRecord(v, i, options));\n const stats = collectTypeStats(records);\n\n const hasAnySynthetic = records.some((r) => r.fullTrace.some((ft) => isSyntheticType(ft.type)));\n const labelForced =\n (options.includeNativeLabel === true || hasAnySynthetic) &&\n stats.countByType.has(LABEL_TYPE_FULL);\n\n const forcedSet = new Set<string>();\n if (labelForced) forcedSet.add(LABEL_TYPE_FULL);\n\n const { mainTypes, secondaryTypes } = classifyTypes(stats, values.length);\n\n const build = (typeSet: Set<string>, force: boolean) =>\n buildLabels(records, typeSet, forceTraceElements, separator, force);\n\n if (mainTypes.length === 0) {\n if (secondaryTypes.length !== 0)\n throw new Error(\"Non-empty secondary types list while main types list is empty.\");\n\n return (\n build(new Set([LABEL_TYPE_FULL]), true) ??\n throwError(\"Failed to derive labels using native column labels\")\n );\n }\n\n let includedCount = 0;\n let additionalType = -1;\n while (includedCount < mainTypes.length) {\n const currentSet = new Set<string>(forcedSet);\n for (let i = 0; i < includedCount; ++i) currentSet.add(mainTypes[i]);\n if (additionalType >= 0) currentSet.add(mainTypes[additionalType]);\n\n const candidateResult = build(currentSet, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) === values.length) {\n const minimized = minimizeTypeSet(\n currentSet,\n records,\n stats,\n forceTraceElements,\n forcedSet,\n separator,\n );\n const rendered = build(minimized, false) ?? throwError(\"Failed to derive unique labels\");\n return repairBareByPresence(\n rendered,\n minimized,\n records,\n stats,\n forcedSet,\n forceTraceElements,\n separator,\n );\n }\n\n additionalType++;\n if (additionalType >= mainTypes.length) {\n includedCount++;\n additionalType = includedCount;\n }\n }\n\n const fallbackSet = new Set([...forcedSet, ...mainTypes, ...secondaryTypes]);\n const minimized = minimizeTypeSet(\n fallbackSet,\n records,\n stats,\n forceTraceElements,\n forcedSet,\n separator,\n );\n const rendered = build(minimized, true) ?? throwError(\"Failed to derive unique labels\");\n return repairBareByPresence(\n rendered,\n minimized,\n records,\n stats,\n forcedSet,\n forceTraceElements,\n separator,\n );\n}\n\n// --- Pure helpers ---\ntype FullTraceEntry = ExtendedTraceEntry & { fullType: string; occurrenceIndex: number };\n\ntype EnrichedRecord = {\n fullTrace: FullTraceEntry[];\n};\n\nfunction extractEntryParts(entry: Entry): {\n spec: PObjectSpec;\n extraTrace: ExtendedTraceEntry[] | undefined;\n linkerPath: LinkerStep[] | undefined;\n qualifications: MatchQualifications | undefined;\n} {\n const isEnriched = \"spec\" in entry && typeof entry.spec === \"object\";\n if (!isEnriched) {\n return {\n spec: entry as PObjectSpec,\n extraTrace: undefined,\n linkerPath: undefined,\n qualifications: undefined,\n };\n }\n return {\n spec: entry.spec,\n extraTrace: entry.extraTrace,\n linkerPath: entry.linkerPath,\n qualifications: entry.qualifications,\n };\n}\n\nfunction formatQualification(q: AxisQualification): string {\n const ctx = q.contextDomain ?? {};\n const keys = Object.keys(ctx);\n if (keys.length === 0) return q.axis.name;\n const pairs = keys.map((k) => `${k}=${ctx[k]}`).join(\", \");\n return Object.prototype.hasOwnProperty.call(ctx, q.axis.name) ? pairs : `${q.axis.name} ${pairs}`;\n}\n\nfunction formatQualifications(qs: AxisQualification[]): string {\n return qs.map(formatQualification).join(\"; \");\n}\n\nfunction buildFullTrace(trace: ExtendedTraceEntry[]): FullTraceEntry[] {\n const result: FullTraceEntry[] = [];\n const occurrences = new Map<string, number>();\n\n for (let i = trace.length - 1; i >= 0; --i) {\n const entry = trace[i];\n const occurrenceIndex = (occurrences.get(entry.type) ?? 0) + 1;\n occurrences.set(entry.type, occurrenceIndex);\n result.push({\n ...entry,\n fullType: `${entry.type}@${occurrenceIndex}`,\n occurrenceIndex,\n });\n }\n\n result.reverse();\n return result;\n}\n\nfunction enrichRecord(value: Entry, index: number, options: DeriveLabelsOptions): EnrichedRecord {\n const { spec, extraTrace, qualifications } = extractEntryParts(value);\n const formatters = options.formatters;\n\n const rawLabel = readAnnotation(spec, Annotation.Label);\n const traceStr = readAnnotation(spec, Annotation.Trace);\n const baseTrace = traceStr\n ? (parseJson(traceStr as StringifiedJson<ExtendedTraceEntry[]>) ?? [])\n : [];\n const prefixExtra = extraTrace?.filter((e) => e.position === \"prefix\") ?? [];\n const suffixExtra = extraTrace?.filter((e) => e.position !== \"prefix\") ?? [];\n const trace: ExtendedTraceEntry[] = [...prefixExtra, ...baseTrace, ...suffixExtra];\n\n if (!isNil(rawLabel)) {\n const label = isFunction(formatters?.native)\n ? formatters.native(rawLabel, spec, index)\n : rawLabel;\n if (!isNil(label)) {\n const labelEntry = { label, type: LABEL_TYPE, importance: -2 };\n if (options.addLabelAsSuffix === true) trace.push(labelEntry);\n else trace.splice(0, 0, labelEntry);\n }\n }\n\n if (qualifications !== undefined && qualifications.forQueries !== undefined) {\n for (const [anchorId, qs] of Object.entries(qualifications.forQueries)) {\n if (qs.length === 0) continue;\n const anchorText = isFunction(formatters?.anchorQualification)\n ? formatters.anchorQualification(anchorId as PObjectId, qs, spec, index)\n : `[${anchorId}: ${formatQualifications(qs)}]`;\n if (isNil(anchorText)) continue;\n trace.push({\n type: `${ANCHOR_QUAL_TYPE_PREFIX}${anchorId}`,\n label: anchorText,\n importance: -11,\n });\n }\n if (qualifications.forHit !== undefined && qualifications.forHit.length > 0) {\n const hitText = isFunction(formatters?.hitQualification)\n ? formatters.hitQualification(qualifications.forHit, spec, index)\n : `[${formatQualifications(qualifications.forHit)}]`;\n if (!isNil(hitText)) {\n trace.push({ type: HIT_QUAL_TYPE, label: hitText, importance: -12 });\n }\n }\n }\n\n return { fullTrace: buildFullTrace(trace) };\n}\n\ntype TypeStats = {\n importances: Map<string, number>;\n countByType: Map<string, number>;\n};\n\nfunction collectTypeStats(records: EnrichedRecord[]): TypeStats {\n const importances = new Map<string, number>();\n const countByType = new Map<string, number>();\n\n for (const record of records) {\n for (let i = 0; i < record.fullTrace.length; i++) {\n const { fullType, importance: rawImportance } = record.fullTrace[i];\n const importance = rawImportance ?? 0;\n const distance = (record.fullTrace.length - i) * DISTANCE_PENALTY;\n\n countByType.set(fullType, (countByType.get(fullType) ?? 0) + 1);\n importances.set(\n fullType,\n Math.max(importances.get(fullType) ?? Number.NEGATIVE_INFINITY, importance - distance),\n );\n }\n }\n\n return { importances, countByType };\n}\n\nfunction classifyTypes(\n stats: TypeStats,\n totalRecords: number,\n): { mainTypes: string[]; secondaryTypes: string[] } {\n const sorted = [...stats.importances].sort(([, i1], [, i2]) => i2 - i1);\n\n const mainTypes: string[] = [];\n const secondaryTypes: string[] = [];\n\n for (const [typeName] of sorted) {\n if (typeName.endsWith(\"@1\") || stats.countByType.get(typeName) === totalRecords)\n mainTypes.push(typeName);\n else secondaryTypes.push(typeName);\n }\n\n return { mainTypes, secondaryTypes };\n}\n\nfunction renderRecordLabel(\n record: EnrichedRecord,\n includedTypes: Set<string>,\n forceTraceElements: Set<string> | undefined,\n separator: string,\n): string | undefined {\n const traceParts: string[] = [];\n const anchorParts: string[] = [];\n let hitLabel: string | undefined;\n\n for (const ft of record.fullTrace) {\n if (!(includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type))) continue;\n if (ft.type === HIT_QUAL_TYPE) hitLabel = ft.label;\n else if (isAnchorQualType(ft.type)) anchorParts.push(ft.label);\n else traceParts.push(ft.label);\n }\n\n const isEmpty = traceParts.length === 0 && anchorParts.length === 0 && hitLabel === undefined;\n\n if (isEmpty) return undefined;\n\n let label = traceParts.join(separator);\n const append = (part: string) => {\n label = label.length === 0 ? part : `${label} ${part}`;\n };\n for (const a of anchorParts) append(a);\n if (hitLabel !== undefined) append(hitLabel);\n\n return label;\n}\n\nfunction buildLabels(\n records: EnrichedRecord[],\n includedTypes: Set<string>,\n forceTraceElements: Set<string> | undefined,\n separator: string,\n force: boolean,\n): string[] | undefined {\n const result: string[] = [];\n\n for (const r of records) {\n const rendered = renderRecordLabel(r, includedTypes, forceTraceElements, separator);\n if (rendered === undefined) {\n if (!force) return undefined;\n result.push(\"Unlabeled\");\n continue;\n }\n result.push(rendered);\n }\n\n return result;\n}\n\nfunction countUniqueLabels(result: string[] | undefined): number {\n if (result === undefined) return 0;\n return new Set(result).size;\n}\n\nfunction minimizeTypeSet(\n typeSet: Set<string>,\n records: EnrichedRecord[],\n stats: TypeStats,\n forceTraceElements: Set<string> | undefined,\n forcedSet: Set<string>,\n separator: string,\n): Set<string> {\n const initialResult = buildLabels(records, typeSet, forceTraceElements, separator, false);\n if (initialResult === undefined) return typeSet;\n\n const targetCardinality = countUniqueLabels(initialResult);\n const result = new Set(typeSet);\n\n const removable = [...result]\n .filter((t) => !forceTraceElements?.has(t.split(\"@\")[0]) && !forcedSet.has(t))\n .sort((a, b) => (stats.importances.get(a) ?? 0) - (stats.importances.get(b) ?? 0));\n\n for (const typeToRemove of removable) {\n const candidate = new Set(result);\n candidate.delete(typeToRemove);\n const candidateResult = buildLabels(records, candidate, forceTraceElements, separator, false);\n if (candidateResult !== undefined && countUniqueLabels(candidateResult) >= targetCardinality) {\n result.delete(typeToRemove);\n }\n }\n\n return result;\n}\n\nconst ABSENT_VALUE = \"\u0000absent\"; // sentinel value for \"row has no entry of this type\"\n\n/** Whether `fullType`'s rendered value differs across the group (absence counts as a value). */\nfunction typeDistinguishes(records: EnrichedRecord[], group: number[], fullType: string): boolean {\n const values = new Set<string>();\n for (const i of group) {\n const ft = records[i].fullTrace.find((e) => e.fullType === fullType);\n values.add(ft?.label ?? ABSENT_VALUE);\n if (values.size > 1) return true;\n }\n return false;\n}\n\n/** The highest-importance trace type this row HAS (outside `typeSet`) that sets it apart from its\n * group peers. `undefined` when the row carries nothing distinguishing of its own. */\nfunction bestDistinguishingType(\n records: EnrichedRecord[],\n group: number[],\n row: number,\n typeSet: Set<string>,\n forcedSet: Set<string>,\n forceTraceElements: Set<string> | undefined,\n stats: TypeStats,\n): string | undefined {\n return records[row].fullTrace.reduce<{ type: string; imp: number } | undefined>((best, ft) => {\n if (typeSet.has(ft.fullType) || forcedSet.has(ft.fullType) || forceTraceElements?.has(ft.type))\n return best;\n if (!typeDistinguishes(records, group, ft.fullType)) return best;\n const imp = stats.importances.get(ft.fullType) ?? 0;\n return best === undefined || imp > best.imp ? { type: ft.fullType, imp } : best;\n }, undefined)?.type;\n}\n\n/**\n * Un-bare columns distinguished only \"by absence\". After minimization a column can end up with a\n * bare trace zone (only the forced native label) while a peer sharing that same base renders extra\n * tokens — the column reads as unique purely because it LACKS what the peer has. For each such bare\n * column this re-renders JUST that column's label with the highest-importance trace type it actually\n * carries that tells it apart from its peers, so every colliding column is distinguished by a token\n * it HAS rather than by omission.\n *\n * Patches individual labels rather than the shared type set on purpose: the set is global, so adding\n * a type there would also decorate unrelated columns in other groups that happen to carry it. Only\n * bare columns' labels grow (a superset of their previous value), so uniqueness is preserved. Groups\n * are keyed by the base = the label rendered from the forced types alone.\n */\nfunction repairBareByPresence(\n labels: string[],\n minimized: Set<string>,\n records: EnrichedRecord[],\n stats: TypeStats,\n forcedSet: Set<string>,\n forceTraceElements: Set<string> | undefined,\n separator: string,\n): string[] {\n const base = records.map((r) => renderRecordLabel(r, forcedSet, forceTraceElements, separator));\n const isBare = records.map(\n (r, i) => renderRecordLabel(r, minimized, forceTraceElements, separator) === base[i],\n );\n\n const groups = records.reduce<Map<string, number[]>>(\n (acc, _, i) => acc.set(base[i] ?? \"\", [...(acc.get(base[i] ?? \"\") ?? []), i]),\n new Map(),\n );\n\n const patched = [...labels];\n for (const group of groups.values()) {\n // Only asymmetric groups need repair: a bare column beside a richer peer. When every column is\n // equally bare there is nothing to un-hide (they carry no distinguishing token of their own).\n if (!group.some((i) => !isBare[i])) continue;\n for (const i of group) {\n if (!isBare[i]) continue;\n const chosen = bestDistinguishingType(\n records,\n group,\n i,\n minimized,\n forcedSet,\n forceTraceElements,\n stats,\n );\n if (chosen === undefined) continue;\n const withToken = new Set([...minimized, chosen]);\n patched[i] =\n renderRecordLabel(records[i], withToken, forceTraceElements, separator) ?? patched[i];\n }\n }\n\n return patched;\n}\n"],"mappings":";;;;;AAkBA,MAAM,mBAAmB;AACzB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AACxB,MAAM,gBAAgB;AACtB,MAAM,0BAA0B;AAEhC,SAAS,iBAAiB,GAAoB;CAC5C,OAAO,EAAE,WAAW,uBAAuB;AAC7C;AAEA,SAAS,gBAAgB,GAAoB;CAC3C,OAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAClD;;;;;;;;AAyEA,SAAgB,qBAAqB,QAAiB,UAA+B,CAAC,GAAa;CACjG,MAAM,QAAQ,YAAY,QAAQ,OAAO;CACzC,OAAOA,8BAAAA,gBACL,OAAO,KAAK,GAAG,MAAM;EACnB,MAAM,EAAE,MAAM,eAAe,kBAAkB,CAAC;EAChD,OAAO;GACL,MAAM,MAAM;GAEZ,KAAK;GACL,UAAU,cAAc,CAAC,EAAA,CAAG,KAAK,MAAM,EAAE,IAAI;EAC/C;CACF,CAAC,GACD,QAAQ,YAAY,MACtB;AACF;;AAGA,SAAS,YAAY,QAAiB,SAAwC;CAC5E,MAAM,qBACJ,QAAQ,uBAAuB,KAAA,KAAa,QAAQ,mBAAmB,SAAS,IAC5E,IAAI,IAAI,QAAQ,kBAAkB,IAClC,KAAA;CACN,MAAM,YAAY,QAAQ,aAAa;CAEvC,MAAM,UAAU,OAAO,KAAK,GAAG,MAAM,aAAa,GAAG,GAAG,OAAO,CAAC;CAChE,MAAM,QAAQ,iBAAiB,OAAO;CAEtC,MAAM,kBAAkB,QAAQ,MAAM,MAAM,EAAE,UAAU,MAAM,OAAO,gBAAgB,GAAG,IAAI,CAAC,CAAC;CAC9F,MAAM,eACH,QAAQ,uBAAuB,QAAQ,oBACxC,MAAM,YAAY,IAAI,eAAe;CAEvC,MAAM,4BAAY,IAAI,IAAY;CAClC,IAAI,aAAa,UAAU,IAAI,eAAe;CAE9C,MAAM,EAAE,WAAW,mBAAmB,cAAc,OAAO,OAAO,MAAM;CAExE,MAAM,SAAS,SAAsB,UACnC,YAAY,SAAS,SAAS,oBAAoB,WAAW,KAAK;CAEpE,IAAI,UAAU,WAAW,GAAG;EAC1B,IAAI,eAAe,WAAW,GAC5B,MAAM,IAAI,MAAM,gEAAgE;EAElF,OACE,sBAAM,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,IAAI,MAAA,GAAA,wBAAA,WAAA,CAC3B,oDAAoD;CAEnE;CAEA,IAAI,gBAAgB;CACpB,IAAI,iBAAiB;CACrB,OAAO,gBAAgB,UAAU,QAAQ;EACvC,MAAM,aAAa,IAAI,IAAY,SAAS;EAC5C,KAAK,IAAI,IAAI,GAAG,IAAI,eAAe,EAAE,GAAG,WAAW,IAAI,UAAU,EAAE;EACnE,IAAI,kBAAkB,GAAG,WAAW,IAAI,UAAU,eAAe;EAEjE,MAAM,kBAAkB,MAAM,YAAY,KAAK;EAC/C,IAAI,oBAAoB,KAAA,KAAa,kBAAkB,eAAe,MAAM,OAAO,QAAQ;GACzF,MAAM,YAAY,gBAChB,YACA,SACA,OACA,oBACA,WACA,SACF;GAEA,OAAO,qBADU,MAAM,WAAW,KAAK,MAAA,GAAA,wBAAA,WAAA,CAAgB,gCAAgC,GAGrF,WACA,SACA,OACA,WACA,oBACA,SACF;EACF;EAEA;EACA,IAAI,kBAAkB,UAAU,QAAQ;GACtC;GACA,iBAAiB;EACnB;CACF;CAGA,MAAM,YAAY,gCAChB,IAFsB,IAAI;EAAC,GAAG;EAAW,GAAG;EAAW,GAAG;CAAc,CAE9D,GACV,SACA,OACA,oBACA,WACA,SACF;CAEA,OAAO,qBADU,MAAM,WAAW,IAAI,MAAA,GAAA,wBAAA,WAAA,CAAgB,gCAAgC,GAGpF,WACA,SACA,OACA,WACA,oBACA,SACF;AACF;AASA,SAAS,kBAAkB,OAKzB;CAEA,IAAI,EADe,UAAU,SAAS,OAAO,MAAM,SAAS,WAE1D,OAAO;EACL,MAAM;EACN,YAAY,KAAA;EACZ,YAAY,KAAA;EACZ,gBAAgB,KAAA;CAClB;CAEF,OAAO;EACL,MAAM,MAAM;EACZ,YAAY,MAAM;EAClB,YAAY,MAAM;EAClB,gBAAgB,MAAM;CACxB;AACF;AAEA,SAAS,oBAAoB,GAA8B;CACzD,MAAM,MAAM,EAAE,iBAAiB,CAAC;CAChC,MAAM,OAAO,OAAO,KAAK,GAAG;CAC5B,IAAI,KAAK,WAAW,GAAG,OAAO,EAAE,KAAK;CACrC,MAAM,QAAQ,KAAK,KAAK,MAAM,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,KAAK,IAAI;CACzD,OAAO,OAAO,UAAU,eAAe,KAAK,KAAK,EAAE,KAAK,IAAI,IAAI,QAAQ,GAAG,EAAE,KAAK,KAAK,GAAG;AAC5F;AAEA,SAAS,qBAAqB,IAAiC;CAC7D,OAAO,GAAG,IAAI,mBAAmB,CAAC,CAAC,KAAK,IAAI;AAC9C;AAEA,SAAS,eAAe,OAA+C;CACrE,MAAM,SAA2B,CAAC;CAClC,MAAM,8BAAc,IAAI,IAAoB;CAE5C,KAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,EAAE,GAAG;EAC1C,MAAM,QAAQ,MAAM;EACpB,MAAM,mBAAmB,YAAY,IAAI,MAAM,IAAI,KAAK,KAAK;EAC7D,YAAY,IAAI,MAAM,MAAM,eAAe;EAC3C,OAAO,KAAK;GACV,GAAG;GACH,UAAU,GAAG,MAAM,KAAK,GAAG;GAC3B;EACF,CAAC;CACH;CAEA,OAAO,QAAQ;CACf,OAAO;AACT;AAEA,SAAS,aAAa,OAAc,OAAe,SAA8C;CAC/F,MAAM,EAAE,MAAM,YAAY,mBAAmB,kBAAkB,KAAK;CACpE,MAAM,aAAa,QAAQ;CAE3B,MAAM,YAAA,GAAA,gCAAA,eAAA,CAA0B,MAAMC,gCAAAA,WAAW,KAAK;CACtD,MAAM,YAAA,GAAA,gCAAA,eAAA,CAA0B,MAAMA,gCAAAA,WAAW,KAAK;CACtD,MAAM,YAAY,YAAA,GAAA,gCAAA,UAAA,CACH,QAAiD,KAAK,CAAC,IAClE,CAAC;CACL,MAAM,cAAc,YAAY,QAAQ,MAAM,EAAE,aAAa,QAAQ,KAAK,CAAC;CAC3E,MAAM,cAAc,YAAY,QAAQ,MAAM,EAAE,aAAa,QAAQ,KAAK,CAAC;CAC3E,MAAM,QAA8B;EAAC,GAAG;EAAa,GAAG;EAAW,GAAG;CAAW;CAEjF,IAAI,EAAA,GAAA,WAAA,MAAA,CAAO,QAAQ,GAAG;EACpB,MAAM,SAAA,GAAA,WAAA,WAAA,CAAmB,YAAY,MAAM,IACvC,WAAW,OAAO,UAAU,MAAM,KAAK,IACvC;EACJ,IAAI,EAAA,GAAA,WAAA,MAAA,CAAO,KAAK,GAAG;GACjB,MAAM,aAAa;IAAE;IAAO,MAAM;IAAY,YAAY;GAAG;GAC7D,IAAI,QAAQ,qBAAqB,MAAM,MAAM,KAAK,UAAU;QACvD,MAAM,OAAO,GAAG,GAAG,UAAU;EACpC;CACF;CAEA,IAAI,mBAAmB,KAAA,KAAa,eAAe,eAAe,KAAA,GAAW;EAC3E,KAAK,MAAM,CAAC,UAAU,OAAO,OAAO,QAAQ,eAAe,UAAU,GAAG;GACtE,IAAI,GAAG,WAAW,GAAG;GACrB,MAAM,cAAA,GAAA,WAAA,WAAA,CAAwB,YAAY,mBAAmB,IACzD,WAAW,oBAAoB,UAAuB,IAAI,MAAM,KAAK,IACrE,IAAI,SAAS,IAAI,qBAAqB,EAAE,EAAE;GAC9C,KAAA,GAAA,WAAA,MAAA,CAAU,UAAU,GAAG;GACvB,MAAM,KAAK;IACT,MAAM,GAAG,0BAA0B;IACnC,OAAO;IACP,YAAY;GACd,CAAC;EACH;EACA,IAAI,eAAe,WAAW,KAAA,KAAa,eAAe,OAAO,SAAS,GAAG;GAC3E,MAAM,WAAA,GAAA,WAAA,WAAA,CAAqB,YAAY,gBAAgB,IACnD,WAAW,iBAAiB,eAAe,QAAQ,MAAM,KAAK,IAC9D,IAAI,qBAAqB,eAAe,MAAM,EAAE;GACpD,IAAI,EAAA,GAAA,WAAA,MAAA,CAAO,OAAO,GAChB,MAAM,KAAK;IAAE,MAAM;IAAe,OAAO;IAAS,YAAY;GAAI,CAAC;EAEvE;CACF;CAEA,OAAO,EAAE,WAAW,eAAe,KAAK,EAAE;AAC5C;AAOA,SAAS,iBAAiB,SAAsC;CAC9D,MAAM,8BAAc,IAAI,IAAoB;CAC5C,MAAM,8BAAc,IAAI,IAAoB;CAE5C,KAAK,MAAM,UAAU,SACnB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,UAAU,QAAQ,KAAK;EAChD,MAAM,EAAE,UAAU,YAAY,kBAAkB,OAAO,UAAU;EACjE,MAAM,aAAa,iBAAiB;EACpC,MAAM,YAAY,OAAO,UAAU,SAAS,KAAK;EAEjD,YAAY,IAAI,WAAW,YAAY,IAAI,QAAQ,KAAK,KAAK,CAAC;EAC9D,YAAY,IACV,UACA,KAAK,IAAI,YAAY,IAAI,QAAQ,KAAK,OAAO,mBAAmB,aAAa,QAAQ,CACvF;CACF;CAGF,OAAO;EAAE;EAAa;CAAY;AACpC;AAEA,SAAS,cACP,OACA,cACmD;CACnD,MAAM,SAAS,CAAC,GAAG,MAAM,WAAW,CAAC,CAAC,MAAM,GAAG,KAAK,GAAG,QAAQ,KAAK,EAAE;CAEtE,MAAM,YAAsB,CAAC;CAC7B,MAAM,iBAA2B,CAAC;CAElC,KAAK,MAAM,CAAC,aAAa,QACvB,IAAI,SAAS,SAAS,IAAI,KAAK,MAAM,YAAY,IAAI,QAAQ,MAAM,cACjE,UAAU,KAAK,QAAQ;MACpB,eAAe,KAAK,QAAQ;CAGnC,OAAO;EAAE;EAAW;CAAe;AACrC;AAEA,SAAS,kBACP,QACA,eACA,oBACA,WACoB;CACpB,MAAM,aAAuB,CAAC;CAC9B,MAAM,cAAwB,CAAC;CAC/B,IAAI;CAEJ,KAAK,MAAM,MAAM,OAAO,WAAW;EACjC,IAAI,EAAE,cAAc,IAAI,GAAG,QAAQ,KAAK,oBAAoB,IAAI,GAAG,IAAI,IAAI;EAC3E,IAAI,GAAG,SAAS,eAAe,WAAW,GAAG;OACxC,IAAI,iBAAiB,GAAG,IAAI,GAAG,YAAY,KAAK,GAAG,KAAK;OACxD,WAAW,KAAK,GAAG,KAAK;CAC/B;CAIA,IAFgB,WAAW,WAAW,KAAK,YAAY,WAAW,KAAK,aAAa,KAAA,GAEvE,OAAO,KAAA;CAEpB,IAAI,QAAQ,WAAW,KAAK,SAAS;CACrC,MAAM,UAAU,SAAiB;EAC/B,QAAQ,MAAM,WAAW,IAAI,OAAO,GAAG,MAAM,GAAG;CAClD;CACA,KAAK,MAAM,KAAK,aAAa,OAAO,CAAC;CACrC,IAAI,aAAa,KAAA,GAAW,OAAO,QAAQ;CAE3C,OAAO;AACT;AAEA,SAAS,YACP,SACA,eACA,oBACA,WACA,OACsB;CACtB,MAAM,SAAmB,CAAC;CAE1B,KAAK,MAAM,KAAK,SAAS;EACvB,MAAM,WAAW,kBAAkB,GAAG,eAAe,oBAAoB,SAAS;EAClF,IAAI,aAAa,KAAA,GAAW;GAC1B,IAAI,CAAC,OAAO,OAAO,KAAA;GACnB,OAAO,KAAK,WAAW;GACvB;EACF;EACA,OAAO,KAAK,QAAQ;CACtB;CAEA,OAAO;AACT;AAEA,SAAS,kBAAkB,QAAsC;CAC/D,IAAI,WAAW,KAAA,GAAW,OAAO;CACjC,OAAO,IAAI,IAAI,MAAM,CAAC,CAAC;AACzB;AAEA,SAAS,gBACP,SACA,SACA,OACA,oBACA,WACA,WACa;CACb,MAAM,gBAAgB,YAAY,SAAS,SAAS,oBAAoB,WAAW,KAAK;CACxF,IAAI,kBAAkB,KAAA,GAAW,OAAO;CAExC,MAAM,oBAAoB,kBAAkB,aAAa;CACzD,MAAM,SAAS,IAAI,IAAI,OAAO;CAE9B,MAAM,YAAY,CAAC,GAAG,MAAM,CAAC,CAC1B,QAAQ,MAAM,CAAC,oBAAoB,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAC7E,MAAM,GAAG,OAAO,MAAM,YAAY,IAAI,CAAC,KAAK,MAAM,MAAM,YAAY,IAAI,CAAC,KAAK,EAAE;CAEnF,KAAK,MAAM,gBAAgB,WAAW;EACpC,MAAM,YAAY,IAAI,IAAI,MAAM;EAChC,UAAU,OAAO,YAAY;EAC7B,MAAM,kBAAkB,YAAY,SAAS,WAAW,oBAAoB,WAAW,KAAK;EAC5F,IAAI,oBAAoB,KAAA,KAAa,kBAAkB,eAAe,KAAK,mBACzE,OAAO,OAAO,YAAY;CAE9B;CAEA,OAAO;AACT;AAEA,MAAM,eAAe;;AAGrB,SAAS,kBAAkB,SAA2B,OAAiB,UAA2B;CAChG,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,KAAK,OAAO;EACrB,MAAM,KAAK,QAAQ,EAAE,CAAC,UAAU,MAAM,MAAM,EAAE,aAAa,QAAQ;EACnE,OAAO,IAAI,IAAI,SAAS,YAAY;EACpC,IAAI,OAAO,OAAO,GAAG,OAAO;CAC9B;CACA,OAAO;AACT;;;AAIA,SAAS,uBACP,SACA,OACA,KACA,SACA,WACA,oBACA,OACoB;CACpB,OAAO,QAAQ,IAAI,CAAC,UAAU,QAAmD,MAAM,OAAO;EAC5F,IAAI,QAAQ,IAAI,GAAG,QAAQ,KAAK,UAAU,IAAI,GAAG,QAAQ,KAAK,oBAAoB,IAAI,GAAG,IAAI,GAC3F,OAAO;EACT,IAAI,CAAC,kBAAkB,SAAS,OAAO,GAAG,QAAQ,GAAG,OAAO;EAC5D,MAAM,MAAM,MAAM,YAAY,IAAI,GAAG,QAAQ,KAAK;EAClD,OAAO,SAAS,KAAA,KAAa,MAAM,KAAK,MAAM;GAAE,MAAM,GAAG;GAAU;EAAI,IAAI;CAC7E,GAAG,KAAA,CAAS,CAAC,EAAE;AACjB;;;;;;;;;;;;;;AAeA,SAAS,qBACP,QACA,WACA,SACA,OACA,WACA,oBACA,WACU;CACV,MAAM,OAAO,QAAQ,KAAK,MAAM,kBAAkB,GAAG,WAAW,oBAAoB,SAAS,CAAC;CAC9F,MAAM,SAAS,QAAQ,KACpB,GAAG,MAAM,kBAAkB,GAAG,WAAW,oBAAoB,SAAS,MAAM,KAAK,EACpF;CAEA,MAAM,SAAS,QAAQ,QACpB,KAAK,GAAG,MAAM,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,GAAI,IAAI,IAAI,KAAK,MAAM,EAAE,KAAK,CAAC,GAAI,CAAC,CAAC,mBAC5E,IAAI,IAAI,CACV;CAEA,MAAM,UAAU,CAAC,GAAG,MAAM;CAC1B,KAAK,MAAM,SAAS,OAAO,OAAO,GAAG;EAGnC,IAAI,CAAC,MAAM,MAAM,MAAM,CAAC,OAAO,EAAE,GAAG;EACpC,KAAK,MAAM,KAAK,OAAO;GACrB,IAAI,CAAC,OAAO,IAAI;GAChB,MAAM,SAAS,uBACb,SACA,OACA,GACA,WACA,WACA,oBACA,KACF;GACA,IAAI,WAAW,KAAA,GAAW;GAC1B,MAAM,4BAAY,IAAI,IAAI,CAAC,GAAG,WAAW,MAAM,CAAC;GAChD,QAAQ,KACN,kBAAkB,QAAQ,IAAI,WAAW,oBAAoB,SAAS,KAAK,QAAQ;EACvF;CACF;CAEA,OAAO;AACT"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LinkerFormatter } from "./linked_column_postfix.js";
|
|
2
|
+
import { AxisQualification, MatchQualifications, PColumnSpec, PObjectId, PObjectSpec, Trace, Trace as Trace$1, TraceEntry } from "@milaboratories/pl-model-common";
|
|
2
3
|
|
|
3
4
|
//#region src/labels/derive_distinct_labels.d.ts
|
|
4
5
|
/** SDK-internal trace shape — adds fields used by this algorithm only, not part of the on-disk contract. */
|
|
@@ -7,12 +8,14 @@ type ExtendedTraceEntry = Trace[number] & {
|
|
|
7
8
|
position?: "prefix" | "suffix";
|
|
8
9
|
};
|
|
9
10
|
type LinkerStep = {
|
|
10
|
-
spec:
|
|
11
|
+
/** Linker column spec — its `axesSpec` yields the source axis (root); its `LinkLabel`/`Label` names it. */spec: PColumnSpec; /** Axis qualifications applied on this hop. Not yet consumed by the postfix (deferred). */
|
|
11
12
|
qualifications?: AxisQualification[];
|
|
12
13
|
};
|
|
13
14
|
type Entry = PObjectSpec | {
|
|
14
15
|
spec: PObjectSpec; /** Extra trace entries merged with the base trace from annotations. */
|
|
15
|
-
extraTrace?: ExtendedTraceEntry[];
|
|
16
|
+
extraTrace?: ExtendedTraceEntry[];
|
|
17
|
+
/** Linker steps (`[0]` source-most) traversed to reach this column; rendered as a "via …"
|
|
18
|
+
* postfix only when needed for uniqueness — see {@link derivePostfixes}. */
|
|
16
19
|
linkerPath?: LinkerStep[]; /** Axis qualifications applied to the hit column / already-bound anchors; rendered as "[…]" suffixes. */
|
|
17
20
|
qualifications?: MatchQualifications;
|
|
18
21
|
};
|
|
@@ -22,12 +25,9 @@ type Entry = PObjectSpec | {
|
|
|
22
25
|
*/
|
|
23
26
|
type DeriveLabelsFormatters = {
|
|
24
27
|
/** Native column label. Default: identity. `undefined` → label entry not added (treated as if spec had no label). */native?: (label: string, spec: PObjectSpec, index: number) => string | undefined;
|
|
25
|
-
/** Linker zone (
|
|
26
|
-
*
|
|
27
|
-
linker?:
|
|
28
|
-
/** Per-step linker qualifications inlined into the step base label.
|
|
29
|
-
* Default: `[${formatQualifications(qs)}]`. `undefined` → step rendered without quals. */
|
|
30
|
-
linkerStepQualification?: (qualifications: AxisQualification[], stepIndex: number, stepSpec: PObjectSpec) => string | undefined; /** Hit-axis qualifications block. Default: `[${formatQualifications(qs)}]`. */
|
|
28
|
+
/** Linker/source postfix zone (phase 2). Receives the distinguishing tokens (`{ root, linkers }`)
|
|
29
|
+
* and returns the "via …" text, or `undefined` to suppress. */
|
|
30
|
+
linker?: LinkerFormatter; /** Hit-axis qualifications block. Default: `[${formatQualifications(qs)}]`. */
|
|
31
31
|
hitQualification?: (qualifications: AxisQualification[], spec: PObjectSpec, index: number) => string | undefined; /** Per-anchor qualifications block. Default: `[${anchorId}: ${formatQualifications(qs)}]`. */
|
|
32
32
|
anchorQualification?: (anchorId: PObjectId, qualifications: AxisQualification[], spec: PObjectSpec, index: number) => string | undefined;
|
|
33
33
|
};
|
|
@@ -38,6 +38,13 @@ type DeriveLabelsOptions = {
|
|
|
38
38
|
forceTraceElements?: string[]; /** Per-zone custom formatters. Returning `undefined` from any formatter suppresses the corresponding zone. */
|
|
39
39
|
formatters?: DeriveLabelsFormatters;
|
|
40
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* Distinct labels for a set of columns. Two phases:
|
|
43
|
+
* 1. {@link deriveStems} — treats each column as a single entity (native label + trace + hit/anchor
|
|
44
|
+
* qualifications) and produces the minimal distinguishing "stem".
|
|
45
|
+
* 2. {@link derivePostfixes} — for columns still colliding on their stem, appends a "via …" postfix
|
|
46
|
+
* describing the difference between their linker sources (root axis, then linker chain).
|
|
47
|
+
*/
|
|
41
48
|
declare function deriveDistinctLabels(values: Entry[], options?: DeriveLabelsOptions): string[];
|
|
42
49
|
//#endregion
|
|
43
50
|
export { DeriveLabelsFormatters, DeriveLabelsOptions, Entry, LinkerStep, type Trace$1 as Trace, type TraceEntry, deriveDistinctLabels };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive_distinct_labels.d.ts","names":[],"sources":["../../src/labels/derive_distinct_labels.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"derive_distinct_labels.d.ts","names":[],"sources":["../../src/labels/derive_distinct_labels.ts"],"mappings":";;;;;KAiCK,kBAAA,GAAqB,KAAK;EAC7B,UAAA;EACA,QAAA;AAAA;AAAA,KAGU,UAAA;EAJV,2GAMA,IAAA,EAAM,WAAA,EALE;EAOR,cAAA,GAAiB,iBAAiB;AAAA;AAAA,KAGxB,KAAA,GACR,WAAA;EAEE,IAAA,EAAM,WAAA,EANwB;EAQ9B,UAAA,GAAa,kBAAA;EAVX;;EAaF,UAAA,GAAa,UAAA,IAXiB;EAa9B,cAAA,GAAiB,mBAAA;AAAA;;;;;KAOX,sBAAA;EATO,qHAWjB,MAAA,IAAU,KAAA,UAAe,IAAA,EAAM,WAAA,EAAa,KAAA;EATJ;;EAYxC,MAAA,GAAS,eAAA,EAnBL;EAqBJ,gBAAA,IACE,cAAA,EAAgB,iBAAA,IAChB,IAAA,EAAM,WAAA,EACN,KAAA,iCAtBE;EAyBJ,mBAAA,IACE,QAAA,EAAU,SAAA,EACV,cAAA,EAAgB,iBAAA,IAChB,IAAA,EAAM,WAAA,EACN,KAAA;AAAA;AAAA,KAIQ,mBAAA;EA5BN,+DA8BJ,SAAA,WA9BwC;EAgCxC,gBAAA,YAzBU;EA2BV,kBAAA;EAEA,kBAAA,aAxBS;EA0BT,UAAA,GAAa,sBAAsB;AAAA;;;;;;;;iBAUrB,oBAAA,CAAqB,MAAA,EAAQ,KAAA,IAAS,OAAA,GAAS,mBAAwB"}
|