fix: layout do clast.dat e registro em branco no bloco de intercambio do sistema.dat - #123
Merged
rjmalves merged 5 commits intoAug 2, 2026
Conversation
O template do arquivo reserva 10 caracteres para TIPO COMB. (colunas 20-29), mas o modelo declarava LiteralField(12, 19), invadindo os dois primeiros caracteres do primeiro campo de custo (FloatField(8, 29)). Usinas com CVU >= 1000 tinham o combustivel lido como 'Gas 1' em vez de 'Gas'. No deck NW202701 isso afetava 82 das 159 usinas. A escrita mascarava o defeito, pois o campo de custo sobrescrevia os bytes invadidos - o dano era restrito ao DataFrame exposto ao usuario. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
O bloco de UTEs fixava 5 colunas de custo (range(5)) na construcao da Line. O parametro numero_anos_planejamento existia no read(), mas a Line ja havia sido montada no __init__ e Clast.read() nunca repassava o argumento - na pratica o valor era sempre 5. Decks com 4 colunas ganhavam uma coluna fantasma de NaN; decks com horizonte estendido para 6 anos eram truncados silenciosamente na escrita. O numero de colunas passa a ser detectado a partir da linha de template (XXXX.XX), que ja esta no cabecalho do arquivo, com fallback para o comportamento anterior caso ela esteja ausente. A escrita tambem ajusta o cabecalho quando o DataFrame ganha ou perde anos, para que a releitura nao trunque os dados de volta. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
O teste anterior so conferia a contagem de XXXX.XX no cabecalho, que ja era 4 antes da correcao - passava mesmo sem o fix. Passa a fazer o round-trip completo (leitura, escrita, releitura) e exige que a releitura mantenha indice_ano_estudo em [1,2,3,4], sem a 5a coluna fantasma que o codigo antigo perpetuava. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
O manual do NEWAVE v30.0.2 (secao 3.7, Bloco 3) determina que os grupos de registros tipo 2 (A->B) e tipo 3 (B->A) sejam 'separados por um registro em branco, de existencia obrigatoria'. O leitor ja respeitava isso - interpreta a linha em branco como virada de grupo. O escritor, porem, repetia o cabecalho tipo 1 no lugar do registro em branco. O round-trip dentro do inewave funcionava, porque o leitor aceita as duas formas, mas o arquivo gerado divergia do formato documentado. O cabecalho tipo 1 passa a ser emitido apenas quando o par de submercados muda; a virada de sentido emite a linha em branco.
Guard defensivo em test_tipo_combustivel_nao_invade_campo_custo: sem o short-circuit, um tipo_combustivel vazio apos strip() levantaria IndexError em c.strip()[-1]. Nao ocorre nos mocks atuais, mas evita fragilidade caso um mock futuro inclua combustivel em branco. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #123 +/- ##
==========================================
+ Coverage 91.07% 91.10% +0.02%
==========================================
Files 526 526
Lines 18944 18976 +32
==========================================
+ Hits 17254 17288 +34
+ Misses 1690 1688 -2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
rjmalves
added a commit
that referenced
this pull request
Aug 2, 2026
- Fixa `ruff<0.16` no extra de lint. Como o uv.lock é gitignored, a CI resolvia o ruff para a release mais recente a cada execução; uma versão nova passou a reordenar imports em todo o repositório e quebrou o lint sem relação com a mudança em revisão. - Adiciona a entrada de changelog das três correções do #123, creditando @dcpirex. - Move as constantes de layout do clast (INICIO_CUSTOS/LARGURA_CUSTO) para antes do __init__, junto de FIM_BLOCO, seguindo a convenção do arquivo (neutro em comportamento). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Três correções encontradas ao usar o inewave para montar um estudo de
extensão de horizonte (5 → 6 anos) sobre um deck de PMO. Cada uma está
em seu próprio commit e são independentes entre si.
1.
clast.dat: campotipo_combustivelinvade a coluna de custoO template do arquivo reserva 10 caracteres para
TIPO COMB.:O modelo declarava
LiteralField(12, 19), cobrindo os índices 19–30 einvadindo os dois primeiros caracteres do primeiro campo de custo
(
FloatField(8, 29)).Efeito: usinas com CVU ≥ 1000 saem com o combustível corrompido.
Num deck de PMO isso atingia 82 das 159 usinas. A escrita mascarava o
defeito — o campo de custo sobrescreve os bytes invadidos e o arquivo
sai idêntico —, então o dano ficava restrito ao DataFrame exposto ao
usuário: qualquer agregação por tipo de combustível gera categorias
fantasma.
2.
clast.dat: número de colunas de custo fixo em 5campos_custosusavarange(5). Oread()aceitanumero_anos_planejamento, mas aLineé montada no__init__eClast.read()nunca repassa o argumento — na prática o valor erasempre 5.
Decks com 4 colunas ganhavam uma coluna fantasma de
NaN; decks comhorizonte estendido para 6 anos eram truncados na escrita.
A contagem passa a vir da linha de template (
XXXX.XX), que já está nocabeçalho do arquivo, com fallback para o comportamento anterior caso
ela esteja ausente. A escrita também ajusta o cabeçalho quando o
DataFrame ganha ou perde anos.
3.
sistema.dat: registro em branco no bloco de intercâmbioManual do NEWAVE v30.0.2, seção 3.7, Bloco 3:
O leitor já respeita isso. O escritor repetia o cabeçalho tipo 1 no
lugar do registro em branco. O round-trip dentro do inewave funciona
(o leitor aceita as duas formas), mas o arquivo gerado diverge do
formato documentado.
Testes
Toda a suíte continua passando (1170 testes). Foram adicionados:
test_tipo_combustivel_nao_invade_campo_custotest_leitura_clast_4_colunas_custo/test_leitura_clast_6_colunas_custotest_leitura_escrita_clast_4_colunas_custo(round-trip completo)test_escrita_clast_ajusta_cabecalho_ao_estender_horizontetest_escrita_intercambio_usa_registro_em_brancoValidação adicional contra um deck de PMO real: round-trip byte-a-byte
idêntico nas 159 usinas do bloco de UTEs do
clast.date no bloco deintercâmbio do
sistema.dat.Notas de escopo
Dois pontos correlatos ficaram de fora deste PR por não serem
correções isoladas — vou abrir uma issue para discuti-los:
sentidofunde o flag do campo 3 doregistro tipo 1 (
0= limite,1= intercâmbio mínimo obrigatório)com o sentido A→B / B→A, que pelo manual é posicional. Corrigir isso é
breaking change no DataFrame público.
clast.dat, as datas saem com mêszero-padded (
"07 2026") onde o original usa espaço (" 7 2026"). Omesmo
format="%m %Y"é usado emagrint.dateexpt.dat, e não hácorreção portável de uma linha (
%_mé extensão do glibc), entãoprefiro discutir a abordagem antes.