Files

148 lines
5.1 KiB
Python

"""Utilidades generales: parseo, descubrimiento de documentos y recolección.
Incluye heurísticas para elegir el documento a procesar por radicado,
priorizando TIFF cuando existe y recurriendo a PDF/TIFF por keywords o patrones.
"""
import os
import fnmatch
from typing import List, Dict
# Extensiones de documentos soportados para búsqueda por patrones
ALLOWED_DOC_EXTS = (".pdf", ".tif", ".tiff")
def _matches_pattern(filename: str, pattern: str) -> bool:
"""Determina si `filename` coincide con `pattern` usando semántica tipo Windows.
- Admite comodines `*` y `?` en el patrón.
- Si el patrón incluye una extensión válida (.pdf/.tif/.tiff), se compara contra
el nombre completo (incluida la extensión).
- Si el patrón no incluye una extensión válida, se compara solo contra el nombre
sin extensión, pero siempre restringiendo a archivos con extensiones permitidas.
- Si el patrón no tiene comodines, se interpreta como 'contiene' (case-insensitive)
sobre el nombre completo, respetando igualmente las extensiones permitidas.
"""
if not isinstance(filename, str) or not isinstance(pattern, str):
return False
fname = filename.lower()
pat = pattern.lower().strip()
if not pat:
return False
# Solo considerar archivos con extensión permitida
stem, ext = os.path.splitext(fname)
if ext not in ALLOWED_DOC_EXTS:
return False
has_wildcards = "*" in pat or "?" in pat
if has_wildcards:
p_stem, p_ext = os.path.splitext(pat)
# Patrón con extensión explícita soportada: aplicar sobre nombre completo
if p_ext in ALLOWED_DOC_EXTS:
return fnmatch.fnmatch(fname, pat)
# Patrón sin extensión explícita: aplicar sobre el nombre sin extensión
return fnmatch.fnmatch(stem, pat)
# Sin comodines: interpretar como 'contiene' en el nombre completo
return pat in fname
def find_pdf_in_dir_by_keywords(dir_path: str, keywords: List[str]) -> str | None:
"""Devuelve la primera ruta (PDF/TIFF) cuyo nombre coincide con alguno de los patrones.
- Acepta keywords simples (sin `*`/`?`) como 'contiene' insensible a mayúsculas.
- Acepta patrones con `*` y `?` tipo Windows (p. ej. `*_1`, `*_OK?.pdf`). Solo
se consideran archivos con extensiones en `ALLOWED_DOC_EXTS`.
"""
if not os.path.isdir(dir_path):
return None
files = [f for f in os.listdir(dir_path) if f.lower().endswith(ALLOWED_DOC_EXTS)]
if not files or not keywords:
return None
for kw in keywords:
matches = [f for f in files if _matches_pattern(f, kw)]
if matches:
matches.sort()
return os.path.join(dir_path, matches[0])
return None
def find_first_tiff(dir_path: str) -> str | None:
"""Devuelve la primera ruta a TIFF encontrada (orden lexicográfico)."""
if not os.path.isdir(dir_path):
return None
for f in sorted(os.listdir(dir_path)):
if f.lower().endswith(('.tif', '.tiff')):
return os.path.join(dir_path, f)
return None
def collect_radicado_docs(root_path: str, keywords: List[str]) -> List[Dict[str, str]]:
"""Para cada radicado, prioriza TIFF; si no hay, busca PDF/TIFF por keywords/patrones."""
entries: List[Dict[str, str]] = []
if not os.path.isdir(root_path):
raise ValueError(f"La ruta raíz no es un directorio: {root_path}")
for radicado in sorted(os.listdir(root_path)):
subdir = os.path.join(root_path, radicado)
if not os.path.isdir(subdir):
continue
selected = find_first_tiff(subdir)
if not selected:
selected = find_pdf_in_dir_by_keywords(subdir, keywords)
if not selected:
continue
entries.append({
"radicado": radicado,
"path": selected,
})
return entries
def collect_documents_flat(root_path: str, keywords: List[str] = None) -> List[Dict[str, str]]:
"""Recolecta documentos PDF/TIFF directamente en la raíz.
El 'radicado' es el nombre del archivo sin extensión.
Si se proveen keywords, solo incluye archivos que coincidan.
"""
entries: List[Dict[str, str]] = []
if not os.path.isdir(root_path):
raise ValueError(f"La ruta raíz no es un directorio: {root_path}")
files = sorted([f for f in os.listdir(root_path) if f.lower().endswith(ALLOWED_DOC_EXTS)])
for f in files:
# Si hay keywords, filtrar
if keywords:
match = False
for kw in keywords:
if _matches_pattern(f, kw):
match = True
break
if not match:
continue
radicado = os.path.splitext(f)[0]
entries.append({
"radicado": radicado,
"path": os.path.join(root_path, f)
})
return entries
def find_first_matching_file(files: list[str], keyword: str) -> str | None:
"""Devuelve el primer nombre de archivo que coincide con el keyword/patrón.
- Usa `_matches_pattern` para soportar comodines `*` y `?`.
- Solo considera archivos con extensiones en `ALLOWED_DOC_EXTS`.
- Retorna el nombre de archivo tal cual se recibió.
"""
if not files or not keyword:
return None
candidates = [f for f in files if _matches_pattern(f, keyword)]
if not candidates:
return None
candidates.sort()
return candidates[0]