Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -5,12 +5,14 @@ import csv, os, time
|
|
5 |
import gradio as gr
|
6 |
import matplotlib.pyplot as plt
|
7 |
|
|
|
|
|
|
|
8 |
DEFAULT_COLS = [
|
9 |
"Código", "Indicador", "Score (0–4)",
|
10 |
"Entailment medio", "Evidencias (hipótesis)", "Descripción"
|
11 |
]
|
12 |
|
13 |
-
# --------- UI: meta y estilos ---------
|
14 |
CUSTOM_CSS = """
|
15 |
#app {max-width: 1200px; margin: 0 auto;}
|
16 |
.badge {
|
@@ -26,7 +28,9 @@ CUSTOM_CSS = """
|
|
26 |
.small {font-size: 12px; opacity: .9;}
|
27 |
"""
|
28 |
|
29 |
-
#
|
|
|
|
|
30 |
INDICATOR_META = {
|
31 |
"4.4.5.1": ("Iniciativa y ayuda proactiva",
|
32 |
"Inicia acciones sin que se lo pidan; ofrece ayuda, anticipa y equilibra riesgos."),
|
@@ -40,16 +44,30 @@ INDICATOR_META = {
|
|
40 |
"Toma decisiones bajo incertidumbre; explica razones; revisa con nueva evidencia; comunica con claridad.")
|
41 |
}
|
42 |
|
43 |
-
#
|
|
|
|
|
44 |
_llm = None
|
45 |
_llm_tok = None
|
46 |
_gen = None
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
#
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
STAR_PROMPT = """Eres evaluador ICB4. Toma el texto del candidato y devuélvelo en formato STAR como JSON válido con claves:
|
55 |
"situation" (<=3 frases), "task" (<=2 frases), "action" (lista de viñetas, verbos de acción), "result" (lista de viñetas, resultados/indicadores/aprendizajes).
|
@@ -91,18 +109,17 @@ HYP: Dict[str, List[str]] = {
|
|
91 |
]
|
92 |
}
|
93 |
|
94 |
-
#
|
|
|
|
|
95 |
def lazy_load_llm():
|
96 |
-
"""
|
97 |
global _llm, _llm_tok, _gen
|
|
|
98 |
if _gen is not None:
|
99 |
return _gen
|
100 |
-
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
|
101 |
_llm_tok = AutoTokenizer.from_pretrained(LLM_ID)
|
102 |
-
_llm = AutoModelForCausalLM.from_pretrained(
|
103 |
-
LLM_ID,
|
104 |
-
device_map="auto"
|
105 |
-
)
|
106 |
_gen = pipeline(
|
107 |
"text-generation",
|
108 |
model=_llm,
|
@@ -113,22 +130,24 @@ def lazy_load_llm():
|
|
113 |
)
|
114 |
return _gen
|
115 |
|
116 |
-
def lazy_load_nli():
|
117 |
-
"""NLI
|
118 |
-
global _nli
|
119 |
-
if _nli is not None:
|
120 |
-
return _nli
|
121 |
from transformers import pipeline
|
122 |
-
|
|
|
|
|
123 |
"text-classification",
|
124 |
-
model=
|
125 |
-
tokenizer=
|
126 |
-
return_all_scores=True, #
|
127 |
-
truncation=True
|
128 |
)
|
129 |
-
|
|
|
130 |
|
131 |
-
#
|
|
|
|
|
132 |
def extract_json_block(text: str) -> str:
|
133 |
start = text.find("{")
|
134 |
end = text.rfind("}")
|
@@ -186,9 +205,17 @@ def extract_star(user_text: str) -> Dict:
|
|
186 |
"result": [str(r).strip(" •-") for r in data["result"] if str(r).strip()],
|
187 |
}
|
188 |
|
189 |
-
|
190 |
-
|
191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
|
193 |
def _trim(s: str, limit=900):
|
194 |
s = (s or "").strip()
|
@@ -221,40 +248,49 @@ def nli_entails(premise: str, hypothesis: str) -> float:
|
|
221 |
return 0.0
|
222 |
return 0.0
|
223 |
|
224 |
-
def map_prob_to_score(p: float) -> int:
|
225 |
-
|
226 |
-
if p >=
|
227 |
-
if p >=
|
228 |
-
if p >=
|
|
|
229 |
return 0
|
230 |
|
231 |
-
def score_indicator(premise: str, hyps: List[str]
|
232 |
-
|
|
|
|
|
233 |
avg = sum(p for _, p in probs) / max(1, len(probs))
|
234 |
-
score = map_prob_to_score(avg)
|
235 |
probs_sorted = sorted(probs, key=lambda x: x[1], reverse=True)[:2]
|
236 |
return score, probs_sorted, avg
|
237 |
|
238 |
-
|
239 |
-
|
|
|
|
|
|
|
240 |
try:
|
241 |
if not texto or not texto.strip():
|
242 |
return "Introduce un caso en formato STAR (o texto libre).", None, {"columns": [], "data": []}
|
243 |
|
|
|
|
|
|
|
|
|
|
|
|
|
244 |
star = extract_star(texto)
|
245 |
|
246 |
-
# Limita premisa
|
247 |
actions = (star.get("action", []) or [])[:6]
|
248 |
results = (star.get("result", []) or [])[:4]
|
249 |
premise = " ".join(actions) + " " + " ".join(results)
|
250 |
|
251 |
-
#
|
252 |
-
scores = []
|
253 |
-
table_rows = []
|
254 |
-
per_indicator_values = []
|
255 |
-
|
256 |
for ind, hyps in HYP.items():
|
257 |
-
s, ev, avg = score_indicator(premise, hyps)
|
258 |
scores.append(s)
|
259 |
per_indicator_values.append((ind, s))
|
260 |
best_evid = " / ".join([h for h, _ in ev])
|
@@ -263,7 +299,7 @@ def evaluate(texto: str):
|
|
263 |
|
264 |
overall = round(sum(scores) / max(1, len(scores)), 2)
|
265 |
|
266 |
-
#
|
267 |
labels = [f"{k.split('.')[-1]}" for k, _ in per_indicator_values]
|
268 |
values = [v for _, v in per_indicator_values]
|
269 |
fig, ax = plt.subplots(figsize=(8.2, 4.0))
|
@@ -271,19 +307,16 @@ def evaluate(texto: str):
|
|
271 |
ax.set_ylim(0, 4)
|
272 |
ax.set_xlabel("Indicadores 4.4.5.x")
|
273 |
ax.set_ylabel("Score (0–4)")
|
274 |
-
fig.suptitle(f"ICB4 4.4.5 Leadership — Score global: {overall}", y=0.97)
|
275 |
fig.subplots_adjust(top=0.86)
|
276 |
for i, v in enumerate(values):
|
277 |
ax.text(i, v + 0.08, f"{v}", ha="center", va="bottom")
|
278 |
fig.tight_layout()
|
279 |
|
280 |
-
table = {
|
281 |
-
"columns": DEFAULT_COLS,
|
282 |
-
"data": table_rows
|
283 |
-
}
|
284 |
-
|
285 |
msg = (
|
286 |
f"Evaluación completada. Score global (0–4): {overall}\n"
|
|
|
287 |
f"Sugerencia: revisa evidencias y ajusta umbrales según tu rúbrica."
|
288 |
)
|
289 |
return msg, fig, table
|
@@ -291,7 +324,9 @@ def evaluate(texto: str):
|
|
291 |
except Exception as e:
|
292 |
return f"⚠️ Error en evaluate(): {type(e).__name__}: {e}", None, {"columns": [], "data": []}
|
293 |
|
294 |
-
#
|
|
|
|
|
295 |
def make_csv_from_table(table: dict) -> str:
|
296 |
cols = table.get("columns", [])
|
297 |
rows = table.get("data", [])
|
@@ -304,7 +339,9 @@ def make_csv_from_table(table: dict) -> str:
|
|
304 |
writer.writerow(r)
|
305 |
return path if os.path.exists(path) else ""
|
306 |
|
307 |
-
#
|
|
|
|
|
308 |
with gr.Blocks(title="ICB4 4.4.5 Leadership — Evaluación STAR (FRAQX)", css=CUSTOM_CSS, elem_id="app") as demo:
|
309 |
gr.Markdown(
|
310 |
"""
|
@@ -312,14 +349,22 @@ with gr.Blocks(title="ICB4 4.4.5 Leadership — Evaluación STAR (FRAQX)", css=C
|
|
312 |
<img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" height="28">
|
313 |
<h1 style="margin:0;">ICB4 • 4.4.5 Leadership — Evaluación STAR + NLI</h1>
|
314 |
</div>
|
315 |
-
<div class="small">Extracción STAR, scoring (4.4.5.1–4.4.5.5), gráfica y reporte descargable.</div>
|
316 |
"""
|
317 |
)
|
318 |
|
319 |
with gr.Row(equal_height=True):
|
320 |
-
#
|
321 |
with gr.Column(scale=5):
|
322 |
gr.Markdown("<div class='card'><b>Entrada</b></div>")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
323 |
texto = gr.Textbox(
|
324 |
label="Caso (STAR o texto libre)",
|
325 |
lines=16,
|
@@ -344,38 +389,28 @@ with gr.Blocks(title="ICB4 4.4.5 Leadership — Evaluación STAR (FRAQX)", css=C
|
|
344 |
""",
|
345 |
)
|
346 |
|
347 |
-
#
|
348 |
with gr.Column(scale=7):
|
349 |
gr.Markdown("<div class='card'><b>Resultados</b></div>")
|
350 |
status = gr.Markdown(value="**Estado**: —", elem_id="status_md")
|
351 |
-
|
352 |
-
# Cabecera con badge de score (se llena dinámicamente con Markdown)
|
353 |
score_badge = gr.Markdown(value="<span class='badge'>Score global: —</span>")
|
354 |
-
|
355 |
-
# Gráfica de barras
|
356 |
plot = gr.Plot(label="Gráfica de evaluación (0–4)")
|
357 |
-
|
358 |
-
# Tabla explicativa
|
359 |
table = gr.Dataframe(
|
360 |
headers=DEFAULT_COLS,
|
361 |
datatype=["str", "str", "number", "str", "str", "str"],
|
362 |
interactive=False,
|
363 |
label="Detalle por indicador"
|
364 |
)
|
365 |
-
|
366 |
-
# Acciones de exportación
|
367 |
with gr.Row():
|
368 |
download_btn = gr.Button("Descargar CSV")
|
369 |
csv_file = gr.File(label="Archivo CSV", visible=False)
|
370 |
|
371 |
-
#
|
372 |
-
def run_eval(t: str):
|
373 |
-
msg, fig, tbl = evaluate(t)
|
374 |
|
375 |
-
# Estado en Markdown
|
376 |
status_md = "**Estado** \n" + (msg or "").replace("\n", " \n")
|
377 |
|
378 |
-
# Badge de score desde el texto
|
379 |
badge_html = "<span class='badge'>Score global: —</span>"
|
380 |
try:
|
381 |
m = re.search(r"Score global \(0–4\):\s*([0-4](?:\.[0-9])?)", msg or "")
|
@@ -384,7 +419,6 @@ with gr.Blocks(title="ICB4 4.4.5 Leadership — Evaluación STAR (FRAQX)", css=C
|
|
384 |
except Exception:
|
385 |
pass
|
386 |
|
387 |
-
# Tabla segura
|
388 |
cols = (tbl or {}).get("columns") or DEFAULT_COLS
|
389 |
data = (tbl or {}).get("data") or []
|
390 |
safe_data = []
|
@@ -396,7 +430,6 @@ with gr.Blocks(title="ICB4 4.4.5 Leadership — Evaluación STAR (FRAQX)", css=C
|
|
396 |
r = r[:len(cols)]
|
397 |
safe_data.append(r)
|
398 |
|
399 |
-
# Gráfica placeholder si hace falta
|
400 |
if fig is None:
|
401 |
fig, ax = plt.subplots(figsize=(6, 2))
|
402 |
ax.axis("off")
|
@@ -404,29 +437,15 @@ with gr.Blocks(title="ICB4 4.4.5 Leadership — Evaluación STAR (FRAQX)", css=C
|
|
404 |
|
405 |
return status_md, badge_html, fig, gr.update(value=safe_data, headers=cols)
|
406 |
|
407 |
-
btn.click(
|
408 |
-
fn=run_eval,
|
409 |
-
inputs=[texto],
|
410 |
-
outputs=[status, score_badge, plot, table]
|
411 |
-
)
|
412 |
|
413 |
-
|
414 |
-
|
415 |
-
_, _, tbl = evaluate(t)
|
416 |
path = make_csv_from_table(tbl)
|
417 |
return path, gr.update(visible=True)
|
418 |
|
419 |
-
download_btn.click(
|
420 |
-
fn=export_csv_handler,
|
421 |
-
inputs=[texto],
|
422 |
-
outputs=[csv_file, csv_file]
|
423 |
-
)
|
424 |
|
425 |
-
#
|
426 |
if __name__ == "__main__":
|
427 |
-
demo.queue(
|
428 |
-
max_size=16
|
429 |
-
).launch(
|
430 |
-
ssr_mode=False,
|
431 |
-
show_error=True
|
432 |
-
)
|
|
|
5 |
import gradio as gr
|
6 |
import matplotlib.pyplot as plt
|
7 |
|
8 |
+
# ==========================
|
9 |
+
# Config & estilos
|
10 |
+
# ==========================
|
11 |
DEFAULT_COLS = [
|
12 |
"Código", "Indicador", "Score (0–4)",
|
13 |
"Entailment medio", "Evidencias (hipótesis)", "Descripción"
|
14 |
]
|
15 |
|
|
|
16 |
CUSTOM_CSS = """
|
17 |
#app {max-width: 1200px; margin: 0 auto;}
|
18 |
.badge {
|
|
|
28 |
.small {font-size: 12px; opacity: .9;}
|
29 |
"""
|
30 |
|
31 |
+
# ==========================
|
32 |
+
# Metadatos IPMA ICB4 4.4.5.x
|
33 |
+
# ==========================
|
34 |
INDICATOR_META = {
|
35 |
"4.4.5.1": ("Iniciativa y ayuda proactiva",
|
36 |
"Inicia acciones sin que se lo pidan; ofrece ayuda, anticipa y equilibra riesgos."),
|
|
|
44 |
"Toma decisiones bajo incertidumbre; explica razones; revisa con nueva evidencia; comunica con claridad.")
|
45 |
}
|
46 |
|
47 |
+
# ==========================
|
48 |
+
# Modelos (CPU Basic friendly)
|
49 |
+
# ==========================
|
50 |
_llm = None
|
51 |
_llm_tok = None
|
52 |
_gen = None
|
53 |
+
_nli_cache: Dict[str, object] = {} # cache de pipelines NLI por model_id
|
54 |
+
|
55 |
+
LLM_ID = "Qwen/Qwen2.5-0.5B-Instruct" # LLM pequeño multilingüe para extraer STAR
|
56 |
+
|
57 |
+
# Selector de NLI con configuración asociada
|
58 |
+
MODEL_CHOICES = {
|
59 |
+
"Velocidad (MiniLM)": {
|
60 |
+
"id": "MoritzLaurer/multilingual-MiniLMv2-L12-mnli-xnli",
|
61 |
+
"calibrate": True,
|
62 |
+
"thresholds": (0.70, 0.50, 0.30, 0.15) # 4,3,2,1
|
63 |
+
},
|
64 |
+
"Precisión (DeBERTa)": {
|
65 |
+
"id": "MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7",
|
66 |
+
"calibrate": False,
|
67 |
+
"thresholds": (0.80, 0.60, 0.40, 0.20)
|
68 |
+
}
|
69 |
+
}
|
70 |
+
DEFAULT_MODEL_KEY = "Velocidad (MiniLM)" # por defecto en Spaces gratis
|
71 |
|
72 |
STAR_PROMPT = """Eres evaluador ICB4. Toma el texto del candidato y devuélvelo en formato STAR como JSON válido con claves:
|
73 |
"situation" (<=3 frases), "task" (<=2 frases), "action" (lista de viñetas, verbos de acción), "result" (lista de viñetas, resultados/indicadores/aprendizajes).
|
|
|
109 |
]
|
110 |
}
|
111 |
|
112 |
+
# ==========================
|
113 |
+
# Carga perezosa de modelos
|
114 |
+
# ==========================
|
115 |
def lazy_load_llm():
|
116 |
+
"""Pipeline de generación (Qwen 0.5B) para extraer STAR."""
|
117 |
global _llm, _llm_tok, _gen
|
118 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
|
119 |
if _gen is not None:
|
120 |
return _gen
|
|
|
121 |
_llm_tok = AutoTokenizer.from_pretrained(LLM_ID)
|
122 |
+
_llm = AutoModelForCausalLM.from_pretrained(LLM_ID, device_map="auto")
|
|
|
|
|
|
|
123 |
_gen = pipeline(
|
124 |
"text-generation",
|
125 |
model=_llm,
|
|
|
130 |
)
|
131 |
return _gen
|
132 |
|
133 |
+
def lazy_load_nli(model_id: str):
|
134 |
+
"""NLI con salida completa y truncado seguro. Cachea por model_id."""
|
|
|
|
|
|
|
135 |
from transformers import pipeline
|
136 |
+
if model_id in _nli_cache:
|
137 |
+
return _nli_cache[model_id]
|
138 |
+
nli = pipeline(
|
139 |
"text-classification",
|
140 |
+
model=model_id,
|
141 |
+
tokenizer=model_id,
|
142 |
+
return_all_scores=True, # {label, score} para todas las clases
|
143 |
+
truncation=True # evita degradación por textos largos
|
144 |
)
|
145 |
+
_nli_cache[model_id] = nli
|
146 |
+
return nli
|
147 |
|
148 |
+
# ==========================
|
149 |
+
# Utilidades extracción STAR
|
150 |
+
# ==========================
|
151 |
def extract_json_block(text: str) -> str:
|
152 |
start = text.find("{")
|
153 |
end = text.rfind("}")
|
|
|
205 |
"result": [str(r).strip(" •-") for r in data["result"] if str(r).strip()],
|
206 |
}
|
207 |
|
208 |
+
# ==========================
|
209 |
+
# NLI + scoring (dinámico por modelo)
|
210 |
+
# ==========================
|
211 |
+
def calibrate_prob(p: float, use_calibration: bool) -> float:
|
212 |
+
"""Calibración leve solo para MiniLM (p**0.9)."""
|
213 |
+
p = max(0.0, min(1.0, float(p)))
|
214 |
+
return (p ** 0.9) if use_calibration else p
|
215 |
+
|
216 |
+
def nli_entails(premise: str, hypothesis: str, model_id: str) -> float:
|
217 |
+
"""Probabilidad de ENTAILMENT (0..1) robusta a variantes de salida."""
|
218 |
+
nli = lazy_load_nli(model_id)
|
219 |
|
220 |
def _trim(s: str, limit=900):
|
221 |
s = (s or "").strip()
|
|
|
248 |
return 0.0
|
249 |
return 0.0
|
250 |
|
251 |
+
def map_prob_to_score(p: float, thresholds: Tuple[float, float, float, float]) -> int:
|
252 |
+
t4, t3, t2, t1 = thresholds
|
253 |
+
if p >= t4: return 4
|
254 |
+
if p >= t3: return 3
|
255 |
+
if p >= t2: return 2
|
256 |
+
if p >= t1: return 1
|
257 |
return 0
|
258 |
|
259 |
+
def score_indicator(premise: str, hyps: List[str], model_id: str, use_calibration: bool,
|
260 |
+
thresholds: Tuple[float, float, float, float]) -> Tuple[int, List[Tuple[str, float]], float]:
|
261 |
+
raw = [(h, nli_entails(premise, h, model_id)) for h in hyps]
|
262 |
+
probs = [(h, calibrate_prob(p, use_calibration)) for h, p in raw]
|
263 |
avg = sum(p for _, p in probs) / max(1, len(probs))
|
264 |
+
score = map_prob_to_score(avg, thresholds)
|
265 |
probs_sorted = sorted(probs, key=lambda x: x[1], reverse=True)[:2]
|
266 |
return score, probs_sorted, avg
|
267 |
|
268 |
+
# ==========================
|
269 |
+
# Evaluación orquestada
|
270 |
+
# ==========================
|
271 |
+
def evaluate(texto: str, model_key: str):
|
272 |
+
"""Devuelve: status_msg, matplotlib_fig, {"columns":[...], "data":[...] }."""
|
273 |
try:
|
274 |
if not texto or not texto.strip():
|
275 |
return "Introduce un caso en formato STAR (o texto libre).", None, {"columns": [], "data": []}
|
276 |
|
277 |
+
# Config del modelo seleccionado
|
278 |
+
cfg = MODEL_CHOICES.get(model_key, MODEL_CHOICES[DEFAULT_MODEL_KEY])
|
279 |
+
model_id = cfg["id"]
|
280 |
+
use_calibration = cfg["calibrate"]
|
281 |
+
thresholds = cfg["thresholds"]
|
282 |
+
|
283 |
star = extract_star(texto)
|
284 |
|
285 |
+
# Limita premisa para dar señal clara al NLI (6 A + 4 R)
|
286 |
actions = (star.get("action", []) or [])[:6]
|
287 |
results = (star.get("result", []) or [])[:4]
|
288 |
premise = " ".join(actions) + " " + " ".join(results)
|
289 |
|
290 |
+
# Scoring por indicador
|
291 |
+
scores, table_rows, per_indicator_values = [], [], []
|
|
|
|
|
|
|
292 |
for ind, hyps in HYP.items():
|
293 |
+
s, ev, avg = score_indicator(premise, hyps, model_id, use_calibration, thresholds)
|
294 |
scores.append(s)
|
295 |
per_indicator_values.append((ind, s))
|
296 |
best_evid = " / ".join([h for h, _ in ev])
|
|
|
299 |
|
300 |
overall = round(sum(scores) / max(1, len(scores)), 2)
|
301 |
|
302 |
+
# Gráfica
|
303 |
labels = [f"{k.split('.')[-1]}" for k, _ in per_indicator_values]
|
304 |
values = [v for _, v in per_indicator_values]
|
305 |
fig, ax = plt.subplots(figsize=(8.2, 4.0))
|
|
|
307 |
ax.set_ylim(0, 4)
|
308 |
ax.set_xlabel("Indicadores 4.4.5.x")
|
309 |
ax.set_ylabel("Score (0–4)")
|
310 |
+
fig.suptitle(f"ICB4 4.4.5 Leadership — Score global: {overall} | Modelo: {model_key}", y=0.97)
|
311 |
fig.subplots_adjust(top=0.86)
|
312 |
for i, v in enumerate(values):
|
313 |
ax.text(i, v + 0.08, f"{v}", ha="center", va="bottom")
|
314 |
fig.tight_layout()
|
315 |
|
316 |
+
table = {"columns": DEFAULT_COLS, "data": table_rows}
|
|
|
|
|
|
|
|
|
317 |
msg = (
|
318 |
f"Evaluación completada. Score global (0–4): {overall}\n"
|
319 |
+
f"Modelo: {model_key}\n"
|
320 |
f"Sugerencia: revisa evidencias y ajusta umbrales según tu rúbrica."
|
321 |
)
|
322 |
return msg, fig, table
|
|
|
324 |
except Exception as e:
|
325 |
return f"⚠️ Error en evaluate(): {type(e).__name__}: {e}", None, {"columns": [], "data": []}
|
326 |
|
327 |
+
# ==========================
|
328 |
+
# CSV helper
|
329 |
+
# ==========================
|
330 |
def make_csv_from_table(table: dict) -> str:
|
331 |
cols = table.get("columns", [])
|
332 |
rows = table.get("data", [])
|
|
|
339 |
writer.writerow(r)
|
340 |
return path if os.path.exists(path) else ""
|
341 |
|
342 |
+
# ==========================
|
343 |
+
# UI (2 columnas + selector modelo + CSV)
|
344 |
+
# ==========================
|
345 |
with gr.Blocks(title="ICB4 4.4.5 Leadership — Evaluación STAR (FRAQX)", css=CUSTOM_CSS, elem_id="app") as demo:
|
346 |
gr.Markdown(
|
347 |
"""
|
|
|
349 |
<img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" height="28">
|
350 |
<h1 style="margin:0;">ICB4 • 4.4.5 Leadership — Evaluación STAR + NLI</h1>
|
351 |
</div>
|
352 |
+
<div class="small">Extracción STAR, scoring (4.4.5.1–4.4.5.5), gráfica y reporte descargable. Elige el modelo NLI según tu prioridad.</div>
|
353 |
"""
|
354 |
)
|
355 |
|
356 |
with gr.Row(equal_height=True):
|
357 |
+
# Entrada
|
358 |
with gr.Column(scale=5):
|
359 |
gr.Markdown("<div class='card'><b>Entrada</b></div>")
|
360 |
+
|
361 |
+
model_key = gr.Dropdown(
|
362 |
+
choices=list(MODEL_CHOICES.keys()),
|
363 |
+
value=DEFAULT_MODEL_KEY,
|
364 |
+
label="Modelo NLI",
|
365 |
+
info="Velocidad (MiniLM) = más rápido | Precisión (DeBERTa) = mejor calidad"
|
366 |
+
)
|
367 |
+
|
368 |
texto = gr.Textbox(
|
369 |
label="Caso (STAR o texto libre)",
|
370 |
lines=16,
|
|
|
389 |
""",
|
390 |
)
|
391 |
|
392 |
+
# Salida
|
393 |
with gr.Column(scale=7):
|
394 |
gr.Markdown("<div class='card'><b>Resultados</b></div>")
|
395 |
status = gr.Markdown(value="**Estado**: —", elem_id="status_md")
|
|
|
|
|
396 |
score_badge = gr.Markdown(value="<span class='badge'>Score global: —</span>")
|
|
|
|
|
397 |
plot = gr.Plot(label="Gráfica de evaluación (0–4)")
|
|
|
|
|
398 |
table = gr.Dataframe(
|
399 |
headers=DEFAULT_COLS,
|
400 |
datatype=["str", "str", "number", "str", "str", "str"],
|
401 |
interactive=False,
|
402 |
label="Detalle por indicador"
|
403 |
)
|
|
|
|
|
404 |
with gr.Row():
|
405 |
download_btn = gr.Button("Descargar CSV")
|
406 |
csv_file = gr.File(label="Archivo CSV", visible=False)
|
407 |
|
408 |
+
# Lógica
|
409 |
+
def run_eval(t: str, mk: str):
|
410 |
+
msg, fig, tbl = evaluate(t, mk)
|
411 |
|
|
|
412 |
status_md = "**Estado** \n" + (msg or "").replace("\n", " \n")
|
413 |
|
|
|
414 |
badge_html = "<span class='badge'>Score global: —</span>"
|
415 |
try:
|
416 |
m = re.search(r"Score global \(0–4\):\s*([0-4](?:\.[0-9])?)", msg or "")
|
|
|
419 |
except Exception:
|
420 |
pass
|
421 |
|
|
|
422 |
cols = (tbl or {}).get("columns") or DEFAULT_COLS
|
423 |
data = (tbl or {}).get("data") or []
|
424 |
safe_data = []
|
|
|
430 |
r = r[:len(cols)]
|
431 |
safe_data.append(r)
|
432 |
|
|
|
433 |
if fig is None:
|
434 |
fig, ax = plt.subplots(figsize=(6, 2))
|
435 |
ax.axis("off")
|
|
|
437 |
|
438 |
return status_md, badge_html, fig, gr.update(value=safe_data, headers=cols)
|
439 |
|
440 |
+
btn.click(fn=run_eval, inputs=[texto, model_key], outputs=[status, score_badge, plot, table])
|
|
|
|
|
|
|
|
|
441 |
|
442 |
+
def export_csv_handler(t: str, mk: str):
|
443 |
+
_, _, tbl = evaluate(t, mk)
|
|
|
444 |
path = make_csv_from_table(tbl)
|
445 |
return path, gr.update(visible=True)
|
446 |
|
447 |
+
download_btn.click(fn=export_csv_handler, inputs=[texto, model_key], outputs=[csv_file, csv_file])
|
|
|
|
|
|
|
|
|
448 |
|
449 |
+
# Lanzamiento
|
450 |
if __name__ == "__main__":
|
451 |
+
demo.queue(max_size=16).launch(ssr_mode=False, show_error=True)
|
|
|
|
|
|
|
|
|
|