@platforma-open/milaboratories.top-antibodies 4.3.0 → 4.3.2

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/dist/index.js CHANGED
@@ -13996,13 +13996,57 @@ function computeDefaultFilters(scores, anchorRef) {
13996
13996
  }
13997
13997
  return defaultFilters;
13998
13998
  }
13999
+ /**
14000
+ * Which pipeline produced the anchor's records, from the run-id key on its record axis:
14001
+ *
14002
+ * pl7.app/peptide/extractionRunId peptide-extraction
14003
+ * pl7.app/repertoire/extractionRunId synthetic-repertoire-profiler
14004
+ * pl7.app/vdj/clonotypingRunId MiXCR, and import-vdj-data's imported sets
14005
+ *
14006
+ * Reads the domain, not the axis name: clonotypeKey and scClonotypeKey are migrating to
14007
+ * pl7.app/variantKey, after which the name discriminates nothing. The axis-name checks below are
14008
+ * a fallback for producers that have not migrated yet.
14009
+ *
14010
+ * Does not say how a receptor dataset arrived. Uploaded sequences and MiXCR output are both
14011
+ * `vdj`. Whether specific columns exist is a separate question — see {@link hasGeneCalls}.
14012
+ */
14013
+ function recordSource(anchorSpec) {
14014
+ const keyAxis = anchorSpec.axesSpec[1];
14015
+ if (keyAxis === void 0) return "unknown";
14016
+ const domain = keyAxis.domain ?? {};
14017
+ if (domain["pl7.app/peptide/extractionRunId"] !== void 0) return "peptide";
14018
+ if (domain["pl7.app/repertoire/extractionRunId"] !== void 0) return "amplicon";
14019
+ if (domain["pl7.app/vdj/clonotypingRunId"] !== void 0) return "vdj";
14020
+ if (keyAxis.name === "pl7.app/vdj/clonotypeKey" || keyAxis.name === "pl7.app/vdj/scClonotypeKey") return "vdj";
14021
+ return "unknown";
14022
+ }
14023
+ /** True for the two producers whose records are peptides or synthetic variants, not receptors. */
14024
+ function isPeptideOrAmplicon(anchorSpec) {
14025
+ const source = recordSource(anchorSpec);
14026
+ return source === "peptide" || source === "amplicon";
14027
+ }
14028
+ /**
14029
+ * Whether the dataset carries V/J gene calls — needed by CDR3 V Spectratype, V/J Gene Usage and
14030
+ * Kabat renumbering.
14031
+ *
14032
+ * Separate from {@link recordSource} on purpose: an uploaded set and a MiXCR set are both `vdj`,
14033
+ * but only MiXCR's has gene calls.
14034
+ *
14035
+ * TEMPORARY. This infers from the axis, and no variantKey producer emits gene calls today. When
14036
+ * MiXCR migrates to that axis this returns false for MiXCR too and both sections silently
14037
+ * disappear. Nothing structural separates the cases then — same axis name, same
14038
+ * clonotypingRunId — so replace it with a result-pool query for the gene columns.
14039
+ */
14040
+ function hasGeneCalls(anchorSpec) {
14041
+ return anchorSpec.axesSpec[1]?.name !== "pl7.app/variantKey";
14042
+ }
13999
14043
  function computePresets(scores, defaultFilters, anchorRef, anchorSpec) {
14000
- const isPeptide = anchorSpec.axesSpec[1]?.name === "pl7.app/variantKey";
14044
+ const usesPeptidePreset = isPeptideOrAmplicon(anchorSpec);
14001
14045
  const repertoireScore = scores.find((s) => s.getSpec().name === REPERTOIRE_SCORE_COLUMN_NAME);
14002
14046
  const hasRepertoireScore = repertoireScore !== void 0;
14003
14047
  const isEnrichmentColumn = (name) => name.startsWith("pl7.app/enrichment") || name.startsWith("pl7.app/vdj/enrichment");
14004
14048
  const hasEnrichmentScores = scores.some((s) => isEnrichmentColumn(s.getSpec().name));
14005
- const detectedPreset = isPeptide ? "peptide" : hasRepertoireScore ? "in-vivo" : hasEnrichmentScores ? "in-vitro" : void 0;
14049
+ const detectedPreset = usesPeptidePreset ? "peptide" : hasRepertoireScore ? "in-vivo" : hasEnrichmentScores ? "in-vitro" : void 0;
14006
14050
  const defaultRankingOrder = scores.filter((s) => s.getSpec().valueType !== "String").filter((s) => !hasRepertoireScore || s.getSpec().name !== "pl7.app/vdj/repertoireScore").filter((s) => !hasRepertoireScore || !IN_VIVO_MUTATION_COLUMNS.has(s.getSpec().name)).map((s) => ({
14007
14051
  id: `default-rank-${s.id}`,
14008
14052
  value: matchToColumnId(s, anchorRef),
@@ -14471,14 +14515,14 @@ const CLUSTER_ID_SELECTORS = [...CLUSTER_ID_AXIS_NAMES].map((name) => ({ name: e
14471
14515
  const OWN_PLUMBING_SELECTORS = [{ name: exactMatch("pl7.app/selectionStage") }, { name: exactMatch("pl7.app/lead-selection") }];
14472
14516
  /**
14473
14517
  * Adapt column recipes to `PColumn`s for the helpers that still take
14474
- * materialised `PColumn[]` and have no id form (`createPFrameForGraphs`,
14475
- * `PColumnIdAndSpec`).
14518
+ * materialised `PColumn[]` and have no id form (`getRelatedColumns`,
14519
+ * `createPFrameForGraphs`, `PColumnIdAndSpec`).
14476
14520
  *
14477
14521
  * Only bare leaves qualify: `PColumn.id` is typed `PObjectId`, which no wrapper
14478
14522
  * recipe carries — hence `isDataColumn` rather than `hasReachableData`, which
14479
- * also admits spec-overrides over a leaf. Deduped by id: the same leaf can be
14480
- * reachable via more than one path and `createPFrameForGraphs` rejects
14481
- * duplicates.
14523
+ * also admits spec-overrides over a leaf. Deduped by id: these lists travel as
14524
+ * block columns, and a repeated id aborts the collection scan inside
14525
+ * `getRelatedColumns`.
14482
14526
  */
14483
14527
  function toGraphColumns(recipes) {
14484
14528
  return dedupColumns(recipes.filter(isDataColumn).map((c) => ({
@@ -14492,32 +14536,38 @@ function accessorGraphColumns(accessor) {
14492
14536
  if (!accessor) return void 0;
14493
14537
  return toGraphColumns(ColumnsCollection([accessor]).getColumns());
14494
14538
  }
14539
+ function umapPoolColumns(ctx) {
14540
+ const clonotypeAxisName = getSpecByRef(getInputAnchorRef(ctx.data))?.axesSpec[1]?.name;
14541
+ if (clonotypeAxisName === void 0) return void 0;
14542
+ const umap = ColumnsCollection(["result_pool"]).filter({ include: {
14543
+ name: "^pl7\\.app/umap[12]$",
14544
+ axes: [{ name: exactMatch(clonotypeAxisName) }]
14545
+ } }).getColumns();
14546
+ if (umap.length === 0) return void 0;
14547
+ return toGraphColumns(umap);
14548
+ }
14495
14549
  /**
14496
- * UMAP columns from the sequence-space block plus this block's own sampled-rows
14497
- * markers, materialised for `createPFrameForGraphs` / `PColumnIdAndSpec`.
14550
+ * Ids of the columns that join the UMAP projection, discovered once per UMAP
14551
+ * column and unioned, so a second clonotype-space block keyed on its own axis
14552
+ * still contributes.
14498
14553
  *
14499
- * The legacy `{ anchor: "main", idx: 1 }` axis binding has no selector form, so
14500
- * the clonotype axis name is read off the resolved anchor spec and matched by
14501
- * name. Omitting `partialAxesMatch` keeps the old exact-axis-set semantics.
14554
+ * Anchors are specs, never ids, so the pool's own copy of a UMAP column cannot
14555
+ * collide with one handed in here.
14502
14556
  */
14503
- function umapGraphColumns(ctx) {
14504
- const anchorSpec = getSpecByRef(getInputAnchorRef(ctx.data));
14505
- const clonotypeAxisName = anchorSpec?.axesSpec[1]?.name;
14506
- if (anchorSpec === void 0 || clonotypeAxisName === void 0) return void 0;
14507
- const umap = ColumnsCollection(["result_pool"]).discover({
14508
- anchors: { main: anchorSpec },
14509
- include: {
14510
- name: "^pl7\\.app/umap[12]$",
14511
- axes: [{ name: exactMatch(clonotypeAxisName) }]
14512
- }
14513
- }).getColumns();
14514
- if (umap.length === 0) return void 0;
14515
- const sampledRows = ColumnsCollection([ctx.outputs?.resolve({
14557
+ function umapRelatedIds(umap) {
14558
+ return [...new Set(umap.flatMap((c) => ColumnsCollection().discover({
14559
+ mode: "enrichment",
14560
+ maxHops: 4,
14561
+ anchors: { main: c.spec },
14562
+ exclude: [{ annotations: { [Annotation.HideDataFromUi]: exactMatch("true") } }, { annotations: { [Annotation.HideDataFromGraphs]: exactMatch("true") } }]
14563
+ }).getColumnIds()))];
14564
+ }
14565
+ function sampledRowsGraphColumns(ctx) {
14566
+ return accessorGraphColumns(ctx.outputs?.resolve({
14516
14567
  field: "sampledRows",
14517
14568
  assertFieldType: "Input",
14518
14569
  allowPermanentAbsence: true
14519
- })].filter((a) => a !== void 0)).getColumns();
14520
- return toGraphColumns([...umap, ...sampledRows]);
14570
+ }));
14521
14571
  }
14522
14572
  const platforma$1 = BlockModelV3.create({
14523
14573
  dataModel: blockDataModel,
@@ -14581,7 +14631,7 @@ const platforma$1 = BlockModelV3.create({
14581
14631
  }, { retentive: true }).output("modality", (ctx) => {
14582
14632
  const spec = getSpecByRef(getInputAnchorRef(ctx.data));
14583
14633
  if (!spec) return void 0;
14584
- return spec.axesSpec[1]?.name === "pl7.app/variantKey" ? "peptide" : "antibody_tcr";
14634
+ return isPeptideOrAmplicon(spec) ? "peptide" : "antibody_tcr";
14585
14635
  }, { retentive: true }).output("filterConfig", (ctx) => {
14586
14636
  const inputAnchor = getInputAnchorRef(ctx.data);
14587
14637
  const result = buildCollection(inputAnchor);
@@ -14806,13 +14856,14 @@ const platforma$1 = BlockModelV3.create({
14806
14856
  if (ctx.outputs.getIsReadyOrError() === false) return true;
14807
14857
  return false;
14808
14858
  }).outputWithStatus("umapPf", (ctx) => {
14809
- const pCols = umapGraphColumns(ctx);
14810
- if (pCols === void 0) return void 0;
14811
- return createPFrameForGraphs(ctx, pCols);
14859
+ const umap = umapPoolColumns(ctx);
14860
+ if (umap === void 0) return void 0;
14861
+ if (umap.some((c) => c.data === void 0)) return void 0;
14862
+ return ctx.createPFrame([...umapRelatedIds(umap), ...umap.map((c) => c.id)]);
14812
14863
  }).outputWithStatus("umapPcols", (ctx) => {
14813
- const pCols = umapGraphColumns(ctx);
14814
- if (pCols === void 0) return void 0;
14815
- return pCols.map((c) => ({
14864
+ const umap = umapPoolColumns(ctx);
14865
+ if (umap === void 0) return void 0;
14866
+ return [...umap, ...sampledRowsGraphColumns(ctx) ?? []].map((c) => ({
14816
14867
  columnId: c.id,
14817
14868
  spec: c.spec
14818
14869
  }));
@@ -14864,9 +14915,9 @@ const platforma$1 = BlockModelV3.create({
14864
14915
  if (numbered === 0) return "Kabat numbering could not be applied to any clonotype. The framework regions may be too divergent from known germline sequences. Kabat sequence columns will be empty.";
14865
14916
  return `Kabat numbering was applied to ${numbered.toLocaleString()} clonotype${numbered === 1 ? "" : "s"}. Clonotypes that could not be numbered will have empty Kabat sequence columns.`;
14866
14917
  }).output("isRunning", (ctx) => ctx.outputs?.getIsReadyOrError() === false).title(() => "Lead Selection").subtitle((ctx) => ctx.data.customBlockLabel || ctx.data.defaultBlockLabel).sections((ctx) => {
14867
- const keyAxis = getSpecByRef(getInputAnchorRef(ctx.data))?.axesSpec[1];
14868
- const isPeptide = keyAxis?.name === "pl7.app/variantKey";
14869
- const spaceLabel = isPeptide && keyAxis?.domain?.["pl7.app/repertoire/extractionRunId"] !== void 0 ? "Variant Space" : isPeptide ? "Peptide Space" : "Clonotype Space";
14918
+ const anchorSpec = getSpecByRef(getInputAnchorRef(ctx.data));
14919
+ const source = anchorSpec !== void 0 ? recordSource(anchorSpec) : "unknown";
14920
+ const spaceLabel = source === "amplicon" ? "Variant Space" : source === "peptide" ? "Peptide Space" : "Clonotype Space";
14870
14921
  const sections = [
14871
14922
  {
14872
14923
  type: "link",
@@ -14884,7 +14935,7 @@ const platforma$1 = BlockModelV3.create({
14884
14935
  label: "Selection Plot"
14885
14936
  }
14886
14937
  ];
14887
- if (!isPeptide) sections.push({
14938
+ if (anchorSpec !== void 0 && hasGeneCalls(anchorSpec)) sections.push({
14888
14939
  type: "link",
14889
14940
  href: "/spectratype",
14890
14941
  label: "CDR3 V Spectratype"