@@ -12,15 +12,14 @@ render := import("@platforma-sdk/workflow-tengo:render")
1212smart := import("@platforma-sdk/workflow-tengo:smart")
1313ll := import("@platforma-sdk/workflow-tengo:ll")
1414pframes := import("@platforma-sdk/workflow-tengo:pframes")
15- xsv := import("@platforma-sdk/workflow-tengo:pframes.xsv")
16- pSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
1715canonical := import("@platforma-sdk/workflow-tengo:canonical")
1816maps := import("@platforma-sdk/workflow-tengo:maps")
1917constants := import("@platforma-sdk/workflow-tengo:constants")
2018messages := import(":messages")
2119columnSpecs := import(":columns")
2220
2321infoTpl := assets.importTemplate(":info")
22+ processTpl := assets.importTemplate(":process")
2423
2524// JSON resource with sorted-key canonical bytes. smart.createJsonResource uses
2625// Tengo's stdlib json.encode, which preserves Go's randomized map iteration —
@@ -31,21 +30,6 @@ canonicalJsonResource := func(value) {
3130 return smart.createValueResource(constants.RTYPE_JSON, canonical.encode(value))
3231}
3332
34- // Content tag for exported columns, replacing the per-block blockId. We use the
35- // backend's own content key of the source table (info().CanonicalID, base32),
36- // which is deterministic for identical content: identical results across blocks
37- // share the same tag (columns dedupe instead of being made unique per block),
38- // while different results get different tags (no collision).
39- contentHash := func(table) {
40- i := table.info()
41- if !is_undefined(i.CanonicalID) {
42- return ll.base32Encode(i.CanonicalID)
43- }
44- // Field reference: dereference to its value resource.
45- ri := smart.resource(i.Value).info()
46- return ll.base32Encode(ri.CanonicalID)
47- }
48-
4933REQUIRED_FEATURES := ["FR1", "CDR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"]
5034
5135detectMode := func(axisSpec) {
@@ -405,10 +389,8 @@ wf.body(func(args) {
405389 })
406390 infoBlob := infoResult.output("info", 24 * 60 * 60 * 1000)
407391
408- // Build the canonical column list (no blockId, no isOutput, no trace) and
409- // import the Python TSV outputs into parquet-backed PColumn data. With
410- // canonical specs the xsv.importFile renders dedup across blocks; only
411- // the spec-rewrap into the unified pFrame below is per-block.
392+ // Column-build args forwarded to the process template (no blockId, no trace
393+ // — those are stamped there at pframe-build time).
412394 colArgs := {
413395 mode: mode,
414396 receptor: receptor,
@@ -423,72 +405,34 @@ wf.body(func(args) {
423405 visibleKeyAxisSpec := columnSpecs.cloneSpec(keyAxisSpec, undefined, {
424406 "pl7.app/table/visibility": "default"
425407 })
426- canonicalCols := columnSpecs.buildColumns(colArgs)
427- scalarOut := xsv.importFile(propertiesTsv, "tsv", {
428- axes: [{ column: "entity_key", spec: visibleKeyAxisSpec }],
429- columns: canonicalCols,
430- storageFormat: "Parquet",
431- partitionKeyLength: 0
432- }, { splitDataAndSpec: true, cpu: 1, mem: "4GiB" })
433-
434- aaOut := undefined
435- if mode == "peptide" {
436- aaCol := columnSpecs.aaFractionColumn(visibleKeyAxisSpec)
437- aaOut = xsv.importFile(aaFractionTsv, "tsv", {
438- axes: aaCol.axes,
439- columns: [aaCol.column],
440- storageFormat: "Parquet",
441- partitionKeyLength: 0
442- }, { splitDataAndSpec: true, cpu: 1, mem: "4GiB" })
443- }
444408
445- // Stamp every column's spec with a content tag (pl7.app/contentHash, derived
446- // from the source table's CanonicalID instead of the per-block blockId) and
447- // the upstream trace. Identical results across blocks share the tag (columns
448- // dedupe) while different results stay distinct. The same pFrame is published
449- // both as the block's UI output and as the result-pool export. The trace also
450- // lets the block's UI distinguish our property columns from upstream metadata
451- // columns mixed into propertiesPfHandle.
452- //
453- // The trace step is no longer keyed on blockId (the block is no longer the
454- // identity of the result; its content is). `id` is optional in the trace
455- // schema; omitting it keeps the provenance step identical across
456- // content-identical runs. Per-instance trace label resolution lives in the
457- // model (resolveTraceLabel in model/src/label.ts).
458- trace := pSpec.makeTrace( datasetSpec, {
459- type: "milaboratories.sequence-properties",
460- importance: 30,
461- label: args.traceLabel
409+ // Import + per- column content-tagging + trace stamping is deferred to
410+ // process.tpl.tengo: it needs the Python output tables as RESOLVED render
411+ // inputs to read their content key (info().CanonicalID), which is not
412+ // available in this body (the same body that creates the exec). The render
413+ // is blockId-independent, so it dedups across block instances with identical
414+ // inputs.
415+ processResult := render.create(processTpl, {
416+ properties: propertiesTsv,
417+ aaFraction: aaFractionTsv,
418+ params: canonicalJsonResource({
419+ colArgs: colArgs,
420+ visibleKeyAxisSpec: visibleKeyAxisSpec,
421+ traceLabel: args.traceLabel,
422+ datasetSpec: datasetSpec
423+ })
424+ }, {
425+ metaInputs: { mem: args.mem }
462426 })
463- stamp := func(spec, cHash) {
464- return trace.inject(columnSpecs.cloneSpec(spec,
465- { "pl7.app/contentHash": cHash },
466- undefined))
467- }
468-
469- pf := pframes.pFrameBuilder()
470- scalarHash := contentHash(propertiesTsv)
471- for _, k in maps.getKeys(scalarOut) {
472- v := scalarOut[k]
473- pf.add(k, stamp(v.spec, scalarHash), v.data)
474- }
475- if aaOut != undefined {
476- aaHash := contentHash(aaFractionTsv)
477- for _, k in maps.getKeys(aaOut) {
478- v := aaOut[k]
479- pf.add(k, stamp(v.spec, aaHash), v.data)
480- }
481- }
482- pf = pf.build()
483427
484428 return {
485429 outputs: {
486- propertiesPf: pframes.exportFrame(pf ),
430+ propertiesPf: processResult.output("propertiesPf", 24 * 60 * 60 * 1000 ),
487431 info: infoBlob,
488432 processingLog: processingLog
489433 },
490434 exports: {
491- properties: pf
435+ properties: processResult.output("exportPframe", 24 * 60 * 60 * 1000)
492436 }
493437 }
494438})
0 commit comments