Skip to content

Migrate to ptabler - #123

Merged
kevindetry-milaboratories merged 1 commit into
mainfrom
push-tyrqrzqpkroz
Jul 13, 2026
Merged

Migrate to ptabler#123
kevindetry-milaboratories merged 1 commit into
mainfrom
push-tyrqrzqpkroz

Conversation

@kevindetry-milaboratories

@kevindetry-milaboratories kevindetry-milaboratories commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the external paggregateSw binary (ptransform) with the pt (ptabler) library for the TSV deduplication step in ensureUniqueness, and extends ensureUniquenessParamsFromPconvParams to carry column type metadata so that integer columns are correctly re-coerced after reading the TSV with inferSchema: false.

Touched Terms:

  • ensureUniqueness — Deduplicates a multi-sample TSV by key axes. Now implemented as an in-process ptabler DataFrame workflow instead of shelling out to a separate ptransform binary; integer columns (Long/Int) are explicitly re-cast via cast(\"Double\") → round() → cast(type) before dedup.

  • ensureUniquenessParamsFromPconvParams — Converts pfconv export parameters into the shape expected by ensureUniqueness. Previously extracted only column names; now also extracts type (axis.spec.type) and valueType (col.spec.valueType) so ensureUniqueness can apply type-correct casts.

  • aggParams — Variadic arguments controlling deduplication strategy: aggParams[0] was the aggregation-type string ("max_by" / "first"), aggParams[1] was the ranking column for max_by. In the new code, aggParams[0] is ignored; the branch is selected purely by len(aggParams) > 1. Current call sites are unaffected but the type string is no longer validated.

  • intCasts — New internal variable. A list of ptabler column expressions that re-cast integer columns from String (the result of inferSchema: false TSV loading) back to their declared Long/Int types before aggregation/dedup.

  • pt (ptabler) — New import replacing exec, assets, and the paggregateSw software handle. Provides a DataFrame API (workflow, frame, withColumns, groupBy, agg, unique, col, save, run) used to implement in-process table operations.

  • The old max_by code path had a variable-shadowing bug (pickCols := [] re-declared inside the block, then immediately iterated) that caused it to pass an empty pickCols to the aggregation binary; the new implementation fixes this.

  • The aggParams[0] aggregation-type string is now silently discarded; dispatch is by argument count only. All current callers still work correctly but the contract is no longer enforced.

Confidence Score: 4/5

Safe to merge — all current call sites behave correctly; only the dropped aggParams[0] type-string leaves a latent gap in input validation.

The functional change is well-scoped: all three concrete call sites pass either ("first") or ("max_by", rankCol), mapping cleanly onto the new arity-based dispatch. The old max_by path had a variable-shadowing bug that caused an empty column list to be sent to the binary; the new code fixes that. The one gap is that aggParams[0] is now silently ignored, so a mistaken call such as passing only "max_by" without a ranking column silently falls through to dedup-by-first instead of failing fast.

workflow/src/tables-aggregation.lib.tengo — verify the aggParams arity contract is documented or enforced, and confirm ptabler's maxBy semantics on Long-typed columns match the previous ptransform max_by behaviour.

Important Files Changed

Filename Overview
workflow/src/tables-aggregation.lib.tengo Migrates ensureUniqueness from a shell-invoked ptransform binary to a ptabler (pt) DataFrame workflow; adds type-aware integer casting after TSV read; aggParams[0] (aggregation type string) is silently dropped in favour of arity-based dispatch
.changeset/blue-shirts-kneel.md Changeset entry marking both workflow and root packages as minor-bump for the ptabler migration — no code changes

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[ensureUniqueness called\ninputTsv, params, ...aggParams] --> B[Build allCols\naxes + value columns]
    B --> C[Collect keyNames\nfrom params.axes]
    C --> D[pt.workflow\nwf.frame inputTsv as TSV\ninferSchema: false]
    D --> E{Any Long/Int\ncolumns in allCols?}
    E -- Yes --> F[withColumns: cast String to Double to round to Long/Int\nfor each integer column]
    E -- No --> G{len aggParams > 1?}
    F --> G
    G -- Yes: max_by path --> H[rankCol = aggParams 1\nmap params.columns to maxBy rankCol\ndf.groupBy keyNames .agg aggs...]
    G -- No: first path --> I[df.unique subset: keyNames\nkeep: first, maintainOrder: true]
    H --> J[result.save output.tsv]
    I --> J
    J --> K[wf.run .getFile output.tsv\nreturn deduplicated TSV]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[ensureUniqueness called\ninputTsv, params, ...aggParams] --> B[Build allCols\naxes + value columns]
    B --> C[Collect keyNames\nfrom params.axes]
    C --> D[pt.workflow\nwf.frame inputTsv as TSV\ninferSchema: false]
    D --> E{Any Long/Int\ncolumns in allCols?}
    E -- Yes --> F[withColumns: cast String to Double to round to Long/Int\nfor each integer column]
    E -- No --> G{len aggParams > 1?}
    F --> G
    G -- Yes: max_by path --> H[rankCol = aggParams 1\nmap params.columns to maxBy rankCol\ndf.groupBy keyNames .agg aggs...]
    G -- No: first path --> I[df.unique subset: keyNames\nkeep: first, maintainOrder: true]
    H --> J[result.save output.tsv]
    I --> J
    J --> K[wf.run .getFile output.tsv\nreturn deduplicated TSV]
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
workflow/src/tables-aggregation.lib.tengo:46-56
**`aggParams[0]` type string silently ignored**

The old API used `aggParams[0]` as the aggregation-type discriminator ("max_by" or "first"). The new implementation discards it entirely and dispatches only on `len(aggParams) > 1`. A call like `ensureUniqueness(tsv, params, "max_by")` (type provided but ranking column omitted) now silently falls into the `df.unique()` branch instead of raising an error, and a hypothetical future caller passing a third aggregation type with two arguments would silently be treated as `max_by`. Since all current call sites happen to map 1:1 (1 arg → first, 2 args → max_by), there is no current regression, but the contract is no longer enforced.

Reviews (1): Last reviewed commit: "Migrate to ptabler" | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used:

  • Context used - Terms is a types in codebase. Provide the list of ... (source)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the tables aggregation logic in tables-aggregation.lib.tengo to use the ptabler library, replacing the external software execution with native dataframe operations. The feedback suggests adding defensive checks to prevent runtime panics when accessing nested properties of axis and col specifications, and adding an assertion to ensure unsupported aggregation types are not silently ignored when fallback logic is executed.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 8 to 17
ensureUniquenessParamsFromPconvParams := func(pfConvParams) {
return {
axes: slices.map(pfConvParams.axes, func(axis) {
return axis.column
return { column: axis.column, type: axis.spec.type }
}),
columns: slices.map(pfConvParams.columns, func(col) {
return col.column
return { column: col.column, type: col.spec.valueType }
})
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime panics, we should defensively check if axis.spec and col.spec are defined before accessing their properties (type and valueType). In Tengo, accessing properties on an undefined value will cause a runtime panic.

ensureUniquenessParamsFromPconvParams := func(pfConvParams) {
    return {
        axes: slices.map(pfConvParams.axes, func(axis) {
            return { column: axis.column, type: is_undefined(axis.spec) ? undefined : axis.spec.type }
        }),
        columns: slices.map(pfConvParams.columns, func(col) {
            return { column: col.column, type: is_undefined(col.spec) ? undefined : col.spec.valueType }
        })
    }
}

Comment on lines +53 to +56
} else {
// first: dedup rows by key, keeping the first.
result = df.unique({ subset: keyNames, keep: "first", maintainOrder: true })
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The previous implementation supported arbitrary aggregation types passed via aggParams[0]. The new implementation hardcodes keep: "first" when len(aggParams) <= 1, silently ignoring any other aggregation type if specified. To prevent unexpected behavior or silent bugs, we should assert that aggParams[0] is indeed "first" when provided.

    } else {
        if len(aggParams) > 0 {
            ll.assert(aggParams[0] == "first", "Unsupported aggregation type: " + aggParams[0])
        }
        // first: dedup rows by key, keeping the first.
        result = df.unique({ subset: keyNames, keep: "first", maintainOrder: true })
    }

Comment on lines +46 to +56
if len(aggParams) > 1 {
// max_by: keep, per key, the row with the greatest ranking column value.
rankCol := aggParams[1]
aggs := slices.map(params.columns, func(col) {
return pt.col(col.column).maxBy(pt.col(rankCol)).alias(col.column)
})
result = df.groupBy(keyNames...).agg(aggs...)
} else {
// first: dedup rows by key, keeping the first.
result = df.unique({ subset: keyNames, keep: "first", maintainOrder: true })
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 aggParams[0] type string silently ignored

The old API used aggParams[0] as the aggregation-type discriminator ("max_by" or "first"). The new implementation discards it entirely and dispatches only on len(aggParams) > 1. A call like ensureUniqueness(tsv, params, "max_by") (type provided but ranking column omitted) now silently falls into the df.unique() branch instead of raising an error, and a hypothetical future caller passing a third aggregation type with two arguments would silently be treated as max_by. Since all current call sites happen to map 1:1 (1 arg → first, 2 args → max_by), there is no current regression, but the contract is no longer enforced.

Prompt To Fix With AI
This is a comment left during a code review.
Path: workflow/src/tables-aggregation.lib.tengo
Line: 46-56

Comment:
**`aggParams[0]` type string silently ignored**

The old API used `aggParams[0]` as the aggregation-type discriminator ("max_by" or "first"). The new implementation discards it entirely and dispatches only on `len(aggParams) > 1`. A call like `ensureUniqueness(tsv, params, "max_by")` (type provided but ranking column omitted) now silently falls into the `df.unique()` branch instead of raising an error, and a hypothetical future caller passing a third aggregation type with two arguments would silently be treated as `max_by`. Since all current call sites happen to map 1:1 (1 arg → first, 2 args → max_by), there is no current regression, but the contract is no longer enforced.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

@kevindetry-milaboratories
kevindetry-milaboratories added this pull request to the merge queue Jul 13, 2026
Merged via the queue into main with commit 718bb64 Jul 13, 2026
11 checks passed
@kevindetry-milaboratories
kevindetry-milaboratories deleted the push-tyrqrzqpkroz branch July 13, 2026 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants