@platforma-open/milaboratories.sequence-properties.workflow 1.1.1 → 1.2.0

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.
@@ -1,6 +1,6 @@
1
1
   WARN  Issue while reading "/home/runner/work/sequence-properties/sequence-properties/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-open/milaboratories.sequence-properties.workflow@1.1.1 build /home/runner/work/sequence-properties/sequence-properties/workflow
3
+ > @platforma-open/milaboratories.sequence-properties.workflow@1.2.0 build /home/runner/work/sequence-properties/sequence-properties/workflow
4
4
  > shx rm -rf dist && pl-tengo check && pl-tengo build
5
5
 
6
6
  info: Skipping unknown file type: wf.test.ts
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @platforma-open/MiLaboratories.sequence-properties.workflow
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a3eaf4b: Add ΔCharge (pH 7.4 → 6.0) metric — `pl7.app/chargeShift` — emitted at peptide, CDR3 (per chain), and Fv scopes. Captures pH-switching capacity (FcRn recycling, endosomal release); negative values mean the molecule gains positive charge on acidification, the productive direction for histidine-driven pH switching. Histidine dominates the metric (~−0.46 per His; pKa ~6.0 sits in the window). Domain carries the pH endpoints (`pl7.app/pH/from`, `pl7.app/pH/to`) so additional pH pairs can land later without breaking the v1 column identity. Default-visible alongside the static charge column at each scope; not marked `isScore` (interpretive, not a Lead Selection ranking criterion).
8
+
9
+ Performance: cache per-sequence `_prepare`, `ProteinAnalysis`, and `IsoelectricPoint` via a `SequenceContext` so each sequence does the BioPython setup work once instead of per-property. Pipeline reuses the full-chain context for the Fv pass (one `IsoelectricPoint(IPC2_PROTEIN, include_cys=False)` shared between `charge_at_pH(7.0)` and pI bisection per chain). DataFrames are built columnarly (dict-of-lists) instead of via list-of-dicts. Output is byte-identical to pre-refactor on the corpus tests; ~1.7× faster on per-property micro-bench, end-to-end ~40k peptides/s and ~10k antibody-clones/s with full-chain + Fv. CDR3 chain-mode byte-stability tests added.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [a3eaf4b]
14
+ - @platforma-open/milaboratories.sequence-properties.software@1.2.0
15
+
16
+ ## 1.1.2
17
+
18
+ ### Patch Changes
19
+
20
+ - 9d628d9: **Workflow (SD-008):** Derive receptor from `pl7.app/vdj/chain` when `pl7.app/vdj/receptor` is absent. Bulk MiXCR axes carry the chain key (`IGHeavy`, `IGLight`, `TCRAlpha`, `TCRBeta`, `TCRGamma`, `TCRDelta`) without the receptor key, which previously fired R13b's "Receptor type not detected" warning on every bulk run. Detection precedence now: axis receptor → axis chain → per-column receptor → per-column chain → IG default + warning. Inputs that carry receptor explicitly are unaffected.
21
+
22
+ **Model:** Default-visible single upstream amino-acid sequence column matching the analysed coverage tier — peptide (`pl7.app/sequence`, feature=peptide) for peptide mode; full-chain VDJRegion (`pl7.app/vdj/sequence`, feature=VDJRegion or VDJRegionInFrame, chain A) for `full_chain` tier; CDR3 (`pl7.app/vdj/sequence`, feature=CDR3, chain A) for `cdr3_only` and `partial` tiers. Chain B (light / beta / delta) and secondary alleles stay available via the column picker.
23
+
24
+ **UI:** Move the "Input dataset" picker into a Settings slide-out drawer (matches the convention used by clonotype-clustering and other sibling blocks). Drawer auto-opens on first load when no input is selected, auto-closes when the workflow starts running.
25
+
26
+ **Software:** Switch the Python runenv from `runenv-python-3:3.12.10` to `runenv-python-3:3.12.10-scientific-slim`. The scientific-slim image now ships biopython, so the per-block dependency install is faster and matches the convention used by sibling python blocks (e.g. `titeseq-analysis`).
27
+
28
+ - Updated dependencies [9d628d9]
29
+ - @platforma-open/milaboratories.sequence-properties.software@1.1.1
30
+
3
31
  ## 1.1.1
4
32
 
5
33
  ### Patch Changes
@@ -47,6 +47,12 @@ vhh := func() {
47
47
  "disregard these thresholds for nanobody libraries."
48
48
  }
49
49
 
50
+
51
+
52
+ peptidesShortInstability := func() {
53
+ return "Instability Index applies only to peptides ≥10 aa; shorter peptides show blank values."
54
+ }
55
+
50
56
  export ll.toStrict({
51
57
  partialChainMissingFullChain: partialChainMissingFullChain,
52
58
  partialChainNoCdr3: partialChainNoCdr3,
@@ -54,5 +60,6 @@ export ll.toStrict({
54
60
  cdr3OnlyInput: cdr3OnlyInput,
55
61
  gammaDeltaTcr: gammaDeltaTcr,
56
62
  receptorNotDetected: receptorNotDetected,
57
- vhh: vhh
63
+ vhh: vhh,
64
+ peptidesShortInstability: peptidesShortInstability
58
65
  })
Binary file
Binary file
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@platforma-open/milaboratories.sequence-properties.workflow",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Block Workflow",
5
5
  "type": "module",
6
6
  "dependencies": {
7
7
  "@platforma-sdk/workflow-tengo": "5.16.0",
8
- "@platforma-open/milaboratories.sequence-properties.software": "1.1.0"
8
+ "@platforma-open/milaboratories.sequence-properties.software": "1.2.0"
9
9
  },
10
10
  "devDependencies": {
11
11
  "@platforma-sdk/tengo-builder": "2.5.17",
@@ -58,6 +58,44 @@ contains := func(arr, x) {
58
58
  return false
59
59
  }
60
60
 
61
+ // Spec deviation SD-008 — see docs/spec-deviations.md.
62
+ // Bulk MiXCR axes carry `pl7.app/vdj/chain` without the spec-required
63
+ // `pl7.app/vdj/receptor`. The chain enum maps unambiguously to a receptor —
64
+ // derive it when receptor is absent. Returns "" for unrecognised input;
65
+ // caller falls through to the IG default + R13b warning, which still fires
66
+ // for genuinely unknown datasets.
67
+ CHAIN_TO_RECEPTOR := {
68
+ IGHeavy: "IG",
69
+ IGLight: "IG",
70
+ IGKappa: "IG",
71
+ IGLambda: "IG",
72
+ TCRAlpha: "TCRAB",
73
+ TCRBeta: "TCRAB",
74
+ TCRGamma: "TCRGD",
75
+ TCRDelta: "TCRGD"
76
+ }
77
+
78
+ chainToReceptor := func(chain) {
79
+ if chain == undefined { return "" }
80
+ r := CHAIN_TO_RECEPTOR[chain]
81
+ if r == undefined { return "" }
82
+ return r
83
+ }
84
+
85
+ // Resolve receptor from a (receptor, chain) domain pair. Explicit receptor
86
+ // wins; chain-derived receptor is the fallback. Returns { value, seen };
87
+ // `seen` false when neither key resolves.
88
+ resolveReceptor := func(receptorVal, chainVal) {
89
+ if receptorVal == "IG" || receptorVal == "TCRAB" || receptorVal == "TCRGD" {
90
+ return { value: receptorVal, seen: true }
91
+ }
92
+ derived := chainToReceptor(chainVal)
93
+ if derived != "" {
94
+ return { value: derived, seen: true }
95
+ }
96
+ return { value: "", seen: false }
97
+ }
98
+
61
99
  wf.prepare(func(args) {
62
100
  bb := wf.createPBundleBuilder()
63
101
  bb.ignoreMissingDomains()
@@ -132,10 +170,17 @@ wf.body(func(args) {
132
170
  // anchor's secondary axis), not on per-region sequence column domains. Read
133
171
  // from the axis first; the per-column check inside the loop stays as a
134
172
  // fallback for non-MiXCR producers.
173
+ //
174
+ // Spec deviation SD-008 — bulk MiXCR axes lack `pl7.app/vdj/receptor` but
175
+ // carry `pl7.app/vdj/chain`; derive receptor from chain when receptor is
176
+ // absent. See docs/spec-deviations.md.
135
177
  if keyAxisSpec.domain != undefined {
136
- axisR := keyAxisSpec.domain["pl7.app/vdj/receptor"]
137
- if axisR == "IG" || axisR == "TCRAB" || axisR == "TCRGD" {
138
- receptor = axisR
178
+ r := resolveReceptor(
179
+ keyAxisSpec.domain["pl7.app/vdj/receptor"],
180
+ keyAxisSpec.domain["pl7.app/vdj/chain"]
181
+ )
182
+ if r.seen {
183
+ receptor = r.value
139
184
  receptorSeen = true
140
185
  }
141
186
  }
@@ -192,9 +237,10 @@ wf.body(func(args) {
192
237
  }
193
238
 
194
239
  // Same receptor value is expected on every input column; last seen wins.
195
- r := d["pl7.app/vdj/receptor"]
196
- if r == "IG" || r == "TCRAB" || r == "TCRGD" {
197
- receptor = r
240
+ // SD-008: derive from chain when explicit receptor key is absent.
241
+ rec := resolveReceptor(d["pl7.app/vdj/receptor"], d["pl7.app/vdj/chain"])
242
+ if rec.seen {
243
+ receptor = rec.value
198
244
  receptorSeen = true
199
245
  }
200
246
 
@@ -47,6 +47,12 @@ vhh := func() {
47
47
  "disregard these thresholds for nanobody libraries."
48
48
  }
49
49
 
50
+ // R9 — Guruprasad instability index is undefined for sequences shorter than
51
+ // 10 residues; emitted in peptide mode whenever any peptide falls below the floor.
52
+ peptidesShortInstability := func() {
53
+ return "Instability Index applies only to peptides ≥10 aa; shorter peptides show blank values."
54
+ }
55
+
50
56
  export ll.toStrict({
51
57
  partialChainMissingFullChain: partialChainMissingFullChain,
52
58
  partialChainNoCdr3: partialChainNoCdr3,
@@ -54,5 +60,6 @@ export ll.toStrict({
54
60
  cdr3OnlyInput: cdr3OnlyInput,
55
61
  gammaDeltaTcr: gammaDeltaTcr,
56
62
  receptorNotDetected: receptorNotDetected,
57
- vhh: vhh
63
+ vhh: vhh,
64
+ peptidesShortInstability: peptidesShortInstability
58
65
  })
@@ -14,6 +14,17 @@ messages := import(":messages")
14
14
 
15
15
  self.defineOutputs("propertiesPf", "exportPframe", "info")
16
16
 
17
+ // ΔCharge (pH 7.4 → 6.0) endpoints — fixed in v1 per spec; schema accepts
18
+ // additional pH pairs without breaking existing column identities.
19
+ CHARGE_SHIFT_PH_FROM := "7.4"
20
+ CHARGE_SHIFT_PH_TO := "6.0"
21
+
22
+ // Spec R6b — same description on every chargeShift column (peptide / CDR3 / Fv).
23
+ // Trimmed to ~30 words for the column-header tooltip; PlAgDataTableV2 clips the
24
+ // top edge against the page header. Magnitude rule and scope-exclusion caveats
25
+ // live in pcolumn-spec.md / the spec, not in the tooltip.
26
+ CHARGE_SHIFT_DESC := "Net charge change from pH 7.4 (blood) to pH 6.0 (endosome). Negative values mean the molecule gains positive charge on acidification — the productive direction for histidine-driven pH switching. Histidine dominates (~−0.46 per His)."
27
+
17
28
  // Receptor + chain → human label fragments (CDR3 / full-chain).
18
29
  // Spec R13a: PColumn name and chain domain are unchanged; only the label varies.
19
30
  labelFragments := func(receptor, chain) {
@@ -81,6 +92,13 @@ self.body(func(args) {
81
92
  }
82
93
  }
83
94
 
95
+ // R9 — Instability Index is NA for peptides shorter than 10 aa. Surface a
96
+ // banner in peptide mode whenever any row falls below the floor so the
97
+ // user understands why the Instability Index column is blank.
98
+ if mode == "peptide" && stats.hasPeptideBelowInstabilityFloor == true {
99
+ infoMessages += [messages.peptidesShortInstability()]
100
+ }
101
+
84
102
  infoBlob := smart.createValueResource(constants.RTYPE_JSON, canonical.encode({
85
103
  mode: mode,
86
104
  receptor: receptor,
@@ -106,6 +124,18 @@ self.body(func(args) {
106
124
  "pl7.app/table/orderPriority": "70000"
107
125
  })]
108
126
 
127
+ columns += [makeCol("chargeShift_peptide", "pl7.app/chargeShift", "Double",
128
+ "Peptide ΔCharge (pH 7.4 → 6.0)", {
129
+ "pl7.app/feature": "peptide",
130
+ "pl7.app/pH/from": CHARGE_SHIFT_PH_FROM,
131
+ "pl7.app/pH/to": CHARGE_SHIFT_PH_TO
132
+ }, {
133
+ "pl7.app/format": ".2f",
134
+ "pl7.app/description": CHARGE_SHIFT_DESC,
135
+ "pl7.app/table/visibility": "default",
136
+ "pl7.app/table/orderPriority": "69950"
137
+ })]
138
+
109
139
  columns += [makeCol("gravy_peptide", "pl7.app/hydrophobicity", "Double",
110
140
  "Hydrophobicity (GRAVY)", dom, {
111
141
  "pl7.app/format": ".3f",
@@ -206,6 +236,18 @@ self.body(func(args) {
206
236
  "pl7.app/table/visibility": "default",
207
237
  "pl7.app/table/orderPriority": string(chargeOrder)
208
238
  })]
239
+ columns += [makeCol("chargeShift_" + chain + "_CDR3", "pl7.app/chargeShift", "Double",
240
+ frag.cdr3 + " ΔCharge (pH 7.4 → 6.0)", {
241
+ "pl7.app/feature": "CDR3",
242
+ "pl7.app/vdj/scClonotypeChain": chain,
243
+ "pl7.app/pH/from": CHARGE_SHIFT_PH_FROM,
244
+ "pl7.app/pH/to": CHARGE_SHIFT_PH_TO
245
+ }, {
246
+ "pl7.app/format": ".2f",
247
+ "pl7.app/description": CHARGE_SHIFT_DESC,
248
+ "pl7.app/table/visibility": "default",
249
+ "pl7.app/table/orderPriority": string(chargeOrder - 50)
250
+ })]
209
251
  columns += [makeCol("gravy_" + chain + "_CDR3", "pl7.app/hydrophobicity", "Double",
210
252
  frag.cdr3 + " Hydrophobicity (GRAVY)", cdr3Dom, {
211
253
  "pl7.app/format": ".3f",
@@ -308,6 +350,17 @@ self.body(func(args) {
308
350
  "pl7.app/table/visibility": "default",
309
351
  "pl7.app/table/orderPriority": "65100"
310
352
  })]
353
+ columns += [makeCol("chargeShift_Fv", "pl7.app/chargeShift", "Double",
354
+ "Fv ΔCharge (pH 7.4 → 6.0)", {
355
+ "pl7.app/feature": "Fv",
356
+ "pl7.app/pH/from": CHARGE_SHIFT_PH_FROM,
357
+ "pl7.app/pH/to": CHARGE_SHIFT_PH_TO
358
+ }, {
359
+ "pl7.app/format": ".2f",
360
+ "pl7.app/description": CHARGE_SHIFT_DESC,
361
+ "pl7.app/table/visibility": "default",
362
+ "pl7.app/table/orderPriority": "65050"
363
+ })]
311
364
  columns += [makeCol("pi_Fv", "pl7.app/isoelectricPoint", "Double",
312
365
  "Fv Isoelectric Point (pI)", fvDom, {
313
366
  "pl7.app/format": ".2f",