| import gradio as gr |
| from joblib import load |
| from pathlib import Path |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| try: |
| model = load('coffe_model.joblib') |
| except Exception as e: |
| model = None |
| load_error = e |
| else: |
| load_error = None |
|
|
|
|
| def classify( |
| aftertaste: float, |
| overall: float, |
| body: float, |
| aroma: float, |
| flavor: float, |
| moisture_percentage: float, |
| ): |
| """ |
| Realiza la predicción del tipo/calidad de café a partir de 3 atributos sensoriales. |
| |
| Parámetros |
| - aftertaste: retrogusto (0 a 10) |
| - overall: evaluación global (0 a 10) |
| - body: cuerpo del café (0 a 10) |
| - aroma: intensidad del aroma (0 a 10) |
| - flavor: sabor (0 a 10) |
| - moisture_percentage: porcentaje de humedad del grano (~5 a 15) |
| |
| Retorna |
| - probs: diccionario {clase: probabilidad} para gr.Label |
| - resumen_md: texto Markdown con el resultado y breve explicación |
| """ |
| if model is None: |
| raise gr.Error( |
| f"No fue posible cargar el modelo en: {MODEL_PATH}. Detalle: {load_error!s}.\n" |
| f"Verifica que 'coffe_model.joblib' exista en 'notebooks/' junto a este app.py." |
| ) |
|
|
| |
| |
| |
| cols = [ |
| "Aftertaste", |
| "Overall", |
| "Body", |
| "Aroma", |
| "Flavor", |
| "Moisture Percentage", |
| ] |
| import pandas as pd |
|
|
| X = pd.DataFrame( |
| [[aftertaste, overall, body, aroma, flavor, moisture_percentage]], |
| columns=cols, |
| ) |
|
|
| pred = model.predict(X)[0] |
|
|
| |
| probs = {} |
| if hasattr(model, "predict_proba"): |
| try: |
| p = model.predict_proba(X)[0] |
| classes = getattr(model, "classes_", [str(i) for i in range(len(p))]) |
| probs = {str(c): float(pc) for c, pc in zip(classes, p)} |
| except Exception: |
| |
| probs = {str(pred): 1.0} |
| else: |
| probs = {str(pred): 1.0} |
|
|
| resumen_md = ( |
| f"### Resultado\n\n" |
| f"☕ Predicción: **{pred}**\n\n" |
| f"Las barras muestran la probabilidad estimada para cada clase." |
| ) |
|
|
| return probs, resumen_md |
|
|
|
|
| |
| |
| |
|
|
| COLOR_YELLOW = "#F7D117" |
| COLOR_BLUE = "#0033A0" |
| COLOR_RED = "#CE1126" |
|
|
| theme = gr.themes.Soft(primary_hue="yellow", secondary_hue="red") |
|
|
| CUSTOM_CSS = f""" |
| .hero {{ |
| background: linear-gradient(90deg, {COLOR_YELLOW} 0%, {COLOR_BLUE} 50%, {COLOR_RED} 100%); |
| color: white; |
| padding: 18px 20px; |
| border-radius: 12px; |
| }} |
| .hero h1 {{ |
| margin: 0 0 6px 0; |
| font-weight: 800; |
| }} |
| .hero p {{ |
| margin: 0; |
| opacity: 0.95; |
| }} |
| .brand-badge {{ |
| display: inline-block; |
| background: white; |
| color: #111827; |
| padding: 2px 10px; |
| border-radius: 999px; |
| font-weight: 700; |
| margin-left: 8px; |
| }} |
| """ |
|
|
| with gr.Blocks(title="Clasificador de Café ☕🇨🇴", theme=theme, css=CUSTOM_CSS) as demo: |
| |
| gr.Markdown( |
| """ |
| <div class="hero"> |
| <h1>☕ Clasificador de Café <span class="brand-badge">Colombia 🇨🇴</span></h1> |
| <p> |
| Modelo de Árbol de Decisión (scikit-learn) integrado en un Pipeline con estandarización. |
| Ingresa atributos sensoriales y obtén la predicción junto a sus probabilidades. |
| </p> |
| </div> |
| """ |
| ) |
|
|
| with gr.Row(equal_height=True): |
| with gr.Column(scale=1): |
| gr.Markdown( |
| """ |
| ### Entradas del modelo (6 features) |
| - Aftertaste: retrogusto (0–10). |
| - Overall: evaluación global (0–10). |
| - Body: sensación en boca, de ligero a denso (0–10). |
| - Aroma: intensidad y complejidad aromática (0–10). |
| - Flavor: sabor (0–10). |
| - Moisture Percentage: porcentaje de humedad del grano (~5–15). |
| |
| Consejo: prueba las muestras para observar cómo cambia la clasificación. |
| """ |
| ) |
|
|
| inp_aftertaste = gr.Slider(0, 10, value=7.5, step=0.1, label="Aftertaste") |
| inp_overall = gr.Slider(0, 10, value=7.0, step=0.1, label="Overall") |
| inp_body = gr.Slider(0, 10, value=6.5, step=0.1, label="Body") |
| inp_aroma = gr.Slider(0, 10, value=7.0, step=0.1, label="Aroma") |
| inp_flavor = gr.Slider(0, 10, value=7.2, step=0.1, label="Flavor") |
| inp_moisture = gr.Slider(5.0, 15.0, value=11.5, step=0.1, label="Moisture Percentage") |
|
|
| btn = gr.Button("Clasificar ☕", variant="primary") |
|
|
| gr.Examples( |
| examples=[ |
| |
| [7.8, 7.5, 7.0, 7.6, 7.4, 11.2], |
| [5.0, 5.2, 5.5, 5.1, 5.0, 12.0], |
| [8.9, 8.5, 8.0, 8.2, 8.6, 10.8], |
| [3.0, 3.5, 3.8, 3.2, 3.0, 13.5], |
| ], |
| inputs=[ |
| inp_aftertaste, |
| inp_overall, |
| inp_body, |
| inp_aroma, |
| inp_flavor, |
| inp_moisture, |
| ], |
| label="Ejemplos rápidos", |
| ) |
|
|
| with gr.Column(scale=1): |
| gr.Markdown("### Resultado y probabilidades") |
| out_label = gr.Label(num_top_classes=3) |
| out_md = gr.Markdown() |
|
|
| with gr.Accordion("¿Cómo funciona este demo?", open=False): |
| gr.Markdown( |
| """ |
| - El modelo fue entrenado con un Árbol de Decisión y normalización (Pipeline de scikit-learn). |
| - Las entradas se transforman y se calcula la probabilidad de cada clase. |
| - La etiqueta mostrada corresponde a la mayor probabilidad. |
| - Este demo es educativo: los rangos 1–10 son relativos y el rendimiento depende del dataset usado. |
| """ |
| ) |
|
|
| with gr.Row(): |
| gr.Markdown( |
| f"Hecho con ❤️ y café colombiano 🇨🇴 | Colores de interfaz: amarillo, azul y rojo | scikit-learn + Gradio" |
| ) |
|
|
| |
| inputs = [ |
| inp_aftertaste, |
| inp_overall, |
| inp_body, |
| inp_aroma, |
| inp_flavor, |
| inp_moisture, |
| ] |
| outputs = [out_label, out_md] |
| btn.click(classify, inputs=inputs, outputs=outputs) |
| for w in inputs: |
| w.change(classify, inputs=inputs, outputs=outputs) |
|
|
|
|
| if __name__ == "__main__": |
| demo.launch() |