diff --git a/src/asic/files/definitions/__init__.py b/src/asic/files/definitions/__init__.py index d2a064f..fedde37 100644 --- a/src/asic/files/definitions/__init__.py +++ b/src/asic/files/definitions/__init__.py @@ -5,6 +5,7 @@ from asic.files.definitions.balcttos import BALCTTOS from asic.files.definitions.pep import PEP from asic.files.definitions.pme import PME +from asic.files.definitions.ptb import PTB from asic.files.definitions.trsd import TRSD from asic.files.definitions.tfroc import TFROC from asic.files.definitions.tgrl import TGRL @@ -29,6 +30,7 @@ FileKind.SNTIE: SNTIE, FileKind.AFAC: AFAC, FileKind.DSPCTTOS: DSPCTTOS, + FileKind.PTB: PTB, } diff --git a/src/asic/files/definitions/adem.py b/src/asic/files/definitions/adem.py index 00ab907..658b4dc 100644 --- a/src/asic/files/definitions/adem.py +++ b/src/asic/files/definitions/adem.py @@ -54,10 +54,10 @@ class ADEM(AsicFile): kind = FileKind.ADEM visibility = VisibilityEnum.PUBLIC - name_pattern = "(?Padem)(?P[0-9]{2})(?P[0-9]{2}).(?P[tT]{1}[xX]{1}[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "Los archivos de demanda comercial" + description = ASIC_FILE_CONFIG[kind].description _format = FORMAT @@ -104,8 +104,6 @@ def preprocess(self, target: Path | BytesIO | StringIO) -> pd.DataFrame: filter = np.full(total.index.shape, True) filter = filter & (total["CODIGO"].isin(["DMRE", "PRRE"])) - if self.agent is not None: - filter = filter & (total["AGENTE"] == self.agent.upper()) total = total[filter] total["FECHA"] = pd.to_datetime( diff --git a/src/asic/files/definitions/aenc.py b/src/asic/files/definitions/aenc.py index acaf1b5..f2ec359 100644 --- a/src/asic/files/definitions/aenc.py +++ b/src/asic/files/definitions/aenc.py @@ -53,10 +53,10 @@ class AENC(AsicFile): kind = FileKind.AENC visibility = VisibilityEnum.AGENT - name_pattern = "(?Paenc)(?P[0-9]{2})(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "Los archivos de demanda de agente por frontera" + description = ASIC_FILE_CONFIG[kind].description # path = None # year = None # month = None diff --git a/src/asic/files/definitions/afac.py b/src/asic/files/definitions/afac.py index b020eed..45a6c2c 100644 --- a/src/asic/files/definitions/afac.py +++ b/src/asic/files/definitions/afac.py @@ -137,10 +137,10 @@ class AFAC(AsicFile): kind = FileKind.AFAC visibility = VisibilityEnum.PUBLIC - name_pattern = "(?Pafac)(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "Muestra para cada uno de los agentes, todos los conceptos de la liquidación del Mercado Colombiano, con los cuales se pueden consolidar las Compras y Ventas Totales del Agente para un proceso de liquidación o ajuste mensual." + description = ASIC_FILE_CONFIG[kind].description _format = FORMAT @property diff --git a/src/asic/files/definitions/balcttos.py b/src/asic/files/definitions/balcttos.py index dcd26cb..8dfe0d3 100644 --- a/src/asic/files/definitions/balcttos.py +++ b/src/asic/files/definitions/balcttos.py @@ -53,7 +53,7 @@ class BALCTTOS(AsicFile): kind = FileKind.BALCTTOS visibility = VisibilityEnum.AGENT - name_pattern = "(?PBalCttos)(?P[0-9]{2})(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template description = "Los archivos de despacho de demanda por mercados R y NR, Nacional, TIE e Internacional" diff --git a/src/asic/files/definitions/dspcttos.py b/src/asic/files/definitions/dspcttos.py index ec55138..63083c0 100644 --- a/src/asic/files/definitions/dspcttos.py +++ b/src/asic/files/definitions/dspcttos.py @@ -77,10 +77,10 @@ class DSPCTTOS(AsicFile): kind = FileKind.DSPCTTOS visibility = VisibilityEnum.AGENT - name_pattern = "(?Pdspcttos)(?P[0-9]{2})(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "Información de los contratos que fueron incluidos en el despacho del respectivo día, para cada agente según sea su posición en el contrato de comprador o de vendedor, para comercializadores." + description = ASIC_FILE_CONFIG[kind].description _format = FORMAT @property diff --git a/src/asic/files/definitions/pep.py b/src/asic/files/definitions/pep.py index c0c8a53..680e971 100644 --- a/src/asic/files/definitions/pep.py +++ b/src/asic/files/definitions/pep.py @@ -23,10 +23,10 @@ class PEP(AsicFile): kind = FileKind.PEP visibility = VisibilityEnum.PUBLIC - name_pattern = "(?Ppep)(?P[0-9]{2})(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "Contiene información de los precios de escasez promedio ponderado por sistema y por agente. Resolución 140 de 2017" + description = ASIC_FILE_CONFIG[kind].description _format = FORMAT diff --git a/src/asic/files/definitions/pme.py b/src/asic/files/definitions/pme.py index 351ee3e..b61d517 100644 --- a/src/asic/files/definitions/pme.py +++ b/src/asic/files/definitions/pme.py @@ -23,10 +23,10 @@ class PME(AsicFile): kind = FileKind.PME visibility = VisibilityEnum.PUBLIC - name_pattern = "(?PPME)(?P140)(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "Contiene información de Insumos del calculo del Precio Marginal de escasez, según Resolución CREG 140/2017" + description = ASIC_FILE_CONFIG[kind].description _format = FORMAT diff --git a/src/asic/files/definitions/ptb.py b/src/asic/files/definitions/ptb.py new file mode 100644 index 0000000..6b30b30 --- /dev/null +++ b/src/asic/files/definitions/ptb.py @@ -0,0 +1,104 @@ +import logging +from io import BytesIO, StringIO +from pathlib import Path, PureWindowsPath + +import numpy as np + +# Third party imports +import pandas as pd + +from asic import ASIC_FILE_CONFIG +from asic.files.file import AsicFile, FileKind, VisibilityEnum + +# Local application imports + +logger = logging.getLogger(__name__) + +FORMAT = { + "type": "csv", + "sep": ";", + "encoding": "cp1252", + "dt_fields": {}, + "dtype": { + "FECHA": str, + "PERIODO": int, + "PBNAL": float, + "DdaCom": float, + "HORA 01": float, + "DEM": float, + "PTB": float, + "OHEFx": float, + "OHEFy": float, + "OHEFz": float, + "GIx": float, + "GIy": float, + "GIz": float, + "GIv": float, + "GINAL_NDC": float, + }, +} + + +class PTB(AsicFile): + kind = FileKind.PTB + visibility = VisibilityEnum.PUBLIC + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern + location_pattern = ASIC_FILE_CONFIG[kind].location_pattern + location = ASIC_FILE_CONFIG[kind].location_template + description = ASIC_FILE_CONFIG[kind].description + + _format = FORMAT + + @property + def path(self) -> PureWindowsPath: + return self._path + + @property + def year(self) -> int: + return self._year + + @property + def month(self) -> int: + return self._month + + @property + def day(self) -> int | None: + return self._day + + @property + def extension(self) -> str: + return self._extension + + @property + def version(self) -> str | None: + return self._version + + @property + def agent(self) -> str | None: + return self._agent + + def preprocess(self, target: Path | BytesIO | StringIO) -> pd.DataFrame: + """ + PTB: es un archivo diario + versiones: TX2, TXR, TXF + """ + total = self.read(target) + + total["FECHA"] = pd.to_datetime( + total["FECHA"], + format="%Y-%m-%d", + ) + + total = ( + total.set_index(["FECHA", "PERIODO"]) + .stack() + .reset_index() + ) + total = total.rename(columns={"PERIODO":"NOMBRE HORA","level_2": "CODIGO", 0: "VALOR"}) + total["HORA"] = total["NOMBRE HORA"] + total["HORA"] = pd.to_timedelta(total["HORA"], unit="h") + total["FECHA_HORA"] = total["FECHA"] + total["HORA"] + + return_cols = ["FECHA_HORA", "CODIGO", "VALOR"] + return total[return_cols] + diff --git a/src/asic/files/definitions/sntie.py b/src/asic/files/definitions/sntie.py index 7ded87c..8241545 100644 --- a/src/asic/files/definitions/sntie.py +++ b/src/asic/files/definitions/sntie.py @@ -52,10 +52,10 @@ class SNTIE(AsicFile): kind = FileKind.SNTIE visibility = VisibilityEnum.PUBLIC - name_pattern = "(?Psntie)(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "Ajuste final de transacciones TIE, denominados Saldos Netos TIE, que se calcula a partir de la diferencia, de los precios informados por el Administrador del Mercado Exportador en la liquidación final, respecto a los valores obtenidos en la segunda liquidación." + description = ASIC_FILE_CONFIG[kind].description _format = FORMAT diff --git a/src/asic/files/definitions/tfroc.py b/src/asic/files/definitions/tfroc.py index 508b514..555c22c 100644 --- a/src/asic/files/definitions/tfroc.py +++ b/src/asic/files/definitions/tfroc.py @@ -32,10 +32,11 @@ class TFROC(AsicFile): kind = FileKind.TFROC visibility = VisibilityEnum.AGENT - name_pattern = "(?Ptfroc)(?P[0-9]{2})(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "Los archivos de factores de perdidas aplicables a los consumos" + description = ASIC_FILE_CONFIG[kind].description + _format = FORMAT @property diff --git a/src/asic/files/definitions/tgrl.py b/src/asic/files/definitions/tgrl.py index e93700a..e6cc289 100644 --- a/src/asic/files/definitions/tgrl.py +++ b/src/asic/files/definitions/tgrl.py @@ -50,10 +50,10 @@ class TGRL(AsicFile): kind = FileKind.TGRL visibility = VisibilityEnum.PUBLIC - name_pattern = "(?Ptgrl)(?P[0-9]{2})(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "Información general horaria que soporta las liquidaciones asociadas con los conceptos de desviación, compra/venta de contratos, restricciones, reconciliaciones, AGC, etc." + description = ASIC_FILE_CONFIG[kind].description _format = FORMAT diff --git a/src/asic/files/definitions/trsd.py b/src/asic/files/definitions/trsd.py index 98befc4..2c95ac3 100644 --- a/src/asic/files/definitions/trsd.py +++ b/src/asic/files/definitions/trsd.py @@ -50,10 +50,10 @@ class TRSD(AsicFile): kind = FileKind.TRSD visibility = VisibilityEnum.PUBLIC - name_pattern = "(?Ptrsd)(?P[0-9]{2})(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "" # TODO + description = ASIC_FILE_CONFIG[kind].description _format = FORMAT diff --git a/src/asic/files/definitions/trsm.py b/src/asic/files/definitions/trsm.py index 78a8ead..4c6bd0e 100644 --- a/src/asic/files/definitions/trsm.py +++ b/src/asic/files/definitions/trsm.py @@ -26,10 +26,11 @@ class TRSM(AsicFile): kind = FileKind.TRSM visibility = VisibilityEnum.PUBLIC - name_pattern = "(?Ptrsm)(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "Contiene información de indicadores económicos, energéticos y financieros." + description = ASIC_FILE_CONFIG[kind].description + _format = FORMAT @property diff --git a/src/asic/files/definitions/tserv.py b/src/asic/files/definitions/tserv.py index 5c780f5..ddb7dd4 100644 --- a/src/asic/files/definitions/tserv.py +++ b/src/asic/files/definitions/tserv.py @@ -29,10 +29,11 @@ class TSERV(AsicFile): kind = FileKind.TSERV visibility = VisibilityEnum.PUBLIC - name_pattern = "(?Ptserv)(?P[0-9]{2}).(?P[a-zA-Z0-9]+)" + name_pattern = ASIC_FILE_CONFIG[kind].name_pattern location_pattern = ASIC_FILE_CONFIG[kind].location_pattern location = ASIC_FILE_CONFIG[kind].location_template - description = "Contiene el soporte a la liquidación de servicios CND, SIC y FAZNI." + description = ASIC_FILE_CONFIG[kind].description + _format = FORMAT @property diff --git a/src/asic/files/file.py b/src/asic/files/file.py index a0b2f45..ccd20cb 100644 --- a/src/asic/files/file.py +++ b/src/asic/files/file.py @@ -71,6 +71,7 @@ class FileKind(str, enum.Enum): SNTIE = "sntie" AFAC = "afac" DSPCTTOS = "dspcttos" + PTB = "ptb" # LDCBMR = "ldcbmr" # PUBFC = "pubfc" @@ -107,7 +108,7 @@ class AsicFile(ABC): name_pattern: str location_pattern: str location: str - description: str + description: str | None @property @abstractmethod diff --git a/tests/RUTA/PUBLICA/DEL/FTP/2025-04/PTB0401.txf b/tests/RUTA/PUBLICA/DEL/FTP/2025-04/PTB0401.txf new file mode 100644 index 0000000..c6c611f --- /dev/null +++ b/tests/RUTA/PUBLICA/DEL/FTP/2025-04/PTB0401.txf @@ -0,0 +1,3 @@ +FECHA;PERIODO;PBNAL;DdaCom;DEM;PTB;OHEFx;OHEFy;OHEFz;GIx;GIy;GIz;GIv;GINAL_NDC +2025-04-01;20;359.984921882;11015609.18;10231103.04;359.984921882;0;0;0;0;1957324.73294819156381247981710532824565;8151609.67705180843618752018289467175435;122168.63;784506.14 +2025-04-01;21;359.984921882;10851648.73;10062303.85;359.984921882;0;0;0;0;1957324.73294819156381247981710532824565;7982876.04705180843618752018289467175435;122103.07;789344.88 diff --git a/tests/TEST_ASIC_FILE_CONFIG.jsonl b/tests/TEST_ASIC_FILE_CONFIG.jsonl index 953c79c..c466df8 100644 --- a/tests/TEST_ASIC_FILE_CONFIG.jsonl +++ b/tests/TEST_ASIC_FILE_CONFIG.jsonl @@ -19,3 +19,4 @@ {"code":"trsm", "visibility": "public","name_pattern":"(?Ptrsm)(?P[0-9]{2}).(?P[a-zA-Z0-9]+)","location_pattern":"/RUTA/PUBLICA/DEL/FTP/(?P[0-9]{4})-(?P[0-9]{2})/","description":"Contiene información de indicadores económicos, energéticos y financieros."} {"code":"afac", "visibility": "public","name_pattern":"(?Pafac)(?P[0-9]{2}).(?P[a-zA-Z0-9]+)","location_pattern":"/RUTA/PUBLICA/DEL/FTP/(?P[0-9]{4})-(?P[0-9]{2})/","description":"Muestra para cada uno de los agentes, todos los conceptos de la liquidación del Mercado Colombiano, con los cuales se pueden consolidar las Compras y Ventas Totales del Agente para un proceso de liquidación o ajuste mensual."} {"code":"tserv", "visibility": "public","name_pattern":"(?Ptserv)(?P[0-9]{2}).(?P[a-zA-Z0-9]+)","location_pattern":"/RUTA/PUBLICA/DEL/FTP/(?P[0-9]{4})-(?P[0-9]{2})/","description":"Contiene el soporte a la liquidación de servicios CND, s y FAZNI."} +{"code":"ptb", "visibility": "public", "name_pattern":"(?Pptb)(?P[0-9]{2})(?P[0-9]{2}).(?P[a-zA-Z0-9]+)","location_pattern":"/RUTA/PUBLICA/DEL/FTP/(?P[0-9]{4})-(?P[0-9]{2})/","description":"El PTB considera tres techos en las transacciones en bolsa cuando el precio de bolsa es mayor a cualquiera de los precios de escasez."} diff --git a/tests/conftest.py b/tests/conftest.py index 3aee791..81ac526 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -160,4 +160,15 @@ "version": "003", "agent": "xxxc", }, + "ptb": { + "path": "/RUTA/PUBLICA/DEL/FTP/2025-04/PTB0401.txf", + "kind": "ptb", + "visibility": "public", + "year": 2025, + "month": 4, + "day": 1, + "extension": ".txf", + "version": "003", + "agent": None, + }, } diff --git a/tests/test_ptb.py b/tests/test_ptb.py new file mode 100644 index 0000000..f8797b4 --- /dev/null +++ b/tests/test_ptb.py @@ -0,0 +1,41 @@ +import pathlib + +from pytest import fixture + +from asic.files.definitions.ptb import PTB + +from .conftest import ALL_FILES, TESTFILES + + +@fixture +def ptb_remote_path(): + ptb_path = ALL_FILES["ptb"]["path"] + path = pathlib.PureWindowsPath(ptb_path) + return path + + +@fixture +def ptb_file(ptb_remote_path): + path = ptb_remote_path + file = PTB.from_remote_path(path) + return file + + +@fixture +def local_ptb_file(ptb_file: PTB, datafiles: pathlib.Path) -> pathlib.Path: + relative_path = ptb_file.path.relative_to(ptb_file.path.anchor) + local_file = datafiles / relative_path + assert local_file.is_file() + return local_file + + +@TESTFILES +def test_ptb_read(ptb_file: PTB, local_ptb_file): + data = ptb_file.read(local_ptb_file) + assert len(data) == 2 + + +@TESTFILES +def test_ptb_preprocess(ptb_file: PTB, local_ptb_file): + long_data = ptb_file.preprocess(local_ptb_file) + assert len(long_data) == 24