Romanes commited on
Commit
0a85d1f
·
verified ·
1 Parent(s): bc09cf4

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -196
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  # -*- coding: utf-8 -*-
2
  import gradio as gr
3
  import pandas as pd
@@ -5,126 +6,48 @@ import numpy as np
5
  from pathlib import Path
6
  from datetime import datetime
7
  import uuid
8
- import os
9
-
10
- # --- Config ---
11
- DEFAULT_EXCEL = Path("Herramienta de evaluación de tecnologias.xlsx")
12
- DEFAULT_TEMPLATE = Path("reporte_template.html")
13
-
14
- def ensure_outdir(base: Path) -> Path:
15
- out_dir = base / "reportes"
16
- out_dir.mkdir(parents=True, exist_ok=True) # <- parents=True para evitar error
17
- return out_dir
18
-
19
- # -------- Utilidades para leer la herramienta --------
20
- def leer_criterios_y_pesos(excel_path: Path):
21
- res = pd.read_excel(excel_path, sheet_name="Resultado", header=None)
22
- res = res.dropna(how="all")
23
- criterios = []
24
- for _, r in res.iterrows():
25
- vals = [v for v in r.tolist() if pd.notna(v)]
26
- if len(vals) >= 2:
27
- texto = None
28
- peso = None
29
- for v in vals:
30
- if isinstance(v, (int, float)):
31
- peso = float(v)
32
- elif isinstance(v, str):
33
- if any(c.isalpha() for c in v) and "." not in v.strip():
34
- texto = v.strip()
35
- if texto and (peso is not None):
36
- criterios.append((texto, peso))
37
- return criterios
38
-
39
- MAPA_HOJAS = {
40
- "Novedad": "1. Novedad",
41
- "Aplicación industrial": "2. Aplicación industrial",
42
- "Nivel inventivo": "3. Nivel inventivo",
43
- "Titularidad": "4. Titularidad ",
44
- "Potencial transferencia": "5. Potencial transferencia",
45
- }
46
-
47
- def extraer_escala(excel_path: Path, hoja: str):
48
- df = pd.read_excel(excel_path, sheet_name=hoja, header=None)
49
- df = df.dropna(how="all")
50
- items = []
51
- for _, r in df.iterrows():
52
- textos = [v for v in r.tolist() if isinstance(v, str) and any(c.isalpha() for c in v)]
53
- nums = [v for v in r.tolist() if isinstance(v, (int, float))]
54
- puntajes = [int(x) for x in nums if 0 <= int(x) <= 20 and abs(x - int(x)) < 1e-9]
55
- if textos and puntajes:
56
- clave = max(textos, key=lambda s: len(s))
57
- items.append((clave.strip(), max(puntajes)))
58
- escala = {}
59
- for t, p in items:
60
- escala[t] = max(p, escala.get(t, -1))
61
- escala = dict(sorted(escala.items(), key=lambda kv: (-kv[1], kv[0])))
62
- return escala
63
-
64
- def cargar_escalas(excel_path: Path, criterios):
65
- out = {}
66
- for crit, _peso in criterios:
67
- clave = None
68
- for k in MAPA_HOJAS:
69
- if k.lower() in crit.lower():
70
- clave = k; break
71
- hoja = MAPA_HOJAS.get(clave, None)
72
- if hoja is None:
73
- out[crit] = {"__error__": "Hoja no encontrada"}
74
- else:
75
- try:
76
- out[clave] = extraer_escala(excel_path, hoja)
77
- except Exception as e:
78
- out[clave] = {"__error__": str(e)}
79
- return out
80
-
81
- def _load_state(excel_file):
82
- """Devuelve (excel_path, criterios, escalas, out_dir, template_path)"""
83
- # 1) Excel: si el usuario sube archivo, usar ese. Si no, usar DEFAULT_EXCEL.
84
- if excel_file is not None and hasattr(excel_file, 'name'):
85
- excel_path = Path(excel_file.name)
86
- else:
87
- excel_path = DEFAULT_EXCEL
88
- if not excel_path.exists():
89
- raise FileNotFoundError(f"No se encontró el Excel: {excel_path}. Súbelo o colócalo junto al app.py")
90
- criterios = leer_criterios_y_pesos(excel_path)
91
- escalas = cargar_escalas(excel_path, criterios)
92
- # 2) Template
93
- template_path = DEFAULT_TEMPLATE if DEFAULT_TEMPLATE.exists() else Path(__file__).parent / "reporte_template.html"
94
- if not template_path.exists():
95
- # fallback mínimo
96
- template_path.write_text("<html><body><h1>Reporte</h1>{filas}</body></html>", encoding="utf-8")
97
- # 3) Carpeta de reportes relativa al excel
98
- out_dir = ensure_outdir(excel_path.parent)
99
- return excel_path, criterios, escalas, out_dir, template_path
100
-
101
- # -------- Lógica de evaluación --------
102
- def evaluar_interactivo(excel_file, titulo, responsable, facultad, descripcion, fecha, *args):
103
- excel_path, criterios, escalas, out_dir, template_path = _load_state(excel_file)
104
-
105
- # Reconstruir tripletas por criterio
106
- elecciones, puntajes_forzados, evidencias = {}, {}, {}
107
- claves = []
108
- for crit, _ in criterios:
109
- clave = None
110
- for k in escalas:
111
- if k.lower() in crit.lower() or crit.lower() in k.lower():
112
- clave = k; break
113
- if clave is None: clave = crit
114
- claves.append(clave)
115
-
116
- for i, c in enumerate(claves):
117
- base = 3*i
118
- elecciones[c] = args[base] if base < len(args) else None
119
- puntajes_forzados[c] = args[base+1] if base+1 < len(args) else ""
120
- evidencias[c] = args[base+2] if base+2 < len(args) else ""
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  filas = []
123
- total_ponderado = 0.0
124
- for (crit, peso), c in zip(criterios, claves):
125
- opcion = (elecciones.get(c) or "").strip()
126
- texto_ev = evidencias.get(c, "") or ""
127
- pf = (puntajes_forzados.get(c, "") or "").strip()
 
 
 
 
128
  usar_forzado = False
129
  try:
130
  pf_val = float(pf)
@@ -134,50 +57,39 @@ def evaluar_interactivo(excel_file, titulo, responsable, facultad, descripcion,
134
  if usar_forzado:
135
  puntaje = pf_val
136
  else:
137
- puntaje = escalas.get(c, {}).get(opcion, np.nan)
138
- ppond = (puntaje if pd.notna(puntaje) else 0) * float(peso)
139
- filas.append({
140
- "criterio": c, "peso": peso, "opcion": opcion,
141
- "puntaje": puntaje, "puntaje_ponderado": ppond,
142
- "evidencia": texto_ev
143
- })
144
- total_ponderado += ppond
145
 
146
- # Render reporte
147
- html = Path(template_path).read_text(encoding="utf-8")
148
  filas_html = ""
149
  for f in filas:
150
  filas_html += "<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>\n".format(
151
  f['criterio'], f['peso'], f['opcion'], f['puntaje'], f['puntaje_ponderado'], f['evidencia']
152
  )
153
- fecha_str = fecha if fecha else datetime.now().strftime("%Y-%m-%d")
154
  html = (html
155
- .replace("{titulo}", str(titulo or ""))
156
- .replace("{responsable}", str(responsable or ""))
157
- .replace("{facultad}", str(facultad or ""))
158
- .replace("{fecha}", str(fecha_str))
159
- .replace("{descripcion}", str(descripcion or ""))
160
- .replace("{filas}", filas_html)
161
- .replace("{total}", "{:.2f}".format(total_ponderado))
162
- .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.")
163
- .replace("{archivo_fuente}", excel_path.name)
164
- .replace("{fecha_gen}", datetime.now().strftime("%Y-%m-%d %H:%M"))
165
- )
166
 
167
  rid = uuid.uuid4().hex[:8]
168
- out_html = out_dir / f"reporte_{rid}.html"
169
- out_csv = out_dir / f"detalle_{rid}.csv"
170
  pd.DataFrame(filas).to_csv(out_csv, index=False)
171
  out_html.write_text(html, encoding="utf-8")
172
 
173
- return pd.DataFrame(filas), float(total_ponderado), str(out_html), str(out_csv)
174
 
175
- # -------- UI --------
176
- with gr.Blocks(title="Evaluación de Tecnologías") as demo:
177
- gr.Markdown("# Evaluación de Tecnologías")
178
- gr.Markdown("Suba el Excel de la herramienta (si no está en el mismo directorio), complete la información y genere el reporte.")
179
-
180
- excel_file = gr.File(label="Excel de la herramienta (.xlsx)", file_types=[".xlsx"])
181
 
182
  with gr.Row():
183
  titulo = gr.Textbox(label="Título de la tecnología")
@@ -187,61 +99,25 @@ with gr.Blocks(title="Evaluación de Tecnologías") as demo:
187
  fecha = gr.Textbox(label="Fecha (YYYY-MM-DD)")
188
  descripcion = gr.Textbox(label="Descripción breve", lines=3)
189
 
190
- # Cargamos criterios/escala dinámicamente cuando se cambie el Excel
191
- state_criterios = gr.State([])
192
- state_escalas = gr.State({})
193
-
194
- def cargar_campos(excel_file):
195
- try:
196
- excel_path, criterios, escalas, out_dir, template_path = _load_state(excel_file)
197
- # Devolvemos lista de criterios y para cada criterio, la lista de opciones
198
- claves, opciones_por = [], []
199
- for crit, _ in criterios:
200
- clave = None
201
- for k in escalas:
202
- if k.lower() in crit.lower() or crit.lower() in k.lower():
203
- clave = k; break
204
- if clave is None: clave = crit
205
- claves.append(clave)
206
- opts = list(escalas.get(clave, {}).keys())
207
- if "__error__" in opts or not opts:
208
- opts = ["(No se pudo leer opciones)"]
209
- opciones_por.append(opts)
210
- return claves, opciones_por, gr.update(visible=False), ""
211
- except Exception as e:
212
- return [], [], gr.update(visible=True), f"Error: {e}"
213
-
214
- cargar_btn = gr.Button("Cargar criterios desde el Excel")
215
- error_box = gr.Markdown(visible=False)
216
-
217
- # Contenedores dinámicos
218
- criterios_container = gr.Group()
219
- inputs_dinamicos = []
220
-
221
- def construir_form(claves, opciones_por):
222
- # reconstruye los inputs por criterio
223
- comps = []
224
- for clave, opciones in zip(claves, opciones_por):
225
- drop = gr.Dropdown(choices=opciones, label=f"Opción para '{clave}'", multiselect=False)
226
  pf = gr.Textbox(label="Puntaje forzado (opcional)")
227
  ev = gr.Textbox(label="Evidencia / comentarios", lines=2)
228
- comps.extend([drop, pf, ev])
229
- return comps
230
-
231
- cargar_btn.click(cargar_campos, inputs=[excel_file], outputs=[state_criterios, state_escalas, error_box, error_box])
232
 
233
- # Botón de evaluar
234
- evaluar_btn = gr.Button("Evaluar y generar reporte")
235
  tabla = gr.Dataframe(headers=["criterio","peso","opcion","puntaje","puntaje_ponderado","evidencia"], label="Detalle")
236
  total = gr.Number(label="Total ponderado")
237
  out_html = gr.File(label="Descargar reporte HTML")
238
  out_csv = gr.File(label="Descargar detalle CSV")
239
 
240
- # NOTA: por simplicidad en Spaces, pedimos al usuario dar clic en "Cargar criterios..." antes de evaluar.
241
- # Para unir inputs dinámicos, recibimos un número flexible de *args.
242
- evaluar_btn.click(evaluar_interactivo,
243
- inputs=[excel_file, titulo, responsable, facultad, descripcion, fecha],
244
- outputs=[tabla, total, out_html, out_csv])
245
 
246
  if __name__ == "__main__":
247
  demo.launch()
 
1
+
2
  # -*- coding: utf-8 -*-
3
  import gradio as gr
4
  import pandas as pd
 
6
  from pathlib import Path
7
  from datetime import datetime
8
  import uuid
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ SCHEMA = [{"criterio": "Novedad", "peso": 9.0, "opciones": []}, {"criterio": "Aplicación industrial", "peso": 10.0, "opciones": []}, {"criterio": "Nivel inventivo", "peso": 8.0, "opciones": []}, {"criterio": "Titularidad", "peso": 13.0, "opciones": []}, {"criterio": "Potencial transferencia", "peso": 14.0, "opciones": []}]
11
+
12
+ TEMPLATE_HTML = Path("reporte_template.html")
13
+ if not TEMPLATE_HTML.exists():
14
+ TEMPLATE_HTML.write_text("""<!doctype html>
15
+ <html><head><meta charset='utf-8'><title>Reporte</title>
16
+ <style>
17
+ body { font-family: Arial, sans-serif; margin:24px; }
18
+ table { border-collapse: collapse; width:100%; }
19
+ th, td { border:1px solid #ccc; padding:8px; vertical-align:top; }
20
+ th { background:#f5f5f5; text-align:left; }
21
+ </style>
22
+ </head><body>
23
+ <h1>Reporte de Evaluación de Tecnologías</h1>
24
+ <p><b>Título:</b> {titulo}<br>
25
+ <b>Responsable:</b> {responsable}<br>
26
+ <b>Facultad:</b> {facultad}<br>
27
+ <b>Fecha:</b> {fecha}<br>
28
+ <b>Descripción:</b> {descripcion}</p>
29
+ <table>
30
+ <thead><tr><th>Criterio</th><th>Peso</th><th>Opción</th><th>Puntaje</th><th>Ponderado</th><th>Evidencia</th></tr></thead>
31
+ <tbody>{filas}</tbody>
32
+ <tfoot><tr><th colspan='4' style='text-align:right'>Total ponderado</th><th>{total}</th><th></th></tr></tfoot>
33
+ </table>
34
+ <p style='color:#777;font-size:12px'>Generado {fecha_gen}</p>
35
+ </body></html>""", encoding="utf-8")
36
+
37
+ OUT_DIR = Path("reportes")
38
+ OUT_DIR.mkdir(parents=True, exist_ok=True)
39
+
40
+ def evaluar(titulo, responsable, facultad, fecha, descripcion, *args):
41
  filas = []
42
+ total = 0.0
43
+ for i, item in enumerate(SCHEMA):
44
+ c = item["criterio"]
45
+ peso = float(item["peso"])
46
+ opciones = { o["texto"]: o["puntaje"] for o in item["opciones"] }
47
+ base = 3*i
48
+ opcion = (args[base] or "").strip()
49
+ pf = (args[base+1] or "").strip()
50
+ evidencia = args[base+2] or ""
51
  usar_forzado = False
52
  try:
53
  pf_val = float(pf)
 
57
  if usar_forzado:
58
  puntaje = pf_val
59
  else:
60
+ puntaje = opciones.get(opcion, np.nan)
61
+ ppond = (puntaje if pd.notna(puntaje) else 0) * peso
62
+ total += ppond
63
+ filas.append({"criterio": c, "peso": peso, "opcion": opcion, "puntaje": puntaje, "puntaje_ponderado": ppond, "evidencia": evidencia})
 
 
 
 
64
 
65
+ html = TEMPLATE_HTML.read_text(encoding="utf-8")
 
66
  filas_html = ""
67
  for f in filas:
68
  filas_html += "<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>\n".format(
69
  f['criterio'], f['peso'], f['opcion'], f['puntaje'], f['puntaje_ponderado'], f['evidencia']
70
  )
 
71
  html = (html
72
+ .replace("{titulo}", str(titulo or ""))
73
+ .replace("{responsable}", str(responsable or ""))
74
+ .replace("{facultad}", str(facultad or ""))
75
+ .replace("{fecha}", str(fecha or ""))
76
+ .replace("{descripcion}", str(descripcion or ""))
77
+ .replace("{filas}", filas_html)
78
+ .replace("{total}", "{:.2f}".format(total))
79
+ .replace("{fecha_gen}", datetime.now().strftime("%Y-%m-%d %H:%M"))
80
+ )
 
 
81
 
82
  rid = uuid.uuid4().hex[:8]
83
+ out_html = OUT_DIR / f"reporte_{rid}.html"
84
+ out_csv = OUT_DIR / f"detalle_{rid}.csv"
85
  pd.DataFrame(filas).to_csv(out_csv, index=False)
86
  out_html.write_text(html, encoding="utf-8")
87
 
88
+ return pd.DataFrame(filas), float(total), str(out_html), str(out_csv)
89
 
90
+ with gr.Blocks(title="Evaluación de Tecnologías (Autocontenida)") as demo:
91
+ gr.Markdown("# Evaluación de Tecnologías (Autocontenida)")
92
+ gr.Markdown("Diligencie la información. No requiere subir Excel.")
 
 
 
93
 
94
  with gr.Row():
95
  titulo = gr.Textbox(label="Título de la tecnología")
 
99
  fecha = gr.Textbox(label="Fecha (YYYY-MM-DD)")
100
  descripcion = gr.Textbox(label="Descripción breve", lines=3)
101
 
102
+ inputs = [titulo, responsable, facultad, fecha, descripcion]
103
+ gr.Markdown("## Criterios")
104
+ for item in SCHEMA:
105
+ c = item["criterio"]; peso = item["peso"]
106
+ opciones = [o["texto"] for o in item["opciones"]]
107
+ with gr.Group():
108
+ gr.Markdown("### {} — Peso: **{}**".format(c, peso))
109
+ dd = gr.Dropdown(choices=opciones, label="Opción")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  pf = gr.Textbox(label="Puntaje forzado (opcional)")
111
  ev = gr.Textbox(label="Evidencia / comentarios", lines=2)
112
+ inputs += [dd, pf, ev]
 
 
 
113
 
114
+ btn = gr.Button("Evaluar y generar reporte")
 
115
  tabla = gr.Dataframe(headers=["criterio","peso","opcion","puntaje","puntaje_ponderado","evidencia"], label="Detalle")
116
  total = gr.Number(label="Total ponderado")
117
  out_html = gr.File(label="Descargar reporte HTML")
118
  out_csv = gr.File(label="Descargar detalle CSV")
119
 
120
+ btn.click(evaluar, inputs=inputs, outputs=[tabla, total, out_html, out_csv])
 
 
 
 
121
 
122
  if __name__ == "__main__":
123
  demo.launch()