100 lines
3.1 KiB
Python
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()
|