Romanes commited on
Commit
dee1c9e
·
verified ·
1 Parent(s): a7c633c

Upload app_gradio.py

Browse files
Files changed (1) hide show
  1. app_gradio.py +224 -0
app_gradio.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ # App de Evaluación de Tecnologías (Gradio)
3
+ # Requisitos: pip install gradio pandas openpyxl
4
+
5
+ import gradio as gr
6
+ import pandas as pd
7
+ import numpy as np
8
+ from pathlib import Path
9
+ from datetime import datetime
10
+ import uuid
11
+
12
+ EXCEL_PATH = Path("/mnt/data/Herramienta de evaluación de tecnologias.xlsx")
13
+ TEMPLATE_PATH = Path("/mnt/data/reporte_template.html")
14
+ OUT_DIR = EXCEL_PATH.parent / "reportes"
15
+ OUT_DIR.mkdir(exist_ok=True)
16
+
17
+ def leer_criterios_y_pesos(excel_path: str):
18
+ res = pd.read_excel(excel_path, sheet_name="Resultado", header=None)
19
+ res = res.dropna(how="all")
20
+ criterios = []
21
+ for _, r in res.iterrows():
22
+ vals = [v for v in r.tolist() if pd.notna(v)]
23
+ if len(vals) >= 2:
24
+ texto = None
25
+ peso = None
26
+ for v in vals:
27
+ if isinstance(v, (int, float)):
28
+ peso = float(v)
29
+ elif isinstance(v, str):
30
+ if any(c.isalpha() for c in v) and "." not in v.strip():
31
+ texto = v.strip()
32
+ if texto and (peso is not None):
33
+ criterios.append((texto, peso))
34
+ return criterios
35
+
36
+ MAPA_HOJAS = {
37
+ "Novedad": "1. Novedad",
38
+ "Aplicación industrial": "2. Aplicación industrial",
39
+ "Nivel inventivo": "3. Nivel inventivo",
40
+ "Titularidad": "4. Titularidad ",
41
+ "Potencial transferencia": "5. Potencial transferencia",
42
+ }
43
+
44
+ def extraer_escala(excel_path: str, hoja: str):
45
+ df = pd.read_excel(excel_path, sheet_name=hoja, header=None)
46
+ df = df.dropna(how="all")
47
+ items = []
48
+ for _, r in df.iterrows():
49
+ textos = [v for v in r.tolist() if isinstance(v, str) and any(c.isalpha() for c in v)]
50
+ nums = [v for v in r.tolist() if isinstance(v, (int, float))]
51
+ puntajes = [int(x) for x in nums if 0 <= int(x) <= 20 and abs(x - int(x)) < 1e-9]
52
+ if textos and puntajes:
53
+ clave = max(textos, key=lambda s: len(s))
54
+ items.append((clave.strip(), max(puntajes)))
55
+ escala = {}
56
+ for t, p in items:
57
+ escala[t] = max(p, escala.get(t, -1))
58
+ escala = dict(sorted(escala.items(), key=lambda kv: (-kv[1], kv[0])))
59
+ return escala
60
+
61
+ def cargar_escalas(excel_path: str, criterios):
62
+ out = {}
63
+ for crit, _peso in criterios:
64
+ clave = None
65
+ for k in MAPA_HOJAS:
66
+ if k.lower() in crit.lower():
67
+ clave = k; break
68
+ hoja = MAPA_HOJAS.get(clave, None)
69
+ if hoja is None:
70
+ out[crit] = {"__error__": "Hoja no encontrada"}
71
+ else:
72
+ try:
73
+ out[clave] = extraer_escala(excel_path, hoja)
74
+ except Exception as e:
75
+ out[clave] = {"__error__": str(e)}
76
+ return out
77
+
78
+ CRITERIOS = leer_criterios_y_pesos(EXCEL_PATH)
79
+ ESCALAS = cargar_escalas(EXCEL_PATH, CRITERIOS)
80
+
81
+ def evaluar_interactivo(titulo, responsable, facultad, descripcion, fecha, elecciones, evidencias, puntajes_forzados):
82
+ filas = []
83
+ total_ponderado = 0.0
84
+ total_pesos = 0.0
85
+
86
+ for crit, peso in CRITERIOS:
87
+ clave = None
88
+ for k in ESCALAS:
89
+ if k.lower() in crit.lower() or crit.lower() in k.lower():
90
+ clave = k; break
91
+ if clave is None: clave = crit
92
+
93
+ opcion = elecciones.get(clave, "") or ""
94
+ texto_ev = evidencias.get(clave, "") or ""
95
+ pf = puntajes_forzados.get(clave, "").strip()
96
+ usar_forzado = False
97
+ try:
98
+ pf_val = float(pf)
99
+ usar_forzado = True
100
+ except:
101
+ pf_val = None
102
+
103
+ if usar_forzado:
104
+ puntaje = pf_val
105
+ else:
106
+ escala = ESCALAS.get(clave, {})
107
+ puntaje = escala.get(opcion.strip(), np.nan)
108
+
109
+ puntaje_pond = (puntaje if pd.notna(puntaje) else 0) * float(peso)
110
+ filas.append({
111
+ "criterio": clave, "peso": peso, "opcion": opcion,
112
+ "puntaje": puntaje, "puntaje_ponderado": puntaje_pond,
113
+ "evidencia": texto_ev
114
+ })
115
+ total_ponderado += puntaje_pond
116
+ total_pesos += float(peso)
117
+
118
+ html = TEMPLATE_PATH.read_text(encoding="utf-8")
119
+ filas_html = ""
120
+ for f in filas:
121
+ filas_html += "<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>\n".format(
122
+ f['criterio'], f['peso'], f['opcion'], f['puntaje'], f['puntaje_ponderado'], f['evidencia']
123
+ )
124
+
125
+ fecha_str = fecha if fecha else datetime.now().strftime("%Y-%m-%d")
126
+ html = (html
127
+ .replace("{{titulo}}", str(titulo or ""))
128
+ .replace("{{responsable}}", str(responsable or ""))
129
+ .replace("{{facultad}}", str(facultad or ""))
130
+ .replace("{{fecha}}", str(fecha_str))
131
+ .replace("{{descripcion}}", str(descripcion or ""))
132
+ .replace("{{filas}}", filas_html)
133
+ .replace("{{total}}", "{:.2f}".format(total_ponderado))
134
+ .replace("{{resumen}}", "La evaluación se realizó con base en los criterios y pesos de la herramienta. Se adjuntan observaciones por criterio en la tabla.")
135
+ .replace("{{archivo_fuente}}", EXCEL_PATH.name)
136
+ .replace("{{fecha_gen}}", datetime.now().strftime("%Y-%m-%d %H:%M"))
137
+ )
138
+
139
+ report_id = uuid.uuid4().hex[:8]
140
+ out_html = OUT_DIR / "reporte_{}.html".format(report_id)
141
+ out_csv = OUT_DIR / "detalle_{}.csv".format(report_id)
142
+
143
+ pd.DataFrame(filas).to_csv(out_csv, index=False)
144
+ out_html.write_text(html, encoding="utf-8")
145
+
146
+ df = pd.DataFrame(filas)
147
+ return df, float(total_ponderado), str(out_html), str(out_csv)
148
+
149
+ with gr.Blocks(title="Evaluación de Tecnologías") as demo:
150
+ gr.Markdown("# Evaluación de Tecnologías")
151
+ gr.Markdown("Complete la información de la tecnología y seleccione una opción por criterio. Opcionalmente puede forzar un puntaje numérico.")
152
+
153
+ with gr.Row():
154
+ titulo = gr.Textbox(label="Título de la tecnología", placeholder="p.ej., Sensor de humedad IoT para suelos")
155
+ responsable = gr.Textbox(label="Responsable / Autor", placeholder="Nombre y cargo")
156
+ with gr.Row():
157
+ facultad = gr.Textbox(label="Facultad / Dependencia", placeholder="p.ej., Facultad de Ciencias")
158
+ fecha = gr.Textbox(label="Fecha (YYYY-MM-DD)", placeholder="2025-09-16")
159
+ descripcion = gr.Textbox(label="Descripción breve", lines=3)
160
+
161
+ elecciones = {}
162
+ evidencias = {}
163
+ puntajes_forzados = {}
164
+
165
+ gr.Markdown("## Criterios")
166
+ for crit, peso in CRITERIOS:
167
+ clave = None
168
+ for k in ESCALAS:
169
+ if k.lower() in crit.lower() or crit.lower() in k.lower():
170
+ clave = k; break
171
+ if clave is None: clave = crit
172
+
173
+ opciones = list(ESCALAS.get(clave, {}).keys())
174
+ if "__error__" in opciones or not opciones:
175
+ opciones = ["(No se pudo leer opciones desde el Excel)"]
176
+
177
+ with gr.Group():
178
+ gr.Markdown("### {} <span class='badge'>Peso: {}</span>".format(clave, peso))
179
+ elecciones[clave] = gr.Dropdown(choices=opciones, label="Opción para '{}'".format(clave), multiselect=False)
180
+ puntajes_forzados[clave] = gr.Textbox(label="Puntaje forzado (opcional, número)")
181
+ evidencias[clave] = gr.Textbox(label="Evidencia / comentarios", lines=2)
182
+
183
+ btn = gr.Button("Evaluar y generar reporte")
184
+ tabla = gr.Dataframe(headers=["criterio","peso","opcion","puntaje","puntaje_ponderado","evidencia"], label="Detalle")
185
+ total = gr.Number(label="Total ponderado")
186
+ out_html = gr.File(label="Descargar reporte HTML")
187
+ out_csv = gr.File(label="Descargar detalle CSV")
188
+
189
+ def _evaluate_wrapper(titulo, responsable, facultad, descripcion, fecha, *args):
190
+ e = {}
191
+ pf = {}
192
+ ev = {}
193
+
194
+ tripletas = []
195
+ for crit, _ in CRITERIOS:
196
+ clave = None
197
+ for k in ESCALAS:
198
+ if k.lower() in crit.lower() or crit.lower() in k.lower():
199
+ clave = k; break
200
+ if clave is None: clave = crit
201
+ tripletas.append(clave)
202
+
203
+ for i, c in enumerate(tripletas):
204
+ base = 3*i
205
+ e[c] = args[base] if base < len(args) else None
206
+ pf[c] = args[base+1] if base+1 < len(args) else ""
207
+ ev[c] = args[base+2] if base+2 < len(args) else ""
208
+
209
+ df, tot, path_html, path_csv = evaluar_interactivo(titulo, responsable, facultad, descripcion, fecha, e, ev, pf)
210
+ return df, tot, path_html, path_csv
211
+
212
+ inputs = [titulo, responsable, facultad, descripcion, fecha]
213
+ for crit, _ in CRITERIOS:
214
+ clave = None
215
+ for k in ESCALAS:
216
+ if k.lower() in crit.lower() or crit.lower() in k.lower():
217
+ clave = k; break
218
+ if clave is None: clave = crit
219
+ inputs += [elecciones[clave], puntajes_forzados[clave], evidencias[clave]]
220
+
221
+ btn.click(_evaluate_wrapper, inputs=inputs, outputs=[tabla, total, out_html, out_csv])
222
+
223
+ if __name__ == "__main__":
224
+ demo.launch()