mobixconsulting/eatc-class4
0
1from fastapi import FastAPI, Query, HTTPException2import csv3import unicodedata4from typing import Dict5 6# ==============================7# CONFIGURACIÓN DE CLASIFICADORES8# ==============================9 10CLASIFICADORES = {11 "abaco": {12 "csv": "asociaciones_abaco.csv",13 "categoria_tipo": {14 'Sin Categoria': 'Sin Categoria',15 'Otros No Alimentos': 'other',16 'Alimentos de hoteles, restaurantes o casinos': 'food',17 'Juguetes': 'other',18 'Muebles': 'other',19 'Pequeño Aparato': 'other',20 'Textiles': 'other',21 'Bebidas Alcohólicas': 'food',22 'Dulces y Postres': 'food',23 'Papelería': 'other',24 'Bebidas Azucaradas': 'food',25 'Bebidas Azúcaradas': 'food',26 'Carnes': 'food',27 'Panadería de sal': 'food',28 'Materiales de construcción': 'other',29 'Hogar': 'other',30 'Otras bebidas': 'food',31 'Grasas tipo 3 (saturadas)': 'food',32 'Otros Alimentos': 'food',33 'Cereales': 'food',34 'Frutas': 'food',35 'Personal': 'other',36 'Gama Blanca': 'other',37 'Gama Marron': 'other',38 'Frutos secos y semillas': 'food',39 'Paquetes/snacks': 'food',40 'Agua': 'food',41 'Productos lácteos grasa entera': 'food',42 'Dulces y postres': 'food',43 'Otras fórmulas especiales': 'food',44 'Leches Enteras': 'food',45 'Fórmulas infantiles': 'food',46 'Leguminosas Secas': 'food',47 'Tuberculos': 'food',48 'Raíz': 'food',49 'Plátano': 'food',50 'Verduras': 'food',51 'Panadería dulce': 'food',52 'Huevos': 'food',53 'Azúcares Simples': 'food',54 'Medicamentos sin fórmula médica': 'other',55 'Suplementos nutricionales': 'food',56 'Cereales ': 'food',57 'Alimentos para Mascotas': 'other',58 'Grasas tipo 2 (poliinsaturadas)': 'food',59 'Grasas tipo 1 (monoinsaturadas)': 'food'60 }61 },62 "mexico": {63 "csv": "asociaciones_bamx.csv",64 "categoria_tipo": {65 'No Comestible': 'other',66 'Fruta y verdura': 'food',67 'Pan': 'food',68 'Cereales y Leguminosas': 'food',69 'Bebidas Saborizadas': 'food',70 'Azucares': 'food',71 'Abarrotes': 'food',72 'Proteina animal': 'food',73 'Alimento Preparado': 'food',74 'Refrigerados y Congelados': 'food',75 'Lactéos': 'food',76 'Agua': 'food',77 'Jugo y Néctar': 'food',78 'Leguminosa': 'food',79 'Grasas Animales': 'food',80 'Confiteria': 'food',81 'Suplementos': 'food',82 'Salsas': 'food',83 'Café y Té': 'food',84 'Sabores listos para comer': 'food',85 'Aceites Vegetales': 'food',86 'Raices, Tuberculos': 'food',87 'Semillas y Nueces': 'food',88 'Productos veganos': 'food'89 }90 }91}92 93# ==============================94# FUNCIONES DE PROCESAMIENTO95# ==============================96 97def quitar_tildes(texto: str) -> str:98 """Normaliza texto removiendo acentos y caracteres especiales"""99 return unicodedata.normalize('NFD', texto).encode('ascii', 'ignore').decode('utf-8').lower()100 101def cargar_asociaciones(ruta_csv: str) -> Dict[str, str]:102 """Carga y procesa las asociaciones desde un CSV clave-valor"""103 try:104 with open(ruta_csv, mode='r', encoding='utf-8') as archivo:105 lector = csv.reader(archivo)106 next(lector) # Saltar cabecera107 return {quitar_tildes(fila[0]): fila[1] for fila in lector}108 except Exception as e:109 raise RuntimeError(f"Error cargando {ruta_csv}: {str(e)}")110 111# ==============================112# INICIALIZACIÓN DE LA APLICACIÓN113# ==============================114 115app = FastAPI()116clasificadores_cargados = {}117 118@app.on_event("startup")119async def inicializar_clasificadores():120 """Carga las asociaciones al iniciar la aplicación"""121 for nombre, config in CLASIFICADORES.items():122 try:123 clasificadores_cargados[nombre] = {124 "asociaciones": cargar_asociaciones(config["csv"]),125 "categoria_tipo": config["categoria_tipo"]126 }127 print(f"✅ Clasificador {nombre} cargado correctamente")128 except Exception as e:129 print(f"❌ Error inicializando {nombre}: {str(e)}")130 raise131 132# ==============================133# ENDPOINT PRINCIPAL134# ==============================135 136@app.get("/categorize")137def categorizar_producto(138 cua_master: str = Query(..., description="Selección de clasificador (abaco/mexico)"),139 eatc_typology: str = Query(..., description="Valor de typology a clasificar")140):141 # Validar clasificador142 cua_master = cua_master.lower()143 if cua_master not in clasificadores_cargados:144 raise HTTPException(145 status_code=400,146 detail=f"Clasificador no válido. Opciones: {', '.join(CLASIFICADORES.keys())}"147 )148 149 # Obtener configuración del clasificador150 config = clasificadores_cargados[cua_master]151 asociaciones = config["asociaciones"]152 mapeo_tipos = config["categoria_tipo"]153 154 # Procesar entrada155 typology_limpio = quitar_tildes(eatc_typology)156 categoria = asociaciones.get(typology_limpio, "Sin Categoria")157 158 # Mapear a tipo general159 tipo = mapeo_tipos.get(categoria, "Sin Categoria")160 161 return {162 "eatc-odd_typology_b": categoria,163 "eatc-odd_typology_a": tipo164 }165 