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 = {} @app.on_event("startup") 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 # ============================== @app.get("/categorize") 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 }