@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.
@@ -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.10.12";
1
+ export declare const PlatformaSDKVersion = "1.12.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,YAAY,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,WAAW,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/model",
3
- "version": "1.10.12",
3
+ "version": "1.12.0",
4
4
  "description": "Platforma.bio SDK / Block Model",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",
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
- labelFn: (spec: PObjectSpec, ref: Ref) => string = this.defaultLabelFn
44
+ label?: ((spec: PObjectSpec, ref: Ref) => string) | LabelDerivationOps
44
45
  ): Option[] {
45
- return this.getSpecs()
46
- .entries.filter((s) => predicate(s.obj))
47
- .map((s) => ({
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: labelFn(s.obj, s.ref)
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
+ }