@platforma-sdk/model 1.68.5 → 1.68.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/columns/column_collection_builder.cjs +8 -2
  2. package/dist/columns/column_collection_builder.cjs.map +1 -1
  3. package/dist/columns/column_collection_builder.d.ts +14 -3
  4. package/dist/columns/column_collection_builder.d.ts.map +1 -1
  5. package/dist/columns/column_collection_builder.js +8 -2
  6. package/dist/columns/column_collection_builder.js.map +1 -1
  7. package/dist/columns/ctx_column_sources.d.ts +1 -1
  8. package/dist/columns/index.d.ts +1 -1
  9. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +50 -50
  10. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
  11. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts +5 -10
  12. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts.map +1 -1
  13. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +50 -50
  14. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
  15. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +16 -17
  16. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -1
  17. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts +4 -4
  18. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts.map +1 -1
  19. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +16 -17
  20. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -1
  21. package/dist/components/PlDataTable/createPlDataTable/utils.cjs +8 -2
  22. package/dist/components/PlDataTable/createPlDataTable/utils.cjs.map +1 -1
  23. package/dist/components/PlDataTable/createPlDataTable/utils.js +8 -2
  24. package/dist/components/PlDataTable/createPlDataTable/utils.js.map +1 -1
  25. package/dist/components/PlDatasetSelector/filter_discovery.d.ts +1 -1
  26. package/dist/index.d.ts +6 -6
  27. package/dist/labels/derive_distinct_labels.cjs +121 -50
  28. package/dist/labels/derive_distinct_labels.cjs.map +1 -1
  29. package/dist/labels/derive_distinct_labels.d.ts +30 -14
  30. package/dist/labels/derive_distinct_labels.d.ts.map +1 -1
  31. package/dist/labels/derive_distinct_labels.js +121 -50
  32. package/dist/labels/derive_distinct_labels.js.map +1 -1
  33. package/dist/labels/derive_distinct_tooltips.cjs +0 -10
  34. package/dist/labels/derive_distinct_tooltips.cjs.map +1 -1
  35. package/dist/labels/derive_distinct_tooltips.d.ts +2 -3
  36. package/dist/labels/derive_distinct_tooltips.d.ts.map +1 -1
  37. package/dist/labels/derive_distinct_tooltips.js +0 -10
  38. package/dist/labels/derive_distinct_tooltips.js.map +1 -1
  39. package/dist/labels/index.d.ts +1 -1
  40. package/dist/package.cjs +1 -1
  41. package/dist/package.js +1 -1
  42. package/package.json +4 -4
  43. package/src/columns/column_collection_builder.test.ts +0 -2
  44. package/src/columns/column_collection_builder.ts +26 -3
  45. package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +90 -75
  46. package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +31 -34
  47. package/src/components/PlDataTable/createPlDataTable/utils.test.ts +1 -1
  48. package/src/components/PlDataTable/createPlDataTable/utils.ts +11 -4
  49. package/src/labels/derive_distinct_labels.test.ts +396 -52
  50. package/src/labels/derive_distinct_labels.ts +205 -103
  51. package/src/labels/derive_distinct_tooltips.test.ts +1 -22
  52. package/src/labels/derive_distinct_tooltips.ts +1 -18
@@ -6,64 +6,84 @@ let es_toolkit = require("es-toolkit");
6
6
  const DISTANCE_PENALTY = .001;
7
7
  const LABEL_TYPE = "__LABEL__";
8
8
  const LABEL_TYPE_FULL = "__LABEL__@1";
9
+ const LINKER_TYPE = "__LINKER__";
10
+ const LINKER_TYPE_FULL = "__LINKER__@1";
11
+ const HIT_QUAL_TYPE = "__HIT_QUAL__";
12
+ const ANCHOR_QUAL_TYPE_PREFIX = "__ANCHOR_QUAL__:";
13
+ function isAnchorQualType(t) {
14
+ return t.startsWith(ANCHOR_QUAL_TYPE_PREFIX);
15
+ }
16
+ function isSyntheticType(t) {
17
+ return t === LINKER_TYPE || t === HIT_QUAL_TYPE || isAnchorQualType(t);
18
+ }
9
19
  function deriveDistinctLabels(values, options = {}) {
10
20
  const forceTraceElements = options.forceTraceElements !== void 0 && options.forceTraceElements.length > 0 ? new Set(options.forceTraceElements) : void 0;
11
21
  const separator = options.separator ?? " / ";
12
- const linkerSuffixes = values.map((v, i) => {
13
- const spec = "spec" in v && typeof v.spec === "object" ? v.spec : v;
14
- const linkerLabels = extractLinkerLabels(v);
15
- if (linkerLabels.length === 0) return void 0;
16
- return (0, es_toolkit.isFunction)(options.linkerLabelFormatter) ? options.linkerLabelFormatter(linkerLabels, spec, i) : `via ${linkerLabels.join(" > ")}`;
17
- });
18
- const records = values.map((v) => enrichRecord(v, options));
22
+ const records = values.map((v, i) => enrichRecord(v, i, options));
19
23
  const stats = collectTypeStats(records);
24
+ const hasAnySynthetic = records.some((r) => r.fullTrace.some((ft) => isSyntheticType(ft.type)));
25
+ const labelForced = (options.includeNativeLabel === true || hasAnySynthetic) && stats.countByType.has(LABEL_TYPE_FULL);
26
+ const linkerForced = stats.countByType.get(LINKER_TYPE_FULL) === values.length;
27
+ const forcedSet = /* @__PURE__ */ new Set();
28
+ if (labelForced) forcedSet.add(LABEL_TYPE_FULL);
29
+ if (linkerForced) forcedSet.add(LINKER_TYPE_FULL);
20
30
  const { mainTypes, secondaryTypes } = classifyTypes(stats, values.length);
21
31
  const build = (typeSet, force) => buildLabels(records, typeSet, forceTraceElements, separator, force);
22
32
  if (mainTypes.length === 0) {
23
33
  if (secondaryTypes.length !== 0) throw new Error("Non-empty secondary types list while main types list is empty.");
24
- return applyLinkerSuffixes(build(new Set(LABEL_TYPE_FULL), true) ?? (0, _milaboratories_helpers.throwError)("Failed to derive labels using native column labels"), linkerSuffixes);
34
+ return build(new Set([LABEL_TYPE_FULL]), true) ?? (0, _milaboratories_helpers.throwError)("Failed to derive labels using native column labels");
25
35
  }
26
36
  let includedCount = 0;
27
37
  let additionalType = -1;
28
38
  while (includedCount < mainTypes.length) {
29
- const currentSet = /* @__PURE__ */ new Set();
30
- if (options.includeNativeLabel) currentSet.add(LABEL_TYPE_FULL);
39
+ const currentSet = new Set(forcedSet);
31
40
  for (let i = 0; i < includedCount; ++i) currentSet.add(mainTypes[i]);
32
41
  if (additionalType >= 0) currentSet.add(mainTypes[additionalType]);
33
42
  const candidateResult = build(currentSet, false);
34
- if (candidateResult !== void 0 && countUniqueLabels(candidateResult) === values.length) return applyLinkerSuffixes(build(minimizeTypeSet(currentSet, records, stats, forceTraceElements, options, separator), false) ?? (0, _milaboratories_helpers.throwError)("Failed to derive unique labels"), linkerSuffixes);
43
+ if (candidateResult !== void 0 && countUniqueLabels(candidateResult) === values.length) return build(minimizeTypeSet(currentSet, records, stats, forceTraceElements, forcedSet, separator), false) ?? (0, _milaboratories_helpers.throwError)("Failed to derive unique labels");
35
44
  additionalType++;
36
45
  if (additionalType >= mainTypes.length) {
37
46
  includedCount++;
38
47
  additionalType = includedCount;
39
48
  }
40
49
  }
41
- return applyLinkerSuffixes(build(minimizeTypeSet(new Set([...mainTypes, ...secondaryTypes]), records, stats, forceTraceElements, options, separator), true) ?? (0, _milaboratories_helpers.throwError)("Failed to derive unique labels"), linkerSuffixes);
42
- }
43
- /** Apply pre-formatted linker suffixes to labels that have them. */
44
- function applyLinkerSuffixes(labels, suffixes) {
45
- return labels.map((label, i) => (0, es_toolkit.isNil)(suffixes[i]) ? label : `${label} ${suffixes[i]}`);
46
- }
47
- /** Extract linker labels from every step of the linkers path. */
48
- function extractLinkerLabels(entry) {
49
- if (!("spec" in entry) || typeof entry.spec !== "object") return [];
50
- const path = entry.linkerPath;
51
- if (path === void 0 || path.length === 0) return [];
52
- const labels = [];
53
- for (const step of path) {
54
- const label = ((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();
55
- if (label !== void 0 && label.length > 0) labels.push(label);
56
- }
57
- return labels;
50
+ return build(minimizeTypeSet(new Set([
51
+ ...forcedSet,
52
+ ...mainTypes,
53
+ ...secondaryTypes
54
+ ]), records, stats, forceTraceElements, forcedSet, separator), true) ?? (0, _milaboratories_helpers.throwError)("Failed to derive unique labels");
58
55
  }
59
- function extractSpecAndTrace(entry) {
60
- const isEnriched = "spec" in entry && typeof entry.spec === "object";
56
+ function extractEntryParts(entry) {
57
+ if (!("spec" in entry && typeof entry.spec === "object")) return {
58
+ spec: entry,
59
+ extraTrace: void 0,
60
+ linkerPath: void 0,
61
+ qualifications: void 0
62
+ };
61
63
  return {
62
- spec: isEnriched ? entry.spec : entry,
63
- extraTrace: isEnriched ? entry.extraTrace : void 0,
64
- linkerPath: isEnriched ? entry.linkerPath : void 0
64
+ spec: entry.spec,
65
+ extraTrace: entry.extraTrace,
66
+ linkerPath: entry.linkerPath,
67
+ qualifications: entry.qualifications
65
68
  };
66
69
  }
70
+ function formatQualification(q) {
71
+ const ctx = q.contextDomain ?? {};
72
+ const keys = Object.keys(ctx);
73
+ if (keys.length === 0) return q.axis.name;
74
+ const pairs = keys.map((k) => `${k}=${ctx[k]}`).join(", ");
75
+ return Object.prototype.hasOwnProperty.call(ctx, q.axis.name) ? pairs : `${q.axis.name} ${pairs}`;
76
+ }
77
+ function formatQualifications(qs) {
78
+ return qs.map(formatQualification).join("; ");
79
+ }
80
+ function computeStepLabel(step, stepIndex, formatters) {
81
+ 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();
82
+ if ((0, es_toolkit.isNil)(base) || base.length === 0) return void 0;
83
+ if (step.qualifications === void 0 || step.qualifications.length === 0) return base;
84
+ const qualText = (0, es_toolkit.isFunction)(formatters?.linkerStepQualification) ? formatters.linkerStepQualification(step.qualifications, stepIndex, step.spec) : `[${formatQualifications(step.qualifications)}]`;
85
+ return (0, es_toolkit.isNil)(qualText) ? base : `${base} ${qualText}`;
86
+ }
67
87
  function buildFullTrace(trace) {
68
88
  const result = [];
69
89
  const occurrences = /* @__PURE__ */ new Map();
@@ -80,9 +100,10 @@ function buildFullTrace(trace) {
80
100
  result.reverse();
81
101
  return result;
82
102
  }
83
- function enrichRecord(value, options) {
84
- const { spec, extraTrace } = extractSpecAndTrace(value);
85
- const label = (0, _milaboratories_pl_model_common.readAnnotation)(spec, _milaboratories_pl_model_common.Annotation.Label);
103
+ function enrichRecord(value, index, options) {
104
+ const { spec, extraTrace, linkerPath, qualifications } = extractEntryParts(value);
105
+ const formatters = options.formatters;
106
+ const rawLabel = (0, _milaboratories_pl_model_common.readAnnotation)(spec, _milaboratories_pl_model_common.Annotation.Label);
86
107
  const traceStr = (0, _milaboratories_pl_model_common.readAnnotation)(spec, _milaboratories_pl_model_common.Annotation.Trace);
87
108
  const baseTrace = traceStr ? (0, _milaboratories_pl_model_common.parseJson)(traceStr) ?? [] : [];
88
109
  const prefixExtra = extraTrace?.filter((e) => e.position === "prefix") ?? [];
@@ -92,14 +113,48 @@ function enrichRecord(value, options) {
92
113
  ...baseTrace,
93
114
  ...suffixExtra
94
115
  ];
95
- if (label !== void 0) {
96
- const labelEntry = {
97
- label,
98
- type: LABEL_TYPE,
99
- importance: -2
100
- };
101
- if (options.addLabelAsSuffix) trace.push(labelEntry);
102
- else trace.splice(0, 0, labelEntry);
116
+ if (!(0, es_toolkit.isNil)(rawLabel)) {
117
+ const label = (0, es_toolkit.isFunction)(formatters?.native) ? formatters.native(rawLabel, spec, index) : rawLabel;
118
+ if (!(0, es_toolkit.isNil)(label)) {
119
+ const labelEntry = {
120
+ label,
121
+ type: LABEL_TYPE,
122
+ importance: -2
123
+ };
124
+ if (options.addLabelAsSuffix === true) trace.push(labelEntry);
125
+ else trace.splice(0, 0, labelEntry);
126
+ }
127
+ }
128
+ if (linkerPath !== void 0 && linkerPath.length > 0) {
129
+ const stepLabels = linkerPath.map((step, i) => computeStepLabel(step, i, formatters)).filter((s) => !(0, es_toolkit.isNil)(s));
130
+ if (stepLabels.length > 0) {
131
+ const linkerText = (0, es_toolkit.isFunction)(formatters?.linker) ? formatters.linker(stepLabels, spec, index) : `via ${stepLabels.join(" > ")}`;
132
+ if (!(0, es_toolkit.isNil)(linkerText)) trace.push({
133
+ type: LINKER_TYPE,
134
+ label: linkerText,
135
+ importance: -10
136
+ });
137
+ }
138
+ }
139
+ if (qualifications !== void 0) {
140
+ for (const [anchorId, qs] of Object.entries(qualifications.forQueries)) {
141
+ if (qs.length === 0) continue;
142
+ const anchorText = (0, es_toolkit.isFunction)(formatters?.anchorQualification) ? formatters.anchorQualification(anchorId, qs, spec, index) : `[${anchorId}: ${formatQualifications(qs)}]`;
143
+ if ((0, es_toolkit.isNil)(anchorText)) continue;
144
+ trace.push({
145
+ type: `${ANCHOR_QUAL_TYPE_PREFIX}${anchorId}`,
146
+ label: anchorText,
147
+ importance: -11
148
+ });
149
+ }
150
+ if (qualifications.forHit.length > 0) {
151
+ const hitText = (0, es_toolkit.isFunction)(formatters?.hitQualification) ? formatters.hitQualification(qualifications.forHit, spec, index) : `[${formatQualifications(qualifications.forHit)}]`;
152
+ if (!(0, es_toolkit.isNil)(hitText)) trace.push({
153
+ type: HIT_QUAL_TYPE,
154
+ label: hitText,
155
+ importance: -12
156
+ });
157
+ }
103
158
  }
104
159
  return { fullTrace: buildFullTrace(trace) };
105
160
  }
@@ -132,14 +187,30 @@ function classifyTypes(stats, totalRecords) {
132
187
  function buildLabels(records, includedTypes, forceTraceElements, separator, force) {
133
188
  const result = [];
134
189
  for (const r of records) {
135
- const parts = [];
136
- for (const ft of r.fullTrace) if (includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type)) parts.push(ft.label);
137
- if (parts.length === 0) {
190
+ const traceParts = [];
191
+ const anchorParts = [];
192
+ let linkerLabel;
193
+ let hitLabel;
194
+ for (const ft of r.fullTrace) {
195
+ if (!(includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type))) continue;
196
+ if (ft.type === LINKER_TYPE) linkerLabel = ft.label;
197
+ else if (ft.type === HIT_QUAL_TYPE) hitLabel = ft.label;
198
+ else if (isAnchorQualType(ft.type)) anchorParts.push(ft.label);
199
+ else traceParts.push(ft.label);
200
+ }
201
+ if (traceParts.length === 0 && anchorParts.length === 0 && linkerLabel === void 0 && hitLabel === void 0) {
138
202
  if (!force) return void 0;
139
203
  result.push("Unlabeled");
140
204
  continue;
141
205
  }
142
- result.push(parts.join(separator));
206
+ let label = traceParts.join(separator);
207
+ const append = (part) => {
208
+ label = label.length === 0 ? part : `${label} ${part}`;
209
+ };
210
+ if (linkerLabel !== void 0) append(linkerLabel);
211
+ for (const a of anchorParts) append(a);
212
+ if (hitLabel !== void 0) append(hitLabel);
213
+ result.push(label);
143
214
  }
144
215
  return result;
145
216
  }
@@ -147,12 +218,12 @@ function countUniqueLabels(result) {
147
218
  if (result === void 0) return 0;
148
219
  return new Set(result).size;
149
220
  }
150
- function minimizeTypeSet(typeSet, records, stats, forceTraceElements, options, separator) {
221
+ function minimizeTypeSet(typeSet, records, stats, forceTraceElements, forcedSet, separator) {
151
222
  const initialResult = buildLabels(records, typeSet, forceTraceElements, separator, false);
152
223
  if (initialResult === void 0) return typeSet;
153
224
  const targetCardinality = countUniqueLabels(initialResult);
154
225
  const result = new Set(typeSet);
155
- const removable = [...result].filter((t) => !forceTraceElements?.has(t.split("@")[0]) && !(options.includeNativeLabel && t === LABEL_TYPE_FULL)).sort((a, b) => (stats.importances.get(a) ?? 0) - (stats.importances.get(b) ?? 0));
226
+ const removable = [...result].filter((t) => !forceTraceElements?.has(t.split("@")[0]) && !forcedSet.has(t)).sort((a, b) => (stats.importances.get(a) ?? 0) - (stats.importances.get(b) ?? 0));
156
227
  for (const typeToRemove of removable) {
157
228
  const candidate = new Set(result);
158
229
  candidate.delete(typeToRemove);
@@ -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 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\";\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 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; used to append \"via $linkLabel\" to derived labels. */\n linkerPath?: { spec: PObjectSpec }[];\n };\n\nexport type DeriveLabelsOptions = {\n /** Separator to use between label parts (\" / \" by default) */\n separator?: string;\n /** If true, label will be added as suffix (at the end of the generated label). By default label added as a prefix. */\n addLabelAsSuffix?: boolean;\n /** Force inclusion of native column label */\n includeNativeLabel?: boolean;\n /** Trace elements list that will be forced to be included in the label. */\n forceTraceElements?: string[];\n /** Custom formatter for linker path suffix. Receives the array of linker labels from the full traversal chain,\n * the column spec, and the column index.\n * If returns undefined, no linker suffix is appended. By default labels are joined with \" > \" and prefixed with \"via \". */\n linkerLabelFormatter?: (\n linkerLabels: string[],\n spec: PObjectSpec,\n index: number,\n ) => string | undefined;\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 // Collect per-entry linker suffixes before disambiguation\n const linkerSuffixes = values.map((v, i) => {\n const spec = \"spec\" in v && typeof v.spec === \"object\" ? v.spec : (v as PObjectSpec);\n const linkerLabels = extractLinkerLabels(v);\n if (linkerLabels.length === 0) return undefined;\n return isFunction(options.linkerLabelFormatter)\n ? options.linkerLabelFormatter(linkerLabels, spec, i)\n : `via ${linkerLabels.join(\" > \")}`;\n });\n\n // Phase 1: enrich each value with parsed trace\n const records = values.map((v) => enrichRecord(v, options));\n\n // Phase 2: collect global type statistics\n const stats = collectTypeStats(records);\n\n // Phase 3: classify types into main (present everywhere) and secondary\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 applyLinkerSuffixes(\n build(new Set(LABEL_TYPE_FULL), true) ??\n throwError(\"Failed to derive labels using native column labels\"),\n linkerSuffixes,\n );\n }\n\n // Phase 4: search for minimal type set that produces unique labels\n //\n // includedCount = 2\n // * *\n // T0 T1 T2 T3 T4 T5\n // *\n // additionalType = 3\n //\n // Resulting set: T0, T1, T3\n //\n let includedCount = 0;\n let additionalType = -1;\n while (includedCount < mainTypes.length) {\n const currentSet = new Set<string>();\n if (options.includeNativeLabel) currentSet.add(LABEL_TYPE_FULL);\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 options,\n separator,\n );\n return applyLinkerSuffixes(\n build(minimized, false) ?? throwError(\"Failed to derive unique labels\"),\n linkerSuffixes,\n );\n }\n\n additionalType++;\n if (additionalType >= mainTypes.length) {\n includedCount++;\n additionalType = includedCount;\n }\n }\n\n // Fallback: include all types, then minimize\n const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);\n const minimized = minimizeTypeSet(\n fallbackSet,\n records,\n stats,\n forceTraceElements,\n options,\n separator,\n );\n return applyLinkerSuffixes(\n build(minimized, true) ?? throwError(\"Failed to derive unique labels\"),\n linkerSuffixes,\n );\n}\n\n/** Apply pre-formatted linker suffixes to labels that have them. */\nfunction applyLinkerSuffixes(labels: string[], suffixes: (string | undefined)[]): string[] {\n return labels.map((label, i) => (isNil(suffixes[i]) ? label : `${label} ${suffixes[i]}`));\n}\n\n/** Extract linker labels from every step of the linkers path. */\nfunction extractLinkerLabels(entry: Entry): string[] {\n if (!(\"spec\" in entry) || typeof entry.spec !== \"object\") return [];\n const path = entry.linkerPath;\n if (path === undefined || path.length === 0) return [];\n const labels: string[] = [];\n for (const step of path) {\n const label = (\n readAnnotation(step.spec, Annotation.LinkLabel) ?? readAnnotation(step.spec, Annotation.Label)\n )?.trim();\n if (label !== undefined && label.length > 0) {\n labels.push(label);\n }\n }\n return labels;\n}\n\n// --- Pure helpers ---\ntype FullTraceEntry = ExtendedTraceEntry & { fullType: string; occurrenceIndex: number };\n\ntype EnrichedRecord = {\n fullTrace: FullTraceEntry[];\n};\n\nfunction extractSpecAndTrace(entry: Entry): {\n spec: PObjectSpec;\n extraTrace: ExtendedTraceEntry[] | undefined;\n linkerPath: { spec: PObjectSpec }[] | undefined;\n} {\n const isEnriched = \"spec\" in entry && typeof entry.spec === \"object\";\n return {\n spec: isEnriched ? entry.spec : (entry as PObjectSpec),\n extraTrace: isEnriched ? entry.extraTrace : undefined,\n linkerPath: isEnriched ? entry.linkerPath : undefined,\n };\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, options: DeriveLabelsOptions): EnrichedRecord {\n const { spec, extraTrace } = extractSpecAndTrace(value);\n\n const label = 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 = [...prefixExtra, ...baseTrace, ...suffixExtra];\n\n if (label !== undefined) {\n const labelEntry = { label, type: LABEL_TYPE, importance: -2 };\n if (options.addLabelAsSuffix) trace.push(labelEntry);\n else trace.splice(0, 0, labelEntry);\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 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 parts: string[] = [];\n for (const ft of r.fullTrace) {\n if (includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type)) {\n parts.push(ft.label);\n }\n }\n\n if (parts.length === 0) {\n if (!force) return undefined;\n result.push(\"Unlabeled\");\n continue;\n }\n\n result.push(parts.join(separator));\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 options: DeriveLabelsOptions,\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(\n (t) =>\n !forceTraceElements?.has(t.split(\"@\")[0]) &&\n !(options.includeNativeLabel && t === LABEL_TYPE_FULL),\n )\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":";;;;;AAaA,MAAM,mBAAmB;AACzB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AAqCxB,SAAgB,qBAAqB,QAAiB,UAA+B,EAAE,EAAY;CACjG,MAAM,qBACJ,QAAQ,uBAAuB,KAAA,KAAa,QAAQ,mBAAmB,SAAS,IAC5E,IAAI,IAAI,QAAQ,mBAAmB,GACnC,KAAA;CACN,MAAM,YAAY,QAAQ,aAAa;CAGvC,MAAM,iBAAiB,OAAO,KAAK,GAAG,MAAM;EAC1C,MAAM,OAAO,UAAU,KAAK,OAAO,EAAE,SAAS,WAAW,EAAE,OAAQ;EACnE,MAAM,eAAe,oBAAoB,EAAE;AAC3C,MAAI,aAAa,WAAW,EAAG,QAAO,KAAA;AACtC,UAAA,GAAA,WAAA,YAAkB,QAAQ,qBAAqB,GAC3C,QAAQ,qBAAqB,cAAc,MAAM,EAAE,GACnD,OAAO,aAAa,KAAK,MAAM;GACnC;CAGF,MAAM,UAAU,OAAO,KAAK,MAAM,aAAa,GAAG,QAAQ,CAAC;CAG3D,MAAM,QAAQ,iBAAiB,QAAQ;CAGvC,MAAM,EAAE,WAAW,mBAAmB,cAAc,OAAO,OAAO,OAAO;CAEzE,MAAM,SAAS,SAAsB,UACnC,YAAY,SAAS,SAAS,oBAAoB,WAAW,MAAM;AAErE,KAAI,UAAU,WAAW,GAAG;AAC1B,MAAI,eAAe,WAAW,EAC5B,OAAM,IAAI,MAAM,iEAAiE;AAEnF,SAAO,oBACL,MAAM,IAAI,IAAI,gBAAgB,EAAE,KAAK,KAAA,GAAA,wBAAA,YACxB,qDAAqD,EAClE,eACD;;CAaH,IAAI,gBAAgB;CACpB,IAAI,iBAAiB;AACrB,QAAO,gBAAgB,UAAU,QAAQ;EACvC,MAAM,6BAAa,IAAI,KAAa;AACpC,MAAI,QAAQ,mBAAoB,YAAW,IAAI,gBAAgB;AAC/D,OAAK,IAAI,IAAI,GAAG,IAAI,eAAe,EAAE,EAAG,YAAW,IAAI,UAAU,GAAG;AACpE,MAAI,kBAAkB,EAAG,YAAW,IAAI,UAAU,gBAAgB;EAElE,MAAM,kBAAkB,MAAM,YAAY,MAAM;AAChD,MAAI,oBAAoB,KAAA,KAAa,kBAAkB,gBAAgB,KAAK,OAAO,OASjF,QAAO,oBACL,MATgB,gBAChB,YACA,SACA,OACA,oBACA,SACA,UACD,EAEkB,MAAM,KAAA,GAAA,wBAAA,YAAe,iCAAiC,EACvE,eACD;AAGH;AACA,MAAI,kBAAkB,UAAU,QAAQ;AACtC;AACA,oBAAiB;;;AAcrB,QAAO,oBACL,MATgB,gBADE,IAAI,IAAI,CAAC,GAAG,WAAW,GAAG,eAAe,CAAC,EAG5D,SACA,OACA,oBACA,SACA,UACD,EAEkB,KAAK,KAAA,GAAA,wBAAA,YAAe,iCAAiC,EACtE,eACD;;;AAIH,SAAS,oBAAoB,QAAkB,UAA4C;AACzF,QAAO,OAAO,KAAK,OAAO,OAAA,GAAA,WAAA,OAAa,SAAS,GAAG,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,KAAM;;;AAI3F,SAAS,oBAAoB,OAAwB;AACnD,KAAI,EAAE,UAAU,UAAU,OAAO,MAAM,SAAS,SAAU,QAAO,EAAE;CACnE,MAAM,OAAO,MAAM;AACnB,KAAI,SAAS,KAAA,KAAa,KAAK,WAAW,EAAG,QAAO,EAAE;CACtD,MAAM,SAAmB,EAAE;AAC3B,MAAK,MAAM,QAAQ,MAAM;EACvB,MAAM,UAAA,GAAA,gCAAA,gBACW,KAAK,MAAMA,gCAAAA,WAAW,UAAU,KAAA,GAAA,gCAAA,gBAAmB,KAAK,MAAMA,gCAAAA,WAAW,MAAM,GAC7F,MAAM;AACT,MAAI,UAAU,KAAA,KAAa,MAAM,SAAS,EACxC,QAAO,KAAK,MAAM;;AAGtB,QAAO;;AAUT,SAAS,oBAAoB,OAI3B;CACA,MAAM,aAAa,UAAU,SAAS,OAAO,MAAM,SAAS;AAC5D,QAAO;EACL,MAAM,aAAa,MAAM,OAAQ;EACjC,YAAY,aAAa,MAAM,aAAa,KAAA;EAC5C,YAAY,aAAa,MAAM,aAAa,KAAA;EAC7C;;AAGH,SAAS,eAAe,OAA+C;CACrE,MAAM,SAA2B,EAAE;CACnC,MAAM,8BAAc,IAAI,KAAqB;AAE7C,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,EAAE,GAAG;EAC1C,MAAM,QAAQ,MAAM;EACpB,MAAM,mBAAmB,YAAY,IAAI,MAAM,KAAK,IAAI,KAAK;AAC7D,cAAY,IAAI,MAAM,MAAM,gBAAgB;AAC5C,SAAO,KAAK;GACV,GAAG;GACH,UAAU,GAAG,MAAM,KAAK,GAAG;GAC3B;GACD,CAAC;;AAGJ,QAAO,SAAS;AAChB,QAAO;;AAGT,SAAS,aAAa,OAAc,SAA8C;CAChF,MAAM,EAAE,MAAM,eAAe,oBAAoB,MAAM;CAEvD,MAAM,SAAA,GAAA,gCAAA,gBAAuB,MAAMA,gCAAAA,WAAW,MAAM;CACpD,MAAM,YAAA,GAAA,gCAAA,gBAA0B,MAAMA,gCAAAA,WAAW,MAAM;CACvD,MAAM,YAAY,YAAA,GAAA,gCAAA,WACH,SAAkD,IAAI,EAAE,GACnE,EAAE;CACN,MAAM,cAAc,YAAY,QAAQ,MAAM,EAAE,aAAa,SAAS,IAAI,EAAE;CAC5E,MAAM,cAAc,YAAY,QAAQ,MAAM,EAAE,aAAa,SAAS,IAAI,EAAE;CAC5E,MAAM,QAAQ;EAAC,GAAG;EAAa,GAAG;EAAW,GAAG;EAAY;AAE5D,KAAI,UAAU,KAAA,GAAW;EACvB,MAAM,aAAa;GAAE;GAAO,MAAM;GAAY,YAAY;GAAI;AAC9D,MAAI,QAAQ,iBAAkB,OAAM,KAAK,WAAW;MAC/C,OAAM,OAAO,GAAG,GAAG,WAAW;;AAGrC,QAAO,EAAE,WAAW,eAAe,MAAM,EAAE;;AAQ7C,SAAS,iBAAiB,SAAsC;CAC9D,MAAM,8BAAc,IAAI,KAAqB;CAC7C,MAAM,8BAAc,IAAI,KAAqB;AAE7C,MAAK,MAAM,UAAU,QACnB,MAAK,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;AAEjD,cAAY,IAAI,WAAW,YAAY,IAAI,SAAS,IAAI,KAAK,EAAE;AAC/D,cAAY,IACV,UACA,KAAK,IAAI,YAAY,IAAI,SAAS,IAAI,OAAO,mBAAmB,aAAa,SAAS,CACvF;;AAIL,QAAO;EAAE;EAAa;EAAa;;AAGrC,SAAS,cACP,OACA,cACmD;CACnD,MAAM,SAAS,CAAC,GAAG,MAAM,YAAY,CAAC,MAAM,GAAG,KAAK,GAAG,QAAQ,KAAK,GAAG;CAEvE,MAAM,YAAsB,EAAE;CAC9B,MAAM,iBAA2B,EAAE;AAEnC,MAAK,MAAM,CAAC,aAAa,OACvB,KAAI,SAAS,SAAS,KAAK,IAAI,MAAM,YAAY,IAAI,SAAS,KAAK,aACjE,WAAU,KAAK,SAAS;KACrB,gBAAe,KAAK,SAAS;AAGpC,QAAO;EAAE;EAAW;EAAgB;;AAGtC,SAAS,YACP,SACA,eACA,oBACA,WACA,OACsB;CACtB,MAAM,SAAmB,EAAE;AAE3B,MAAK,MAAM,KAAK,SAAS;EACvB,MAAM,QAAkB,EAAE;AAC1B,OAAK,MAAM,MAAM,EAAE,UACjB,KAAI,cAAc,IAAI,GAAG,SAAS,IAAI,oBAAoB,IAAI,GAAG,KAAK,CACpE,OAAM,KAAK,GAAG,MAAM;AAIxB,MAAI,MAAM,WAAW,GAAG;AACtB,OAAI,CAAC,MAAO,QAAO,KAAA;AACnB,UAAO,KAAK,YAAY;AACxB;;AAGF,SAAO,KAAK,MAAM,KAAK,UAAU,CAAC;;AAGpC,QAAO;;AAGT,SAAS,kBAAkB,QAAsC;AAC/D,KAAI,WAAW,KAAA,EAAW,QAAO;AACjC,QAAO,IAAI,IAAI,OAAO,CAAC;;AAGzB,SAAS,gBACP,SACA,SACA,OACA,oBACA,SACA,WACa;CACb,MAAM,gBAAgB,YAAY,SAAS,SAAS,oBAAoB,WAAW,MAAM;AACzF,KAAI,kBAAkB,KAAA,EAAW,QAAO;CAExC,MAAM,oBAAoB,kBAAkB,cAAc;CAC1D,MAAM,SAAS,IAAI,IAAI,QAAQ;CAE/B,MAAM,YAAY,CAAC,GAAG,OAAO,CAC1B,QACE,MACC,CAAC,oBAAoB,IAAI,EAAE,MAAM,IAAI,CAAC,GAAG,IACzC,EAAE,QAAQ,sBAAsB,MAAM,iBACzC,CACA,MAAM,GAAG,OAAO,MAAM,YAAY,IAAI,EAAE,IAAI,MAAM,MAAM,YAAY,IAAI,EAAE,IAAI,GAAG;AAEpF,MAAK,MAAM,gBAAgB,WAAW;EACpC,MAAM,YAAY,IAAI,IAAI,OAAO;AACjC,YAAU,OAAO,aAAa;EAC9B,MAAM,kBAAkB,YAAY,SAAS,WAAW,oBAAoB,WAAW,MAAM;AAC7F,MAAI,oBAAoB,KAAA,KAAa,kBAAkB,gBAAgB,IAAI,kBACzE,QAAO,OAAO,aAAa;;AAI/B,QAAO"}
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 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 type { MatchQualifications } from \"../columns/column_collection_builder\";\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 return build(minimized, false) ?? throwError(\"Failed to derive unique labels\");\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 return build(minimized, true) ?? throwError(\"Failed to derive unique labels\");\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) {\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.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 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 traceParts: string[] = [];\n const anchorParts: string[] = [];\n let linkerLabel: string | undefined;\n let hitLabel: string | undefined;\n\n for (const ft of r.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) {\n if (!force) return undefined;\n result.push(\"Unlabeled\");\n continue;\n }\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 result.push(label);\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"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { PObjectSpec, Trace, Trace as Trace$1, TraceEntry } from "@milaboratories/pl-model-common";
1
+ import { MatchQualifications } from "../columns/column_collection_builder.js";
2
+ import { AxisQualification, 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. */
@@ -6,24 +7,39 @@ type ExtendedTraceEntry = Trace[number] & {
6
7
  importance?: number;
7
8
  position?: "prefix" | "suffix";
8
9
  };
10
+ type LinkerStep = {
11
+ spec: PObjectSpec;
12
+ qualifications?: AxisQualification[];
13
+ };
9
14
  type Entry = PObjectSpec | {
10
15
  spec: PObjectSpec; /** Extra trace entries merged with the base trace from annotations. */
11
- extraTrace?: ExtendedTraceEntry[]; /** Linker steps traversed to discover this column; used to append "via $linkLabel" to derived labels. */
12
- linkerPath?: {
13
- spec: PObjectSpec;
14
- }[];
16
+ extraTrace?: ExtendedTraceEntry[]; /** Linker steps traversed to discover this column; rendered as "via " only when needed for uniqueness. */
17
+ linkerPath?: LinkerStep[]; /** Axis qualifications applied to the hit column / already-bound anchors; rendered as "[…]" suffixes. */
18
+ qualifications?: MatchQualifications;
19
+ };
20
+ /**
21
+ * Per-zone formatters. Each one receives raw inputs and returns the rendered text for that zone,
22
+ * or `undefined` to suppress the zone entirely (no synthetic injection → no minimization, no render).
23
+ */
24
+ type DeriveLabelsFormatters = {
25
+ /** 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;
26
+ /** Linker zone (whole "via …" piece). Receives step labels with step-quals already inlined.
27
+ * Default: `via ${steps.join(" > ")}`. */
28
+ linker?: (linkerLabels: string[], spec: PObjectSpec, index: number) => string | undefined;
29
+ /** Per-step linker qualifications inlined into the step base label.
30
+ * Default: `[${formatQualifications(qs)}]`. `undefined` → step rendered without quals. */
31
+ linkerStepQualification?: (qualifications: AxisQualification[], stepIndex: number, stepSpec: PObjectSpec) => string | undefined; /** Hit-axis qualifications block. Default: `[${formatQualifications(qs)}]`. */
32
+ hitQualification?: (qualifications: AxisQualification[], spec: PObjectSpec, index: number) => string | undefined; /** Per-anchor qualifications block. Default: `[${anchorId}: ${formatQualifications(qs)}]`. */
33
+ anchorQualification?: (anchorId: PObjectId, qualifications: AxisQualification[], spec: PObjectSpec, index: number) => string | undefined;
15
34
  };
16
35
  type DeriveLabelsOptions = {
17
- /** Separator to use between label parts (" / " by default) */separator?: string; /** If true, label will be added as suffix (at the end of the generated label). By default label added as a prefix. */
18
- addLabelAsSuffix?: boolean; /** Force inclusion of native column label */
19
- includeNativeLabel?: boolean; /** Trace elements list that will be forced to be included in the label. */
20
- forceTraceElements?: string[];
21
- /** Custom formatter for linker path suffix. Receives the array of linker labels from the full traversal chain,
22
- * the column spec, and the column index.
23
- * If returns undefined, no linker suffix is appended. By default labels are joined with " > " and prefixed with "via ". */
24
- linkerLabelFormatter?: (linkerLabels: string[], spec: PObjectSpec, index: number) => string | undefined;
36
+ /** Separator to use between label parts (" / " by default). */separator?: string; /** If true, native label is appended at the end of the trace zone. By default it is prepended (label is the most important name). */
37
+ addLabelAsSuffix?: boolean; /** Force inclusion of native column label even when not needed for uniqueness. */
38
+ includeNativeLabel?: boolean; /** Trace types that must be included in the label. */
39
+ forceTraceElements?: string[]; /** Per-zone custom formatters. Returning `undefined` from any formatter suppresses the corresponding zone. */
40
+ formatters?: DeriveLabelsFormatters;
25
41
  };
26
42
  declare function deriveDistinctLabels(values: Entry[], options?: DeriveLabelsOptions): string[];
27
43
  //#endregion
28
- export { DeriveLabelsOptions, Entry, type Trace$1 as Trace, type TraceEntry, deriveDistinctLabels };
44
+ export { DeriveLabelsFormatters, DeriveLabelsOptions, Entry, LinkerStep, type Trace$1 as Trace, type TraceEntry, deriveDistinctLabels };
29
45
  //# sourceMappingURL=derive_distinct_labels.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"derive_distinct_labels.d.ts","names":[],"sources":["../../src/labels/derive_distinct_labels.ts"],"mappings":";;;;KAkBK,kBAAA,GAAqB,KAAA;EACxB,UAAA;EACA,QAAA;AAAA;AAAA,KAGU,KAAA,GACR,WAAA;EAEE,IAAA,EAAM,WAAA,EANV;EAQI,UAAA,GAAa,kBAAA,IART;EAUJ,UAAA;IAAe,IAAA,EAAM,WAAA;EAAA;AAAA;AAAA,KAGf,mBAAA;EALO,8DAOjB,SAAA,WALoC;EAOpC,gBAAA,YAbE;EAeF,kBAAA,YAbU;EAeV,kBAAA;EAbiB;;;EAiBjB,oBAAA,IACE,YAAA,YACA,IAAA,EAAM,WAAA,EACN,KAAA;AAAA;AAAA,iBAIY,oBAAA,CAAqB,MAAA,EAAQ,KAAA,IAAS,OAAA,GAAS,mBAAA"}
1
+ {"version":3,"file":"derive_distinct_labels.d.ts","names":[],"sources":["../../src/labels/derive_distinct_labels.ts"],"mappings":";;;;;KAiCK,kBAAA,GAAqB,KAAA;EACxB,UAAA;EACA,QAAA;AAAA;AAAA,KAGU,UAAA;EACV,IAAA,EAAM,WAAA;EACN,cAAA,GAAiB,iBAAA;AAAA;AAAA,KAGP,KAAA,GACR,WAAA;EAEE,IAAA,EAAM,WAAA,EARU;EAUhB,UAAA,GAAa,kBAAA,IARiB;EAU9B,UAAA,GAAa,UAAA,IAXX;EAaF,cAAA,GAAiB,mBAAA;AAAA;;;AATvB;;KAgBY,sBAAA;EAfR,qHAiBF,MAAA,IAAU,KAAA,UAAe,IAAA,EAAM,WAAA,EAAa,KAAA;EAb3B;;EAgBjB,MAAA,IAAU,YAAA,YAAwB,IAAA,EAAM,WAAA,EAAa,KAAA;EAZb;;EAexC,uBAAA,IACE,cAAA,EAAgB,iBAAA,IAChB,SAAA,UACA,QAAA,EAAU,WAAA,yBAxBR;EA2BJ,gBAAA,IACE,cAAA,EAAgB,iBAAA,IAChB,IAAA,EAAM,WAAA,EACN,KAAA,iCA5BE;EA+BJ,mBAAA,IACE,QAAA,EAAU,SAAA,EACV,cAAA,EAAgB,iBAAA,IAChB,IAAA,EAAM,WAAA,EACN,KAAA;AAAA;AAAA,KAIQ,mBAAA;EAnCN,+DAqCJ,SAAA,WArCwC;EAuCxC,gBAAA,YAhCU;EAkCV,kBAAA;EAEA,kBAAA,aA/BwC;EAiCxC,UAAA,GAAa,sBAAA;AAAA;AAAA,iBAGC,oBAAA,CAAqB,MAAA,EAAQ,KAAA,IAAS,OAAA,GAAS,mBAAA"}
@@ -5,64 +5,84 @@ import { isFunction, isNil as isNil$1 } from "es-toolkit";
5
5
  const DISTANCE_PENALTY = .001;
6
6
  const LABEL_TYPE = "__LABEL__";
7
7
  const LABEL_TYPE_FULL = "__LABEL__@1";
8
+ const LINKER_TYPE = "__LINKER__";
9
+ const LINKER_TYPE_FULL = "__LINKER__@1";
10
+ const HIT_QUAL_TYPE = "__HIT_QUAL__";
11
+ const ANCHOR_QUAL_TYPE_PREFIX = "__ANCHOR_QUAL__:";
12
+ function isAnchorQualType(t) {
13
+ return t.startsWith(ANCHOR_QUAL_TYPE_PREFIX);
14
+ }
15
+ function isSyntheticType(t) {
16
+ return t === LINKER_TYPE || t === HIT_QUAL_TYPE || isAnchorQualType(t);
17
+ }
8
18
  function deriveDistinctLabels(values, options = {}) {
9
19
  const forceTraceElements = options.forceTraceElements !== void 0 && options.forceTraceElements.length > 0 ? new Set(options.forceTraceElements) : void 0;
10
20
  const separator = options.separator ?? " / ";
11
- const linkerSuffixes = values.map((v, i) => {
12
- const spec = "spec" in v && typeof v.spec === "object" ? v.spec : v;
13
- const linkerLabels = extractLinkerLabels(v);
14
- if (linkerLabels.length === 0) return void 0;
15
- return isFunction(options.linkerLabelFormatter) ? options.linkerLabelFormatter(linkerLabels, spec, i) : `via ${linkerLabels.join(" > ")}`;
16
- });
17
- const records = values.map((v) => enrichRecord(v, options));
21
+ const records = values.map((v, i) => enrichRecord(v, i, options));
18
22
  const stats = collectTypeStats(records);
23
+ const hasAnySynthetic = records.some((r) => r.fullTrace.some((ft) => isSyntheticType(ft.type)));
24
+ const labelForced = (options.includeNativeLabel === true || hasAnySynthetic) && stats.countByType.has(LABEL_TYPE_FULL);
25
+ const linkerForced = stats.countByType.get(LINKER_TYPE_FULL) === values.length;
26
+ const forcedSet = /* @__PURE__ */ new Set();
27
+ if (labelForced) forcedSet.add(LABEL_TYPE_FULL);
28
+ if (linkerForced) forcedSet.add(LINKER_TYPE_FULL);
19
29
  const { mainTypes, secondaryTypes } = classifyTypes(stats, values.length);
20
30
  const build = (typeSet, force) => buildLabels(records, typeSet, forceTraceElements, separator, force);
21
31
  if (mainTypes.length === 0) {
22
32
  if (secondaryTypes.length !== 0) throw new Error("Non-empty secondary types list while main types list is empty.");
23
- return applyLinkerSuffixes(build(new Set(LABEL_TYPE_FULL), true) ?? throwError("Failed to derive labels using native column labels"), linkerSuffixes);
33
+ return build(new Set([LABEL_TYPE_FULL]), true) ?? throwError("Failed to derive labels using native column labels");
24
34
  }
25
35
  let includedCount = 0;
26
36
  let additionalType = -1;
27
37
  while (includedCount < mainTypes.length) {
28
- const currentSet = /* @__PURE__ */ new Set();
29
- if (options.includeNativeLabel) currentSet.add(LABEL_TYPE_FULL);
38
+ const currentSet = new Set(forcedSet);
30
39
  for (let i = 0; i < includedCount; ++i) currentSet.add(mainTypes[i]);
31
40
  if (additionalType >= 0) currentSet.add(mainTypes[additionalType]);
32
41
  const candidateResult = build(currentSet, false);
33
- if (candidateResult !== void 0 && countUniqueLabels(candidateResult) === values.length) return applyLinkerSuffixes(build(minimizeTypeSet(currentSet, records, stats, forceTraceElements, options, separator), false) ?? throwError("Failed to derive unique labels"), linkerSuffixes);
42
+ if (candidateResult !== void 0 && countUniqueLabels(candidateResult) === values.length) return build(minimizeTypeSet(currentSet, records, stats, forceTraceElements, forcedSet, separator), false) ?? throwError("Failed to derive unique labels");
34
43
  additionalType++;
35
44
  if (additionalType >= mainTypes.length) {
36
45
  includedCount++;
37
46
  additionalType = includedCount;
38
47
  }
39
48
  }
40
- return applyLinkerSuffixes(build(minimizeTypeSet(new Set([...mainTypes, ...secondaryTypes]), records, stats, forceTraceElements, options, separator), true) ?? throwError("Failed to derive unique labels"), linkerSuffixes);
41
- }
42
- /** Apply pre-formatted linker suffixes to labels that have them. */
43
- function applyLinkerSuffixes(labels, suffixes) {
44
- return labels.map((label, i) => isNil$1(suffixes[i]) ? label : `${label} ${suffixes[i]}`);
45
- }
46
- /** Extract linker labels from every step of the linkers path. */
47
- function extractLinkerLabels(entry) {
48
- if (!("spec" in entry) || typeof entry.spec !== "object") return [];
49
- const path = entry.linkerPath;
50
- if (path === void 0 || path.length === 0) return [];
51
- const labels = [];
52
- for (const step of path) {
53
- const label = (readAnnotation(step.spec, Annotation.LinkLabel) ?? readAnnotation(step.spec, Annotation.Label))?.trim();
54
- if (label !== void 0 && label.length > 0) labels.push(label);
55
- }
56
- return labels;
49
+ return build(minimizeTypeSet(new Set([
50
+ ...forcedSet,
51
+ ...mainTypes,
52
+ ...secondaryTypes
53
+ ]), records, stats, forceTraceElements, forcedSet, separator), true) ?? throwError("Failed to derive unique labels");
57
54
  }
58
- function extractSpecAndTrace(entry) {
59
- const isEnriched = "spec" in entry && typeof entry.spec === "object";
55
+ function extractEntryParts(entry) {
56
+ if (!("spec" in entry && typeof entry.spec === "object")) return {
57
+ spec: entry,
58
+ extraTrace: void 0,
59
+ linkerPath: void 0,
60
+ qualifications: void 0
61
+ };
60
62
  return {
61
- spec: isEnriched ? entry.spec : entry,
62
- extraTrace: isEnriched ? entry.extraTrace : void 0,
63
- linkerPath: isEnriched ? entry.linkerPath : void 0
63
+ spec: entry.spec,
64
+ extraTrace: entry.extraTrace,
65
+ linkerPath: entry.linkerPath,
66
+ qualifications: entry.qualifications
64
67
  };
65
68
  }
69
+ function formatQualification(q) {
70
+ const ctx = q.contextDomain ?? {};
71
+ const keys = Object.keys(ctx);
72
+ if (keys.length === 0) return q.axis.name;
73
+ const pairs = keys.map((k) => `${k}=${ctx[k]}`).join(", ");
74
+ return Object.prototype.hasOwnProperty.call(ctx, q.axis.name) ? pairs : `${q.axis.name} ${pairs}`;
75
+ }
76
+ function formatQualifications(qs) {
77
+ return qs.map(formatQualification).join("; ");
78
+ }
79
+ function computeStepLabel(step, stepIndex, formatters) {
80
+ const base = (readAnnotation(step.spec, Annotation.LinkLabel) ?? readAnnotation(step.spec, Annotation.Label))?.trim();
81
+ if (isNil$1(base) || base.length === 0) return void 0;
82
+ if (step.qualifications === void 0 || step.qualifications.length === 0) return base;
83
+ const qualText = isFunction(formatters?.linkerStepQualification) ? formatters.linkerStepQualification(step.qualifications, stepIndex, step.spec) : `[${formatQualifications(step.qualifications)}]`;
84
+ return isNil$1(qualText) ? base : `${base} ${qualText}`;
85
+ }
66
86
  function buildFullTrace(trace) {
67
87
  const result = [];
68
88
  const occurrences = /* @__PURE__ */ new Map();
@@ -79,9 +99,10 @@ function buildFullTrace(trace) {
79
99
  result.reverse();
80
100
  return result;
81
101
  }
82
- function enrichRecord(value, options) {
83
- const { spec, extraTrace } = extractSpecAndTrace(value);
84
- const label = readAnnotation(spec, Annotation.Label);
102
+ function enrichRecord(value, index, options) {
103
+ const { spec, extraTrace, linkerPath, qualifications } = extractEntryParts(value);
104
+ const formatters = options.formatters;
105
+ const rawLabel = readAnnotation(spec, Annotation.Label);
85
106
  const traceStr = readAnnotation(spec, Annotation.Trace);
86
107
  const baseTrace = traceStr ? parseJson(traceStr) ?? [] : [];
87
108
  const prefixExtra = extraTrace?.filter((e) => e.position === "prefix") ?? [];
@@ -91,14 +112,48 @@ function enrichRecord(value, options) {
91
112
  ...baseTrace,
92
113
  ...suffixExtra
93
114
  ];
94
- if (label !== void 0) {
95
- const labelEntry = {
96
- label,
97
- type: LABEL_TYPE,
98
- importance: -2
99
- };
100
- if (options.addLabelAsSuffix) trace.push(labelEntry);
101
- else trace.splice(0, 0, labelEntry);
115
+ if (!isNil$1(rawLabel)) {
116
+ const label = isFunction(formatters?.native) ? formatters.native(rawLabel, spec, index) : rawLabel;
117
+ if (!isNil$1(label)) {
118
+ const labelEntry = {
119
+ label,
120
+ type: LABEL_TYPE,
121
+ importance: -2
122
+ };
123
+ if (options.addLabelAsSuffix === true) trace.push(labelEntry);
124
+ else trace.splice(0, 0, labelEntry);
125
+ }
126
+ }
127
+ if (linkerPath !== void 0 && linkerPath.length > 0) {
128
+ const stepLabels = linkerPath.map((step, i) => computeStepLabel(step, i, formatters)).filter((s) => !isNil$1(s));
129
+ if (stepLabels.length > 0) {
130
+ const linkerText = isFunction(formatters?.linker) ? formatters.linker(stepLabels, spec, index) : `via ${stepLabels.join(" > ")}`;
131
+ if (!isNil$1(linkerText)) trace.push({
132
+ type: LINKER_TYPE,
133
+ label: linkerText,
134
+ importance: -10
135
+ });
136
+ }
137
+ }
138
+ if (qualifications !== void 0) {
139
+ for (const [anchorId, qs] of Object.entries(qualifications.forQueries)) {
140
+ if (qs.length === 0) continue;
141
+ const anchorText = isFunction(formatters?.anchorQualification) ? formatters.anchorQualification(anchorId, qs, spec, index) : `[${anchorId}: ${formatQualifications(qs)}]`;
142
+ if (isNil$1(anchorText)) continue;
143
+ trace.push({
144
+ type: `${ANCHOR_QUAL_TYPE_PREFIX}${anchorId}`,
145
+ label: anchorText,
146
+ importance: -11
147
+ });
148
+ }
149
+ if (qualifications.forHit.length > 0) {
150
+ const hitText = isFunction(formatters?.hitQualification) ? formatters.hitQualification(qualifications.forHit, spec, index) : `[${formatQualifications(qualifications.forHit)}]`;
151
+ if (!isNil$1(hitText)) trace.push({
152
+ type: HIT_QUAL_TYPE,
153
+ label: hitText,
154
+ importance: -12
155
+ });
156
+ }
102
157
  }
103
158
  return { fullTrace: buildFullTrace(trace) };
104
159
  }
@@ -131,14 +186,30 @@ function classifyTypes(stats, totalRecords) {
131
186
  function buildLabels(records, includedTypes, forceTraceElements, separator, force) {
132
187
  const result = [];
133
188
  for (const r of records) {
134
- const parts = [];
135
- for (const ft of r.fullTrace) if (includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type)) parts.push(ft.label);
136
- if (parts.length === 0) {
189
+ const traceParts = [];
190
+ const anchorParts = [];
191
+ let linkerLabel;
192
+ let hitLabel;
193
+ for (const ft of r.fullTrace) {
194
+ if (!(includedTypes.has(ft.fullType) || forceTraceElements?.has(ft.type))) continue;
195
+ if (ft.type === LINKER_TYPE) linkerLabel = ft.label;
196
+ else if (ft.type === HIT_QUAL_TYPE) hitLabel = ft.label;
197
+ else if (isAnchorQualType(ft.type)) anchorParts.push(ft.label);
198
+ else traceParts.push(ft.label);
199
+ }
200
+ if (traceParts.length === 0 && anchorParts.length === 0 && linkerLabel === void 0 && hitLabel === void 0) {
137
201
  if (!force) return void 0;
138
202
  result.push("Unlabeled");
139
203
  continue;
140
204
  }
141
- result.push(parts.join(separator));
205
+ let label = traceParts.join(separator);
206
+ const append = (part) => {
207
+ label = label.length === 0 ? part : `${label} ${part}`;
208
+ };
209
+ if (linkerLabel !== void 0) append(linkerLabel);
210
+ for (const a of anchorParts) append(a);
211
+ if (hitLabel !== void 0) append(hitLabel);
212
+ result.push(label);
142
213
  }
143
214
  return result;
144
215
  }
@@ -146,12 +217,12 @@ function countUniqueLabels(result) {
146
217
  if (result === void 0) return 0;
147
218
  return new Set(result).size;
148
219
  }
149
- function minimizeTypeSet(typeSet, records, stats, forceTraceElements, options, separator) {
220
+ function minimizeTypeSet(typeSet, records, stats, forceTraceElements, forcedSet, separator) {
150
221
  const initialResult = buildLabels(records, typeSet, forceTraceElements, separator, false);
151
222
  if (initialResult === void 0) return typeSet;
152
223
  const targetCardinality = countUniqueLabels(initialResult);
153
224
  const result = new Set(typeSet);
154
- const removable = [...result].filter((t) => !forceTraceElements?.has(t.split("@")[0]) && !(options.includeNativeLabel && t === LABEL_TYPE_FULL)).sort((a, b) => (stats.importances.get(a) ?? 0) - (stats.importances.get(b) ?? 0));
225
+ const removable = [...result].filter((t) => !forceTraceElements?.has(t.split("@")[0]) && !forcedSet.has(t)).sort((a, b) => (stats.importances.get(a) ?? 0) - (stats.importances.get(b) ?? 0));
155
226
  for (const typeToRemove of removable) {
156
227
  const candidate = new Set(result);
157
228
  candidate.delete(typeToRemove);