@@ -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