Skip to content

Commit d4a3569

Browse files
committed
drop structureId placeholder axis from scores PFrame
The scores PFrame used to declare a `pl7.app/structure/id` axis with a constant `"static"` value on every row. It was added as a defensive workaround for what we thought was an xsv-import-pt "at least one axis" requirement back when the scores frame was emitted via the legacy single-PDB path. On the PrimaryRef path that's now the only path, `pframes.processColumn` auto-prepends the upstream PDB column's axes (`[sampleId, scClonotypeKey]`) to every per-row output, which already uniquely keys every scores row. Re-reading `xsv-import-pt.lib.tengo:110-116`, the parquet path's `PFCONV_IMPORT_CFG_SCHEMA` accepts an empty `axes` array and the generated polars schema is built from `columns` only when axes is empty. So the placeholder column was rendering for no reason — it showed up in the scoresTable as a redundant "Structure" row-axis cell with the same string `"static"` on every row. Changes: - `workflow/src/specs.lib.tengo` — `scoresAxesSpec` is now `[]`, `buildScoresXsvSettings` returns `axes: []`. Comments rewritten so the next reader doesn't reintroduce the placeholder. - `software/src/main.py` — drop `structureId` from `_SCORES_AXES` and from the `_build_scores_row` dict, drop the `_PLACEHOLDER_STRUCTURE_ID` constant. - `workflow/src/process-pdb.tpl.tengo` — refresh the header comment so it documents the actual current behavior (axes = `[sampleId, scClonotypeKey]` from prepend, nothing block-side). NB for local dev: the software image needs rebuilding for the python change to land at runtime — `cd software && PL_DOCKER_BUILD=true PL_DOCKER_NO_AUTOPUSH=true pnpm build`. The workflow-side empty-axes change is forward-compatible with the old software (pfconv silently ignored undeclared parquet columns), so a workflow-only deploy still works against a stale image.
1 parent d34ecb8 commit d4a3569

3 files changed

Lines changed: 23 additions & 44 deletions

File tree

software/src/main.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,14 @@ def chown_to_host(path: Path) -> None:
134134
ColumnSchema(id="partnerIcode", type="String"),
135135
]
136136

137-
# Per-structure scalar PFrame (spec R38). One row per structure. The
138-
# `structureId` axis is a placeholder until upstream provides a clonotype
139-
# key via PrimaryRef (R1-R6). R23 summary counts, R38 motif/score scalars,
140-
# R24-R30 surface metrics, R36 low-conf fractions, R39 threshold flags
141-
# all live here. Workflow side annotates `*Flag` columns with
142-
# `pl7.app/isScore: "true"` (R40); raw metrics ship as plain features.
143-
_SCORES_AXES = [
144-
AxisSchema(id="structureId", type="String"),
145-
]
137+
# Per-structure scalar PFrame (spec R38). One row per structure. No
138+
# block-side axes — `pframes.processColumn` prepends the upstream PDB
139+
# column's `[sampleId, scClonotypeKey]` axes to every emitted PColumn,
140+
# which is what keys each row. R23 summary counts, R38 motif/score
141+
# scalars, R24-R30 surface metrics, R36 low-conf fractions, R39
142+
# threshold flags all live here. Workflow side annotates `*Flag` columns
143+
# with `pl7.app/isScore: "true"` (R40); raw metrics ship as plain features.
144+
_SCORES_AXES: list[AxisSchema] = []
146145
_SCORES_COLUMNS = [
147146
# Spec R7 mode
148147
ColumnSchema(id="mode", type="String"),
@@ -183,9 +182,6 @@ def chown_to_host(path: Path) -> None:
183182
ColumnSchema(id="cdrh3CompactnessFlag", type="String"),
184183
]
185184

186-
# Placeholder axis value for the scores PColumn — replaced by the upstream
187-
# clonotype key once a PrimaryRef-emitting structure-prediction block lands.
188-
_PLACEHOLDER_STRUCTURE_ID = "static"
189185
# Spec R39 — sentinel used when a flag isn't applicable to the current mode.
190186
_FLAG_SENTINEL = "-"
191187

@@ -232,10 +228,6 @@ def _build_scores_row(
232228
missing_canonical = sum(1 for h in cys_hits if h.cysClass == "disulfide_missing")
233229

234230
return {
235-
# Placeholder axis until upstream provides a clonotype key via
236-
# PrimaryRef (R1-R6). The workflow swaps this column out for the
237-
# real scClonotypeKey axis on the PrimaryRef path.
238-
"structureId": _PLACEHOLDER_STRUCTURE_ID,
239231
"mode": mode,
240232
# R23 summary counts.
241233
"extraCysCount": extra_cys,

workflow/src/process-pdb.tpl.tengo

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@
1212
// The orchestrator (main.tpl.tengo) converts these into PColumns via target
1313
// outputs of type Resource (liabilitiesJson) and Xsv parquet (motifs/cys/scores).
1414
// `pframes.processColumn` prepends the input column's axes
15-
// (`pl7.app/vdj/scClonotypeKey`) to each output PColumn automatically, so the
16-
// scores frame's Xsv axes are empty here (the python keeps writing the
17-
// legacy `structureId="static"` axis column — pfconv ignores undeclared
18-
// parquet columns, matching the spec's PrimaryRef R1-R6 wording that
19-
// structureId is replaced by the upstream clonotype key).
15+
// (`[sampleId, scClonotypeKey]`) to each output PColumn automatically, so
16+
// the scores frame declares no block-side axes (every row is uniquely
17+
// keyed by the prepended pair).
2018

2119
self := import("@platforma-sdk/workflow-tengo:tpl")
2220
ll := import("@platforma-sdk/workflow-tengo:ll")

workflow/src/specs.lib.tengo

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -333,22 +333,14 @@ cysColumnSpecs := {
333333
cysColumnIds := ["cysClass", "chainRole", "bondingState", "rsasa", "sidechainRsasa", "sasa", "sidechainSasa", "partnerChainId", "partnerResSeq", "partnerIcode"]
334334

335335
// ──────────────── Scores PFrame ────────────────
336-
// processColumn's xsv-import-pt requires at least one declared axis even
337-
// though the upstream `pl7.app/vdj/scClonotypeKey` axis is auto-prepended.
338-
// We keep `pl7.app/structure/id` as a "static"-valued placeholder so the
339-
// xsv settings have a non-empty axes list; the final PColumn axes end up
340-
// as [scClonotypeKey, structureId="static"]. Marked optional so AG-Grid
341-
// hides it by default.
342-
scoresAxesSpec := [
343-
{
344-
name: "pl7.app/structure/id",
345-
type: "String",
346-
annotations: {
347-
"pl7.app/label": "Structure",
348-
"pl7.app/table/visibility": "optional"
349-
}
350-
}
351-
]
336+
// The xsv axes list is empty here — processColumn auto-prepends the
337+
// upstream PDB column's axes (`[sampleId, scClonotypeKey]`) to every
338+
// output, which already uniquely keys each row. xsv-import-pt + pfconv
339+
// accept an empty `axes` array on the parquet path (schema is built
340+
// from `columns` only), so we drop the historical `structureId="static"`
341+
// placeholder column that otherwise rendered as a redundant "Structure"
342+
// row-axis cell with the same constant value on every row.
343+
scoresAxesSpec := []
352344

353345
// Default visibility + orderPriority per spec R51:
354346
// Default visible: mode, structuralDevelopabilityRisk, structuralIntegrityRisk,
@@ -625,15 +617,12 @@ buildCysXsvSettings := func(blockId) {
625617
}
626618
}
627619

628-
// Scores frame: parquet carries a `structureId="static"` column the
629-
// python writer emits for the xsv.importFile "at least one axis"
630-
// requirement (see `scoresAxesSpec` comment above). Final PColumn axes:
631-
// [scClonotypeKey, structureId="static"].
620+
// Scores frame: empty axes; processColumn prepends `[sampleId,
621+
// scClonotypeKey]` so each per-clonotype row is uniquely keyed without
622+
// us declaring anything xsv-side.
632623
buildScoresXsvSettings := func(blockId) {
633624
return {
634-
axes: [
635-
{ column: "structureId", spec: scoresAxesSpec[0] }
636-
],
625+
axes: [],
637626
columns: buildXsvColumns(scoresColumnIds, scoresColumnSpecs, blockId),
638627
storageFormat: "Parquet"
639628
}

0 commit comments

Comments
 (0)