Files
IPSExtractor/services/bot_descarga/domain/excel_split.py
T

100 lines
3.1 KiB
Python

"""Parseo y partición de Excel/CSV de radicados para descarga distribuida."""
from __future__ import annotations
import csv
import io
import re
from typing import Any
from openpyxl import Workbook, load_workbook
_HEADER_NAMES = ("radicado", "proceso", "numero", "número")
def _clean_nit(valor: Any) -> str:
valor = str(valor).strip()
valor = re.sub(r'[\\/:*?"<>|]', "_", valor)
return valor if valor else "SIN_NIT"
def parse_radicado_rows(data: bytes, filename: str = "input.xlsx") -> list[tuple[str, str]]:
"""Devuelve lista de (radicado, nit)."""
name = (filename or "input.xlsx").lower()
if name.endswith(".csv"):
return _parse_csv(data)
return _parse_xlsx(data)
def _parse_csv(data: bytes) -> list[tuple[str, str]]:
text = None
for enc in ("utf-8-sig", "utf-8", "latin-1", "cp1252"):
try:
text = data.decode(enc)
break
except UnicodeDecodeError:
continue
if text is None:
text = data.decode("utf-8", errors="replace")
sample = text[:4096]
try:
dialect = csv.Sniffer().sniff(sample, delimiters="|,;\t")
delim = dialect.delimiter
except csv.Error:
delim = "|" if sample.count("|") >= sample.count(",") else ","
reader = csv.reader(io.StringIO(text), delimiter=delim)
out: list[tuple[str, str]] = []
for fila in reader:
if not fila:
continue
radicado = fila[0] if len(fila) > 0 else None
nit = fila[1] if len(fila) > 1 else None
if not radicado or str(radicado).strip() in ("", "None"):
continue
radicado_s = str(radicado).strip()
if radicado_s.lower() in _HEADER_NAMES:
continue
out.append((radicado_s, _clean_nit(nit)))
return out
def _parse_xlsx(data: bytes) -> list[tuple[str, str]]:
wb = load_workbook(io.BytesIO(data), data_only=True)
ws = wb.active
out: list[tuple[str, str]] = []
for fila in ws.iter_rows(values_only=True):
radicado = fila[0] if len(fila) > 0 else None
nit = fila[1] if len(fila) > 1 else None
if not radicado or str(radicado).strip() in ("", "None"):
continue
radicado_s = str(radicado).strip()
if radicado_s.lower() in _HEADER_NAMES:
continue
out.append((radicado_s, _clean_nit(nit)))
return out
def split_rows(rows: list[tuple[str, str]], n: int) -> list[list[tuple[str, str]]]:
"""Parte filas en n trozos lo más equilibrados posible (chunks contiguos)."""
if n <= 1 or not rows:
return [rows]
n = min(n, len(rows))
size, rem = divmod(len(rows), n)
chunks: list[list[tuple[str, str]]] = []
idx = 0
for i in range(n):
take = size + (1 if i < rem else 0)
chunks.append(rows[idx : idx + take])
idx += take
return [c for c in chunks if c]
def rows_to_xlsx_bytes(rows: list[tuple[str, str]]) -> bytes:
wb = Workbook()
ws = wb.active
ws.append(["radicado", "nit"])
for radicado, nit in rows:
ws.append([radicado, nit])
buf = io.BytesIO()
wb.save(buf)
return buf.getvalue()