Spaces:
Sleeping
Sleeping
from fastapi import FastAPI, Query, HTTPException | |
import csv | |
import re | |
import unicodedata | |
from typing import Dict, Any | |
# ============================== | |
# CONFIGURACIÓN DE CLASIFICADORES | |
# ============================== | |
CLASIFICADORES = { | |
"abaco": { | |
"csv": "diccionario_catabaco.csv", | |
"categoria_tipo": { | |
'Sin Categoria': 'Sin Categoria', | |
'Otros No Alimentos': 'other', | |
'Alimentos de hoteles, restaurantes o casinos': 'food', | |
'Juguetes': 'other', | |
'Muebles': 'other', | |
'Pequeño Aparato': 'other', | |
'Textiles': 'other', | |
'Bebidas Alcohólicas': 'food', | |
'Dulces y Postres': 'food', | |
'Papelería': 'other', | |
'Bebidas Azucaradas': 'food', | |
'Bebidas Azúcaradas': 'food', | |
'Carnes': 'food', | |
'Panadería de sal': 'food', | |
'Materiales de construcción': 'other', | |
'Hogar': 'other', | |
'Otras bebidas': 'food', | |
'Grasas tipo 3 (saturadas)': 'food', | |
'Otros Alimentos': 'food', | |
'Cereales': 'food', | |
'Frutas': 'food', | |
'Personal': 'other', | |
'Gama Blanca': 'other', | |
'Gama Marron': 'other', | |
'Frutos secos y semillas': 'food', | |
'Paquetes/snacks': 'food', | |
'Agua': 'food', | |
'Productos lácteos grasa entera': 'food', | |
'Dulces y postres': 'food', | |
'Otras fórmulas especiales': 'food', | |
'Leches Enteras': 'food', | |
'Fórmulas infantiles': 'food', | |
'Leguminosas Secas': 'food', | |
'Tuberculos': 'food', | |
'Raíz': 'food', | |
'Plátano': 'food', | |
'Verduras': 'food', | |
'Panadería dulce': 'food', | |
'Huevos': 'food', | |
'Azúcares Simples': 'food', | |
'Medicamentos sin fórmula médica': 'other', | |
'Suplementos nutricionales': 'food', | |
'Cereales ': 'food', | |
'Alimentos para Mascotas': 'other', | |
'Grasas tipo 2 (poliinsaturadas)': 'food', | |
'Grasas tipo 1 (monoinsaturadas)': 'food' | |
} | |
}, | |
"mexico": { | |
"csv": "diccionario_catebamx2.csv", | |
"categoria_tipo": { | |
'No Comestible': 'other', | |
'Fruta y verdura': 'food', | |
'Pan': 'food', | |
'Cereales y Leguminosas': 'food', | |
'Bebidas Saborizadas': 'food', | |
'Azucares': 'food', | |
'Abarrotes': 'food', | |
'Proteina animal': 'food', | |
'Alimento Preparado': 'food', | |
'Refrigerados y Congelados': 'food', | |
'Lactéos': 'food', | |
'Agua': 'food', | |
'Jugo y Néctar': 'food', | |
'Leguminosa': 'food', | |
'Grasas Animales': 'food', | |
'Confiteria': 'food', | |
'Suplementos': 'food', | |
'Salsas': 'food', | |
'Café y Té': 'food', | |
'Sabores listos para comer': 'food', | |
'Aceites Vegetales': 'food', | |
'Raices, Tuberculos': 'food', | |
'Semillas y Nueces': 'food', | |
'Productos veganos': 'food' | |
} | |
} | |
} | |
# ============================== | |
# FUNCIONES DE PROCESAMIENTO | |
# ============================== | |
def quitar_tildes(texto: str) -> str: | |
"""Normaliza texto removiendo acentos y caracteres especiales""" | |
return unicodedata.normalize('NFD', texto).encode('ascii', 'ignore').decode('utf-8').lower() | |
def cargar_diccionario(ruta_csv: str) -> Dict[str, list]: | |
"""Carga y procesa el diccionario de categorías desde un CSV""" | |
try: | |
with open(ruta_csv, mode='r', encoding='utf-8') as archivo: | |
lector = csv.reader(archivo) | |
next(lector) # Saltar cabecera | |
return { | |
fila[0]: [quitar_tildes(palabra) for palabra in fila[1].split(', ')] | |
for fila in lector | |
} | |
except Exception as e: | |
raise RuntimeError(f"Error cargando {ruta_csv}: {str(e)}") | |
# ============================== | |
# INICIALIZACIÓN DE LA APLICACIÓN | |
# ============================== | |
app = FastAPI() | |
clasificadores_cargados = {} | |
async def inicializar_clasificadores(): | |
"""Carga los diccionarios al iniciar la aplicación""" | |
for nombre, config in CLASIFICADORES.items(): | |
try: | |
clasificadores_cargados[nombre] = { | |
"diccionario": cargar_diccionario(config["csv"]), | |
"categoria_tipo": config["categoria_tipo"] | |
} | |
print(f"✅ Clasificador {nombre} cargado correctamente") | |
except Exception as e: | |
print(f"❌ Error inicializando {nombre}: {str(e)}") | |
raise | |
# ============================== | |
# ENDPOINT PRINCIPAL | |
# ============================== | |
def categorizar_producto( | |
cua_master: str = Query(..., description="Selección de clasificador (abaco/mexico)"), | |
odd_name: str = Query(..., description="Nombre del producto a clasificar") | |
): | |
# Validar clasificador | |
cua_master = cua_master.lower() | |
if cua_master not in clasificadores_cargados: | |
raise HTTPException( | |
status_code=400, | |
detail=f"Clasificador no válido. Opciones: {', '.join(CLASIFICADORES.keys())}" | |
) | |
# Obtener configuración del clasificador | |
config = clasificadores_cargados[cua_master] | |
diccionario = config["diccionario"] | |
mapeo_tipos = config["categoria_tipo"] | |
# Procesar producto | |
producto_limpio = quitar_tildes(odd_name) | |
categoria = "Sin categoría" | |
# Buscar coincidencias en el diccionario | |
for cat, palabras in diccionario.items(): | |
if any(re.search(rf'\b{palabra}\b', producto_limpio) for palabra in palabras): | |
categoria = cat | |
break | |
# Mapear a tipo general | |
tipo = mapeo_tipos.get(categoria, "Sin Categoria") | |
return { | |
"eatc-odd_typology_b": categoria, | |
"eatc-odd_typology_a": tipo | |
} | |