venthrive-api / bsa /scoring.py
GitHub Actions
Sync from GitHub · 2026-10-22 23:15 UTC
b324c71
Raw History Blame Contribute Delete
1.98 kB
"""
Re-scores a Business Strength Archetype submission on the server, so what we
store never depends on numbers the browser sent. Mirrors lib/bsa/score.ts; the
question bank comes from questions.json, built by scripts/bsa/build_content.py.
"""
import json
import math
from pathlib import Path
_DATA = json.loads((Path(__file__).resolve().parent / "questions.json").read_text(encoding="utf-8"))
ORDER: list[str] = _DATA["order"]
STAGES: list[str] = _DATA["stages"]
AGES: list[str] = _DATA["ages"]
QUESTIONS: dict[int, dict] = {q["id"]: q for q in _DATA["questions"]}
def percentages(raw: dict[str, int]) -> dict[str, int]:
"""Whole percentages that add up to 100 (largest remainder method)."""
total = sum(raw.values()) or 1
exact = [raw[d] / total * 100 for d in ORDER]
pct = [math.floor(e) for e in exact]
order = sorted(range(len(ORDER)), key=lambda i: (-(exact[i] - pct[i]), i))
for i in order[: 100 - sum(pct)]:
pct[i] += 1
return dict(zip(ORDER, pct))
def score(answers: dict[int, str]) -> dict:
"""answers: question id → strength picked. Raises ValueError if any answer is impossible."""
if set(answers) != set(QUESTIONS):
raise ValueError(f"Expected answers to all {len(QUESTIONS)} questions.")
raw = {d: 0 for d in ORDER}
fit = {d: 0 for d in ORDER}
for qid, dim in answers.items():
q = QUESTIONS[qid]
if dim not in q["dims"]:
raise ValueError(f"Question {qid} has no option for {dim}.")
raw[dim] += 1
if dim == q["naturalFit"]:
fit[dim] += 1
ranked = sorted(ORDER, key=lambda d: (-raw[d], -fit[d], ORDER.index(d)))
primary, secondary = ranked[0], ranked[1]
pair = sorted([primary, secondary], key=ORDER.index)
return {
"raw": raw,
"pct": percentages(raw),
"primary": primary,
"secondary": secondary,
"lowest": [ranked[6], ranked[5]],
"blendKey": f"{pair[0]}-{pair[1]}",
}