@platforma-sdk/model 1.10.12 → 1.12.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.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +344 -281
- package/dist/index.mjs.map +1 -1
- package/dist/render/api.d.ts +2 -1
- package/dist/render/api.d.ts.map +1 -1
- package/dist/render/util/label.d.ts +48 -0
- package/dist/render/util/label.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/render/api.ts +13 -5
- package/src/render/util/label.test.ts +98 -0
- package/src/render/util/label.ts +147 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../../src/render/util/label.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAChD,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAEhD,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,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,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;AAQ1C,wBAAgB,YAAY,CAAC,CAAC,EAC5B,MAAM,EAAE,CAAC,EAAE,EACX,aAAa,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,WAAW,EACtC,GAAG,GAAE,kBAAuB,GAC3B,gBAAgB,CAAC,CAAC,CAAC,EAAE,CA4GvB"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const PlatformaSDKVersion = "1.
|
|
1
|
+
export declare const PlatformaSDKVersion = "1.12.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,WAAW,CAAC"}
|
package/package.json
CHANGED
package/src/render/api.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { getCfgRenderCtx } from '../internal';
|
|
|
24
24
|
import { TreeNodeAccessor } from './accessor';
|
|
25
25
|
import { FutureRef } from './future';
|
|
26
26
|
import { GlobalCfgRenderCtx, MainAccessorName, StagingAccessorName } from './internal';
|
|
27
|
+
import { deriveLabels, LabelDerivationOps } from './util/label';
|
|
27
28
|
|
|
28
29
|
export class ResultPool {
|
|
29
30
|
private readonly ctx: GlobalCfgRenderCtx = getCfgRenderCtx();
|
|
@@ -40,13 +41,20 @@ export class ResultPool {
|
|
|
40
41
|
|
|
41
42
|
public getOptions(
|
|
42
43
|
predicate: (spec: PObjectSpec) => boolean,
|
|
43
|
-
|
|
44
|
+
label?: ((spec: PObjectSpec, ref: Ref) => string) | LabelDerivationOps
|
|
44
45
|
): Option[] {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const filtered = this.getSpecs().entries.filter((s) => predicate(s.obj));
|
|
47
|
+
if (typeof label === 'object' || typeof label === 'undefined') {
|
|
48
|
+
return deriveLabels(filtered, (o) => o.obj, label ?? {}).map(
|
|
49
|
+
({ value: { ref }, label }) => ({
|
|
50
|
+
ref,
|
|
51
|
+
label
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
} else
|
|
55
|
+
return filtered.map((s) => ({
|
|
48
56
|
ref: s.ref,
|
|
49
|
-
label:
|
|
57
|
+
label: label(s.obj, s.ref)
|
|
50
58
|
}));
|
|
51
59
|
}
|
|
52
60
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { test, expect } from '@jest/globals';
|
|
2
|
+
import { deriveLabels, PAnnotationLabel, PAnnotationTrace, Trace } from './label';
|
|
3
|
+
import { PColumnSpec } from '@milaboratories/pl-model-common';
|
|
4
|
+
|
|
5
|
+
function tracesToSpecs(traces: Trace[]) {
|
|
6
|
+
return traces.map(
|
|
7
|
+
(t) =>
|
|
8
|
+
({
|
|
9
|
+
kind: 'PColumn',
|
|
10
|
+
name: 'name',
|
|
11
|
+
valueType: 'Int',
|
|
12
|
+
annotations: {
|
|
13
|
+
[PAnnotationTrace]: JSON.stringify(t),
|
|
14
|
+
[PAnnotationLabel]: 'Label'
|
|
15
|
+
},
|
|
16
|
+
axesSpec: []
|
|
17
|
+
}) satisfies PColumnSpec
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
test.each<{ name: string; traces: Trace[]; labels: string[] }>([
|
|
21
|
+
{
|
|
22
|
+
name: 'simple',
|
|
23
|
+
traces: [[{ type: 't1', label: 'L1' }], [{ type: 't1', label: 'L2' }]],
|
|
24
|
+
labels: ['L1', 'L2']
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'later wins',
|
|
28
|
+
traces: [
|
|
29
|
+
[
|
|
30
|
+
{ type: 't1', label: 'T1L1' },
|
|
31
|
+
{ type: 't2', label: 'T2L1' }
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
{ type: 't1', label: 'T1L2' },
|
|
35
|
+
{ type: 't2', label: 'T2L2' }
|
|
36
|
+
]
|
|
37
|
+
],
|
|
38
|
+
labels: ['T2L1', 'T2L2']
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'importance wins',
|
|
42
|
+
traces: [
|
|
43
|
+
[
|
|
44
|
+
{ type: 't1', importance: 100, label: 'T1L1' },
|
|
45
|
+
{ type: 't2', label: 'T2L1' }
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
{ type: 't1', importance: 100, label: 'T1L2' },
|
|
49
|
+
{ type: 't2', label: 'T2L2' }
|
|
50
|
+
]
|
|
51
|
+
],
|
|
52
|
+
labels: ['T1L1', 'T1L2']
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'uniqueness wins',
|
|
56
|
+
traces: [
|
|
57
|
+
[
|
|
58
|
+
{ type: 't1', label: 'T1L1' },
|
|
59
|
+
{ type: 't2', label: 'T2L1' }
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
{ type: 't1', label: 'T1L2' },
|
|
63
|
+
{ type: 't2', label: 'T2L1' }
|
|
64
|
+
]
|
|
65
|
+
],
|
|
66
|
+
labels: ['T1L1', 'T1L2']
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'combinatoric solution',
|
|
70
|
+
traces: [
|
|
71
|
+
[
|
|
72
|
+
{ type: 't1', label: 'T1L1' },
|
|
73
|
+
{ type: 't2', label: 'T2L1' }
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
{ type: 't1', label: 'T1L1' },
|
|
77
|
+
{ type: 't2', label: 'T2L2' }
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
{ type: 't1', label: 'T1L2' },
|
|
81
|
+
{ type: 't2', label: 'T2L2' }
|
|
82
|
+
]
|
|
83
|
+
],
|
|
84
|
+
labels: ['T1L1 / T2L1', 'T1L1 / T2L2', 'T1L2 / T2L2']
|
|
85
|
+
}
|
|
86
|
+
])('test label derivation: $name', ({ name, traces, labels }) => {
|
|
87
|
+
expect(deriveLabels(tracesToSpecs(traces), (s) => s).map((r) => r.label)).toEqual(labels);
|
|
88
|
+
expect(
|
|
89
|
+
deriveLabels(tracesToSpecs(traces), (s) => s, { includeNativeLabel: true }).map((r) => r.label)
|
|
90
|
+
).toEqual(labels.map((l) => 'Label / ' + l));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('test fallback to native labels in label derivation', () => {
|
|
94
|
+
expect(deriveLabels(tracesToSpecs([[], []]), (s) => s).map((r) => r.label)).toEqual([
|
|
95
|
+
'Label',
|
|
96
|
+
'Label'
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { PObjectSpec } from '@milaboratories/pl-model-common';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export const PAnnotationLabel = 'pl7.app/label';
|
|
5
|
+
export const PAnnotationTrace = 'pl7.app/trace';
|
|
6
|
+
|
|
7
|
+
export type RecordsWithLabel<T> = {
|
|
8
|
+
value: T;
|
|
9
|
+
label: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type LabelDerivationOps = {
|
|
13
|
+
includeNativeLabel?: boolean;
|
|
14
|
+
separator?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const TraceEntry = z.object({
|
|
18
|
+
type: z.string(),
|
|
19
|
+
importance: z.number().optional(),
|
|
20
|
+
id: z.string().optional(),
|
|
21
|
+
label: z.string()
|
|
22
|
+
});
|
|
23
|
+
export type TraceEntry = z.infer<typeof TraceEntry>;
|
|
24
|
+
type FullTraceEntry = TraceEntry & { fullType: string; occurenceIndex: number };
|
|
25
|
+
|
|
26
|
+
export const Trace = z.array(TraceEntry);
|
|
27
|
+
export type Trace = z.infer<typeof Trace>;
|
|
28
|
+
type FullTrace = FullTraceEntry[];
|
|
29
|
+
|
|
30
|
+
const DistancePenalty = 0.001;
|
|
31
|
+
|
|
32
|
+
const LabelType = '__LABEL__';
|
|
33
|
+
const LabelTypeFull = '__LABEL__@1';
|
|
34
|
+
|
|
35
|
+
export function deriveLabels<T>(
|
|
36
|
+
values: T[],
|
|
37
|
+
specExtractor: (obj: T) => PObjectSpec,
|
|
38
|
+
ops: LabelDerivationOps = {}
|
|
39
|
+
): RecordsWithLabel<T>[] {
|
|
40
|
+
const importances = new Map<string, number>();
|
|
41
|
+
|
|
42
|
+
// number of times certain type occured among all of the
|
|
43
|
+
const numberOfRecordsWithType = new Map<string, number>();
|
|
44
|
+
|
|
45
|
+
const enrichedRecords = values.map((value) => {
|
|
46
|
+
const spec = specExtractor(value);
|
|
47
|
+
const label = spec.annotations?.[PAnnotationLabel];
|
|
48
|
+
const traceStr = spec.annotations?.[PAnnotationTrace];
|
|
49
|
+
const trace = (traceStr ? Trace.safeParse(JSON.parse(traceStr)).data : undefined) ?? [];
|
|
50
|
+
|
|
51
|
+
if (label) trace.splice(0, 0, { label, type: LabelType, importance: -2 });
|
|
52
|
+
|
|
53
|
+
const fullTrace: FullTrace = [];
|
|
54
|
+
|
|
55
|
+
const occurences = new Map<string, number>();
|
|
56
|
+
for (let i = trace.length - 1; i >= 0; --i) {
|
|
57
|
+
const { type: typeName } = trace[i];
|
|
58
|
+
const importance = trace[i].importance ?? 0;
|
|
59
|
+
const occurenceIndex = (occurences.get(typeName) ?? 0) + 1;
|
|
60
|
+
occurences.set(typeName, occurenceIndex);
|
|
61
|
+
const fullType = `${typeName}@${occurenceIndex}`;
|
|
62
|
+
numberOfRecordsWithType.set(fullType, (numberOfRecordsWithType.get(fullType) ?? 0) + 1);
|
|
63
|
+
importances.set(
|
|
64
|
+
fullType,
|
|
65
|
+
Math.max(
|
|
66
|
+
importances.get(fullType) ?? Number.NEGATIVE_INFINITY,
|
|
67
|
+
importance - (trace.length - i) * DistancePenalty
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
fullTrace.push({ ...trace[i], fullType, occurenceIndex });
|
|
71
|
+
}
|
|
72
|
+
fullTrace.reverse();
|
|
73
|
+
return {
|
|
74
|
+
value,
|
|
75
|
+
spec,
|
|
76
|
+
label,
|
|
77
|
+
fullTrace
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// excluding repeated types (i.e. ..@2, ..@3, etc.) not found in some records
|
|
82
|
+
const mainTypes: string[] = [];
|
|
83
|
+
// repeated types (i.e. ..@2, ..@3, etc.) not found in some records
|
|
84
|
+
const secondaryTypes: string[] = [];
|
|
85
|
+
|
|
86
|
+
const allTypeRecords = [...importances];
|
|
87
|
+
// sorting: most important types go first
|
|
88
|
+
allTypeRecords.sort(([, i1], [, i2]) => i2 - i1);
|
|
89
|
+
|
|
90
|
+
for (const [typeName] of allTypeRecords) {
|
|
91
|
+
if (typeName.endsWith('@1') || numberOfRecordsWithType.get(typeName) === values.length)
|
|
92
|
+
mainTypes.push(typeName);
|
|
93
|
+
else secondaryTypes.push(typeName);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const calculate = (includedTypes: Set<string>) =>
|
|
97
|
+
enrichedRecords.map((r) => {
|
|
98
|
+
const labelSet = r.fullTrace
|
|
99
|
+
.filter((fm) => includedTypes.has(fm.fullType))
|
|
100
|
+
.map((fm) => fm.label);
|
|
101
|
+
const sep = ops.separator ?? ' / ';
|
|
102
|
+
return {
|
|
103
|
+
label: labelSet.join(sep),
|
|
104
|
+
value: r.value
|
|
105
|
+
} satisfies RecordsWithLabel<T>;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (mainTypes.length === 0) {
|
|
109
|
+
if (secondaryTypes.length !== 0) throw new Error('Assertion error.');
|
|
110
|
+
return calculate(new Set(LabelTypeFull));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//
|
|
114
|
+
// includedTypes = 2
|
|
115
|
+
// * *
|
|
116
|
+
// T0 T1 T2 T3 T4 T5
|
|
117
|
+
// *
|
|
118
|
+
// additinalType = 3
|
|
119
|
+
//
|
|
120
|
+
// Resulting set: T0, T1, T3
|
|
121
|
+
//
|
|
122
|
+
let includedTypes = 0;
|
|
123
|
+
let additinalType = 0;
|
|
124
|
+
while (includedTypes < mainTypes.length) {
|
|
125
|
+
const currentSet = new Set<string>();
|
|
126
|
+
for (let i = 0; i < includedTypes; ++i) currentSet.add(mainTypes[i]);
|
|
127
|
+
currentSet.add(mainTypes[additinalType]);
|
|
128
|
+
|
|
129
|
+
const candidateResult = calculate(currentSet);
|
|
130
|
+
|
|
131
|
+
// checking if labels uniquely separate our records
|
|
132
|
+
if (new Set(candidateResult.map((c) => c.label)).size === values.length) {
|
|
133
|
+
if (ops.includeNativeLabel) {
|
|
134
|
+
currentSet.add(LabelTypeFull);
|
|
135
|
+
return calculate(currentSet);
|
|
136
|
+
} else return candidateResult;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
additinalType++;
|
|
140
|
+
if (additinalType == mainTypes.length) {
|
|
141
|
+
includedTypes++;
|
|
142
|
+
additinalType = includedTypes;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return calculate(new Set([...mainTypes, ...secondaryTypes]));
|
|
147
|
+
}
|