eatc-class3 / app.py
mobixconsulting's picture
Update app.py
ee7b1c4 verified
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
}