invoice-processing-google-d.../services/utils/data_cleaner.py
2025-08-26 12:26:03 +02:00

87 lines
3.2 KiB
Python

# src/cli_invoice_processor/data_cleaner.py
import logging
import locale
from dateutil import parser
from typing import Optional
from datetime import datetime
SPANISH_TO_ENGLISH_MONTHS = {
'enero': 'january',
'febrero': 'february',
'marzo': 'march',
'abril': 'april',
'mayo': 'may',
'junio': 'june',
'julio': 'july',
'agosto': 'august',
'septiembre': 'september',
'octubre': 'october',
'noviembre': 'november',
'diciembre': 'december'
}
def _parse_with_fallback(date_string: str) -> Optional[datetime]:
"""
Intenta parsear la fecha usando un fallback manual que primero limpia
preposiciones comunes en español ("de", "del") y luego traduce los meses.
"""
# 1. Normalizar a minúsculas para trabajar de forma consistente
temp_string = date_string.lower()
# 2. Traducir el mes de español a inglés
for spa, eng in SPANISH_TO_ENGLISH_MONTHS.items():
if spa in temp_string:
temp_string = temp_string.replace(spa, eng)
break # Salimos del bucle una vez que encontramos y reemplazamos el mes
# 3. Eliminar preposiciones comunes, cuidando los espacios para evitar unir palabras
temp_string = temp_string.replace(' de ', ' ')
temp_string = temp_string.replace(' del ', ' ')
# Después de la limpieza, la cadena debería ser algo como '5 january 2030', que es parseable.
try:
logging.info(f"Attempting to parse cleaned date string: '{temp_string}'")
return parser.parse(temp_string)
except (parser.ParserError, ValueError):
# Si incluso después de la limpieza falla, no podemos hacer más.
logging.warning(f"Fallback parsing failed even for cleaned string: '{temp_string}'")
return None
def normalize_date(date_string: str) -> Optional[str]:
"""
Parses a date string from various formats and normalizes it to DD/MM/YYYY.
It first tries using Spanish locale, and if it fails, it uses a manual
cleaning and translation fallback.
"""
if not date_string:
return None
original_locale = locale.getlocale(locale.LC_TIME)
parsed_date = None
# Estrategia 1: Intentar con el locale español
try:
try:
locale.setlocale(locale.LC_TIME, 'es_ES.UTF-8')
except locale.Error:
locale.setlocale(locale.LC_TIME, 'Spanish')
parsed_date = parser.parse(date_string)
except (parser.ParserError, ValueError, locale.Error):
logging.warning(f"Could not parse date '{date_string}' using Spanish locale. Attempting robust fallback.")
# Estrategia 2: Si el locale falla, usar el fallback robusto
parsed_date = _parse_with_fallback(date_string)
finally:
# Siempre restauramos el locale original
locale.setlocale(locale.LC_TIME, original_locale)
if parsed_date:
# Aquí se asegura el formato DD/MM/AAAA.
# '%d' -> día con cero (05), '%m' -> mes con cero (01), '%Y' -> año (2030)
return parsed_date.strftime('%d/%m/%Y')
else:
# Si ambas estrategias fallan, registramos el error final
logging.error(f"Failed to parse date '{date_string}' with all available methods.")
return None