Skip to content

Colunas de entrada polars saem do DataFrame original - #217

Merged
bdcdo merged 4 commits into
mainfrom
fix/polars-colunas-de-entrada
Oct 13, 2026
Merged

bdcdo merged 4 commits into
mainfrom
fix/polars-colunas-de-entrada

Conversation

@bdcdo

@bdcdo bdcdo commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

O quê

Com entrada polars, a saída inteira era convertida de volta do pandas, e as colunas que a execução não escreve voltavam diferentes do que entraram. Um Int64 com nulo voltava Float64, e um id como 9007199254740993 voltava 9007199254740992.0, de modo que um join da saída com a entrada pela coluna de id perdia linhas. Um Date voltava Datetime[ms]. Medindo os demais tipos, UInt64 com nulo, Enum, Decimal(10, 2), Array, Null e o inteiro dentro de Struct e List também mudavam, e uma coluna Object trocava de tipo ou levantava ArrowInvalid depois das chamadas.

Como

to_pandas guarda o DataFrame polars de entrada em ConversionInfo.polars_frame. Na saída, as colunas do usuário que não são campo do modelo saem desse frame, sem passar pelo pandas, e só os campos do modelo e as colunas de controle são convertidos. O tipo e os valores ficam os da entrada para qualquer tipo, sem regra por tipo.

from_pandas recebe model_columns, e os dois retornos de dataframeit() que gravam campos do modelo o passam. A família de cada coluna (usuário, trace, busca, tokens, status) vem de _column_rank, que _reorder_columns também usa. A coluna _trace do agente único entra na família de trace, e a reordenação seleciona por posição.

A ida do polars para o pandas não muda. O campo do modelo que já está na entrada, numa retomada ou num reprocessamento, segue pelo mesmo caminho de antes.

Testes

  • Um caso por tipo de coluna de entrada (11 tipos), de ponta a ponta, conferindo tipo e valor.
  • Ordem das linhas com erro no meio; entrada vazia; colunas já existentes sem resume.
  • Reprocessamento de int para float e retomada de saída interrompida com Union[int, float], Optional[int] e campo bool gravado como Int64 0/1.
  • Cada família de coluna de controle presente na entrada (_trace, _trace_*, _search_credits, tokens, status, _error_details) e os tokens das linhas novas na retomada.
  • Contra o código da main, 20 dos testes deste branch falham.
  • Suíte completa: Python 3.13 com todos os extras (1282 passam, cobertura de ramos em 100%), Python 3.10 (1151 passam) e versões mínimas com polars 0.20.0, pandas 2.1.2 e pyarrow 10 (1150 passam). ruff, ty, vulture e deptry passam.

Os testes de inteiro com nulo e de Date partem do trabalho de mayuriphad no #215.

Closes #195

bdcdo and others added 4 commits October 13, 2026 09:00
Com entrada polars, a saída inteira era convertida de volta do pandas, e
as colunas que a execução não escreve voltavam com outro tipo: Int64 com
nulo virava Float64 e arredondava ids acima de 2**53, Date virava
Datetime, e Enum, Decimal, Array, Null e o inteiro dentro de Struct e
List também mudavam. Uma coluna Object levantava ArrowInvalid depois das
chamadas.

A conversão guarda o DataFrame polars de entrada. Na saída, as colunas do
usuário que não são campo do modelo saem dele, e só os campos do modelo e
as colunas de controle são convertidos do pandas. A família de cada
coluna vem de _column_rank, que _reorder_columns também usa.

Na ida, o inteiro com nulo vira inteiro anulável do pandas, para que o
campo inteiro do modelo numa saída interrompida não seja arredondado na
retomada.

Closes #195

Co-authored-by: mayuriphad <mayuriphad656@gmail.com>
- A coluna _trace do agente único entra na família de trace. Classificada
  como coluna do usuário, ela saía do DataFrame polars de entrada, e a
  retomada ou o reprocessamento de uma saída com save_trace perdia o
  trace das linhas novas.
- Na ida, o inteiro com nulo vem como int do Python numa coluna de
  objetos, e não como inteiro anulável do pandas. É a forma em que a
  execução guarda o campo do modelo: a validação strict aceita de volta
  o inteiro acima de 2**53, um validador before recebe int, e a coluna de
  tokens aceita o valor que o provider devolver.
- _reorder_columns seleciona por posição, e um rótulo repetido no pandas
  não traz a coluna mais de uma vez.
- O CHANGELOG diz que a coluna Object trocava de tipo ou levantava
  ArrowInvalid, conforme o objeto.

Testes novos: cada família de coluna de controle presente na entrada
polars, tokens na retomada, modelo strict com inteiro grande, rótulo
repetido e a posição de _trace.
Com o inteiro com nulo sempre em objetos, um campo bool cuja coluna
chega do polars como Int64 0/1, como a lida de SQLite, deixava de ser
reconhecido na retomada: as linhas processadas ficavam 1 e 0, a nova
vinha True, e a conversão final falhava depois da chamada.

A coluna inteira com nulo fica em float64, como o to_pandas() a entrega,
enquanto todo valor cabe exato no float. É a forma que o pandas daria ao
mesmo dado lido de arquivo e a que a retomada reconhece em campo int e
bool. Só a coluna com valor que o float arredondaria vem como objetos.

O CHANGELOG registra que _trace passa a ficar com as colunas de trace
na ordem da saída.
A conversão do inteiro com nulo na ida saía do escopo da issue e, nas
três formas tentadas, piorava algum caso de retomada: campo bool sobre
coluna Int64 0/1, campo float sobre coluna com inteiro acima de 2**53, e
inteiro acima do Int64, que passavam a falhar depois das chamadas.

A ida fica como na main, e o PR trata só da volta: as colunas que a
execução não escreve saem do DataFrame polars de entrada. Numa matriz de
8.844 combinações de campo do modelo, coluna já presente e modo de
retomada, a única diferença em relação à main é a coluna de entrada
voltar com o tipo e os valores originais.
@bdcdo

bdcdo commented Oct 13, 2026

Copy link
Copy Markdown
Owner Author

Revisão independente antes do merge

Três subagentes novos, um por rodada e sem acesso ao raciocínio da implementação, revisaram o branch em worktrees descartáveis, com suíte, mutações e comparação contra a main. Cada achado abaixo foi reproduzido de novo antes de ser aceito.

Rodada 1, sobre a9cec098

  • Corrigido. Com save_trace, a retomada ou o reprocessamento de uma saída polars perdia o trace das linhas novas: a coluna _trace do agente único não casava com _trace_*, era tratada como coluna do usuário e saía do frame de entrada. _trace entrou na família de trace, com teste para cada família de coluna de controle presente na entrada.
  • Corrigido. Nenhum teste cobria trace, busca ou tokens numa retomada polars, e uma mutação que tirava essas famílias da conversão passava. Há teste unitário por família e um de ponta a ponta para os tokens.
  • Corrigido. _reorder_columns listava duas vezes um rótulo repetido no pandas. A seleção passou a ser por posição.
  • Corrigido. O CHANGELOG dizia que a coluna Object levantava ArrowInvalid; com set ou dict ela trocava de tipo sem erro. Texto ajustado.
  • Recusado. Coluna do usuário com nome de família de controle, como _trace_x, continua passando pelo pandas. É o comportamento da main, e a família é decidida só pelo nome.

Rodadas 2 e 3, sobre a9942376 e 05fb5edb

As duas rodadas acharam regressões na conversão do inteiro com nulo na ida do polars para o pandas, que as versões anteriores deste branch alteravam:

  • campo bool cuja coluna chega como Int64 0/1 com nulo, como a lida de SQLite, falhava com ArrowTypeError depois da chamada;
  • campo float sobre coluna com inteiro acima de 2**53 falhava com ArrowInvalid depois da chamada, onde a main devolvia o valor arredondado;
  • campo int sobre UInt64 acima do Int64 passava de ValueError antes das chamadas para OverflowError depois delas;
  • o limiar da ida não coincidia com o de _same_value para inteiros a partir de 2**53 que cabem exatos no float.

Decisão. A mudança na ida saiu do PR em 4e31a31b. Ela não é necessária para a issue 195, que trata das colunas que a execução não escreve, e nas três formas tentadas piorava algum caso de retomada. A ida voltou a ser o to_pandas() do polars, como na main.

Evidência do estado final (4e31a31b)

  • A matriz que o revisor da rodada 3 escreveu, com 8.844 combinações de campo do modelo (12 tipos, modelo comum e strict=True), coluna já presente na entrada (23 tipos) e modo de retomada (9 modos, mais checkpoint em CSV e parquet), foi rodada de novo contra a saída da main. As únicas diferenças são o tipo e os valores da coluna de entrada que não é campo do modelo, que agora voltam como entraram. Nos 4.968 casos de campo do modelo sobre coluna já presente e nos 284 de checkpoint, a saída é idêntica à da main, inclusive nas exceções.
  • Seis mutações do código da volta derrubam de 1 a 18 testes cada.
  • O commit 4e31a31b é uma remoção e não passou por uma quarta rodada de subagente. A evidência dele é a matriz acima e a suíte.

Fica fora deste PR

Na retomada de uma saída polars, o campo inteiro do modelo com linha pendente continua passando por float, e um valor acima de 2**53 volta arredondado, como na main.

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.

Entrada polars volta com Int64 em Float64 e Date em Datetime

1 participant