@platforma-sdk/model 1.10.2 → 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.2";
1
+ export declare const PlatformaSDKVersion = "1.12.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/model",
3
- "version": "1.10.2",
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",
@@ -90,33 +90,33 @@ export type PlTableFilterNumberNotEquals = {
90
90
  };
91
91
 
92
92
  /** PlTableFilters numeric filter entry */
93
- export type PlTableFilterNumberGreaterThen = {
93
+ export type PlTableFilterNumberGreaterThan = {
94
94
  /** Predicate type */
95
- type: 'number_greaterThen';
95
+ type: 'number_greaterThan';
96
96
  /** Referense value */
97
97
  reference: number;
98
98
  };
99
99
 
100
100
  /** PlTableFilters numeric filter entry */
101
- export type PlTableFilterNumberGreaterThenOrEqualTo = {
101
+ export type PlTableFilterNumberGreaterThanOrEqualTo = {
102
102
  /** Predicate type */
103
- type: 'number_greaterThenOrEqualTo';
103
+ type: 'number_greaterThanOrEqualTo';
104
104
  /** Referense value */
105
105
  reference: number;
106
106
  };
107
107
 
108
108
  /** PlTableFilters numeric filter entry */
109
- export type PlTableFilterNumberLessThen = {
109
+ export type PlTableFilterNumberLessThan = {
110
110
  /** Predicate type */
111
- type: 'number_lessThen';
111
+ type: 'number_lessThan';
112
112
  /** Referense value */
113
113
  reference: number;
114
114
  };
115
115
 
116
116
  /** PlTableFilters numeric filter entry */
117
- export type PlTableFilterNumberLessThenOrEqualTo = {
117
+ export type PlTableFilterNumberLessThanOrEqualTo = {
118
118
  /** Predicate type */
119
- type: 'number_lessThenOrEqualTo';
119
+ type: 'number_lessThanOrEqualTo';
120
120
  /** Referense value */
121
121
  reference: number;
122
122
  };
@@ -140,10 +140,10 @@ export type PlTableFilterNumber =
140
140
  | PlTableFilterCommon
141
141
  | PlTableFilterNumberEquals
142
142
  | PlTableFilterNumberNotEquals
143
- | PlTableFilterNumberGreaterThen
144
- | PlTableFilterNumberGreaterThenOrEqualTo
145
- | PlTableFilterNumberLessThen
146
- | PlTableFilterNumberLessThenOrEqualTo
143
+ | PlTableFilterNumberGreaterThan
144
+ | PlTableFilterNumberGreaterThanOrEqualTo
145
+ | PlTableFilterNumberLessThan
146
+ | PlTableFilterNumberLessThanOrEqualTo
147
147
  | PlTableFilterNumberBetween;
148
148
  /** All types of PlTableFilters numeric filter entries */
149
149
  export type PlTableFilterNumberType = PlTableFilterNumber['type'];
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
+ }