@platforma-sdk/model 1.51.5 → 1.51.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json.cjs +1 -1
- package/dist/package.json.js +1 -1
- package/dist/render/util/label.cjs +27 -4
- package/dist/render/util/label.cjs.map +1 -1
- package/dist/render/util/label.d.ts.map +1 -1
- package/dist/render/util/label.js +27 -4
- package/dist/render/util/label.js.map +1 -1
- package/package.json +5 -5
- package/src/render/util/label.test.ts +56 -0
- package/src/render/util/label.ts +32 -3
package/dist/package.json.cjs
CHANGED
package/dist/package.json.js
CHANGED
|
@@ -110,6 +110,25 @@ function deriveLabels(values, specExtractor, ops = {}) {
|
|
|
110
110
|
}
|
|
111
111
|
return result;
|
|
112
112
|
};
|
|
113
|
+
// Checks if a result has all unique labels
|
|
114
|
+
const hasUniqueLabels = (result) => result !== undefined && new Set(result.map((c) => c.label)).size === values.length;
|
|
115
|
+
// Post-processing: try removing types one by one (lowest importance first) to minimize the label set
|
|
116
|
+
const minimizeTypeSet = (typeSet) => {
|
|
117
|
+
// Get types sorted by importance ascending (lowest first), excluding forced elements
|
|
118
|
+
const removableSorted = [...typeSet]
|
|
119
|
+
.filter((t) => !forceTraceElements?.has(t.split('@')[0])
|
|
120
|
+
&& !(ops.includeNativeLabel && t === LabelTypeFull))
|
|
121
|
+
.sort((a, b) => (importances.get(a) ?? 0) - (importances.get(b) ?? 0));
|
|
122
|
+
for (const typeToRemove of removableSorted) {
|
|
123
|
+
const reducedSet = new Set(typeSet);
|
|
124
|
+
reducedSet.delete(typeToRemove);
|
|
125
|
+
const candidateResult = calculate(reducedSet);
|
|
126
|
+
if (hasUniqueLabels(candidateResult)) {
|
|
127
|
+
typeSet.delete(typeToRemove);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return typeSet;
|
|
131
|
+
};
|
|
113
132
|
if (mainTypes.length === 0) {
|
|
114
133
|
if (secondaryTypes.length !== 0)
|
|
115
134
|
throw new Error('Non-empty secondary types list while main types list is empty.');
|
|
@@ -135,16 +154,20 @@ function deriveLabels(values, specExtractor, ops = {}) {
|
|
|
135
154
|
if (additionalType >= 0)
|
|
136
155
|
currentSet.add(mainTypes[additionalType]);
|
|
137
156
|
const candidateResult = calculate(currentSet);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return
|
|
157
|
+
if (hasUniqueLabels(candidateResult)) {
|
|
158
|
+
minimizeTypeSet(currentSet);
|
|
159
|
+
return calculate(currentSet);
|
|
160
|
+
}
|
|
141
161
|
additionalType++;
|
|
142
162
|
if (additionalType >= mainTypes.length) {
|
|
143
163
|
includedTypes++;
|
|
144
164
|
additionalType = includedTypes;
|
|
145
165
|
}
|
|
146
166
|
}
|
|
147
|
-
|
|
167
|
+
// Fallback: include all types, then try to minimize
|
|
168
|
+
const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);
|
|
169
|
+
minimizeTypeSet(fallbackSet);
|
|
170
|
+
return calculate(fallbackSet, true);
|
|
148
171
|
}
|
|
149
172
|
|
|
150
173
|
exports.Trace = Trace;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"label.cjs","sources":["../../../src/render/util/label.ts"],"sourcesContent":["import { Annotation, parseJson, readAnnotation, type PObjectSpec } from '@milaboratories/pl-model-common';\nimport { z } from 'zod';\n\nexport type RecordsWithLabel<T> = {\n value: T;\n label: string;\n};\n\nexport type LabelDerivationOps = {\n /** Force inclusion of native column label */\n includeNativeLabel?: boolean;\n /** Separator to use between label parts (\" / \" by default) */\n separator?: string;\n /** If true, label will be added as suffix (at the end of the generated label). By default label added as a prefix. */\n addLabelAsSuffix?: boolean;\n /** Trace elements list that will be forced to be included in the label. */\n forceTraceElements?: string[];\n};\n\nexport const TraceEntry = z.object({\n type: z.string(),\n importance: z.number().optional(),\n id: z.string().optional(),\n label: z.string(),\n});\nexport type TraceEntry = z.infer<typeof TraceEntry>;\ntype FullTraceEntry = TraceEntry & { fullType: string; occurrenceIndex: number };\n\nexport const Trace = z.array(TraceEntry);\nexport type Trace = z.infer<typeof Trace>;\ntype FullTrace = FullTraceEntry[];\n\n// Define the possible return types for the specExtractor function\ntype SpecExtractorResult = PObjectSpec | {\n spec: PObjectSpec;\n prefixTrace?: TraceEntry[];\n suffixTrace?: TraceEntry[];\n};\n\nconst DistancePenalty = 0.001;\n\nconst LabelType = '__LABEL__';\nconst LabelTypeFull = '__LABEL__@1';\n\nexport function deriveLabels<T>(\n values: T[],\n specExtractor: (obj: T) => SpecExtractorResult,\n ops: LabelDerivationOps = {},\n): RecordsWithLabel<T>[] {\n const importances = new Map<string, number>();\n\n const forceTraceElements = (ops.forceTraceElements !== undefined && ops.forceTraceElements.length > 0)\n ? new Set(ops.forceTraceElements)\n : undefined;\n\n // number of times certain type occurred among all of the\n const numberOfRecordsWithType = new Map<string, number>();\n\n const enrichedRecords = values.map((value) => {\n const extractorResult = specExtractor(value);\n let spec: PObjectSpec;\n let prefixTrace: TraceEntry[] | undefined;\n let suffixTrace: TraceEntry[] | undefined;\n\n // Check if the result is the new structure or just PObjectSpec\n if ('spec' in extractorResult && typeof extractorResult.spec === 'object') {\n // It's the new structure { spec, prefixTrace?, suffixTrace? }\n spec = extractorResult.spec;\n prefixTrace = extractorResult.prefixTrace;\n suffixTrace = extractorResult.suffixTrace;\n } else {\n // It's just PObjectSpec\n spec = extractorResult as PObjectSpec;\n }\n\n const label = readAnnotation(spec, Annotation.Label);\n const traceStr = readAnnotation(spec, Annotation.Trace);\n const baseTrace = (traceStr ? Trace.safeParse(parseJson(traceStr)).data : undefined) ?? [];\n\n const trace = [\n ...(prefixTrace ?? []),\n ...baseTrace,\n ...(suffixTrace ?? []),\n ];\n\n if (label !== undefined) {\n const labelEntry = { label, type: LabelType, importance: -2 };\n if (ops.addLabelAsSuffix) trace.push(labelEntry);\n else trace.splice(0, 0, labelEntry);\n }\n\n const fullTrace: FullTrace = [];\n\n const occurrences = new Map<string, number>();\n for (let i = trace.length - 1; i >= 0; --i) {\n const { type: typeName } = trace[i];\n const importance = trace[i].importance ?? 0;\n const occurrenceIndex = (occurrences.get(typeName) ?? 0) + 1;\n occurrences.set(typeName, occurrenceIndex);\n const fullType = `${typeName}@${occurrenceIndex}`;\n numberOfRecordsWithType.set(fullType, (numberOfRecordsWithType.get(fullType) ?? 0) + 1);\n importances.set(\n fullType,\n Math.max(\n importances.get(fullType) ?? Number.NEGATIVE_INFINITY,\n importance - (trace.length - i) * DistancePenalty,\n ),\n );\n fullTrace.push({ ...trace[i], fullType, occurrenceIndex: occurrenceIndex });\n }\n fullTrace.reverse();\n return {\n value,\n spec,\n label,\n fullTrace,\n };\n });\n\n // excluding repeated types (i.e. ..@2, ..@3, etc.) not found in some records\n const mainTypes: string[] = [];\n // repeated types (i.e. ..@2, ..@3, etc.) not found in some records\n const secondaryTypes: string[] = [];\n\n const allTypeRecords = [...importances];\n // sorting: most important types go first\n allTypeRecords.sort(([, i1], [, i2]) => i2 - i1);\n\n for (const [typeName] of allTypeRecords) {\n if (typeName.endsWith('@1') || numberOfRecordsWithType.get(typeName) === values.length)\n mainTypes.push(typeName);\n else secondaryTypes.push(typeName);\n }\n\n const calculate = (includedTypes: Set<string>, force: boolean = false) => {\n const result: RecordsWithLabel<T>[] = [];\n for (let i = 0; i < enrichedRecords.length; i++) {\n const r = enrichedRecords[i];\n const includedTrace = r.fullTrace\n .filter((fm) => includedTypes.has(fm.fullType)\n || (forceTraceElements && forceTraceElements.has(fm.type)));\n if (includedTrace.length === 0) {\n if (force)\n result.push({\n label: 'Unlabeled',\n value: r.value,\n } satisfies RecordsWithLabel<T>);\n else return undefined;\n }\n const labelSet = includedTrace\n .map((fm) => fm.label);\n const sep = ops.separator ?? ' / ';\n result.push({\n label: labelSet.join(sep),\n value: r.value,\n } satisfies RecordsWithLabel<T>);\n }\n return result;\n };\n\n if (mainTypes.length === 0) {\n if (secondaryTypes.length !== 0) throw new Error('Non-empty secondary types list while main types list is empty.');\n return calculate(new Set(LabelTypeFull), true)!;\n }\n\n //\n // includedTypes = 2\n // * *\n // T0 T1 T2 T3 T4 T5\n // *\n // additionalType = 3\n //\n // Resulting set: T0, T1, T3\n //\n let includedTypes = 0;\n let additionalType = -1;\n while (includedTypes < mainTypes.length) {\n const currentSet = new Set<string>();\n if (ops.includeNativeLabel) currentSet.add(LabelTypeFull);\n for (let i = 0; i < includedTypes; ++i) currentSet.add(mainTypes[i]);\n if (additionalType >= 0)\n currentSet.add(mainTypes[additionalType]);\n\n const candidateResult = calculate(currentSet);\n\n // checking if labels uniquely separate our records\n if (candidateResult !== undefined && new Set(candidateResult.map((c) => c.label)).size === values.length) return candidateResult;\n\n additionalType++;\n if (additionalType >= mainTypes.length) {\n includedTypes++;\n additionalType = includedTypes;\n }\n }\n\n return calculate(new Set([...mainTypes, ...secondaryTypes]), true)!;\n}\n"],"names":["z","readAnnotation","Annotation","parseJson"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"label.cjs","sources":["../../../src/render/util/label.ts"],"sourcesContent":["import { Annotation, parseJson, readAnnotation, type PObjectSpec } from '@milaboratories/pl-model-common';\nimport { z } from 'zod';\n\nexport type RecordsWithLabel<T> = {\n value: T;\n label: string;\n};\n\nexport type LabelDerivationOps = {\n /** Force inclusion of native column label */\n includeNativeLabel?: boolean;\n /** Separator to use between label parts (\" / \" by default) */\n separator?: string;\n /** If true, label will be added as suffix (at the end of the generated label). By default label added as a prefix. */\n addLabelAsSuffix?: boolean;\n /** Trace elements list that will be forced to be included in the label. */\n forceTraceElements?: string[];\n};\n\nexport const TraceEntry = z.object({\n type: z.string(),\n importance: z.number().optional(),\n id: z.string().optional(),\n label: z.string(),\n});\nexport type TraceEntry = z.infer<typeof TraceEntry>;\ntype FullTraceEntry = TraceEntry & { fullType: string; occurrenceIndex: number };\n\nexport const Trace = z.array(TraceEntry);\nexport type Trace = z.infer<typeof Trace>;\ntype FullTrace = FullTraceEntry[];\n\n// Define the possible return types for the specExtractor function\ntype SpecExtractorResult = PObjectSpec | {\n spec: PObjectSpec;\n prefixTrace?: TraceEntry[];\n suffixTrace?: TraceEntry[];\n};\n\nconst DistancePenalty = 0.001;\n\nconst LabelType = '__LABEL__';\nconst LabelTypeFull = '__LABEL__@1';\n\nexport function deriveLabels<T>(\n values: T[],\n specExtractor: (obj: T) => SpecExtractorResult,\n ops: LabelDerivationOps = {},\n): RecordsWithLabel<T>[] {\n const importances = new Map<string, number>();\n\n const forceTraceElements = (ops.forceTraceElements !== undefined && ops.forceTraceElements.length > 0)\n ? new Set(ops.forceTraceElements)\n : undefined;\n\n // number of times certain type occurred among all of the\n const numberOfRecordsWithType = new Map<string, number>();\n\n const enrichedRecords = values.map((value) => {\n const extractorResult = specExtractor(value);\n let spec: PObjectSpec;\n let prefixTrace: TraceEntry[] | undefined;\n let suffixTrace: TraceEntry[] | undefined;\n\n // Check if the result is the new structure or just PObjectSpec\n if ('spec' in extractorResult && typeof extractorResult.spec === 'object') {\n // It's the new structure { spec, prefixTrace?, suffixTrace? }\n spec = extractorResult.spec;\n prefixTrace = extractorResult.prefixTrace;\n suffixTrace = extractorResult.suffixTrace;\n } else {\n // It's just PObjectSpec\n spec = extractorResult as PObjectSpec;\n }\n\n const label = readAnnotation(spec, Annotation.Label);\n const traceStr = readAnnotation(spec, Annotation.Trace);\n const baseTrace = (traceStr ? Trace.safeParse(parseJson(traceStr)).data : undefined) ?? [];\n\n const trace = [\n ...(prefixTrace ?? []),\n ...baseTrace,\n ...(suffixTrace ?? []),\n ];\n\n if (label !== undefined) {\n const labelEntry = { label, type: LabelType, importance: -2 };\n if (ops.addLabelAsSuffix) trace.push(labelEntry);\n else trace.splice(0, 0, labelEntry);\n }\n\n const fullTrace: FullTrace = [];\n\n const occurrences = new Map<string, number>();\n for (let i = trace.length - 1; i >= 0; --i) {\n const { type: typeName } = trace[i];\n const importance = trace[i].importance ?? 0;\n const occurrenceIndex = (occurrences.get(typeName) ?? 0) + 1;\n occurrences.set(typeName, occurrenceIndex);\n const fullType = `${typeName}@${occurrenceIndex}`;\n numberOfRecordsWithType.set(fullType, (numberOfRecordsWithType.get(fullType) ?? 0) + 1);\n importances.set(\n fullType,\n Math.max(\n importances.get(fullType) ?? Number.NEGATIVE_INFINITY,\n importance - (trace.length - i) * DistancePenalty,\n ),\n );\n fullTrace.push({ ...trace[i], fullType, occurrenceIndex: occurrenceIndex });\n }\n fullTrace.reverse();\n return {\n value,\n spec,\n label,\n fullTrace,\n };\n });\n\n // excluding repeated types (i.e. ..@2, ..@3, etc.) not found in some records\n const mainTypes: string[] = [];\n // repeated types (i.e. ..@2, ..@3, etc.) not found in some records\n const secondaryTypes: string[] = [];\n\n const allTypeRecords = [...importances];\n // sorting: most important types go first\n allTypeRecords.sort(([, i1], [, i2]) => i2 - i1);\n\n for (const [typeName] of allTypeRecords) {\n if (typeName.endsWith('@1') || numberOfRecordsWithType.get(typeName) === values.length)\n mainTypes.push(typeName);\n else secondaryTypes.push(typeName);\n }\n\n const calculate = (includedTypes: Set<string>, force: boolean = false) => {\n const result: RecordsWithLabel<T>[] = [];\n for (let i = 0; i < enrichedRecords.length; i++) {\n const r = enrichedRecords[i];\n const includedTrace = r.fullTrace\n .filter((fm) => includedTypes.has(fm.fullType)\n || (forceTraceElements && forceTraceElements.has(fm.type)));\n if (includedTrace.length === 0) {\n if (force)\n result.push({\n label: 'Unlabeled',\n value: r.value,\n } satisfies RecordsWithLabel<T>);\n else return undefined;\n }\n const labelSet = includedTrace\n .map((fm) => fm.label);\n const sep = ops.separator ?? ' / ';\n result.push({\n label: labelSet.join(sep),\n value: r.value,\n } satisfies RecordsWithLabel<T>);\n }\n return result;\n };\n\n // Checks if a result has all unique labels\n const hasUniqueLabels = (result: RecordsWithLabel<T>[] | undefined): boolean =>\n result !== undefined && new Set(result.map((c) => c.label)).size === values.length;\n\n // Post-processing: try removing types one by one (lowest importance first) to minimize the label set\n const minimizeTypeSet = (typeSet: Set<string>): Set<string> => {\n // Get types sorted by importance ascending (lowest first), excluding forced elements\n const removableSorted = [...typeSet]\n .filter((t) =>\n !forceTraceElements?.has(t.split('@')[0])\n && !(ops.includeNativeLabel && t === LabelTypeFull))\n .sort((a, b) => (importances.get(a) ?? 0) - (importances.get(b) ?? 0));\n\n for (const typeToRemove of removableSorted) {\n const reducedSet = new Set(typeSet);\n reducedSet.delete(typeToRemove);\n const candidateResult = calculate(reducedSet);\n if (hasUniqueLabels(candidateResult)) {\n typeSet.delete(typeToRemove);\n }\n }\n return typeSet;\n };\n\n if (mainTypes.length === 0) {\n if (secondaryTypes.length !== 0) throw new Error('Non-empty secondary types list while main types list is empty.');\n return calculate(new Set(LabelTypeFull), true)!;\n }\n\n //\n // includedTypes = 2\n // * *\n // T0 T1 T2 T3 T4 T5\n // *\n // additionalType = 3\n //\n // Resulting set: T0, T1, T3\n //\n let includedTypes = 0;\n let additionalType = -1;\n while (includedTypes < mainTypes.length) {\n const currentSet = new Set<string>();\n if (ops.includeNativeLabel) currentSet.add(LabelTypeFull);\n for (let i = 0; i < includedTypes; ++i) currentSet.add(mainTypes[i]);\n if (additionalType >= 0)\n currentSet.add(mainTypes[additionalType]);\n\n const candidateResult = calculate(currentSet);\n\n if (hasUniqueLabels(candidateResult)) {\n minimizeTypeSet(currentSet);\n return calculate(currentSet)!;\n }\n\n additionalType++;\n if (additionalType >= mainTypes.length) {\n includedTypes++;\n additionalType = includedTypes;\n }\n }\n\n // Fallback: include all types, then try to minimize\n const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);\n minimizeTypeSet(fallbackSet);\n return calculate(fallbackSet, true)!;\n}\n"],"names":["z","readAnnotation","Annotation","parseJson"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../../src/render/util/label.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyC,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC1G,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,6CAA6C;IAC7C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sHAAsH;IACtH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;EAKrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAGpD,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;WAAsB,CAAC;AACzC,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;AAI1C,KAAK,mBAAmB,GAAG,WAAW,GAAG;IACvC,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B,CAAC;AAOF,wBAAgB,YAAY,CAAC,CAAC,EAC5B,MAAM,EAAE,CAAC,EAAE,EACX,aAAa,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,mBAAmB,EAC9C,GAAG,GAAE,kBAAuB,GAC3B,gBAAgB,CAAC,CAAC,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../../src/render/util/label.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyC,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC1G,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,6CAA6C;IAC7C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sHAAsH;IACtH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;EAKrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAGpD,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;WAAsB,CAAC;AACzC,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;AAI1C,KAAK,mBAAmB,GAAG,WAAW,GAAG;IACvC,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B,CAAC;AAOF,wBAAgB,YAAY,CAAC,CAAC,EAC5B,MAAM,EAAE,CAAC,EAAE,EACX,aAAa,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,mBAAmB,EAC9C,GAAG,GAAE,kBAAuB,GAC3B,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAiLvB"}
|
|
@@ -108,6 +108,25 @@ function deriveLabels(values, specExtractor, ops = {}) {
|
|
|
108
108
|
}
|
|
109
109
|
return result;
|
|
110
110
|
};
|
|
111
|
+
// Checks if a result has all unique labels
|
|
112
|
+
const hasUniqueLabels = (result) => result !== undefined && new Set(result.map((c) => c.label)).size === values.length;
|
|
113
|
+
// Post-processing: try removing types one by one (lowest importance first) to minimize the label set
|
|
114
|
+
const minimizeTypeSet = (typeSet) => {
|
|
115
|
+
// Get types sorted by importance ascending (lowest first), excluding forced elements
|
|
116
|
+
const removableSorted = [...typeSet]
|
|
117
|
+
.filter((t) => !forceTraceElements?.has(t.split('@')[0])
|
|
118
|
+
&& !(ops.includeNativeLabel && t === LabelTypeFull))
|
|
119
|
+
.sort((a, b) => (importances.get(a) ?? 0) - (importances.get(b) ?? 0));
|
|
120
|
+
for (const typeToRemove of removableSorted) {
|
|
121
|
+
const reducedSet = new Set(typeSet);
|
|
122
|
+
reducedSet.delete(typeToRemove);
|
|
123
|
+
const candidateResult = calculate(reducedSet);
|
|
124
|
+
if (hasUniqueLabels(candidateResult)) {
|
|
125
|
+
typeSet.delete(typeToRemove);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return typeSet;
|
|
129
|
+
};
|
|
111
130
|
if (mainTypes.length === 0) {
|
|
112
131
|
if (secondaryTypes.length !== 0)
|
|
113
132
|
throw new Error('Non-empty secondary types list while main types list is empty.');
|
|
@@ -133,16 +152,20 @@ function deriveLabels(values, specExtractor, ops = {}) {
|
|
|
133
152
|
if (additionalType >= 0)
|
|
134
153
|
currentSet.add(mainTypes[additionalType]);
|
|
135
154
|
const candidateResult = calculate(currentSet);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return
|
|
155
|
+
if (hasUniqueLabels(candidateResult)) {
|
|
156
|
+
minimizeTypeSet(currentSet);
|
|
157
|
+
return calculate(currentSet);
|
|
158
|
+
}
|
|
139
159
|
additionalType++;
|
|
140
160
|
if (additionalType >= mainTypes.length) {
|
|
141
161
|
includedTypes++;
|
|
142
162
|
additionalType = includedTypes;
|
|
143
163
|
}
|
|
144
164
|
}
|
|
145
|
-
|
|
165
|
+
// Fallback: include all types, then try to minimize
|
|
166
|
+
const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);
|
|
167
|
+
minimizeTypeSet(fallbackSet);
|
|
168
|
+
return calculate(fallbackSet, true);
|
|
146
169
|
}
|
|
147
170
|
|
|
148
171
|
export { Trace, TraceEntry, deriveLabels };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"label.js","sources":["../../../src/render/util/label.ts"],"sourcesContent":["import { Annotation, parseJson, readAnnotation, type PObjectSpec } from '@milaboratories/pl-model-common';\nimport { z } from 'zod';\n\nexport type RecordsWithLabel<T> = {\n value: T;\n label: string;\n};\n\nexport type LabelDerivationOps = {\n /** Force inclusion of native column label */\n includeNativeLabel?: boolean;\n /** Separator to use between label parts (\" / \" by default) */\n separator?: string;\n /** If true, label will be added as suffix (at the end of the generated label). By default label added as a prefix. */\n addLabelAsSuffix?: boolean;\n /** Trace elements list that will be forced to be included in the label. */\n forceTraceElements?: string[];\n};\n\nexport const TraceEntry = z.object({\n type: z.string(),\n importance: z.number().optional(),\n id: z.string().optional(),\n label: z.string(),\n});\nexport type TraceEntry = z.infer<typeof TraceEntry>;\ntype FullTraceEntry = TraceEntry & { fullType: string; occurrenceIndex: number };\n\nexport const Trace = z.array(TraceEntry);\nexport type Trace = z.infer<typeof Trace>;\ntype FullTrace = FullTraceEntry[];\n\n// Define the possible return types for the specExtractor function\ntype SpecExtractorResult = PObjectSpec | {\n spec: PObjectSpec;\n prefixTrace?: TraceEntry[];\n suffixTrace?: TraceEntry[];\n};\n\nconst DistancePenalty = 0.001;\n\nconst LabelType = '__LABEL__';\nconst LabelTypeFull = '__LABEL__@1';\n\nexport function deriveLabels<T>(\n values: T[],\n specExtractor: (obj: T) => SpecExtractorResult,\n ops: LabelDerivationOps = {},\n): RecordsWithLabel<T>[] {\n const importances = new Map<string, number>();\n\n const forceTraceElements = (ops.forceTraceElements !== undefined && ops.forceTraceElements.length > 0)\n ? new Set(ops.forceTraceElements)\n : undefined;\n\n // number of times certain type occurred among all of the\n const numberOfRecordsWithType = new Map<string, number>();\n\n const enrichedRecords = values.map((value) => {\n const extractorResult = specExtractor(value);\n let spec: PObjectSpec;\n let prefixTrace: TraceEntry[] | undefined;\n let suffixTrace: TraceEntry[] | undefined;\n\n // Check if the result is the new structure or just PObjectSpec\n if ('spec' in extractorResult && typeof extractorResult.spec === 'object') {\n // It's the new structure { spec, prefixTrace?, suffixTrace? }\n spec = extractorResult.spec;\n prefixTrace = extractorResult.prefixTrace;\n suffixTrace = extractorResult.suffixTrace;\n } else {\n // It's just PObjectSpec\n spec = extractorResult as PObjectSpec;\n }\n\n const label = readAnnotation(spec, Annotation.Label);\n const traceStr = readAnnotation(spec, Annotation.Trace);\n const baseTrace = (traceStr ? Trace.safeParse(parseJson(traceStr)).data : undefined) ?? [];\n\n const trace = [\n ...(prefixTrace ?? []),\n ...baseTrace,\n ...(suffixTrace ?? []),\n ];\n\n if (label !== undefined) {\n const labelEntry = { label, type: LabelType, importance: -2 };\n if (ops.addLabelAsSuffix) trace.push(labelEntry);\n else trace.splice(0, 0, labelEntry);\n }\n\n const fullTrace: FullTrace = [];\n\n const occurrences = new Map<string, number>();\n for (let i = trace.length - 1; i >= 0; --i) {\n const { type: typeName } = trace[i];\n const importance = trace[i].importance ?? 0;\n const occurrenceIndex = (occurrences.get(typeName) ?? 0) + 1;\n occurrences.set(typeName, occurrenceIndex);\n const fullType = `${typeName}@${occurrenceIndex}`;\n numberOfRecordsWithType.set(fullType, (numberOfRecordsWithType.get(fullType) ?? 0) + 1);\n importances.set(\n fullType,\n Math.max(\n importances.get(fullType) ?? Number.NEGATIVE_INFINITY,\n importance - (trace.length - i) * DistancePenalty,\n ),\n );\n fullTrace.push({ ...trace[i], fullType, occurrenceIndex: occurrenceIndex });\n }\n fullTrace.reverse();\n return {\n value,\n spec,\n label,\n fullTrace,\n };\n });\n\n // excluding repeated types (i.e. ..@2, ..@3, etc.) not found in some records\n const mainTypes: string[] = [];\n // repeated types (i.e. ..@2, ..@3, etc.) not found in some records\n const secondaryTypes: string[] = [];\n\n const allTypeRecords = [...importances];\n // sorting: most important types go first\n allTypeRecords.sort(([, i1], [, i2]) => i2 - i1);\n\n for (const [typeName] of allTypeRecords) {\n if (typeName.endsWith('@1') || numberOfRecordsWithType.get(typeName) === values.length)\n mainTypes.push(typeName);\n else secondaryTypes.push(typeName);\n }\n\n const calculate = (includedTypes: Set<string>, force: boolean = false) => {\n const result: RecordsWithLabel<T>[] = [];\n for (let i = 0; i < enrichedRecords.length; i++) {\n const r = enrichedRecords[i];\n const includedTrace = r.fullTrace\n .filter((fm) => includedTypes.has(fm.fullType)\n || (forceTraceElements && forceTraceElements.has(fm.type)));\n if (includedTrace.length === 0) {\n if (force)\n result.push({\n label: 'Unlabeled',\n value: r.value,\n } satisfies RecordsWithLabel<T>);\n else return undefined;\n }\n const labelSet = includedTrace\n .map((fm) => fm.label);\n const sep = ops.separator ?? ' / ';\n result.push({\n label: labelSet.join(sep),\n value: r.value,\n } satisfies RecordsWithLabel<T>);\n }\n return result;\n };\n\n if (mainTypes.length === 0) {\n if (secondaryTypes.length !== 0) throw new Error('Non-empty secondary types list while main types list is empty.');\n return calculate(new Set(LabelTypeFull), true)!;\n }\n\n //\n // includedTypes = 2\n // * *\n // T0 T1 T2 T3 T4 T5\n // *\n // additionalType = 3\n //\n // Resulting set: T0, T1, T3\n //\n let includedTypes = 0;\n let additionalType = -1;\n while (includedTypes < mainTypes.length) {\n const currentSet = new Set<string>();\n if (ops.includeNativeLabel) currentSet.add(LabelTypeFull);\n for (let i = 0; i < includedTypes; ++i) currentSet.add(mainTypes[i]);\n if (additionalType >= 0)\n currentSet.add(mainTypes[additionalType]);\n\n const candidateResult = calculate(currentSet);\n\n // checking if labels uniquely separate our records\n if (candidateResult !== undefined && new Set(candidateResult.map((c) => c.label)).size === values.length) return candidateResult;\n\n additionalType++;\n if (additionalType >= mainTypes.length) {\n includedTypes++;\n additionalType = includedTypes;\n }\n }\n\n return calculate(new Set([...mainTypes, ...secondaryTypes]), true)!;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"label.js","sources":["../../../src/render/util/label.ts"],"sourcesContent":["import { Annotation, parseJson, readAnnotation, type PObjectSpec } from '@milaboratories/pl-model-common';\nimport { z } from 'zod';\n\nexport type RecordsWithLabel<T> = {\n value: T;\n label: string;\n};\n\nexport type LabelDerivationOps = {\n /** Force inclusion of native column label */\n includeNativeLabel?: boolean;\n /** Separator to use between label parts (\" / \" by default) */\n separator?: string;\n /** If true, label will be added as suffix (at the end of the generated label). By default label added as a prefix. */\n addLabelAsSuffix?: boolean;\n /** Trace elements list that will be forced to be included in the label. */\n forceTraceElements?: string[];\n};\n\nexport const TraceEntry = z.object({\n type: z.string(),\n importance: z.number().optional(),\n id: z.string().optional(),\n label: z.string(),\n});\nexport type TraceEntry = z.infer<typeof TraceEntry>;\ntype FullTraceEntry = TraceEntry & { fullType: string; occurrenceIndex: number };\n\nexport const Trace = z.array(TraceEntry);\nexport type Trace = z.infer<typeof Trace>;\ntype FullTrace = FullTraceEntry[];\n\n// Define the possible return types for the specExtractor function\ntype SpecExtractorResult = PObjectSpec | {\n spec: PObjectSpec;\n prefixTrace?: TraceEntry[];\n suffixTrace?: TraceEntry[];\n};\n\nconst DistancePenalty = 0.001;\n\nconst LabelType = '__LABEL__';\nconst LabelTypeFull = '__LABEL__@1';\n\nexport function deriveLabels<T>(\n values: T[],\n specExtractor: (obj: T) => SpecExtractorResult,\n ops: LabelDerivationOps = {},\n): RecordsWithLabel<T>[] {\n const importances = new Map<string, number>();\n\n const forceTraceElements = (ops.forceTraceElements !== undefined && ops.forceTraceElements.length > 0)\n ? new Set(ops.forceTraceElements)\n : undefined;\n\n // number of times certain type occurred among all of the\n const numberOfRecordsWithType = new Map<string, number>();\n\n const enrichedRecords = values.map((value) => {\n const extractorResult = specExtractor(value);\n let spec: PObjectSpec;\n let prefixTrace: TraceEntry[] | undefined;\n let suffixTrace: TraceEntry[] | undefined;\n\n // Check if the result is the new structure or just PObjectSpec\n if ('spec' in extractorResult && typeof extractorResult.spec === 'object') {\n // It's the new structure { spec, prefixTrace?, suffixTrace? }\n spec = extractorResult.spec;\n prefixTrace = extractorResult.prefixTrace;\n suffixTrace = extractorResult.suffixTrace;\n } else {\n // It's just PObjectSpec\n spec = extractorResult as PObjectSpec;\n }\n\n const label = readAnnotation(spec, Annotation.Label);\n const traceStr = readAnnotation(spec, Annotation.Trace);\n const baseTrace = (traceStr ? Trace.safeParse(parseJson(traceStr)).data : undefined) ?? [];\n\n const trace = [\n ...(prefixTrace ?? []),\n ...baseTrace,\n ...(suffixTrace ?? []),\n ];\n\n if (label !== undefined) {\n const labelEntry = { label, type: LabelType, importance: -2 };\n if (ops.addLabelAsSuffix) trace.push(labelEntry);\n else trace.splice(0, 0, labelEntry);\n }\n\n const fullTrace: FullTrace = [];\n\n const occurrences = new Map<string, number>();\n for (let i = trace.length - 1; i >= 0; --i) {\n const { type: typeName } = trace[i];\n const importance = trace[i].importance ?? 0;\n const occurrenceIndex = (occurrences.get(typeName) ?? 0) + 1;\n occurrences.set(typeName, occurrenceIndex);\n const fullType = `${typeName}@${occurrenceIndex}`;\n numberOfRecordsWithType.set(fullType, (numberOfRecordsWithType.get(fullType) ?? 0) + 1);\n importances.set(\n fullType,\n Math.max(\n importances.get(fullType) ?? Number.NEGATIVE_INFINITY,\n importance - (trace.length - i) * DistancePenalty,\n ),\n );\n fullTrace.push({ ...trace[i], fullType, occurrenceIndex: occurrenceIndex });\n }\n fullTrace.reverse();\n return {\n value,\n spec,\n label,\n fullTrace,\n };\n });\n\n // excluding repeated types (i.e. ..@2, ..@3, etc.) not found in some records\n const mainTypes: string[] = [];\n // repeated types (i.e. ..@2, ..@3, etc.) not found in some records\n const secondaryTypes: string[] = [];\n\n const allTypeRecords = [...importances];\n // sorting: most important types go first\n allTypeRecords.sort(([, i1], [, i2]) => i2 - i1);\n\n for (const [typeName] of allTypeRecords) {\n if (typeName.endsWith('@1') || numberOfRecordsWithType.get(typeName) === values.length)\n mainTypes.push(typeName);\n else secondaryTypes.push(typeName);\n }\n\n const calculate = (includedTypes: Set<string>, force: boolean = false) => {\n const result: RecordsWithLabel<T>[] = [];\n for (let i = 0; i < enrichedRecords.length; i++) {\n const r = enrichedRecords[i];\n const includedTrace = r.fullTrace\n .filter((fm) => includedTypes.has(fm.fullType)\n || (forceTraceElements && forceTraceElements.has(fm.type)));\n if (includedTrace.length === 0) {\n if (force)\n result.push({\n label: 'Unlabeled',\n value: r.value,\n } satisfies RecordsWithLabel<T>);\n else return undefined;\n }\n const labelSet = includedTrace\n .map((fm) => fm.label);\n const sep = ops.separator ?? ' / ';\n result.push({\n label: labelSet.join(sep),\n value: r.value,\n } satisfies RecordsWithLabel<T>);\n }\n return result;\n };\n\n // Checks if a result has all unique labels\n const hasUniqueLabels = (result: RecordsWithLabel<T>[] | undefined): boolean =>\n result !== undefined && new Set(result.map((c) => c.label)).size === values.length;\n\n // Post-processing: try removing types one by one (lowest importance first) to minimize the label set\n const minimizeTypeSet = (typeSet: Set<string>): Set<string> => {\n // Get types sorted by importance ascending (lowest first), excluding forced elements\n const removableSorted = [...typeSet]\n .filter((t) =>\n !forceTraceElements?.has(t.split('@')[0])\n && !(ops.includeNativeLabel && t === LabelTypeFull))\n .sort((a, b) => (importances.get(a) ?? 0) - (importances.get(b) ?? 0));\n\n for (const typeToRemove of removableSorted) {\n const reducedSet = new Set(typeSet);\n reducedSet.delete(typeToRemove);\n const candidateResult = calculate(reducedSet);\n if (hasUniqueLabels(candidateResult)) {\n typeSet.delete(typeToRemove);\n }\n }\n return typeSet;\n };\n\n if (mainTypes.length === 0) {\n if (secondaryTypes.length !== 0) throw new Error('Non-empty secondary types list while main types list is empty.');\n return calculate(new Set(LabelTypeFull), true)!;\n }\n\n //\n // includedTypes = 2\n // * *\n // T0 T1 T2 T3 T4 T5\n // *\n // additionalType = 3\n //\n // Resulting set: T0, T1, T3\n //\n let includedTypes = 0;\n let additionalType = -1;\n while (includedTypes < mainTypes.length) {\n const currentSet = new Set<string>();\n if (ops.includeNativeLabel) currentSet.add(LabelTypeFull);\n for (let i = 0; i < includedTypes; ++i) currentSet.add(mainTypes[i]);\n if (additionalType >= 0)\n currentSet.add(mainTypes[additionalType]);\n\n const candidateResult = calculate(currentSet);\n\n if (hasUniqueLabels(candidateResult)) {\n minimizeTypeSet(currentSet);\n return calculate(currentSet)!;\n }\n\n additionalType++;\n if (additionalType >= mainTypes.length) {\n includedTypes++;\n additionalType = includedTypes;\n }\n }\n\n // Fallback: include all types, then try to minimize\n const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);\n minimizeTypeSet(fallbackSet);\n return calculate(fallbackSet, true)!;\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platforma-sdk/model",
|
|
3
|
-
"version": "1.51.
|
|
3
|
+
"version": "1.51.6",
|
|
4
4
|
"description": "Platforma.bio SDK / Block Model",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"vitest": "^4.0.16",
|
|
33
33
|
"@vitest/coverage-istanbul": "^4.0.16",
|
|
34
34
|
"fast-json-patch": "^3.1.1",
|
|
35
|
-
"@milaboratories/build-configs": "1.2.1",
|
|
36
35
|
"@milaboratories/eslint-config": "1.0.5",
|
|
37
|
-
"@milaboratories/
|
|
38
|
-
"@milaboratories/
|
|
39
|
-
"@milaboratories/ts-configs": "1.2.0"
|
|
36
|
+
"@milaboratories/build-configs": "1.2.2",
|
|
37
|
+
"@milaboratories/ts-builder": "1.2.2",
|
|
38
|
+
"@milaboratories/ts-configs": "1.2.0",
|
|
39
|
+
"@milaboratories/helpers": "1.13.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"type-check": "ts-builder types --target node",
|
|
@@ -119,6 +119,62 @@ test('test fallback to native labels in label derivation', () => {
|
|
|
119
119
|
]);
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
test.each<{ name: string; traces: Trace[]; labels: string[] }>([
|
|
123
|
+
{
|
|
124
|
+
name: 'removes redundant low-importance type when high-importance alone suffices',
|
|
125
|
+
traces: [
|
|
126
|
+
[
|
|
127
|
+
{ type: 't1', importance: 10, label: 'High1' },
|
|
128
|
+
{ type: 't2', importance: 1, label: 'Low1' }
|
|
129
|
+
],
|
|
130
|
+
[
|
|
131
|
+
{ type: 't1', importance: 10, label: 'High2' },
|
|
132
|
+
{ type: 't2', importance: 1, label: 'Low2' }
|
|
133
|
+
]
|
|
134
|
+
],
|
|
135
|
+
// Both t1 and t2 distinguish, but t2 (low importance) should be removed since t1 alone suffices
|
|
136
|
+
labels: ['High1', 'High2']
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'keeps both types when both are needed for uniqueness',
|
|
140
|
+
traces: [
|
|
141
|
+
[
|
|
142
|
+
{ type: 't1', importance: 10, label: 'A' },
|
|
143
|
+
{ type: 't2', importance: 1, label: 'X' }
|
|
144
|
+
],
|
|
145
|
+
[
|
|
146
|
+
{ type: 't1', importance: 10, label: 'A' },
|
|
147
|
+
{ type: 't2', importance: 1, label: 'Y' }
|
|
148
|
+
],
|
|
149
|
+
[
|
|
150
|
+
{ type: 't1', importance: 10, label: 'B' },
|
|
151
|
+
{ type: 't2', importance: 1, label: 'Y' }
|
|
152
|
+
]
|
|
153
|
+
],
|
|
154
|
+
// Neither t1 nor t2 alone can distinguish all three, need both
|
|
155
|
+
labels: ['A / X', 'A / Y', 'B / Y']
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'removes multiple redundant types greedily',
|
|
159
|
+
traces: [
|
|
160
|
+
[
|
|
161
|
+
{ type: 't1', importance: 100, label: 'Unique1' },
|
|
162
|
+
{ type: 't2', importance: 10, label: 'Same' },
|
|
163
|
+
{ type: 't3', importance: 1, label: 'Same' }
|
|
164
|
+
],
|
|
165
|
+
[
|
|
166
|
+
{ type: 't1', importance: 100, label: 'Unique2' },
|
|
167
|
+
{ type: 't2', importance: 10, label: 'Same' },
|
|
168
|
+
{ type: 't3', importance: 1, label: 'Same' }
|
|
169
|
+
]
|
|
170
|
+
],
|
|
171
|
+
// t1 alone distinguishes; t2 and t3 are redundant and should be removed
|
|
172
|
+
labels: ['Unique1', 'Unique2']
|
|
173
|
+
}
|
|
174
|
+
])('test label minimization: $name', ({ traces, labels }) => {
|
|
175
|
+
expect(deriveLabels(tracesToSpecs(traces), (s) => s).map((r) => r.label)).toEqual(labels);
|
|
176
|
+
});
|
|
177
|
+
|
|
122
178
|
test.each<{ name: string; traces: Trace[]; labels: string[]; forceTraceElements: string[] }>([
|
|
123
179
|
{
|
|
124
180
|
name: 'force one element',
|
package/src/render/util/label.ts
CHANGED
|
@@ -158,6 +158,30 @@ export function deriveLabels<T>(
|
|
|
158
158
|
return result;
|
|
159
159
|
};
|
|
160
160
|
|
|
161
|
+
// Checks if a result has all unique labels
|
|
162
|
+
const hasUniqueLabels = (result: RecordsWithLabel<T>[] | undefined): boolean =>
|
|
163
|
+
result !== undefined && new Set(result.map((c) => c.label)).size === values.length;
|
|
164
|
+
|
|
165
|
+
// Post-processing: try removing types one by one (lowest importance first) to minimize the label set
|
|
166
|
+
const minimizeTypeSet = (typeSet: Set<string>): Set<string> => {
|
|
167
|
+
// Get types sorted by importance ascending (lowest first), excluding forced elements
|
|
168
|
+
const removableSorted = [...typeSet]
|
|
169
|
+
.filter((t) =>
|
|
170
|
+
!forceTraceElements?.has(t.split('@')[0])
|
|
171
|
+
&& !(ops.includeNativeLabel && t === LabelTypeFull))
|
|
172
|
+
.sort((a, b) => (importances.get(a) ?? 0) - (importances.get(b) ?? 0));
|
|
173
|
+
|
|
174
|
+
for (const typeToRemove of removableSorted) {
|
|
175
|
+
const reducedSet = new Set(typeSet);
|
|
176
|
+
reducedSet.delete(typeToRemove);
|
|
177
|
+
const candidateResult = calculate(reducedSet);
|
|
178
|
+
if (hasUniqueLabels(candidateResult)) {
|
|
179
|
+
typeSet.delete(typeToRemove);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return typeSet;
|
|
183
|
+
};
|
|
184
|
+
|
|
161
185
|
if (mainTypes.length === 0) {
|
|
162
186
|
if (secondaryTypes.length !== 0) throw new Error('Non-empty secondary types list while main types list is empty.');
|
|
163
187
|
return calculate(new Set(LabelTypeFull), true)!;
|
|
@@ -183,8 +207,10 @@ export function deriveLabels<T>(
|
|
|
183
207
|
|
|
184
208
|
const candidateResult = calculate(currentSet);
|
|
185
209
|
|
|
186
|
-
|
|
187
|
-
|
|
210
|
+
if (hasUniqueLabels(candidateResult)) {
|
|
211
|
+
minimizeTypeSet(currentSet);
|
|
212
|
+
return calculate(currentSet)!;
|
|
213
|
+
}
|
|
188
214
|
|
|
189
215
|
additionalType++;
|
|
190
216
|
if (additionalType >= mainTypes.length) {
|
|
@@ -193,5 +219,8 @@ export function deriveLabels<T>(
|
|
|
193
219
|
}
|
|
194
220
|
}
|
|
195
221
|
|
|
196
|
-
|
|
222
|
+
// Fallback: include all types, then try to minimize
|
|
223
|
+
const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);
|
|
224
|
+
minimizeTypeSet(fallbackSet);
|
|
225
|
+
return calculate(fallbackSet, true)!;
|
|
197
226
|
}
|