@platforma-sdk/model 1.74.0 → 1.75.0

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 (78) hide show
  1. package/dist/columns/column_collection_builder.cjs +4 -3
  2. package/dist/columns/column_collection_builder.cjs.map +1 -1
  3. package/dist/columns/column_collection_builder.d.ts +5 -5
  4. package/dist/columns/column_collection_builder.d.ts.map +1 -1
  5. package/dist/columns/column_collection_builder.js +4 -3
  6. package/dist/columns/column_collection_builder.js.map +1 -1
  7. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs.map +1 -1
  8. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js.map +1 -1
  9. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs.map +1 -1
  10. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js.map +1 -1
  11. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs.map +1 -1
  12. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.d.ts +1 -1
  13. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js.map +1 -1
  14. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +14 -14
  15. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
  16. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts +3 -2
  17. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts.map +1 -1
  18. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +16 -16
  19. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
  20. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +50 -4
  21. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -1
  22. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +51 -6
  23. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -1
  24. package/dist/components/PlDataTable/createPlDataTable/index.cjs.map +1 -1
  25. package/dist/components/PlDataTable/createPlDataTable/index.js.map +1 -1
  26. package/dist/components/PlDataTable/createPlDataTable/utils.cjs +7 -4
  27. package/dist/components/PlDataTable/createPlDataTable/utils.cjs.map +1 -1
  28. package/dist/components/PlDataTable/createPlDataTable/utils.js +16 -13
  29. package/dist/components/PlDataTable/createPlDataTable/utils.js.map +1 -1
  30. package/dist/components/PlDataTable/createPlDataTableSheet.cjs.map +1 -1
  31. package/dist/components/PlDataTable/createPlDataTableSheet.d.ts +1 -1
  32. package/dist/components/PlDataTable/createPlDataTableSheet.js.map +1 -1
  33. package/dist/components/PlDataTable/index.d.ts +1 -1
  34. package/dist/components/PlDataTable/state-migration.cjs +31 -5
  35. package/dist/components/PlDataTable/state-migration.cjs.map +1 -1
  36. package/dist/components/PlDataTable/state-migration.d.ts +13 -1
  37. package/dist/components/PlDataTable/state-migration.d.ts.map +1 -1
  38. package/dist/components/PlDataTable/state-migration.js +32 -6
  39. package/dist/components/PlDataTable/state-migration.js.map +1 -1
  40. package/dist/components/PlDataTable/typesV5.d.ts +19 -90
  41. package/dist/components/PlDataTable/typesV5.d.ts.map +1 -1
  42. package/dist/components/PlDataTable/typesV6.d.ts +98 -0
  43. package/dist/components/PlDataTable/typesV6.d.ts.map +1 -0
  44. package/dist/components/PlDatasetSelector/enrichment_discovery.cjs +5 -5
  45. package/dist/components/PlDatasetSelector/enrichment_discovery.cjs.map +1 -1
  46. package/dist/components/PlDatasetSelector/enrichment_discovery.js +5 -5
  47. package/dist/components/PlDatasetSelector/enrichment_discovery.js.map +1 -1
  48. package/dist/components/index.d.ts +1 -1
  49. package/dist/index.d.ts +2 -2
  50. package/dist/labels/derive_distinct_labels.cjs +2 -2
  51. package/dist/labels/derive_distinct_labels.cjs.map +1 -1
  52. package/dist/labels/derive_distinct_labels.js +2 -2
  53. package/dist/labels/derive_distinct_labels.js.map +1 -1
  54. package/dist/labels/derive_distinct_tooltips.cjs +2 -1
  55. package/dist/labels/derive_distinct_tooltips.cjs.map +1 -1
  56. package/dist/labels/derive_distinct_tooltips.js +2 -1
  57. package/dist/labels/derive_distinct_tooltips.js.map +1 -1
  58. package/dist/package.cjs +1 -1
  59. package/dist/package.js +1 -1
  60. package/package.json +8 -8
  61. package/src/columns/column_collection_builder.test.ts +3 -7
  62. package/src/columns/column_collection_builder.ts +14 -35
  63. package/src/components/PlDataTable/createPlDataTable/createPTableDefV2.ts +1 -1
  64. package/src/components/PlDataTable/createPlDataTable/createPTableDefV3.ts +1 -1
  65. package/src/components/PlDataTable/createPlDataTable/createPlDataTableV2.ts +1 -1
  66. package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +36 -21
  67. package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +57 -8
  68. package/src/components/PlDataTable/createPlDataTable/index.ts +1 -1
  69. package/src/components/PlDataTable/createPlDataTable/utils.test.ts +1 -1
  70. package/src/components/PlDataTable/createPlDataTable/utils.ts +9 -8
  71. package/src/components/PlDataTable/createPlDataTableSheet.ts +1 -1
  72. package/src/components/PlDataTable/index.ts +1 -2
  73. package/src/components/PlDataTable/state-migration.ts +71 -8
  74. package/src/components/PlDataTable/typesV5.ts +14 -153
  75. package/src/components/PlDataTable/typesV6.ts +152 -0
  76. package/src/components/PlDatasetSelector/enrichment_discovery.ts +5 -5
  77. package/src/labels/derive_distinct_labels.ts +2 -2
  78. package/src/labels/derive_distinct_tooltips.ts +2 -1
@@ -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 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
+ {"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 && 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 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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"}
@@ -135,7 +135,7 @@ function enrichRecord(value, index, options) {
135
135
  });
136
136
  }
137
137
  }
138
- if (qualifications !== void 0) {
138
+ if (qualifications !== void 0 && qualifications.forQueries !== void 0) {
139
139
  for (const [anchorId, qs] of Object.entries(qualifications.forQueries)) {
140
140
  if (qs.length === 0) continue;
141
141
  const anchorText = isFunction(formatters?.anchorQualification) ? formatters.anchorQualification(anchorId, qs, spec, index) : `[${anchorId}: ${formatQualifications(qs)}]`;
@@ -146,7 +146,7 @@ function enrichRecord(value, index, options) {
146
146
  importance: -11
147
147
  });
148
148
  }
149
- if (qualifications.forHit.length > 0) {
149
+ if (qualifications.forHit !== void 0 && qualifications.forHit.length > 0) {
150
150
  const hitText = isFunction(formatters?.hitQualification) ? formatters.hitQualification(qualifications.forHit, spec, index) : `[${formatQualifications(qualifications.forHit)}]`;
151
151
  if (!isNil$1(hitText)) trace.push({
152
152
  type: HIT_QUAL_TYPE,
@@ -1 +1 @@
1
- {"version":3,"file":"derive_distinct_labels.js","names":["isNil"],"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
+ {"version":3,"file":"derive_distinct_labels.js","names":["isNil"],"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 && 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 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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"}
@@ -39,6 +39,7 @@ function formatOriginPath(entry) {
39
39
  }
40
40
  function formatAnchors(q) {
41
41
  if ((0, es_toolkit.isNil)(q)) return void 0;
42
+ if ((0, es_toolkit.isNil)(q.forQueries)) return void 0;
42
43
  const ids = Object.keys(q.forQueries);
43
44
  if (ids.length === 0) return void 0;
44
45
  const lines = [];
@@ -51,7 +52,7 @@ function formatAnchors(q) {
51
52
  return lines.length > 0 ? ["Anchors (bound via this variant)"].concat(lines).join("\n") : void 0;
52
53
  }
53
54
  function formatHit(q) {
54
- if ((0, es_toolkit.isNil)(q) || q.forHit.length === 0) return void 0;
55
+ if ((0, es_toolkit.isNil)(q) || (0, es_toolkit.isNil)(q.forHit) || q.forHit.length === 0) return void 0;
55
56
  const rendered = formatAxisQualifications(q.forHit);
56
57
  if (rendered === void 0) return void 0;
57
58
  return ["Hit column qualifications", `${BULLET_1}${rendered}`].join("\n");
@@ -1 +1 @@
1
- {"version":3,"file":"derive_distinct_tooltips.cjs","names":["Annotation"],"sources":["../../src/labels/derive_distinct_tooltips.ts"],"sourcesContent":["import {\n Annotation,\n PObjectId,\n readAnnotation,\n type AxisQualification,\n type PColumnSpec,\n} from \"@milaboratories/pl-model-common\";\nimport { isNil } from \"es-toolkit\";\nimport type { MatchQualifications, MatchVariant } from \"../columns\";\n\nexport type TooltipEntry = {\n /** Main column spec — used for column-name fallback when no label. */\n spec: PColumnSpec;\n /** Qualifications carried by this variant. */\n qualifications?: MatchQualifications;\n /** Linker steps traversed to reach the hit column. */\n linkerPath?: MatchVariant[\"path\"];\n /** Position of this variant within the same physical column (1-based). */\n variantIndex?: number;\n /** Total variants for the same physical column. */\n variantCount?: number;\n};\n\n/** Format tooltip strings for each entry. Returns `undefined` when nothing useful. */\nexport function deriveDistinctTooltips(entries: TooltipEntry[]): (undefined | string)[] {\n return entries.map(formatTooltip);\n}\n\nfunction formatTooltip(entry: TooltipEntry): undefined | string {\n const sections: string[] = [];\n\n const header = formatHeader(entry);\n if (header !== undefined) sections.push(header);\n\n const origin = formatOriginPath(entry);\n if (origin !== undefined) sections.push(origin);\n\n const anchors = formatAnchors(entry.qualifications);\n if (anchors !== undefined) sections.push(anchors);\n\n const hit = formatHit(entry.qualifications);\n if (hit !== undefined) sections.push(hit);\n\n if (sections.length <= 1) return undefined;\n return sections.join(\"\\n\\n\");\n}\n\nconst BULLET_1 = \" • \";\n\nfunction formatHeader(entry: TooltipEntry): undefined | string {\n const lines: string[] = [];\n if (entry.variantCount !== undefined && entry.variantCount > 1) {\n lines.push(`Variant: ${entry.variantIndex ?? \"?\"} of ${entry.variantCount}`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction formatOriginPath(entry: TooltipEntry): undefined | string {\n const path = entry.linkerPath ?? [];\n if (path.length === 0) return undefined;\n\n const lines = [\"Origin path\"];\n path.forEach((step, i) => {\n const label =\n readAnnotation(step.linker.spec, Annotation.LinkLabel) ??\n readAnnotation(step.linker.spec, Annotation.Label) ??\n step.linker.spec.name;\n lines.push(`${BULLET_1}linker ${i + 1}: ${label}`);\n });\n const hitName = readAnnotation(entry.spec, Annotation.Label) ?? entry.spec.name;\n lines.push(`${BULLET_1}hit column: ${hitName}`);\n return lines.join(\"\\n\");\n}\n\nfunction formatAnchors(q: undefined | MatchQualifications): undefined | string {\n if (isNil(q)) return undefined;\n const ids = Object.keys(q.forQueries);\n if (ids.length === 0) return undefined;\n\n const lines = [];\n for (const id of ids) {\n const item = q.forQueries[id as PObjectId];\n if (item.length === 0) continue;\n const rendered = formatAxisQualifications(item);\n lines.push(`${BULLET_1}${id}${rendered !== undefined ? ` ${rendered}` : \"\"}`);\n }\n return lines.length > 0\n ? [\"Anchors (bound via this variant)\"].concat(lines).join(\"\\n\")\n : undefined;\n}\n\nfunction formatHit(q: undefined | MatchQualifications): undefined | string {\n if (isNil(q) || q.forHit.length === 0) return undefined;\n const rendered = formatAxisQualifications(q.forHit);\n if (rendered === undefined) return undefined;\n return [\"Hit column qualifications\", `${BULLET_1}${rendered}`].join(\"\\n\");\n}\n\nfunction formatAxisQualifications(qs: AxisQualification[]): undefined | string {\n if (qs.length === 0) return undefined;\n return qs.map(formatQualification).join(\"; \");\n}\n\nfunction formatQualification(q: AxisQualification): string {\n const axisName = typeof q.axis === \"string\" ? q.axis : (q.axis.name ?? JSON.stringify(q.axis));\n const entries = Object.entries(q.contextDomain);\n if (entries.length === 0) return axisName;\n const kv = entries.map(([k, v]) => `${k}=${v}`).join(\", \");\n return `${axisName} context: ${kv}`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"derive_distinct_tooltips.cjs","names":["Annotation"],"sources":["../../src/labels/derive_distinct_tooltips.ts"],"sourcesContent":["import {\n Annotation,\n PObjectId,\n readAnnotation,\n type AxisQualification,\n type PColumnSpec,\n} from \"@milaboratories/pl-model-common\";\nimport { isNil } from \"es-toolkit\";\nimport type { MatchQualifications, MatchVariant } from \"../columns\";\n\nexport type TooltipEntry = {\n /** Main column spec — used for column-name fallback when no label. */\n spec: PColumnSpec;\n /** Qualifications carried by this variant. */\n qualifications?: MatchQualifications;\n /** Linker steps traversed to reach the hit column. */\n linkerPath?: MatchVariant[\"path\"];\n /** Position of this variant within the same physical column (1-based). */\n variantIndex?: number;\n /** Total variants for the same physical column. */\n variantCount?: number;\n};\n\n/** Format tooltip strings for each entry. Returns `undefined` when nothing useful. */\nexport function deriveDistinctTooltips(entries: TooltipEntry[]): (undefined | string)[] {\n return entries.map(formatTooltip);\n}\n\nfunction formatTooltip(entry: TooltipEntry): undefined | string {\n const sections: string[] = [];\n\n const header = formatHeader(entry);\n if (header !== undefined) sections.push(header);\n\n const origin = formatOriginPath(entry);\n if (origin !== undefined) sections.push(origin);\n\n const anchors = formatAnchors(entry.qualifications);\n if (anchors !== undefined) sections.push(anchors);\n\n const hit = formatHit(entry.qualifications);\n if (hit !== undefined) sections.push(hit);\n\n if (sections.length <= 1) return undefined;\n return sections.join(\"\\n\\n\");\n}\n\nconst BULLET_1 = \" • \";\n\nfunction formatHeader(entry: TooltipEntry): undefined | string {\n const lines: string[] = [];\n if (entry.variantCount !== undefined && entry.variantCount > 1) {\n lines.push(`Variant: ${entry.variantIndex ?? \"?\"} of ${entry.variantCount}`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction formatOriginPath(entry: TooltipEntry): undefined | string {\n const path = entry.linkerPath ?? [];\n if (path.length === 0) return undefined;\n\n const lines = [\"Origin path\"];\n path.forEach((step, i) => {\n const label =\n readAnnotation(step.linker.spec, Annotation.LinkLabel) ??\n readAnnotation(step.linker.spec, Annotation.Label) ??\n step.linker.spec.name;\n lines.push(`${BULLET_1}linker ${i + 1}: ${label}`);\n });\n const hitName = readAnnotation(entry.spec, Annotation.Label) ?? entry.spec.name;\n lines.push(`${BULLET_1}hit column: ${hitName}`);\n return lines.join(\"\\n\");\n}\n\nfunction formatAnchors(q: undefined | MatchQualifications): undefined | string {\n if (isNil(q)) return undefined;\n if (isNil(q.forQueries)) return undefined;\n const ids = Object.keys(q.forQueries);\n if (ids.length === 0) return undefined;\n\n const lines = [];\n for (const id of ids) {\n const item = q.forQueries[id as PObjectId];\n if (item.length === 0) continue;\n const rendered = formatAxisQualifications(item);\n lines.push(`${BULLET_1}${id}${rendered !== undefined ? ` ${rendered}` : \"\"}`);\n }\n return lines.length > 0\n ? [\"Anchors (bound via this variant)\"].concat(lines).join(\"\\n\")\n : undefined;\n}\n\nfunction formatHit(q: undefined | MatchQualifications): undefined | string {\n if (isNil(q) || isNil(q.forHit) || q.forHit.length === 0) return undefined;\n const rendered = formatAxisQualifications(q.forHit);\n if (rendered === undefined) return undefined;\n return [\"Hit column qualifications\", `${BULLET_1}${rendered}`].join(\"\\n\");\n}\n\nfunction formatAxisQualifications(qs: AxisQualification[]): undefined | string {\n if (qs.length === 0) return undefined;\n return qs.map(formatQualification).join(\"; \");\n}\n\nfunction formatQualification(q: AxisQualification): string {\n const axisName = typeof q.axis === \"string\" ? q.axis : (q.axis.name ?? JSON.stringify(q.axis));\n const entries = Object.entries(q.contextDomain);\n if (entries.length === 0) return axisName;\n const kv = entries.map(([k, v]) => `${k}=${v}`).join(\", \");\n return `${axisName} context: ${kv}`;\n}\n"],"mappings":"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"}
@@ -38,6 +38,7 @@ function formatOriginPath(entry) {
38
38
  }
39
39
  function formatAnchors(q) {
40
40
  if (isNil(q)) return void 0;
41
+ if (isNil(q.forQueries)) return void 0;
41
42
  const ids = Object.keys(q.forQueries);
42
43
  if (ids.length === 0) return void 0;
43
44
  const lines = [];
@@ -50,7 +51,7 @@ function formatAnchors(q) {
50
51
  return lines.length > 0 ? ["Anchors (bound via this variant)"].concat(lines).join("\n") : void 0;
51
52
  }
52
53
  function formatHit(q) {
53
- if (isNil(q) || q.forHit.length === 0) return void 0;
54
+ if (isNil(q) || isNil(q.forHit) || q.forHit.length === 0) return void 0;
54
55
  const rendered = formatAxisQualifications(q.forHit);
55
56
  if (rendered === void 0) return void 0;
56
57
  return ["Hit column qualifications", `${BULLET_1}${rendered}`].join("\n");
@@ -1 +1 @@
1
- {"version":3,"file":"derive_distinct_tooltips.js","names":[],"sources":["../../src/labels/derive_distinct_tooltips.ts"],"sourcesContent":["import {\n Annotation,\n PObjectId,\n readAnnotation,\n type AxisQualification,\n type PColumnSpec,\n} from \"@milaboratories/pl-model-common\";\nimport { isNil } from \"es-toolkit\";\nimport type { MatchQualifications, MatchVariant } from \"../columns\";\n\nexport type TooltipEntry = {\n /** Main column spec — used for column-name fallback when no label. */\n spec: PColumnSpec;\n /** Qualifications carried by this variant. */\n qualifications?: MatchQualifications;\n /** Linker steps traversed to reach the hit column. */\n linkerPath?: MatchVariant[\"path\"];\n /** Position of this variant within the same physical column (1-based). */\n variantIndex?: number;\n /** Total variants for the same physical column. */\n variantCount?: number;\n};\n\n/** Format tooltip strings for each entry. Returns `undefined` when nothing useful. */\nexport function deriveDistinctTooltips(entries: TooltipEntry[]): (undefined | string)[] {\n return entries.map(formatTooltip);\n}\n\nfunction formatTooltip(entry: TooltipEntry): undefined | string {\n const sections: string[] = [];\n\n const header = formatHeader(entry);\n if (header !== undefined) sections.push(header);\n\n const origin = formatOriginPath(entry);\n if (origin !== undefined) sections.push(origin);\n\n const anchors = formatAnchors(entry.qualifications);\n if (anchors !== undefined) sections.push(anchors);\n\n const hit = formatHit(entry.qualifications);\n if (hit !== undefined) sections.push(hit);\n\n if (sections.length <= 1) return undefined;\n return sections.join(\"\\n\\n\");\n}\n\nconst BULLET_1 = \" • \";\n\nfunction formatHeader(entry: TooltipEntry): undefined | string {\n const lines: string[] = [];\n if (entry.variantCount !== undefined && entry.variantCount > 1) {\n lines.push(`Variant: ${entry.variantIndex ?? \"?\"} of ${entry.variantCount}`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction formatOriginPath(entry: TooltipEntry): undefined | string {\n const path = entry.linkerPath ?? [];\n if (path.length === 0) return undefined;\n\n const lines = [\"Origin path\"];\n path.forEach((step, i) => {\n const label =\n readAnnotation(step.linker.spec, Annotation.LinkLabel) ??\n readAnnotation(step.linker.spec, Annotation.Label) ??\n step.linker.spec.name;\n lines.push(`${BULLET_1}linker ${i + 1}: ${label}`);\n });\n const hitName = readAnnotation(entry.spec, Annotation.Label) ?? entry.spec.name;\n lines.push(`${BULLET_1}hit column: ${hitName}`);\n return lines.join(\"\\n\");\n}\n\nfunction formatAnchors(q: undefined | MatchQualifications): undefined | string {\n if (isNil(q)) return undefined;\n const ids = Object.keys(q.forQueries);\n if (ids.length === 0) return undefined;\n\n const lines = [];\n for (const id of ids) {\n const item = q.forQueries[id as PObjectId];\n if (item.length === 0) continue;\n const rendered = formatAxisQualifications(item);\n lines.push(`${BULLET_1}${id}${rendered !== undefined ? ` ${rendered}` : \"\"}`);\n }\n return lines.length > 0\n ? [\"Anchors (bound via this variant)\"].concat(lines).join(\"\\n\")\n : undefined;\n}\n\nfunction formatHit(q: undefined | MatchQualifications): undefined | string {\n if (isNil(q) || q.forHit.length === 0) return undefined;\n const rendered = formatAxisQualifications(q.forHit);\n if (rendered === undefined) return undefined;\n return [\"Hit column qualifications\", `${BULLET_1}${rendered}`].join(\"\\n\");\n}\n\nfunction formatAxisQualifications(qs: AxisQualification[]): undefined | string {\n if (qs.length === 0) return undefined;\n return qs.map(formatQualification).join(\"; \");\n}\n\nfunction formatQualification(q: AxisQualification): string {\n const axisName = typeof q.axis === \"string\" ? q.axis : (q.axis.name ?? JSON.stringify(q.axis));\n const entries = Object.entries(q.contextDomain);\n if (entries.length === 0) return axisName;\n const kv = entries.map(([k, v]) => `${k}=${v}`).join(\", \");\n return `${axisName} context: ${kv}`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"derive_distinct_tooltips.js","names":[],"sources":["../../src/labels/derive_distinct_tooltips.ts"],"sourcesContent":["import {\n Annotation,\n PObjectId,\n readAnnotation,\n type AxisQualification,\n type PColumnSpec,\n} from \"@milaboratories/pl-model-common\";\nimport { isNil } from \"es-toolkit\";\nimport type { MatchQualifications, MatchVariant } from \"../columns\";\n\nexport type TooltipEntry = {\n /** Main column spec — used for column-name fallback when no label. */\n spec: PColumnSpec;\n /** Qualifications carried by this variant. */\n qualifications?: MatchQualifications;\n /** Linker steps traversed to reach the hit column. */\n linkerPath?: MatchVariant[\"path\"];\n /** Position of this variant within the same physical column (1-based). */\n variantIndex?: number;\n /** Total variants for the same physical column. */\n variantCount?: number;\n};\n\n/** Format tooltip strings for each entry. Returns `undefined` when nothing useful. */\nexport function deriveDistinctTooltips(entries: TooltipEntry[]): (undefined | string)[] {\n return entries.map(formatTooltip);\n}\n\nfunction formatTooltip(entry: TooltipEntry): undefined | string {\n const sections: string[] = [];\n\n const header = formatHeader(entry);\n if (header !== undefined) sections.push(header);\n\n const origin = formatOriginPath(entry);\n if (origin !== undefined) sections.push(origin);\n\n const anchors = formatAnchors(entry.qualifications);\n if (anchors !== undefined) sections.push(anchors);\n\n const hit = formatHit(entry.qualifications);\n if (hit !== undefined) sections.push(hit);\n\n if (sections.length <= 1) return undefined;\n return sections.join(\"\\n\\n\");\n}\n\nconst BULLET_1 = \" • \";\n\nfunction formatHeader(entry: TooltipEntry): undefined | string {\n const lines: string[] = [];\n if (entry.variantCount !== undefined && entry.variantCount > 1) {\n lines.push(`Variant: ${entry.variantIndex ?? \"?\"} of ${entry.variantCount}`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction formatOriginPath(entry: TooltipEntry): undefined | string {\n const path = entry.linkerPath ?? [];\n if (path.length === 0) return undefined;\n\n const lines = [\"Origin path\"];\n path.forEach((step, i) => {\n const label =\n readAnnotation(step.linker.spec, Annotation.LinkLabel) ??\n readAnnotation(step.linker.spec, Annotation.Label) ??\n step.linker.spec.name;\n lines.push(`${BULLET_1}linker ${i + 1}: ${label}`);\n });\n const hitName = readAnnotation(entry.spec, Annotation.Label) ?? entry.spec.name;\n lines.push(`${BULLET_1}hit column: ${hitName}`);\n return lines.join(\"\\n\");\n}\n\nfunction formatAnchors(q: undefined | MatchQualifications): undefined | string {\n if (isNil(q)) return undefined;\n if (isNil(q.forQueries)) return undefined;\n const ids = Object.keys(q.forQueries);\n if (ids.length === 0) return undefined;\n\n const lines = [];\n for (const id of ids) {\n const item = q.forQueries[id as PObjectId];\n if (item.length === 0) continue;\n const rendered = formatAxisQualifications(item);\n lines.push(`${BULLET_1}${id}${rendered !== undefined ? ` ${rendered}` : \"\"}`);\n }\n return lines.length > 0\n ? [\"Anchors (bound via this variant)\"].concat(lines).join(\"\\n\")\n : undefined;\n}\n\nfunction formatHit(q: undefined | MatchQualifications): undefined | string {\n if (isNil(q) || isNil(q.forHit) || q.forHit.length === 0) return undefined;\n const rendered = formatAxisQualifications(q.forHit);\n if (rendered === undefined) return undefined;\n return [\"Hit column qualifications\", `${BULLET_1}${rendered}`].join(\"\\n\");\n}\n\nfunction formatAxisQualifications(qs: AxisQualification[]): undefined | string {\n if (qs.length === 0) return undefined;\n return qs.map(formatQualification).join(\"; \");\n}\n\nfunction formatQualification(q: AxisQualification): string {\n const axisName = typeof q.axis === \"string\" ? q.axis : (q.axis.name ?? JSON.stringify(q.axis));\n const entries = Object.entries(q.contextDomain);\n if (entries.length === 0) return axisName;\n const kv = entries.map(([k, v]) => `${k}=${v}`).join(\", \");\n return `${axisName} context: ${kv}`;\n}\n"],"mappings":"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"}
package/dist/package.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "1.74.0";
2
+ var version = "1.75.0";
3
3
  //#endregion
4
4
  Object.defineProperty(exports, "version", {
5
5
  enumerable: true,
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "1.74.0";
2
+ var version = "1.75.0";
3
3
  //#endregion
4
4
  export { version };
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/model",
3
- "version": "1.74.0",
3
+ "version": "1.75.0",
4
4
  "description": "Platforma.bio SDK / Block Model",
5
5
  "files": [
6
6
  "./dist/**/*",
@@ -30,11 +30,11 @@
30
30
  "fast-json-patch": "^3.1.1",
31
31
  "utility-types": "^3.11.0",
32
32
  "zod": "~3.25.76",
33
- "@milaboratories/pl-error-like": "1.12.10",
34
33
  "@milaboratories/helpers": "1.14.1",
35
- "@milaboratories/pl-model-middle-layer": "1.19.0",
36
- "@milaboratories/pl-model-common": "1.40.0",
37
- "@milaboratories/ptabler-expression-js": "1.2.21"
34
+ "@milaboratories/pl-error-like": "1.12.10",
35
+ "@milaboratories/pl-model-middle-layer": "1.19.1",
36
+ "@milaboratories/ptabler-expression-js": "1.2.22",
37
+ "@milaboratories/pl-model-common": "1.41.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@vitest/coverage-istanbul": "^4.1.3",
@@ -42,10 +42,10 @@
42
42
  "typescript": "~5.9.3",
43
43
  "vitest": "^4.1.3",
44
44
  "@milaboratories/build-configs": "2.0.0",
45
- "@milaboratories/ts-configs": "1.2.3",
46
- "@milaboratories/pf-spec-driver": "1.3.11",
45
+ "@milaboratories/pf-spec-driver": "1.3.12",
46
+ "@milaboratories/pf-driver": "1.4.7",
47
47
  "@milaboratories/ts-builder": "1.4.0",
48
- "@milaboratories/pf-driver": "1.4.6"
48
+ "@milaboratories/ts-configs": "1.2.3"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "ts-builder build --target node",
@@ -402,12 +402,8 @@ describe("AnchoredColumnCollection", () => {
402
402
 
403
403
  expect(col1Match.variants.length).toBeGreaterThan(0);
404
404
  for (const v of col1Match.variants) {
405
- expect(v.qualifications.forQueries).toBeDefined();
406
- expect(v.qualifications.forHit).toBeDefined();
407
- // forQueries keys are a subset of anchor ids
408
- for (const key of Object.keys(v.qualifications.forQueries)) {
409
- expect([anchorSnap.id]).toContain(key);
410
- }
405
+ expect(v.qualifications.forQueries).toBeUndefined();
406
+ expect(v.qualifications.forHit).toBeUndefined();
411
407
  }
412
408
  });
413
409
 
@@ -432,7 +428,7 @@ describe("AnchoredColumnCollection", () => {
432
428
 
433
429
  for (const v of c1.variants) {
434
430
  const forQueries = v.qualifications.forQueries;
435
- if (anchorASnap.id in forQueries && anchorBSnap.id in forQueries) {
431
+ if (forQueries && anchorASnap.id in forQueries && anchorBSnap.id in forQueries) {
436
432
  // Shared axes group → equal qualifications for both anchor keys.
437
433
  expect(forQueries[anchorASnap.id]).toStrictEqual(forQueries[anchorBSnap.id]);
438
434
  }
@@ -20,8 +20,6 @@ import type { PFrameSpecDriver, PoolEntry, SpecFrameHandle } from "@milaboratori
20
20
  import { throwError } from "@milaboratories/helpers";
21
21
  import { getService } from "../services";
22
22
 
23
- // --- FindColumnsOptions ---
24
-
25
23
  /** Options for plain collection findColumns. */
26
24
  export interface FindColumnsOptions {
27
25
  /** Include columns matching these selectors. If omitted, includes all columns. */
@@ -30,8 +28,6 @@ export interface FindColumnsOptions {
30
28
  exclude?: ColumnSelector;
31
29
  }
32
30
 
33
- // --- ColumnCollection ---
34
-
35
31
  /** Plain collection — no axis context, selector-based filtering only. */
36
32
  export interface ColumnCollection extends Disposable {
37
33
  /** Release the underlying spec frame WASM resource. */
@@ -43,8 +39,6 @@ export interface ColumnCollection extends Disposable {
43
39
  findColumns(options?: FindColumnsOptions): ColumnSnapshot<PObjectId>[];
44
40
  }
45
41
 
46
- // --- AnchoredColumnCollection ---
47
-
48
42
  /** Axis-aware column collection with anchored identity derivation. */
49
43
  export interface AnchoredColumnCollection extends Disposable {
50
44
  /** Release the underlying spec frame WASM resource. */
@@ -82,12 +76,10 @@ export interface ColumnMatch {
82
76
  export interface ColumnVariant<Id extends PObjectId = PObjectId> {
83
77
  /** Column snapshot with anchored SUniversalPColumnId. */
84
78
  readonly column: ColumnSnapshot<Id>;
85
- /** Full qualifications needed for integration. */
86
- readonly qualifications: MatchQualifications;
87
79
  /** Linker steps traversed to reach this hit; empty for direct matches. */
88
- readonly path: {
89
- linker: ColumnSnapshot<PObjectId>;
90
- }[];
80
+ readonly path?: { linker: ColumnSnapshot<PObjectId> }[];
81
+ /** Full qualifications needed for integration. */
82
+ readonly qualifications?: MatchQualifications;
91
83
  }
92
84
 
93
85
  /** A single mapping variant describing how a hit column can be integrated. */
@@ -95,21 +87,17 @@ export interface MatchVariant {
95
87
  /** Full qualifications needed for integration. */
96
88
  readonly qualifications: MatchQualifications;
97
89
  /** Linker steps traversed to reach this hit; empty for direct matches. */
98
- readonly path: {
99
- linker: ColumnSnapshot<PObjectId>;
100
- }[];
90
+ readonly path: { linker: ColumnSnapshot<PObjectId> }[];
101
91
  }
102
92
 
103
93
  /** Qualifications needed for both already-integrated anchor columns and the hit column. */
104
94
  export interface MatchQualifications {
105
95
  /** Qualifications for already-integrated anchor columns */
106
- readonly forQueries: Record<PObjectId, AxisQualification[]>;
96
+ readonly forQueries?: Record<PObjectId, AxisQualification[]>;
107
97
  /** Qualifications for the hit column. */
108
- readonly forHit: AxisQualification[];
98
+ readonly forHit?: AxisQualification[];
109
99
  }
110
100
 
111
- // --- Build options ---
112
-
113
101
  export interface BuildOptions {
114
102
  allowPartialColumnList?: true;
115
103
  }
@@ -120,8 +108,6 @@ export interface AnchoredBuildOptions extends BuildOptions {
120
108
  anchors: Record<string, AnchorEntry>;
121
109
  }
122
110
 
123
- // --- ColumnCollectionBuilder ---
124
-
125
111
  /**
126
112
  * Mutable builder that accumulates column sources, then produces
127
113
  * a ColumnCollection (plain) or AnchoredColumnCollection (with anchors).
@@ -197,8 +183,6 @@ export class ColumnCollectionBuilder {
197
183
  }
198
184
  }
199
185
 
200
- // --- ColumnCollectionImpl ---
201
-
202
186
  interface ColumnCollectionImplOptions {
203
187
  readonly columns: ColumnSnapshot<PObjectId>[];
204
188
  }
@@ -246,8 +230,6 @@ class ColumnCollectionImpl implements ColumnCollection, Disposable {
246
230
  }
247
231
  }
248
232
 
249
- // --- AnchoredColumnCollectionImpl ---
250
-
251
233
  interface AnchoredColumnCollectionImplOptions extends ColumnCollectionImplOptions {
252
234
  readonly anchors: Record<string, AnchorEntry>;
253
235
  }
@@ -368,15 +350,11 @@ function collectColumns(providers: ColumnSnapshotProvider[]): ColumnSnapshot<POb
368
350
  return result;
369
351
  }
370
352
 
371
- // --- Shared snapshot helpers ---
372
-
373
353
  /** Normalize ColumnSelector (relaxed, single or array) to MultiColumnSelector[]. */
374
354
  function toMultiColumnSelectors(input: ColumnSelector): MultiColumnSelector[] {
375
355
  return convertColumnSelectorToMultiColumnSelector(input);
376
356
  }
377
357
 
378
- // --- Anchor resolution ---
379
-
380
358
  /**
381
359
  * Resolve each anchor entry to a ColumnSnapshot from the collected columns.
382
360
  * - PObjectId (string): looked up by id in the collected columns
@@ -423,7 +401,7 @@ function resolveAnchorMap(
423
401
  excludeColumns: undefined,
424
402
  axes: [],
425
403
  maxHops: 0,
426
- constraints: matchingModeToConstraints("exact"),
404
+ constraints: matchingModeToConstraints("related"),
427
405
  });
428
406
 
429
407
  if (matched.hits.length === 0) {
@@ -461,15 +439,16 @@ function remapFromIdxToId(
461
439
  },
462
440
  anchors: ColumnSnapshot<PObjectId>[],
463
441
  ): MatchQualifications {
464
- const forQueries = qualifications.forQueries.reduce<Record<PObjectId, AxisQualification[]>>(
465
- (acc, qs, i) => (anchors[i] ? ((acc[anchors[i].id] = qs), acc) : acc),
466
- {},
442
+ const forQueries = qualifications.forQueries.reduce(
443
+ (acc, qs, i) => (anchors[i] && qs.length > 0 ? acc.set(anchors[i].id, qs) : acc),
444
+ new Map<PObjectId, AxisQualification[]>(),
467
445
  );
468
- return { forQueries, forHit: qualifications.forHit };
446
+ return {
447
+ forQueries: forQueries.size > 0 ? Object.fromEntries(forQueries) : undefined,
448
+ forHit: qualifications.forHit.length > 0 ? qualifications.forHit : undefined,
449
+ };
469
450
  }
470
451
 
471
- // --- MatchingMode → DiscoverColumnsConstraints ---
472
-
473
452
  function matchingModeToConstraints(mode: MatchingMode): DiscoverColumnsConstraints {
474
453
  switch (mode) {
475
454
  case "enrichment":
@@ -9,7 +9,7 @@ import type {
9
9
  import { getColumnIdAndSpec } from "@milaboratories/pl-model-common";
10
10
  import type { PColumnDataUniversal, TreeNodeAccessor } from "../../../render";
11
11
  import { isFunction } from "es-toolkit";
12
- import type { PlDataTableFilters } from "../typesV5";
12
+ import type { PlDataTableFilters } from "../typesV6";
13
13
  import { createPTableDefV3 } from "./createPTableDefV3";
14
14
 
15
15
  export function createPTableDefV2(params: {
@@ -13,7 +13,7 @@ import type {
13
13
  import { isBooleanExpression } from "@milaboratories/pl-model-common";
14
14
  import type { PColumnDataUniversal } from "../../../render";
15
15
  import { isNil } from "es-toolkit";
16
- import type { PlDataTableFilters } from "../typesV5";
16
+ import type { PlDataTableFilters } from "../typesV6";
17
17
  import { distillFilterSpec, filterSpecToSpecQueryExpr } from "../../../filters";
18
18
  import type { Nil } from "@milaboratories/helpers";
19
19
 
@@ -25,7 +25,7 @@ import type {
25
25
  } from "../../../render";
26
26
  import { allPColumnsReady, deriveLabels, PColumnCollection } from "../../../render";
27
27
  import { identity } from "es-toolkit";
28
- import type { CreatePlDataTableOps, PlDataTableModel } from "../typesV5";
28
+ import type { CreatePlDataTableOps, PlDataTableModel } from "../typesV6";
29
29
  import { upgradePlDataTableStateV2 } from "../state-migration";
30
30
  import type { PlDataTableStateV2 } from "../state-migration";
31
31
  import { getMatchingLabelColumns } from "../labels";