From 6cdff7c059419eb2bdfdea95b3b704656fc168e5 Mon Sep 17 00:00:00 2001 From: Rogerio Alves Date: Tue, 9 Jun 2026 17:46:00 -0300 Subject: [PATCH] feat(hidr): support f64 (832-byte) format with size auto-detection Add RegistroUHEHidrF64 for hidr.dat files that store the volume-cota and cota-area polynomial coefficients in double precision (832-byte records). Hidr auto-detects the format from the file size, recognizing cadastros of 320 or 600 usinas in 792- or 832-byte records, and can convert between precisions via converte_tamanho_registro. Also backfill the missing 1.10.0 CHANGELOG entry and bump the version to 1.11.0. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 20 +++ idecomp/__init__.py | 2 +- idecomp/decomp/hidr.py | 275 ++++++++++++++++++++++-------- idecomp/decomp/modelos/hidr.py | 142 +++++++++++++++ tests/decomp/test_hidr.py | 223 +++++++++++++++++++++--- tests/mocks/arquivos/hidr_f64.dat | Bin 0 -> 266240 bytes 6 files changed, 572 insertions(+), 90 deletions(-) create mode 100644 tests/mocks/arquivos/hidr_f64.dat diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c592d64..61543ba7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,26 @@ O formato segue o [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/). ## [Nao Publicado] +## [1.11.0] - 2026-06-09 + +### Adicionado + +- Suporte ao formato estendido do arquivo `hidr.dat`, com os coeficientes dos polinômios cota-volume e cota-área em precisão dupla (registros de 832 bytes), através do novo registro `RegistroUHEHidrF64`. A classe `Hidr` detecta o formato automaticamente pelo tamanho do arquivo, reconhecendo cadastros de 320 ou 600 usinas em registros de 792 ou 832 bytes, com possibilidade de forçar via `version="f32"` ou `version="f64"`. +- Propriedade `tamanho_registro` e método `converte_tamanho_registro` na classe `Hidr`, permitindo inspecionar e converter entre os formatos de precisão simples e dupla. + +## [1.10.0] - 2026-04-13 + +### Corrigido + +- Precisão decimal insuficiente em campos `FloatField` de diversos registros do `dadger`, que causava corrupção de valores na leitura e escrita (round-trip): + - Registro `PQ` (geração): `decimal_digits` de 0 para 4 com `compact=True`, eliminando 462 valores corrompidos, incluindo o zeramento completo de valores pequenos como `0.0563 → 0`. + - Registro `LU` (limites): `decimal_digits` de 1 para 2, corrigindo truncamentos como `860.55 → 860.5`. + - Registros `TX`, `VI`, `CI`, `CE`, `FP` e `AR`: `decimal_digits` de 0 para 3, por segurança, seguindo o padrão já adotado nos registros `FD` e `MP`. + +### Modificado + +- Dependência cfinterface atualizada para `>=1.10.0`, que disponibiliza o modo `compact` do `FloatField` (omissão do zero à esquerda para `|valor| < 1`, preservando a precisão em campos estreitos de 5 caracteres) e a correção da duplicação de registros no fallback de codificação da leitura. + ## [1.9.0] - 2026-03-10 ### Adicionado diff --git a/idecomp/__init__.py b/idecomp/__init__.py index 49b93171..aac56f73 100644 --- a/idecomp/__init__.py +++ b/idecomp/__init__.py @@ -6,6 +6,6 @@ e saída do DECOMP. """ -__version__ = "1.10.0" +__version__ = "1.11.0" from . import decomp # noqa diff --git a/idecomp/decomp/hidr.py b/idecomp/decomp/hidr.py index 7ad7e781..84878beb 100644 --- a/idecomp/decomp/hidr.py +++ b/idecomp/decomp/hidr.py @@ -1,88 +1,215 @@ +from pathlib import Path from typing import IO, Any, TypeVar +from warnings import warn -import pandas as pd # type: ignore[import-untyped] +import pandas as pd # type: ignore[import-untyped] # no pandas-stubs package from cfinterface.files.registerfile import RegisterFile +from cfinterface.storage import StorageType from idecomp.config import MESES_ABREV -from idecomp.decomp.modelos.hidr import RegistroUHEHidr +from idecomp.decomp.modelos.hidr import RegistroUHEHidr, RegistroUHEHidrF64 class Hidr(RegisterFile): """ - Armazena os dados de entrada do NEWAVE referentes ao cadastro das + Armazena os dados de entrada do DECOMP referentes ao cadastro das usinas hidroelétricas. + + Suporta os dois formatos do arquivo binário: o formato com + registros de 792 bytes (coeficientes dos polinômios volume-cota e + cota-área em 32 bits) e o formato com registros de 832 bytes + (coeficientes em 64 bits). Na leitura, o formato é detectado + automaticamente pelo tamanho do arquivo, que corresponde a 320 ou + 600 registros em um dos dois tamanhos, podendo ser forçado com + `version="f32"` ou `version="f64"`. A escrita é feita sempre no + mesmo formato lido, a menos que seja feita uma conversão explícita + com :meth:`converte_tamanho_registro`. """ T = TypeVar("T") REGISTERS = [RegistroUHEHidr] - STORAGE = "BINARY" + VERSIONS = { + "f32": [RegistroUHEHidr], + "f64": [RegistroUHEHidrF64], + } + STORAGE = StorageType.BINARY + + __REGISTROS_POR_TAMANHO: dict[int, type[RegistroUHEHidr]] = { + RegistroUHEHidr.TAMANHO_REGISTRO: RegistroUHEHidr, + RegistroUHEHidrF64.TAMANHO_REGISTRO: RegistroUHEHidrF64, + } + + # Quantidades de registros (usinas) que o cadastro do hidr pode + # conter. O arquivo é um vetor de tamanho fixo, idêntico nos dois + # formatos de precisão. + __NUMEROS_REGISTROS_SUPORTADOS = (320, 600) + + # Tamanhos de arquivo (em bytes) reconhecidos, mapeando cada tamanho + # válido para a respectiva versão. Derivado das combinações de + # número de registros e tamanho de registro; as quatro combinações + # geram tamanhos distintos, sem ambiguidade entre os formatos. + __TAMANHOS_VALIDOS: dict[int, str] = { + num * tamanho: versao + for num in __NUMEROS_REGISTROS_SUPORTADOS + for versao, tamanho in ( + ("f32", RegistroUHEHidr.TAMANHO_REGISTRO), + ("f64", RegistroUHEHidrF64.TAMANHO_REGISTRO), + ) + } def __init__(self, data: Any = ...) -> None: super().__init__(data) self.__df: pd.DataFrame | None = None + @classmethod + def __detecta_versao(cls, content: str | bytes) -> str | None: + """ + Detecta o formato do arquivo a partir do tamanho do seu + conteúdo. Um arquivo hidr válido contém 320 ou 600 registros, + de 792 bytes (coeficientes em 32 bits) ou de 832 bytes + (coeficientes em 64 bits), resultando em um dos tamanhos + reconhecidos. Tamanhos diferentes geram um aviso e a leitura + assume o formato de 792 bytes. + """ + if isinstance(content, bytes): + num_bytes = len(content) + elif isinstance(content, str) and Path(content).is_file(): + num_bytes = Path(content).stat().st_size + else: + return None + versao = cls.__TAMANHOS_VALIDOS.get(num_bytes) + if versao is not None: + return versao + warn( + f"O tamanho do arquivo hidr ({num_bytes} bytes) não" + + " corresponde a nenhum formato conhecido (320 ou 600" + + " registros de 792 ou 832 bytes). Assumindo registros de" + + f" {RegistroUHEHidr.TAMANHO_REGISTRO} bytes. Use o" + + ' argumento version="f32" ou version="f64" para forçar o' + + " formato.", + stacklevel=3, + ) + return None + + @classmethod + def read( + cls, + content: str | bytes, + *args: Any, + version: str | None = None, + **kwargs: Any, + ) -> "Hidr": + if version is None: + version = cls.__detecta_versao(content) + h = super().read(content, *args, version=version, **kwargs) + assert isinstance(h, Hidr) + return h + + @property + def tamanho_registro(self) -> int: + """ + O tamanho em bytes dos registros do arquivo: 792 para o + formato com coeficientes dos polinômios em 32 bits ou 832 + para o formato em 64 bits. + + :return: O tamanho do registro em bytes + :rtype: int + """ + for r in self.data.of_type(RegistroUHEHidr): + return type(r).TAMANHO_REGISTRO + return RegistroUHEHidr.TAMANHO_REGISTRO + + def converte_tamanho_registro(self, precisao: str) -> None: + """ + Converte os registros do arquivo para o formato com o tamanho + de registro dado, de modo que a próxima escrita seja feita + neste formato. A conversão de 832 para 792 bytes implica em + perda de precisão nos coeficientes dos polinômios volume-cota + e cota-área. + + :param precisao: A precisão dos coeficients dos polinômios + ('f32' ou 'f64') + """ + + mapa_tamanhos = {"f32": 792, "f64": 832} + + tamanho_registro = mapa_tamanhos.get(precisao) + if tamanho_registro is None: + raise ValueError( + f"Precisão inválida: '{precisao}'. Os valores" + + " suportados são 'f32' e 'f64'." + ) + classe_registro = self.__class__.__REGISTROS_POR_TAMANHO[ + tamanho_registro + ] + for r in list(self.data.of_type(RegistroUHEHidr)): + if type(r) is classe_registro: + continue + novo = classe_registro(data=list(r.data)) + self.data.add_after(r, novo) + self.data.remove(r) + def write(self, to: str | IO[Any], *args: Any, **kwargs: Any) -> None: self.__atualiza_registros() super().write(to, *args, **kwargs) def __monta_df_de_registros(self) -> pd.DataFrame | None: - registros = self.data.get_registers_of_type(RegistroUHEHidr) - if registros is None: + registros: list[RegistroUHEHidr] = [ + r for r in self.data.of_type(RegistroUHEHidr) + ] + if len(registros) == 0: return None - elif isinstance(registros, RegistroUHEHidr): - registros = [registros] - df = pd.DataFrame( - columns=[ - "nome_usina", - "posto", - "submercado", - "empresa", - "codigo_usina_jusante", - "desvio", - "volume_minimo", - "volume_maximo", - "volume_vertedouro", - "volume_desvio", - "cota_minima", - "cota_maxima", - *[f"a{i}_volume_cota" for i in range(5)], - *[f"a{i}_cota_area" for i in range(5)], - *[f"evaporacao_{m}" for m in MESES_ABREV], - "numero_conjuntos_maquinas", - *[f"maquinas_conjunto_{i}" for i in range(1, 6)], - *[f"potencia_nominal_conjunto_{i}" for i in range(1, 6)], - *[f"queda_nominal_conjunto_{i}" for i in range(1, 6)], - *[f"vazao_nominal_conjunto_{i}" for i in range(1, 6)], - "produtibilidade_especifica", - "perdas", - "numero_polinomios_jusante", - *[f"a{i}_jusante_1" for i in range(5)], - *[f"a{i}_jusante_2" for i in range(5)], - *[f"a{i}_jusante_3" for i in range(5)], - *[f"a{i}_jusante_4" for i in range(5)], - *[f"a{i}_jusante_5" for i in range(5)], - *[f"a{i}_jusante_6" for i in range(5)], - *[f"referencia_jusante_{i}" for i in range(1, 7)], - "canal_fuga_medio", - "influencia_vertimento_canal_fuga", - "fator_carga_maximo", - "fator_carga_minimo", - "vazao_minima_historica", - "numero_unidades_base", - "tipo_turbina", - "representacao_conjunto", - "teif", - "ip", - "tipo_perda", - "data", - "observacao", - "volume_referencia", - "tipo_regulacao", - ] - ) - for i, r in enumerate(registros): - df.loc[i + 1] = [ + colunas = [ + "nome_usina", + "posto", + "submercado", + "empresa", + "codigo_usina_jusante", + "desvio", + "volume_minimo", + "volume_maximo", + "volume_vertedouro", + "volume_desvio", + "cota_minima", + "cota_maxima", + *[f"a{i}_volume_cota" for i in range(5)], + *[f"a{i}_cota_area" for i in range(5)], + *[f"evaporacao_{m}" for m in MESES_ABREV], + "numero_conjuntos_maquinas", + *[f"maquinas_conjunto_{i}" for i in range(1, 6)], + *[f"potencia_nominal_conjunto_{i}" for i in range(1, 6)], + *[f"queda_nominal_conjunto_{i}" for i in range(1, 6)], + *[f"vazao_nominal_conjunto_{i}" for i in range(1, 6)], + "produtibilidade_especifica", + "perdas", + "numero_polinomios_jusante", + *[f"a{i}_jusante_1" for i in range(5)], + *[f"a{i}_jusante_2" for i in range(5)], + *[f"a{i}_jusante_3" for i in range(5)], + *[f"a{i}_jusante_4" for i in range(5)], + *[f"a{i}_jusante_5" for i in range(5)], + *[f"a{i}_jusante_6" for i in range(5)], + *[f"referencia_jusante_{i}" for i in range(1, 7)], + "canal_fuga_medio", + "influencia_vertimento_canal_fuga", + "fator_carga_maximo", + "fator_carga_minimo", + "vazao_minima_historica", + "numero_unidades_base", + "tipo_turbina", + "representacao_conjunto", + "teif", + "ip", + "tipo_perda", + "data", + "observacao", + "volume_referencia", + "tipo_regulacao", + ] + + dados = [ + [ r.nome, r.posto, r.subsistema, @@ -123,16 +250,24 @@ def __monta_df_de_registros(self) -> pd.DataFrame | None: r.volume_referencia, r.tipo_regulacao, ] + for r in registros + ] + + df = pd.DataFrame( + data=dados, + index=range(1, len(registros) + 1), + columns=colunas, + ) df.index.name = "codigo_usina" df = df.astype( { "nome_usina": str, - "posto": int, - "submercado": int, - "empresa": int, - "codigo_usina_jusante": int, - "desvio": int, + "posto": "Int64", + "submercado": "Int64", + "empresa": "Int64", + "codigo_usina_jusante": "Int64", + "desvio": "Int64", "volume_minimo": float, "volume_maximo": float, "volume_vertedouro": float, @@ -142,16 +277,16 @@ def __monta_df_de_registros(self) -> pd.DataFrame | None: "produtibilidade_especifica": float, "perdas": float, "canal_fuga_medio": float, - "influencia_vertimento_canal_fuga": int, + "influencia_vertimento_canal_fuga": "Int64", "fator_carga_maximo": float, "fator_carga_minimo": float, - "vazao_minima_historica": int, - "numero_unidades_base": int, - "tipo_turbina": int, - "representacao_conjunto": int, + "vazao_minima_historica": "Int64", + "numero_unidades_base": "Int64", + "tipo_turbina": "Int64", + "representacao_conjunto": "Int64", "teif": float, "ip": float, - "tipo_perda": int, + "tipo_perda": "Int64", "data": str, "observacao": str, "volume_referencia": float, diff --git a/idecomp/decomp/modelos/hidr.py b/idecomp/decomp/modelos/hidr.py index 9f556173..58c31fc3 100644 --- a/idecomp/decomp/modelos/hidr.py +++ b/idecomp/decomp/modelos/hidr.py @@ -140,6 +140,8 @@ class RegistroUHEHidr(Register): storage="BINARY", ) + TAMANHO_REGISTRO = 792 + @property def nome(self) -> str: return self.data[0] @@ -451,3 +453,143 @@ def tipo_regulacao(self) -> str: @tipo_regulacao.setter def tipo_regulacao(self, v: str) -> None: self.data[110] = v + + +class RegistroUHEHidrF64(RegistroUHEHidr): + """ + Registro com os dados associados a uma UHE, existentes no + arquivo HIDR, no formato em que os coeficientes dos polinômios + volume-cota e cota-área são armazenados em ponto flutuante + de 64 bits (registros de 832 bytes). + """ + + __slots__: list[str] = [] + + TAMANHO_REGISTRO = 832 + + LINE = Line( + [ + LiteralField(size=12, starting_position=0), # Nome + IntegerField(size=4, starting_position=12), # Posto + IntegerField(size=8, starting_position=16), # Posto BDH + IntegerField(size=4, starting_position=24), # Subsistema + IntegerField(size=4, starting_position=28), # Empresa + IntegerField(size=4, starting_position=32), # Jusante + IntegerField(size=4, starting_position=36), # Desvio + FloatField(size=4, starting_position=40), # Volume Mínimo + FloatField(size=4, starting_position=44), # Volume Máximo + FloatField(size=4, starting_position=48), # Volume Vertedouro + FloatField(size=4, starting_position=52), # Volume Desvio + FloatField(size=4, starting_position=56), # Cota Mínima + FloatField(size=4, starting_position=60), # Cota Máxima + FloatField(size=8, starting_position=64), # Pol VC 0 + FloatField(size=8, starting_position=72), # Pol VC 1 + FloatField(size=8, starting_position=80), # Pol VC 2 + FloatField(size=8, starting_position=88), # Pol VC 3 + FloatField(size=8, starting_position=96), # Pol VC 4 + FloatField(size=8, starting_position=104), # Pol CA 0 + FloatField(size=8, starting_position=112), # Pol CA 1 + FloatField(size=8, starting_position=120), # Pol CA 2 + FloatField(size=8, starting_position=128), # Pol CA 3 + FloatField(size=8, starting_position=136), # Pol CA 4 + IntegerField(size=4, starting_position=144), # Evap JAN + IntegerField(size=4, starting_position=148), # Evap FEV + IntegerField(size=4, starting_position=152), # Evap MAR + IntegerField(size=4, starting_position=156), # Evap ABR + IntegerField(size=4, starting_position=160), # Evap MAI + IntegerField(size=4, starting_position=164), # Evap JUN + IntegerField(size=4, starting_position=168), # Evap JUL + IntegerField(size=4, starting_position=172), # Evap AGO + IntegerField(size=4, starting_position=176), # Evap SET + IntegerField(size=4, starting_position=180), # Evap OUT + IntegerField(size=4, starting_position=184), # Evap NOV + IntegerField(size=4, starting_position=188), # Evap DEZ + IntegerField(size=4, starting_position=192), # Num. Conj. Máquinas + IntegerField( + size=4, starting_position=196 + ), # Num. Máquinas Conj. 1 + IntegerField( + size=4, starting_position=200 + ), # Num. Máquinas Conj. 2 + IntegerField( + size=4, starting_position=204 + ), # Num. Máquinas Conj. 3 + IntegerField( + size=4, starting_position=208 + ), # Num. Máquinas Conj. 4 + IntegerField( + size=4, starting_position=212 + ), # Num. Máquinas Conj. 5 + FloatField(size=4, starting_position=216), # Potência Conj. 1 + FloatField(size=4, starting_position=220), # Potência Conj. 2 + FloatField(size=4, starting_position=224), # Potência Conj. 3 + FloatField(size=4, starting_position=228), # Potência Conj. 4 + FloatField(size=4, starting_position=232), # Potência Conj. 5 + LiteralField(size=300, starting_position=236), # Campos ignorados + FloatField(size=4, starting_position=536), # H Nominal 1 + FloatField(size=4, starting_position=540), # H Nominal 2 + FloatField(size=4, starting_position=544), # H Nominal 3 + FloatField(size=4, starting_position=548), # H Nominal 4 + FloatField(size=4, starting_position=552), # H Nominal 5 + IntegerField(size=4, starting_position=556), # Q Nominal 1 + IntegerField(size=4, starting_position=560), # Q Nominal 2 + IntegerField(size=4, starting_position=564), # Q Nominal 3 + IntegerField(size=4, starting_position=568), # Q Nominal 4 + IntegerField(size=4, starting_position=572), # Q Nominal 5 + FloatField(size=4, starting_position=576), # Produtibilidade + FloatField(size=4, starting_position=580), # Perdas + IntegerField(size=4, starting_position=584), # Núm. Pol. Jus. + FloatField(size=4, starting_position=588), # Pol. Jus. 1 - 0 + FloatField(size=4, starting_position=592), # Pol. Jus. 1 - 1 + FloatField(size=4, starting_position=596), # Pol. Jus. 1 - 2 + FloatField(size=4, starting_position=600), # Pol. Jus. 1 - 3 + FloatField(size=4, starting_position=604), # Pol. Jus. 1 - 4 + FloatField(size=4, starting_position=608), # Pol. Jus. 2 - 0 + FloatField(size=4, starting_position=612), # Pol. Jus. 2 - 1 + FloatField(size=4, starting_position=616), # Pol. Jus. 2 - 2 + FloatField(size=4, starting_position=620), # Pol. Jus. 2 - 3 + FloatField(size=4, starting_position=624), # Pol. Jus. 2 - 4 + FloatField(size=4, starting_position=628), # Pol. Jus. 3 - 0 + FloatField(size=4, starting_position=632), # Pol. Jus. 3 - 1 + FloatField(size=4, starting_position=636), # Pol. Jus. 3 - 2 + FloatField(size=4, starting_position=640), # Pol. Jus. 3 - 3 + FloatField(size=4, starting_position=644), # Pol. Jus. 3 - 4 + FloatField(size=4, starting_position=648), # Pol. Jus. 4 - 0 + FloatField(size=4, starting_position=652), # Pol. Jus. 4 - 1 + FloatField(size=4, starting_position=656), # Pol. Jus. 4 - 2 + FloatField(size=4, starting_position=660), # Pol. Jus. 4 - 3 + FloatField(size=4, starting_position=664), # Pol. Jus. 4 - 4 + FloatField(size=4, starting_position=668), # Pol. Jus. 5 - 0 + FloatField(size=4, starting_position=672), # Pol. Jus. 5 - 1 + FloatField(size=4, starting_position=676), # Pol. Jus. 5 - 2 + FloatField(size=4, starting_position=680), # Pol. Jus. 5 - 3 + FloatField(size=4, starting_position=684), # Pol. Jus. 5 - 4 + FloatField(size=4, starting_position=688), # Pol. Jus. 6 - 0 + FloatField(size=4, starting_position=692), # Pol. Jus. 6 - 1 + FloatField(size=4, starting_position=696), # Pol. Jus. 6 - 2 + FloatField(size=4, starting_position=700), # Pol. Jus. 6 - 3 + FloatField(size=4, starting_position=704), # Pol. Jus. 6 - 4 + FloatField(size=4, starting_position=708), # Pol. Jus. REF - 1 + FloatField(size=4, starting_position=712), # Pol. Jus. REF - 2 + FloatField(size=4, starting_position=716), # Pol. Jus. REF - 3 + FloatField(size=4, starting_position=720), # Pol. Jus. REF - 4 + FloatField(size=4, starting_position=724), # Pol. Jus. REF - 5 + FloatField(size=4, starting_position=728), # Pol. Jus. REF - 6 + FloatField(size=4, starting_position=732), # Canal Fuga Médio + IntegerField(size=4, starting_position=736), # Influência no vert + FloatField(size=4, starting_position=740), # Fator de Carga Máximo + FloatField(size=4, starting_position=744), # Fator de Carga Mínimo + IntegerField(size=4, starting_position=748), # Vazão Mínima Hist. + IntegerField(size=4, starting_position=752), # Núm. de Unid. Base + IntegerField(size=4, starting_position=756), # Tipo de Turbina + IntegerField(size=4, starting_position=760), # Repr. Conjunto + FloatField(size=4, starting_position=764), # TEIF + FloatField(size=4, starting_position=768), # IP + IntegerField(size=4, starting_position=772), # Tipo Perda + LiteralField(size=12, starting_position=776), # Data + LiteralField(size=39, starting_position=788), # Observação + FloatField(size=4, starting_position=827), # Volume de Referência + LiteralField(size=1, starting_position=831), # Tipo de Regular. + ], + storage="BINARY", + ) diff --git a/tests/decomp/test_hidr.py b/tests/decomp/test_hidr.py index 27391e32..c7082c97 100644 --- a/tests/decomp/test_hidr.py +++ b/tests/decomp/test_hidr.py @@ -1,56 +1,232 @@ -from idecomp.decomp.modelos.hidr import RegistroUHEHidr -from idecomp.decomp.hidr import Hidr +from io import BytesIO +from unittest.mock import MagicMock, patch + +import pandas as pd # type: ignore[import-untyped] +import pytest -from os.path import join +from idecomp.decomp.hidr import Hidr +from idecomp.decomp.modelos.hidr import RegistroUHEHidr, RegistroUHEHidrF64 from tests.mocks.mock_open import mock_open -from unittest.mock import MagicMock, patch +ARQ_TESTE = "./tests/mocks/arquivos/hidr.dat" +ARQ_TESTE_F64 = "./tests/mocks/arquivos/hidr_f64.dat" -ARQ_TEST = "./tests/mocks/arquivos/hidr.dat" +# Os dois mocks são arquivos reais independentes, com quantidades de +# usinas diferentes: o mock f32 (hidr.dat) tem 600 registros e o mock +# f64 (hidr_f64.dat) tem 320. +NUM_REGISTROS_F32 = 600 +NUM_REGISTROS_F64 = 320 + + +def converte_arq_teste_para_f64() -> bytes: + h = Hidr.read(ARQ_TESTE) + h.converte_tamanho_registro("f64") + buffer = BytesIO() + h.write(buffer) + return buffer.getvalue() def test_registro_uhe_hidr(): r = RegistroUHEHidr() - with open(join(ARQ_TEST), "rb") as fp: + with open(ARQ_TESTE, "rb") as fp: r.read(fp, storage="BINARY") assert len(r.data) == 111 assert r.nome == "CAMARGOS" +def test_registro_uhe_hidr_f64(): + r = RegistroUHEHidrF64() + with open(ARQ_TESTE_F64, "rb") as fp: + r.read(fp, storage="BINARY") + + assert len(r.data) == 111 + assert r.nome == "CAMARGOS" + assert r.posto == 1 + assert r.volume_minimo == pytest.approx(107.6, rel=1e-6) + assert r.polinomio_volume_cota == pytest.approx( + [893.0279, 0.06682515, -0.0001183834, 1.282643e-07, -5.560146e-11], + rel=1e-12, + ) + assert r.polinomio_cota_area == pytest.approx( + [3211416.0, -10547.2, 11.54139, -0.004207735, 0.0], + rel=1e-12, + abs=1e-15, + ) + assert r.canal_fuga_medio == pytest.approx(885.72906, rel=1e-6) + assert r.data_referencia == "13-05-26" + assert r.tipo_regulacao == "M" + + def test_atributos_encontrados_hidr(): - h = Hidr.read(ARQ_TEST) + h = Hidr.read(ARQ_TESTE) + assert h.cadastro is not None + + +def test_atributos_encontrados_hidr_f64(): + h = Hidr.read(ARQ_TESTE_F64) assert h.cadastro is not None def test_atributos_nao_encontrados_hidr(): - m: MagicMock = mock_open(read_data=b"") + m: MagicMock = mock_open(read_data="") + with patch("builtins.open", m): + ad = Hidr.read(ARQ_TESTE) + assert ad.cadastro is None + + +def test_atributos_nao_encontrados_hidr_f64(): + m: MagicMock = mock_open(read_data="") with patch("builtins.open", m): - ad = Hidr.read(ARQ_TEST) + ad = Hidr.read(ARQ_TESTE_F64) assert ad.cadastro is None def test_eq_hidr(): - h1 = Hidr.read(ARQ_TEST) - h2 = Hidr.read(ARQ_TEST) + h1 = Hidr.read(ARQ_TESTE) + h2 = Hidr.read(ARQ_TESTE) + assert h1 == h2 + + +def test_eq_hidr_f64(): + h1 = Hidr.read(ARQ_TESTE_F64) + h2 = Hidr.read(ARQ_TESTE_F64) assert h1 == h2 def test_neq_hidr(): - h1 = Hidr.read(ARQ_TEST) - h2 = Hidr.read(ARQ_TEST) + h1 = Hidr.read(ARQ_TESTE) + h2 = Hidr.read(ARQ_TESTE) h2.cadastro.iloc[0, 0] = "TESTE" - m: MagicMock = mock_open(read_data=b"") + m: MagicMock = mock_open(read_data="") with patch("builtins.open", m): - h2.write("") + h2.write(ARQ_TESTE) assert h1 != h2 +def test_neq_hidr_f64(): + h1 = Hidr.read(ARQ_TESTE_F64) + h2 = Hidr.read(ARQ_TESTE_F64) + h2.cadastro.iloc[0, 0] = "TESTE" + h2.write(BytesIO()) + assert h1 != h2 + + +def test_deteccao_formato_792(): + # Mock f32 com 600 registros + h = Hidr.read(ARQ_TESTE) + assert h.tamanho_registro == RegistroUHEHidr.TAMANHO_REGISTRO + assert len(h.cadastro) == NUM_REGISTROS_F32 + + +def test_deteccao_formato_832(): + # Mock f64 com 320 registros + h = Hidr.read(ARQ_TESTE_F64) + assert h.tamanho_registro == RegistroUHEHidrF64.TAMANHO_REGISTRO + assert len(h.cadastro) == NUM_REGISTROS_F64 + + +def test_deteccao_formato_f32_320(): + # Os dois tamanhos de registro podem conter 320 ou 600 usinas. O + # mock f32 tem 600 registros; truncamos para 320 para validar a + # detecção do tamanho f32 menor. + with open(ARQ_TESTE, "rb") as fp: + conteudo = fp.read(NUM_REGISTROS_F64 * RegistroUHEHidr.TAMANHO_REGISTRO) + h = Hidr.read(conteudo) + assert h.tamanho_registro == RegistroUHEHidr.TAMANHO_REGISTRO + assert len(h.cadastro) == NUM_REGISTROS_F64 + + +def test_deteccao_formato_f64_600(): + # Converte o mock f32 (600 registros) para f64, obtendo um conteúdo + # de 600 registros de 832 bytes, e valida a detecção desse formato. + h32 = Hidr.read(ARQ_TESTE) + h32.converte_tamanho_registro("f64") + buffer = BytesIO() + h32.write(buffer) + h = Hidr.read(buffer.getvalue()) + assert h.tamanho_registro == RegistroUHEHidrF64.TAMANHO_REGISTRO + assert len(h.cadastro) == NUM_REGISTROS_F32 + + +def test_leitura_com_versao_explicita(): + h32 = Hidr.read(ARQ_TESTE, version="f32") + assert h32.tamanho_registro == RegistroUHEHidr.TAMANHO_REGISTRO + h64 = Hidr.read(ARQ_TESTE_F64, version="f64") + assert h64.tamanho_registro == RegistroUHEHidrF64.TAMANHO_REGISTRO + + +def test_escrita_mantem_formato_f64(): + h64 = Hidr.read(ARQ_TESTE_F64) + buffer = BytesIO() + h64.write(buffer) + assert ( + len(buffer.getvalue()) + == NUM_REGISTROS_F64 * RegistroUHEHidrF64.TAMANHO_REGISTRO + ) + + +def test_conversao_para_f64(): + conteudo = converte_arq_teste_para_f64() + assert ( + len(conteudo) == NUM_REGISTROS_F32 * RegistroUHEHidrF64.TAMANHO_REGISTRO + ) + h32 = Hidr.read(ARQ_TESTE) + h64 = Hidr.read(conteudo) + assert h64.tamanho_registro == RegistroUHEHidrF64.TAMANHO_REGISTRO + pd.testing.assert_frame_equal(h32.cadastro, h64.cadastro) + + +def test_conversao_para_f32(): + h64 = Hidr.read(ARQ_TESTE_F64) + h32 = Hidr.read(ARQ_TESTE_F64) + h32.converte_tamanho_registro("f32") + buffer = BytesIO() + h32.write(buffer) + assert ( + len(buffer.getvalue()) + == NUM_REGISTROS_F64 * RegistroUHEHidr.TAMANHO_REGISTRO + ) + relido = Hidr.read(buffer.getvalue()) + assert relido.tamanho_registro == RegistroUHEHidr.TAMANHO_REGISTRO + # A conversão para f32 perde precisão nos coeficientes dos + # polinômios, então a comparação é aproximada + pd.testing.assert_frame_equal( + h64.cadastro, relido.cadastro, check_exact=False, rtol=1e-6 + ) + + +def test_conversao_ida_e_volta_preserva_valores(): + h1 = Hidr.read(ARQ_TESTE) + h2 = Hidr.read(ARQ_TESTE) + h2.converte_tamanho_registro("f64") + h2.converte_tamanho_registro("f32") + assert h2.tamanho_registro == RegistroUHEHidr.TAMANHO_REGISTRO + assert h1 == h2 + + +def test_conversao_precisao_invalida(): + h = Hidr.read(ARQ_TESTE) + with pytest.raises(ValueError, match="Precisão inválida: 'f16'"): + h.converte_tamanho_registro("f16") + + +def test_deteccao_formato_desconhecido(): + # 82368 bytes não corresponde a 320 nem a 600 registros de 792 ou + # 832 bytes, então o tamanho não é reconhecido: a leitura emite um + # aviso e assume o formato f32 (792 bytes). + with open(ARQ_TESTE, "rb") as fp: + conteudo = fp.read(82368) + with pytest.warns(UserWarning, match="não corresponde a nenhum formato"): + h = Hidr.read(conteudo) + assert h.tamanho_registro == RegistroUHEHidr.TAMANHO_REGISTRO + + def test_leitura_escrita_hidr(): - h1 = Hidr.read(ARQ_TEST) - m_escrita: MagicMock = mock_open(read_data=b"") + h1 = Hidr.read(ARQ_TESTE) + m_escrita: MagicMock = mock_open(read_data="") with patch("builtins.open", m_escrita): - h1.write("") + h1.write(ARQ_TESTE) # Recupera o que foi escrito chamadas = m_escrita.mock_calls linhas_escritas = [ @@ -58,5 +234,14 @@ def test_leitura_escrita_hidr(): ] m_releitura: MagicMock = mock_open(read_data=b"".join(linhas_escritas)) with patch("builtins.open", m_releitura): - h2 = Hidr.read(ARQ_TEST) + h2 = Hidr.read(ARQ_TESTE) assert h1 == h2 + + +def test_leitura_escrita_hidr_f64(): + h1 = Hidr.read(ARQ_TESTE_F64) + buffer = BytesIO() + h1.write(buffer) + h2 = Hidr.read(buffer.getvalue()) + assert h2.tamanho_registro == RegistroUHEHidrF64.TAMANHO_REGISTRO + assert h1 == h2 diff --git a/tests/mocks/arquivos/hidr_f64.dat b/tests/mocks/arquivos/hidr_f64.dat new file mode 100644 index 0000000000000000000000000000000000000000..52eb05568130bcece7c3e805609a29dcb28384d2 GIT binary patch literal 266240 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 I0$