Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -270,41 +270,87 @@ def _responses_for_student_stage(uri, db, responses_coll, student: str, stage: s
|
|
| 270 |
return [d for d in docs if (d.get("answer") or "").strip()]
|
| 271 |
except Exception:
|
| 272 |
return []
|
| 273 |
-
|
| 274 |
import re
|
| 275 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
def _fix_cutoff_quotes(quotes: list[str], responses: list[dict]) -> list[str]:
|
| 277 |
"""
|
| 278 |
-
Replace truncated quotes with full answers
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
| 280 |
"""
|
| 281 |
if not quotes:
|
| 282 |
return []
|
| 283 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
fulls = []
|
| 285 |
for q in quotes:
|
| 286 |
-
|
| 287 |
-
if not
|
| 288 |
continue
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
return fulls
|
| 309 |
|
| 310 |
def _top3_answers_by_skill_sum(responses: list[dict]) -> list[str]:
|
|
|
|
| 270 |
return [d for d in docs if (d.get("answer") or "").strip()]
|
| 271 |
except Exception:
|
| 272 |
return []
|
|
|
|
| 273 |
import re
|
| 274 |
|
| 275 |
+
def _answer_total_score(resp: dict) -> float:
|
| 276 |
+
skills = resp.get("skills") or {}
|
| 277 |
+
total = 0.0
|
| 278 |
+
for v in skills.values():
|
| 279 |
+
try:
|
| 280 |
+
total += float(v)
|
| 281 |
+
except Exception:
|
| 282 |
+
pass
|
| 283 |
+
return total
|
| 284 |
+
|
| 285 |
+
def _norm_text(s: str) -> str:
|
| 286 |
+
# lower, collapse whitespace, strip surrounding punctuation/dots
|
| 287 |
+
return re.sub(r"\s+", " ", (s or "").lower()).strip(" .,\"'`”’“‘-–—()[]{}")
|
| 288 |
+
|
| 289 |
+
def _fragments_in_order(answer_norm: str, frags_norm: list[str]) -> bool:
|
| 290 |
+
"""Return True if all fragments appear in order anywhere in the answer."""
|
| 291 |
+
start = 0
|
| 292 |
+
for frag in frags_norm:
|
| 293 |
+
idx = answer_norm.find(frag, start)
|
| 294 |
+
if idx == -1:
|
| 295 |
+
return False
|
| 296 |
+
start = idx + len(frag)
|
| 297 |
+
return True
|
| 298 |
+
|
| 299 |
def _fix_cutoff_quotes(quotes: list[str], responses: list[dict]) -> list[str]:
|
| 300 |
"""
|
| 301 |
+
Replace truncated quotes with full answers when possible.
|
| 302 |
+
Works for:
|
| 303 |
+
- '...' ellipsis in the middle (checks fragments in order, anywhere)
|
| 304 |
+
- plain middle fragments (substring match)
|
| 305 |
+
If multiple matches, pick the response with highest total skill score.
|
| 306 |
"""
|
| 307 |
if not quotes:
|
| 308 |
return []
|
| 309 |
+
# Precompute normalized answers + scores
|
| 310 |
+
norm_answers = []
|
| 311 |
+
for r in responses:
|
| 312 |
+
ans = (r.get("answer") or "").strip()
|
| 313 |
+
if not ans:
|
| 314 |
+
continue
|
| 315 |
+
norm_answers.append((
|
| 316 |
+
_norm_text(ans),
|
| 317 |
+
ans,
|
| 318 |
+
_answer_total_score(r)
|
| 319 |
+
))
|
| 320 |
+
|
| 321 |
fulls = []
|
| 322 |
for q in quotes:
|
| 323 |
+
q_raw = (q or "").strip()
|
| 324 |
+
if not q_raw:
|
| 325 |
continue
|
| 326 |
+
|
| 327 |
+
q_norm = _norm_text(q_raw)
|
| 328 |
+
candidates = []
|
| 329 |
+
|
| 330 |
+
if "..." in q_raw:
|
| 331 |
+
# split on ellipses, keep non-empty normalized fragments
|
| 332 |
+
parts = [p.strip() for p in re.split(r"\.\.\.|…", q_raw)]
|
| 333 |
+
parts_norm = [_norm_text(p) for p in parts if _norm_text(p)]
|
| 334 |
+
if parts_norm:
|
| 335 |
+
for ans_norm, ans_full, score in norm_answers:
|
| 336 |
+
if _fragments_in_order(ans_norm, parts_norm):
|
| 337 |
+
candidates.append((score, ans_full))
|
| 338 |
+
else:
|
| 339 |
+
# plain substring anywhere in the answer
|
| 340 |
+
for ans_norm, ans_full, score in norm_answers:
|
| 341 |
+
if q_norm and q_norm in ans_norm:
|
| 342 |
+
candidates.append((score, ans_full))
|
| 343 |
+
|
| 344 |
+
if candidates:
|
| 345 |
+
# pick highest scoring answer
|
| 346 |
+
candidates.sort(key=lambda x: x[0], reverse=True)
|
| 347 |
+
fulls.append(candidates[0][1])
|
| 348 |
+
else:
|
| 349 |
+
# no match found → keep original
|
| 350 |
+
fulls.append(q_raw)
|
| 351 |
+
|
| 352 |
+
return fulls
|
| 353 |
+
|
| 354 |
return fulls
|
| 355 |
|
| 356 |
def _top3_answers_by_skill_sum(responses: list[dict]) -> list[str]:
|