Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/vhh-presence-driven-chain-menu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@platforma-open/milaboratories.vj-usage.model": patch
"@platforma-open/milaboratories.vj-usage.ui": patch
"@platforma-open/milaboratories.vj-usage": patch
---

Only offer single-cell chains that actually have columns. The chain selector was
built from the receptor type alone, so on heavy-chain-only (VHH) single-cell input
it still offered "Light" — picking it failed the workflow with `expected exactly 1
V gene column for chain, got 0`. A new `availableScChains` model output reports the
chains present for the selected dataset; the selector now lists only those (and is
hidden when a single chain leaves nothing to choose).
39 changes: 38 additions & 1 deletion model/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { BlockModelV3, createPFrameForGraphs } from "@platforma-sdk/model";
import { blockDataModel } from "./dataModel";
import type { BlockArgs } from "./types";

export { getDefaultBlockLabel } from "./label";
export { blockDataModel } from "./dataModel";
export { getDefaultBlockLabel } from "./label";
export type { BlockArgs, BlockData } from "./types";

export const platforma = BlockModelV3.create(blockDataModel)
Expand Down Expand Up @@ -45,6 +45,43 @@ export const platforma = BlockModelV3.create(blockDataModel)
return ctx.resultPool.getPColumnSpecByRef(ctx.data.datasetRef);
})

// Single-cell IG chain letters ("A" = heavy, "B" = light) that actually have columns
// for the selected dataset. Returns undefined — meaning "don't filter" — for bulk
// data, non-IG receptors, and while the pool is resolving.
.output("availableScChains", (ctx) => {
const ref = ctx.data.datasetRef;
if (ref === undefined) return undefined;

const spec = ctx.resultPool.getPColumnSpecByRef(ref);
// Only single-cell IG can be single-chain (heavy-only VHH): bulk has no chain axis,
// and single-cell TCR is always paired — nothing to filter in those cases.
if (spec?.axesSpec[1]?.name !== "pl7.app/vdj/scClonotypeKey") return undefined;
if (spec.axesSpec[1]?.domain?.["pl7.app/vdj/receptor"] !== "IG") return undefined;
Comment thread
julenmendieta marked this conversation as resolved.

// Ask for the V-gene-hit column per chain
const vGeneCols = ctx.resultPool.getAnchoredPColumns(
{ main: ref },
[
{
axes: [{ anchor: "main", idx: 1 }],
name: "pl7.app/vdj/geneHit",
domain: { "pl7.app/vdj/reference": "VGene" },
},
],
{ ignoreMissingDomains: true },
);
if (vGeneCols === undefined) return undefined; // pool still resolving

const chains = new Set<string>();
for (const col of vGeneCols) {
const domain = col.spec.domain;
if (domain?.["pl7.app/vdj/scClonotypeChain/index"] !== "primary") continue;
const letter = domain?.["pl7.app/vdj/scClonotypeChain"];
if (letter) chains.add(letter);
}
return [...chains].sort();
})

.outputWithStatus("pf", (ctx) => {
const pCols = ctx.outputs?.resolve("pf")?.getPColumns();
if (pCols === undefined) {
Expand Down
Loading
Loading