Spaces:
Running on Zero
Running on Zero
Download app/scoring.py from RealFalconsAI/DecisionLab: direct link, hf CLI and curl.
- Browser
- Download file 2.91 kB
-
https://huggingface.co/spaces/RealFalconsAI/DecisionLab/resolve/main/app/scoring.py
- Command line
-
hf download hf://spaces/RealFalconsAI/DecisionLab/app/scoring.py
-
curl -L -o scoring.py https://huggingface.co/spaces/RealFalconsAI/DecisionLab/resolve/main/app/scoring.py
2.91 kB
| """Normalisation of model answers into one comparable record per question. | |
| Pure Python (math only): no torch, no model code, so it can be tested anywhere. | |
| Moved verbatim from app/models.py in 1.2.0. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| def option_keys(q: dict) -> list[str]: | |
| qtype = q.get("type", "choice") | |
| crit = q.get("criteria", q.get("options")) | |
| if qtype == "noul": | |
| return ["true", "false"] | |
| if qtype == "score": | |
| return [str(i) for i in range(len(crit or []))] | |
| if isinstance(crit, dict): | |
| return [str(k) for k in crit] | |
| return [str(c) for c in (crit or [])] | |
| def _lookup(raw: dict, key: str): | |
| """Tolerant key lookup: exact, str(), lower-case, and bool spellings.""" | |
| if not isinstance(raw, dict): | |
| return None | |
| candidates = [key, key.lower(), key.capitalize()] | |
| if key == "true": | |
| candidates += [True, "True", "yes", "Yes", 1, "1"] | |
| if key == "false": | |
| candidates += [False, "False", "no", "No", 0, "0"] | |
| if key.isdigit(): | |
| candidates.append(int(key)) | |
| for c in candidates: | |
| if c in raw: | |
| return raw[c] | |
| return None | |
| def normalise(q: dict, probs_raw: dict | None, choice=None, p_true=None, level=None) -> dict: | |
| """One comparable record per question, whatever the model returned.""" | |
| qtype = q.get("type", "choice") | |
| keys = option_keys(q) | |
| probs = {} | |
| if qtype == "noul" and p_true is not None: | |
| p = float(p_true) | |
| probs = {"true": p, "false": 1.0 - p} | |
| elif probs_raw: | |
| for k in keys: | |
| v = _lookup(probs_raw, k) | |
| probs[k] = float(v) if v is not None else 0.0 | |
| if not probs or sum(probs.values()) <= 0: | |
| # The model gave only its answer: represent it as a point mass so the UI still works. | |
| probs = {k: 0.0 for k in keys} | |
| if qtype == "score" and level is not None: | |
| probs[str(int(round(float(level))))] = 1.0 | |
| elif choice is not None and str(choice).lower() in {k.lower() for k in keys}: | |
| probs[next(k for k in keys if k.lower() == str(choice).lower())] = 1.0 | |
| s = sum(probs.values()) or 1.0 | |
| probs = {k: v / s for k, v in probs.items()} | |
| top = max(probs, key=probs.get) | |
| n = len(probs) | |
| ent = -sum(p * math.log(p) for p in probs.values() if p > 0) | |
| ent_conf = 1.0 - ent / math.log(n) if n > 1 else 1.0 | |
| ent_conf = max(0.0, min(1.0, ent_conf)) | |
| # Laya reports 1 - normalised entropy for choice/score, and max(p, 1 - p) for yes/no questions. | |
| laya_conf = probs[top] if qtype == "noul" else ent_conf | |
| rec = dict(type=qtype, choice=top, probs=probs, top_prob=probs[top], entropy_conf=ent_conf, laya_conf=laya_conf) | |
| if qtype == "score": | |
| rec["expected_level"] = sum(int(k) * p for k, p in probs.items()) | |
| rec["levels"] = len(keys) | |
| if qtype == "noul": | |
| rec["p_true"] = probs["true"] | |
| return rec | |