@platforma-open/milaboratories.humanization-score.model 0.2.0 → 0.3.1

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/package.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "@platforma-open/milaboratories.humanization-score.model",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Block model",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "dependencies": {
9
- "@platforma-sdk/model": "1.77.15",
10
- "@milaboratories/helpers": "1.14.2"
9
+ "@platforma-sdk/model": "1.77.17",
10
+ "@milaboratories/helpers": "1.14.2",
11
+ "@milaboratories/graph-maker": "1.4.4"
11
12
  },
12
13
  "devDependencies": {
13
14
  "@milaboratories/ts-builder": "1.5.0",
14
15
  "@milaboratories/ts-configs": "1.2.3",
15
- "@platforma-sdk/block-tools": "2.9.3",
16
+ "@platforma-sdk/block-tools": "2.10.0",
16
17
  "@platforma-sdk/eslint-config": "1.2.0",
17
18
  "eslint": "^9.25.1",
18
19
  "vitest": "^4.0.7",
package/src/index.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import type { GraphMakerState } from '@milaboratories/graph-maker';
1
2
  import type {
3
+ PColumnIdAndSpec,
4
+ PFrameHandle,
2
5
  PlDataTableStateV2,
3
6
  PlRef,
4
7
  } from '@platforma-sdk/model';
@@ -6,6 +9,7 @@ import {
6
9
  ArrayColumnProvider,
7
10
  BlockModelV3,
8
11
  DataModelBuilder,
12
+ createPFrameForGraphs,
9
13
  createPlDataTableStateV2,
10
14
  createPlDataTableV3,
11
15
  } from '@platforma-sdk/model';
@@ -19,6 +23,7 @@ type OldArgs = {
19
23
 
20
24
  type OldUiState = {
21
25
  tableState: PlDataTableStateV2;
26
+ graphStateHistogram?: GraphMakerState;
22
27
  };
23
28
 
24
29
  export type BlockData = {
@@ -26,17 +31,54 @@ export type BlockData = {
26
31
  inputAnchor?: PlRef;
27
32
  mem?: number;
28
33
  tableState: PlDataTableStateV2;
34
+ // Distribution of the per-clonotype humanness score across the whole dataset.
35
+ graphStateHistogram: GraphMakerState;
29
36
  };
30
37
 
38
+ // Humanness score column name emitted by `clonotype-process.tpl.tengo`.
39
+ export const HUMANNESS_SCORE_COLUMN = 'pl7.app/humannessScore';
40
+
41
+ export const defaultGraphStateHistogram = (): GraphMakerState => ({
42
+ title: 'Humanness Score Distribution',
43
+ template: 'bins',
44
+ currentTab: null,
45
+ axesSettings: {
46
+ other: { binsCount: 20 },
47
+ },
48
+ // Give the bars a solid fill instead of the default white — colour values
49
+ // taken from graph-maker's fixed palette ("Blue").
50
+ layersSettings: {
51
+ bins: { fillColor: '#2D93FA' },
52
+ },
53
+ });
54
+
55
+ // Selectors for the input dataset anchor — shared between `inputOptions`
56
+ // (the dropdown) and `subtitle` (so the default label matches the dataset name).
57
+ const inputSelectors = [{
58
+ axes: [
59
+ { name: 'pl7.app/sampleId' },
60
+ { name: 'pl7.app/vdj/clonotypeKey' },
61
+ ],
62
+ annotations: { 'pl7.app/isAnchor': 'true' },
63
+ }, {
64
+ axes: [
65
+ { name: 'pl7.app/sampleId' },
66
+ { name: 'pl7.app/vdj/scClonotypeKey' },
67
+ ],
68
+ annotations: { 'pl7.app/isAnchor': 'true' },
69
+ }];
70
+
31
71
  const dataModel = new DataModelBuilder()
32
72
  .from<BlockData>('v1')
33
73
  .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({
34
74
  ...args,
35
75
  tableState: uiState.tableState,
76
+ graphStateHistogram: uiState.graphStateHistogram ?? defaultGraphStateHistogram(),
36
77
  }))
37
78
  .init(() => ({
38
79
  customBlockLabel: '',
39
80
  tableState: createPlDataTableStateV2(),
81
+ graphStateHistogram: defaultGraphStateHistogram(),
40
82
  }));
41
83
 
42
84
  export const platforma = BlockModelV3.create(dataModel)
@@ -45,30 +87,20 @@ export const platforma = BlockModelV3.create(dataModel)
45
87
  if (!data.inputAnchor) throw new Error('Input anchor is required');
46
88
 
47
89
  return {
48
- customBlockLabel: data.customBlockLabel || 'Humanness Score',
90
+ // Empty when unset; the workflow falls back to the input dataset name so
91
+ // the provenance trace label matches the block subtitle.
92
+ customBlockLabel: data.customBlockLabel || '',
49
93
  inputAnchor: data.inputAnchor,
50
94
  mem: data.mem,
51
95
  };
52
96
  })
53
97
 
54
98
  .output('inputOptions', (ctx) =>
55
- ctx.resultPool.getOptions([{
56
- axes: [
57
- { name: 'pl7.app/sampleId' },
58
- { name: 'pl7.app/vdj/clonotypeKey' },
59
- ],
60
- annotations: { 'pl7.app/isAnchor': 'true' },
61
- }, {
62
- axes: [
63
- { name: 'pl7.app/sampleId' },
64
- { name: 'pl7.app/vdj/scClonotypeKey' },
65
- ],
66
- annotations: { 'pl7.app/isAnchor': 'true' },
67
- }]),
99
+ ctx.resultPool.getOptions(inputSelectors),
68
100
  )
69
101
 
70
102
  .outputWithStatus('pt', (ctx) => {
71
- const pCols = ctx.outputs?.resolve('outputLiabilities')?.getPColumns();
103
+ const pCols = ctx.outputs?.resolve('outputHumanness')?.getPColumns();
72
104
  if (pCols === undefined) {
73
105
  return undefined;
74
106
  }
@@ -80,14 +112,37 @@ export const platforma = BlockModelV3.create(dataModel)
80
112
  });
81
113
  })
82
114
 
115
+ // --- Score distribution (histogram) ---------------------------------------
116
+ // One row per clonotype, so the histogram counts UNIQUE clonotypes by humanness
117
+ // score — it is deliberately NOT weighted by clonotype abundance. The question it
118
+ // answers is "how many distinct candidates sit below/above a humanness level"
119
+ // (i.e. how much humanization work is there), not "how human is the repertoire by
120
+ // read mass". No human-like threshold line is drawn: this score is a 9-mer
121
+ // fraction rescaled to 0..100, not a cutoff validated against therapeutic mAbs.
122
+ .outputWithStatus('histogramPf', (ctx): PFrameHandle | undefined => {
123
+ const pCols = ctx.outputs?.resolve('outputHumanness')?.getPColumns();
124
+ if (pCols === undefined) return undefined;
125
+ return createPFrameForGraphs(ctx, pCols);
126
+ })
127
+
128
+ .output('histogramPfPcols', (ctx): PColumnIdAndSpec[] | undefined => {
129
+ const pCols = ctx.outputs?.resolve('outputHumanness')?.getPColumns();
130
+ if (pCols === undefined || pCols.length === 0) return undefined;
131
+ return pCols.map((c) => ({ columnId: c.id, spec: c.spec }));
132
+ })
133
+
83
134
  .output('isRunning', (ctx) => ctx.outputs?.getIsReadyOrError() === false)
84
135
 
85
- .title(() => 'Humanness Score')
136
+ .title(() => 'Humanization Score')
86
137
 
87
- .subtitle((ctx) => ctx.data.customBlockLabel || 'Humanness Score')
138
+ .subtitle((ctx) => {
139
+ if (ctx.data.customBlockLabel) return ctx.data.customBlockLabel;
140
+ return 'Humanization Score';
141
+ })
88
142
 
89
143
  .sections((_) => [
90
144
  { type: 'link', href: '/', label: 'Table' },
145
+ { type: 'link', href: '/histogram', label: 'Score Distribution' },
91
146
  ])
92
147
 
93
148
  .done();