Skip to content

Commit a3bc195

Browse files
authored
Merge pull request #11 from platforma-open/MILAB-6720_accept-variantkey-datasets
Milab-6720: accept variantkey datasets
2 parents 43c6ea3 + 0031d85 commit a3bc195

9 files changed

Lines changed: 393 additions & 32 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@platforma-open/milaboratories.3d-structure-based-liabilities.model': minor
3+
'@platforma-open/milaboratories.3d-structure-based-liabilities.workflow': patch
4+
'@platforma-open/milaboratories.3d-structure-based-liabilities': minor
5+
---
6+
7+
Join the scores table on whichever record-key axis the input uses
8+
9+
The dataset selector already accepted any anchor-marked `pl7.app/structure/pdb` column, so
10+
structures of an `import-vdj-data` bare antibody set — keyed on `pl7.app/variantKey` — could be
11+
picked and scored. Two things downstream of that were still keyed by name.
12+
13+
**The enrichment join was hardcoded to `pl7.app/vdj/scClonotypeKey`.** Label, CDRH3 length and
14+
the clustering columns were queried on that axis literally, so they came back empty for any
15+
input that was not legacy MiXCR single-cell — bulk `pl7.app/vdj/clonotypeKey` included. The
16+
axis name is now read off the scores columns (`recordAxisName`) and the query built from it.
17+
Single-cell behaviour is unchanged: the query is still by axis name and type with no domain,
18+
which is exactly what the constant expressed.
19+
20+
**The viewer-trigger button did not attach** for imported sets: `clonotypeAxisId` matched
21+
`scClonotypeKey` or `clonotypeKey` and returned undefined for `variantKey`. It now matches all
22+
three.
23+
24+
No functional workflow change — the output axis spec is already inherited from the input PDB
25+
column. One panic message that named `scClonotypeKey` now names the record-key axis generically.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@platforma-open/milaboratories.3d-structure-based-liabilities': patch
3+
---
4+
5+
Pass `--registry-serve-url` when publishing the block
6+
7+
`block-tools` made `--registry-serve-url` a required option for `publish`, and the facade's
8+
`prepublishOnly` predates that. With block-tools moving from 2.11.0 to 2.14.3 on this branch, the
9+
release would have failed the way 3d-structure-prediction's already did:
10+
11+
```
12+
error: required option '--registry-serve-url <url>' not specified
13+
```
14+
15+
The component packages publish to npm before the facade runs, so the failure leaves the block
16+
itself unpublished at a version whose parts are already out. Fixed before that happens rather
17+
than after.
18+
19+
The redundant `block-tools pack &&` prefix goes with it: `build` already runs
20+
`shx rm -rf ./block-pack && block-tools pack`, and CI publishes with `build-script-name: 'build'`.

.github/workflows/build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
with:
2525
app-name: 'Block: 3d Structure-Based Liabilities'
2626
app-name-slug: 'block-3d-structure-based-liabilities'
27-
node-version: '20.x'
27+
node-version: '22.x'
2828
build-script-name: 'build'
2929
pnpm-recursive-build: false
3030

.github/workflows/mark-stable.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
uses: milaboratory/github-ci/.github/workflows/block-mark-stable.yaml@v4
1616
with:
1717
app-name: 'Block: 3d Structure Based Liabilities - Mark Stable'
18-
node-version: '20.x'
18+
node-version: '22.x'
1919
npmrc-config: |
2020
{
2121
"registries": {

block/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
],
88
"scripts": {
99
"build": "shx rm -rf ./block-pack && block-tools pack",
10-
"prepublishOnly": "block-tools pack && block-tools publish -r 's3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1'",
10+
"prepublishOnly": "block-tools publish -r s3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1 --registry-serve-url https://blocks.pl-open.science",
1111
"do-pack": "shx rm -f *.tgz && block-tools pack && pnpm pack && shx mv *.tgz package.tgz",
1212
"mark-stable": "block-tools mark-stable -r 's3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1'"
1313
},

model/src/index.ts

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,42 @@ function specFromScores(ctx: ScoresCtx, name: string): PColumnIdAndSpec | undefi
9898
return col ? { columnId: col.id, spec: col.spec } : undefined;
9999
}
100100

101-
const SC_CLONOTYPE_AXIS = { type: "String", name: "pl7.app/vdj/scClonotypeKey" } as const;
101+
/**
102+
* Name of the record-key axis this run is keyed on, read off the scores columns.
103+
*
104+
* It is inherited all the way from the dataset 3D Structure Prediction was pointed at, so it
105+
* is `pl7.app/vdj/scClonotypeKey` for legacy MiXCR single-cell, `pl7.app/vdj/clonotypeKey` for
106+
* bulk, and `pl7.app/variantKey` for import-vdj-data's bare antibody sets. Reading it rather
107+
* than naming it is what lets the enrichment join below work in all three regimes.
108+
*
109+
* Derived from `scoresData` for the same reason `clonotypeAxisId` is: those are the columns
110+
* the table is built from, so their axis is the one the enrichment has to meet.
111+
*/
112+
function recordAxisName(ctx: ScoresCtx): string | undefined {
113+
let cols: ScoresPColumn[] | undefined;
114+
try {
115+
cols = ctx.outputs?.resolve("scoresData")?.getPColumns() as ScoresPColumn[] | undefined;
116+
} catch {
117+
return undefined;
118+
}
119+
return cols?.[0]?.spec.axesSpec[0]?.name;
120+
}
102121

103-
function findOnScClonotype(ctx: ScoresCtx, name: string, valueType: ValueType) {
122+
/**
123+
* Upstream columns to join onto the scores table, matched on the record-key axis.
124+
*
125+
* Queried by axis NAME only, with no domain — the query this replaced hardcoded
126+
* `pl7.app/vdj/scClonotypeKey`, so single-cell behaviour is unchanged and bulk and imported
127+
* sets stop silently coming back empty.
128+
*/
129+
function findOnRecordAxis(ctx: ScoresCtx, name: string, valueType: ValueType) {
130+
const axisName = recordAxisName(ctx);
131+
if (axisName === undefined) return [] as ScoresPColumn[];
104132
return ctx.resultPool.findDataWithCompatibleSpec({
105133
kind: "PColumn",
106134
name,
107135
valueType,
108-
axesSpec: [SC_CLONOTYPE_AXIS],
136+
axesSpec: [{ type: "String", name: axisName }],
109137
}) as ScoresPColumn[];
110138
}
111139

@@ -139,22 +167,23 @@ export const platforma = BlockModelV3.create(dataModel)
139167
// Dataset-level mode, consumed by the mode-specific histogram page and the
140168
// default block label.
141169
.output("detectedMode", (ctx): DetectedMode | undefined => resolveMode(ctx))
142-
// Per-clonotype scalar metrics table. Score columns are primary; upstream
143-
// label / cdrh3Length / cluster columns join on the shared scClonotypeKey
144-
// axis. Mode-specific flag columns default-visible only in their mode.
170+
// Per-record scalar metrics table. Score columns are primary; upstream
171+
// label / cdrh3Length / cluster columns join on the shared record-key axis
172+
// (see `recordAxisName`). Mode-specific flag columns default-visible only in
173+
// their mode.
145174
.outputWithStatus("scoresTable", (ctx) => {
146175
const scoreCols = ctx.outputs?.resolve("scoresData")?.getPColumns() as
147176
| ScoresPColumn[]
148177
| undefined;
149178
if (scoreCols === undefined) return undefined;
150179

151180
const enrich = [
152-
findOnScClonotype(ctx, "pl7.app/structure/cdrh3Length", "Long"),
153-
findOnScClonotype(ctx, "pl7.app/label", "String"),
154-
findOnScClonotype(ctx, "pl7.app/clusterId", "String"),
155-
findOnScClonotype(ctx, "pl7.app/structure/clustering/isCentroid", "Int"),
156-
findOnScClonotype(ctx, "pl7.app/structure/clustering/tmDistanceToCentroid", "Double"),
157-
findOnScClonotype(ctx, "pl7.app/structure/clustering/tmScoreToCentroid", "Double"),
181+
findOnRecordAxis(ctx, "pl7.app/structure/cdrh3Length", "Long"),
182+
findOnRecordAxis(ctx, "pl7.app/label", "String"),
183+
findOnRecordAxis(ctx, "pl7.app/clusterId", "String"),
184+
findOnRecordAxis(ctx, "pl7.app/structure/clustering/isCentroid", "Int"),
185+
findOnRecordAxis(ctx, "pl7.app/structure/clustering/tmDistanceToCentroid", "Double"),
186+
findOnRecordAxis(ctx, "pl7.app/structure/clustering/tmScoreToCentroid", "Double"),
158187
].flat();
159188

160189
const variants = [
@@ -218,8 +247,8 @@ export const platforma = BlockModelV3.create(dataModel)
218247
if (!parsed.isComplete) return undefined;
219248
return parsed.data;
220249
})
221-
// Axis id of the clonotype-key axis (scClonotypeKey for single-cell,
222-
// clonotypeKey for bulk), used to attach the viewer-trigger
250+
// Axis id of the record-key axis (scClonotypeKey for single-cell,
251+
// clonotypeKey for bulk, variantKey for imported sets), used to attach the viewer-trigger
223252
// button to that column in the table. Derive from the actual scoresData
224253
// column (which is what populates the table), so the AxisId matches the
225254
// table column's id byte-for-byte. Deriving from the PDB col's spec
@@ -233,11 +262,15 @@ export const platforma = BlockModelV3.create(dataModel)
233262
return undefined;
234263
}
235264
const first = cols?.[0];
236-
// The clonotype-key axis is named `scClonotypeKey` for single-cell data
237-
// and `clonotypeKey` for bulk; match either so the viewer-trigger button
238-
// attaches in both regimes (the scores column is single-keyed on it).
265+
// The record-key axis is named `scClonotypeKey` for single-cell data,
266+
// `clonotypeKey` for bulk and `variantKey` for imported antibody sets; match
267+
// any of them so the viewer-trigger button attaches in every regime (the
268+
// scores column is single-keyed on it).
239269
const found = first?.spec.axesSpec.find(
240-
(a) => a.name === "pl7.app/vdj/scClonotypeKey" || a.name === "pl7.app/vdj/clonotypeKey",
270+
(a) =>
271+
a.name === "pl7.app/vdj/scClonotypeKey" ||
272+
a.name === "pl7.app/vdj/clonotypeKey" ||
273+
a.name === "pl7.app/variantKey",
241274
);
242275
if (!found) return undefined;
243276
return getAxisId(found);

0 commit comments

Comments
 (0)