@platforma-sdk/model 1.69.0 → 1.70.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 (67) hide show
  1. package/dist/columns/column_collection_builder.cjs +1 -4
  2. package/dist/columns/column_collection_builder.cjs.map +1 -1
  3. package/dist/columns/column_collection_builder.d.ts +0 -2
  4. package/dist/columns/column_collection_builder.d.ts.map +1 -1
  5. package/dist/columns/column_collection_builder.js +1 -4
  6. package/dist/columns/column_collection_builder.js.map +1 -1
  7. package/dist/columns/ctx_column_sources.cjs +5 -8
  8. package/dist/columns/ctx_column_sources.cjs.map +1 -1
  9. package/dist/columns/ctx_column_sources.d.ts +4 -7
  10. package/dist/columns/ctx_column_sources.d.ts.map +1 -1
  11. package/dist/columns/ctx_column_sources.js +5 -8
  12. package/dist/columns/ctx_column_sources.js.map +1 -1
  13. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +1 -4
  14. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
  15. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +1 -4
  16. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
  17. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +1 -2
  18. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -1
  19. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +1 -2
  20. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -1
  21. package/dist/components/PlDataTable/createPlDataTable/utils.cjs +1 -4
  22. package/dist/components/PlDataTable/createPlDataTable/utils.cjs.map +1 -1
  23. package/dist/components/PlDataTable/createPlDataTable/utils.js +1 -4
  24. package/dist/components/PlDataTable/createPlDataTable/utils.js.map +1 -1
  25. package/dist/components/PlDatasetSelector/build_dataset_options.cjs +23 -11
  26. package/dist/components/PlDatasetSelector/build_dataset_options.cjs.map +1 -1
  27. package/dist/components/PlDatasetSelector/build_dataset_options.d.ts +9 -2
  28. package/dist/components/PlDatasetSelector/build_dataset_options.d.ts.map +1 -1
  29. package/dist/components/PlDatasetSelector/build_dataset_options.js +22 -11
  30. package/dist/components/PlDatasetSelector/build_dataset_options.js.map +1 -1
  31. package/dist/components/PlDatasetSelector/dataset_selection.cjs +20 -0
  32. package/dist/components/PlDatasetSelector/dataset_selection.cjs.map +1 -0
  33. package/dist/components/PlDatasetSelector/dataset_selection.d.ts +23 -0
  34. package/dist/components/PlDatasetSelector/dataset_selection.d.ts.map +1 -0
  35. package/dist/components/PlDatasetSelector/dataset_selection.js +19 -0
  36. package/dist/components/PlDatasetSelector/dataset_selection.js.map +1 -0
  37. package/dist/components/PlDatasetSelector/enrichment_discovery.cjs +75 -0
  38. package/dist/components/PlDatasetSelector/enrichment_discovery.cjs.map +1 -0
  39. package/dist/components/PlDatasetSelector/enrichment_discovery.js +73 -0
  40. package/dist/components/PlDatasetSelector/enrichment_discovery.js.map +1 -0
  41. package/dist/components/PlDatasetSelector/index.cjs +1 -0
  42. package/dist/components/PlDatasetSelector/index.d.ts +1 -0
  43. package/dist/components/PlDatasetSelector/index.js +1 -0
  44. package/dist/components/index.cjs +1 -0
  45. package/dist/components/index.d.ts +1 -0
  46. package/dist/components/index.js +1 -0
  47. package/dist/index.cjs +3 -0
  48. package/dist/index.d.ts +2 -1
  49. package/dist/index.js +2 -1
  50. package/dist/labels/derive_distinct_tooltips.cjs +0 -3
  51. package/dist/labels/derive_distinct_tooltips.cjs.map +1 -1
  52. package/dist/labels/derive_distinct_tooltips.js +0 -3
  53. package/dist/labels/derive_distinct_tooltips.js.map +1 -1
  54. package/dist/package.cjs +1 -1
  55. package/dist/package.js +1 -1
  56. package/package.json +9 -9
  57. package/src/columns/column_collection_builder.ts +0 -3
  58. package/src/columns/ctx_column_sources.ts +6 -12
  59. package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +0 -1
  60. package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +0 -1
  61. package/src/components/PlDataTable/createPlDataTable/utils.ts +0 -1
  62. package/src/components/PlDatasetSelector/build_dataset_options.ts +48 -17
  63. package/src/components/PlDatasetSelector/dataset_selection.ts +37 -0
  64. package/src/components/PlDatasetSelector/enrichment_discovery.ts +111 -0
  65. package/src/components/PlDatasetSelector/index.ts +1 -0
  66. package/src/labels/derive_distinct_tooltips.test.ts +6 -16
  67. package/src/labels/derive_distinct_tooltips.ts +0 -3
@@ -9,32 +9,26 @@ import { ResourceTypeName } from "@milaboratories/pl-model-common";
9
9
  import type { ValueOf } from "@milaboratories/helpers";
10
10
 
11
11
  /**
12
- * Collect ColumnSnapshotProviders from all render context sources:
13
- *
14
- * - **resultPool** all upstream columns (always included)
15
- * - **outputs** PFrame fields from block execution outputs
16
- * - **prerun** — PFrame fields from prerun/staging results
17
- *
18
- * Returns an array of providers suitable for `ColumnCollectionBuilder.addSource()`.
12
+ * Collect ColumnSnapshotProviders from `outputs`, `prerun`, and
13
+ * `resultPool` in that order. Dedup keeps the first occurrence per
14
+ * `NativePObjectId`, so a block re-publishing its own columns keeps
15
+ * the `outputs`-rooted canonical id instead of the result-pool variant.
19
16
  */
20
17
  export function collectCtxColumnSnapshotProviders(ctx: RenderCtxBase): ColumnSnapshotProvider[] {
21
18
  const providers: ColumnSnapshotProvider[] = [];
22
19
 
23
- // ResultPool — all upstream columns
24
- providers.push(new ResultPoolColumnSnapshotProvider(ctx.resultPool));
25
-
26
- // Outputs — each PFrame-like output field becomes a provider
27
20
  const outputs = ctx.outputs;
28
21
  if (outputs) {
29
22
  providers.push(...collectPFrameProviders(outputs));
30
23
  }
31
24
 
32
- // Prerun — same treatment as outputs
33
25
  const prerun = ctx.prerun;
34
26
  if (prerun) {
35
27
  providers.push(...collectPFrameProviders(prerun));
36
28
  }
37
29
 
30
+ providers.push(new ResultPoolColumnSnapshotProvider(ctx.resultPool));
31
+
38
32
  return providers;
39
33
  }
40
34
 
@@ -405,7 +405,6 @@ function buildSecondaryGroups(
405
405
  entries: [
406
406
  ...lc.path.map((s) => ({
407
407
  column: resolveSnapshot(s.linker),
408
- qualifications: s.qualifications,
409
408
  })),
410
409
  { column: resolveSnapshot(lc.column), qualifications: lc.qualifications.forHit },
411
410
  ],
@@ -102,7 +102,6 @@ function mapToTableColumnVariants(
102
102
  path: variant.path.map((p) => ({
103
103
  type: "linker",
104
104
  column: p.linker.id,
105
- qualifications: p.qualifications,
106
105
  })),
107
106
  columnQualifications: variant.qualifications.forHit,
108
107
  queriesQualifications: variant.qualifications.forQueries,
@@ -241,7 +241,6 @@ export function deriveAllLabels(options: {
241
241
  spec: c.spec,
242
242
  linkerPath: c.linkerPath?.map((step) => ({
243
243
  spec: step.linker.spec,
244
- qualifications: step.qualifications,
245
244
  })),
246
245
  qualifications: c.qualifications,
247
246
  }));
@@ -1,20 +1,25 @@
1
- import type {
2
- DatasetOption,
3
- Option,
4
- PColumnSelector,
5
- PObjectSpec,
6
- } from "@milaboratories/pl-model-common";
1
+ import type { MultiColumnSelector, Option, PObjectSpec } from "@milaboratories/pl-model-common";
2
+ import { multiColumnSelectorsToPredicate } from "@milaboratories/pl-model-common";
7
3
  import type { DeriveLabelsOptions } from "../../labels/derive_distinct_labels";
8
4
  import type { RenderCtxBase } from "../../render";
9
5
  import { ColumnCollectionBuilder } from "../../columns/column_collection_builder";
10
6
  import { collectCtxColumnSnapshotProviders } from "../../columns/ctx_column_sources";
7
+ import type { DatasetOption } from "./dataset_selection";
11
8
  import { buildRefMap, filterMatchesToOptions, findFilterColumns } from "./filter_discovery";
9
+ import { enrichmentVariantsToRefs, findEnrichmentColumns } from "./enrichment_discovery";
12
10
 
13
11
  export type BuildDatasetOptions = {
14
12
  /** Which result pool columns qualify as datasets. Defaults to all. */
15
- selector?: PColumnSelector | PColumnSelector[] | ((spec: PObjectSpec) => boolean);
13
+ primary?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean);
16
14
  /** Formatting options for filter labels. */
17
15
  labelOptions?: DeriveLabelsOptions;
16
+ /**
17
+ * Enables enrichment discovery and filters hits attached to
18
+ * `DatasetOption.enrichments`. Use `() => true` to accept all; omit to disable.
19
+ */
20
+ withEnrichments?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean);
21
+ /** Maximum linker hops considered. Only used when `withEnrichments` is set. */
22
+ enrichmentMaxHops?: number;
18
23
  };
19
24
 
20
25
  /**
@@ -27,28 +32,54 @@ export function buildDatasetOptions(
27
32
  ctx: RenderCtxBase,
28
33
  opts?: BuildDatasetOptions,
29
34
  ): DatasetOption[] | undefined {
30
- const predicate = opts?.selector ?? (() => true);
31
- const options = ctx.resultPool.getOptions(predicate, { refsWithEnrichments: true });
35
+ const primary = opts?.primary;
36
+ const primaryPredicate =
37
+ primary === undefined
38
+ ? () => true
39
+ : typeof primary === "function"
40
+ ? primary
41
+ : multiColumnSelectorsToPredicate(primary);
42
+ const options = ctx.resultPool.getOptions(primaryPredicate, { refsWithEnrichments: true });
32
43
  if (options.length === 0) return [];
33
44
 
34
45
  const columnSources = collectCtxColumnSnapshotProviders(ctx);
35
46
  const refMap = buildRefMap(ctx.resultPool.getSpecs().entries);
36
47
  const pframeSpec = ctx.getService("pframeSpec");
37
48
 
38
- return options.map((o: Option): DatasetOption => {
39
- const datasetSpec = ctx.resultPool.getPColumnSpecByRef(o.ref);
40
- if (!datasetSpec) return o;
49
+ return options.map((primary: Option): DatasetOption => {
50
+ const datasetSpec = ctx.resultPool.getPColumnSpecByRef(primary.ref);
51
+ if (!datasetSpec) return { primary };
41
52
 
42
53
  const builder = new ColumnCollectionBuilder(pframeSpec);
43
54
  for (const src of columnSources) builder.addSource(src);
44
55
  const collection = builder.build({ anchors: { main: datasetSpec } });
45
- if (!collection) return o;
56
+ if (!collection) return { primary };
46
57
 
47
58
  try {
48
- const matches = findFilterColumns(collection);
49
- if (matches.length === 0) return o;
50
- const filters = filterMatchesToOptions(matches, refMap, opts?.labelOptions);
51
- return { ...o, filters };
59
+ const filterMatches = findFilterColumns(collection);
60
+ const filters =
61
+ filterMatches.length === 0
62
+ ? undefined
63
+ : filterMatchesToOptions(filterMatches, refMap, opts?.labelOptions);
64
+
65
+ let enrichments;
66
+ if (opts?.withEnrichments !== undefined) {
67
+ const enrichmentVariants = findEnrichmentColumns(collection, {
68
+ maxHops: opts.enrichmentMaxHops,
69
+ ...(typeof opts.withEnrichments === "function"
70
+ ? { predicate: opts.withEnrichments }
71
+ : { include: opts.withEnrichments }),
72
+ });
73
+ if (enrichmentVariants.length > 0) {
74
+ enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts.labelOptions);
75
+ }
76
+ }
77
+
78
+ return {
79
+ primary,
80
+ ...(filters !== undefined && filters.length > 0 ? { filters } : {}),
81
+ ...(enrichments !== undefined && enrichments.length > 0 ? { enrichments } : {}),
82
+ };
52
83
  } finally {
53
84
  collection.dispose();
54
85
  }
@@ -0,0 +1,37 @@
1
+ import type { LabeledEnrichmentRefs, Option, PrimaryRef } from "@milaboratories/pl-model-common";
2
+
3
+ /** Dataset picker entry: user picks {@link primary}, gets {@link enrichments} attached. */
4
+ export type DatasetOption = {
5
+ readonly primary: Option;
6
+ readonly filters?: readonly Option[];
7
+ readonly enrichments?: LabeledEnrichmentRefs;
8
+ };
9
+
10
+ /**
11
+ * Picked dataset bundle emitted by `PlDatasetSelector`. Stored opaquely in
12
+ * block data; block authors unbundle inside their args resolver.
13
+ */
14
+ export type DatasetSelection = {
15
+ readonly __isDatasetSelection: "v1";
16
+ readonly primary: PrimaryRef;
17
+ readonly enrichments?: LabeledEnrichmentRefs;
18
+ };
19
+
20
+ export function isDatasetSelection(value: unknown): value is DatasetSelection {
21
+ return (
22
+ typeof value === "object" &&
23
+ value !== null &&
24
+ (value as { __isDatasetSelection?: unknown }).__isDatasetSelection === "v1" &&
25
+ "primary" in value
26
+ );
27
+ }
28
+
29
+ export function createDatasetSelection(
30
+ primary: PrimaryRef,
31
+ enrichments?: LabeledEnrichmentRefs,
32
+ ): DatasetSelection {
33
+ if (enrichments !== undefined && enrichments.length > 0) {
34
+ return { __isDatasetSelection: "v1", primary, enrichments };
35
+ }
36
+ return { __isDatasetSelection: "v1", primary };
37
+ }
@@ -0,0 +1,111 @@
1
+ import { Annotation, createEnrichmentRef } from "@milaboratories/pl-model-common";
2
+ import type {
3
+ EnrichmentStep,
4
+ LabeledEnrichmentRef,
5
+ LabeledEnrichmentRefs,
6
+ MultiColumnSelector,
7
+ PObjectId,
8
+ PObjectSpec,
9
+ } from "@milaboratories/pl-model-common";
10
+ import type {
11
+ AnchoredColumnCollection,
12
+ ColumnVariant,
13
+ } from "../../columns/column_collection_builder";
14
+ import {
15
+ deriveDistinctLabels,
16
+ type DeriveLabelsOptions,
17
+ type Entry,
18
+ } from "../../labels/derive_distinct_labels";
19
+
20
+ /**
21
+ * True for global-form ids — `canonicalize({__isRef: true, blockId, name})` —
22
+ * which the workflow can resolve via bquery. Local-form ids (`resolvePath`)
23
+ * fail this check and are excluded from auto-discovery; prerun/outputs hops
24
+ * must be supplied as resolved `{spec, data}` instead.
25
+ */
26
+ function isGloballyAddressable(id: PObjectId): boolean {
27
+ try {
28
+ const decoded = JSON.parse(id);
29
+ return (
30
+ typeof decoded === "object" &&
31
+ decoded !== null &&
32
+ decoded.__isRef === true &&
33
+ typeof decoded.blockId === "string" &&
34
+ typeof decoded.name === "string"
35
+ );
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Linker-reached hits attached to the anchor primary. Drops zero-hop variants
43
+ * (filters / the primary itself) and structural hits (subset, linker, label
44
+ * columns). Narrow further with `include` selectors or a `predicate`.
45
+ */
46
+ export function findEnrichmentColumns(
47
+ collection: AnchoredColumnCollection,
48
+ options?: {
49
+ maxHops?: number;
50
+ include?: MultiColumnSelector | MultiColumnSelector[];
51
+ predicate?: (spec: PObjectSpec) => boolean;
52
+ },
53
+ ): ColumnVariant[] {
54
+ const include =
55
+ options?.include === undefined
56
+ ? undefined
57
+ : Array.isArray(options.include)
58
+ ? options.include
59
+ : [options.include];
60
+ const variants = collection.findColumnVariants({
61
+ mode: "enrichment",
62
+ maxHops: options?.maxHops ?? 4,
63
+ include,
64
+ exclude: [
65
+ { annotations: { [Annotation.IsSubset]: "true" } },
66
+ { annotations: { [Annotation.IsLinkerColumn]: "true" } },
67
+ { name: Annotation.Label },
68
+ ],
69
+ });
70
+ const predicate = options?.predicate;
71
+ return variants.filter((v) => {
72
+ if (v.path.length === 0) return false;
73
+ if (predicate !== undefined && !predicate(v.column.spec)) return false;
74
+ if (!isGloballyAddressable(v.column.id)) return false;
75
+ if (v.path.some((p) => !isGloballyAddressable(p.linker.id))) return false;
76
+ return true;
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Pair each variant with a path-disambiguated label (so export headers stay
82
+ * unique) and carry hit/linker `PObjectId`s through verbatim. Propagates
83
+ * `qualifications.forHit`; `forQueries` is re-derived by the table builder.
84
+ */
85
+ export function enrichmentVariantsToRefs(
86
+ variants: ColumnVariant[],
87
+ labelOptions?: DeriveLabelsOptions,
88
+ ): LabeledEnrichmentRefs {
89
+ if (variants.length === 0) return [];
90
+
91
+ const entries: Entry[] = variants.map((variant) => ({
92
+ spec: variant.column.spec,
93
+ linkerPath: variant.path.map((p) => ({ spec: p.linker.spec })),
94
+ qualifications: variant.qualifications,
95
+ }));
96
+ const labels = deriveDistinctLabels(entries, labelOptions);
97
+
98
+ return variants.map((variant, i): LabeledEnrichmentRef => {
99
+ const path: EnrichmentStep[] = variant.path.map((step) => ({
100
+ type: "linker",
101
+ linker: step.linker.id,
102
+ }));
103
+ return {
104
+ ref: createEnrichmentRef(variant.column.id, {
105
+ path,
106
+ qualifications: variant.qualifications.forHit,
107
+ }),
108
+ label: labels[i],
109
+ };
110
+ });
111
+ }
@@ -1,2 +1,3 @@
1
+ export * from "./dataset_selection";
1
2
  export * from "./filter_discovery";
2
3
  export * from "./build_dataset_options";
@@ -34,12 +34,8 @@ function linkerSnapshot(name: string, label?: string): ColumnSnapshot<PObjectId>
34
34
  };
35
35
  }
36
36
 
37
- function pathStep(
38
- linkerName: string,
39
- qualifications: AxisQualification[],
40
- label?: string,
41
- ): MatchVariant["path"][number] {
42
- return { linker: linkerSnapshot(linkerName, label), qualifications };
37
+ function pathStep(linkerName: string, label?: string): MatchVariant["path"][number] {
38
+ return { linker: linkerSnapshot(linkerName, label) };
43
39
  }
44
40
 
45
41
  describe("deriveDistinctTooltips", () => {
@@ -63,16 +59,13 @@ describe("deriveDistinctTooltips", () => {
63
59
  const entries: TooltipEntry[] = [
64
60
  {
65
61
  spec: createSpec("hit_col", "Hit Col"),
66
- linkerPath: [
67
- pathStep("linker_a", [axisQualification("sample", { batch: "A" })], "Linker A"),
68
- ],
62
+ linkerPath: [pathStep("linker_a", "Linker A")],
69
63
  },
70
64
  ];
71
65
  const [tooltip] = deriveDistinctTooltips(entries);
72
66
  expect(tooltip).toBeDefined();
73
67
  expect(tooltip).toContain("Origin path");
74
68
  expect(tooltip).toContain("linker 1: Linker A");
75
- expect(tooltip).toContain("qualifies: sample context: batch=A");
76
69
  expect(tooltip).toContain("hit column: Hit Col");
77
70
  });
78
71
 
@@ -146,7 +139,7 @@ describe("deriveDistinctTooltips", () => {
146
139
  {
147
140
  spec: createSpec("col1", "Col 1"),
148
141
  qualifications: { forQueries: {}, forHit: [] },
149
- linkerPath: [pathStep("linker_a", [], "Linker A")],
142
+ linkerPath: [pathStep("linker_a", "Linker A")],
150
143
  },
151
144
  ];
152
145
  const [tooltip] = deriveDistinctTooltips(entries);
@@ -157,10 +150,7 @@ describe("deriveDistinctTooltips", () => {
157
150
  const entries: TooltipEntry[] = [
158
151
  {
159
152
  spec: createSpec("hit_col", "Hit Col"),
160
- linkerPath: [
161
- pathStep("linker_a", [], "Linker A"),
162
- pathStep("linker_b", [axisQualification("sample", { batch: "B" })], "Linker B"),
163
- ],
153
+ linkerPath: [pathStep("linker_a", "Linker A"), pathStep("linker_b", "Linker B")],
164
154
  },
165
155
  ];
166
156
  const [tooltip] = deriveDistinctTooltips(entries);
@@ -174,7 +164,7 @@ describe("deriveDistinctTooltips", () => {
174
164
  spec: createSpec("hit_col", "Hit"),
175
165
  variantIndex: 2,
176
166
  variantCount: 2,
177
- linkerPath: [pathStep("linker_a", [axisQualification("sample", { batch: "B" })], "LA")],
167
+ linkerPath: [pathStep("linker_a", "LA")],
178
168
  qualifications: {
179
169
  forQueries: { ["main" as PObjectId]: [axisQualification("sample", { batch: "B" })] },
180
170
  forHit: [axisQualification("sample", { batch: "B" })],
@@ -46,7 +46,6 @@ function formatTooltip(entry: TooltipEntry): undefined | string {
46
46
  }
47
47
 
48
48
  const BULLET_1 = " • ";
49
- const SUB_BULLET = " ";
50
49
 
51
50
  function formatHeader(entry: TooltipEntry): undefined | string {
52
51
  const lines: string[] = [];
@@ -67,8 +66,6 @@ function formatOriginPath(entry: TooltipEntry): undefined | string {
67
66
  readAnnotation(step.linker.spec, Annotation.Label) ??
68
67
  step.linker.spec.name;
69
68
  lines.push(`${BULLET_1}linker ${i + 1}: ${label}`);
70
- const qs = formatAxisQualifications(step.qualifications);
71
- if (qs !== undefined) lines.push(`${SUB_BULLET}qualifies: ${qs}`);
72
69
  });
73
70
  const hitName = readAnnotation(entry.spec, Annotation.Label) ?? entry.spec.name;
74
71
  lines.push(`${BULLET_1}hit column: ${hitName}`);