87 lines
3.2 KiB
Python
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 |