Files
server-core/app/training/opc_engine.py
T

111 lines
4.4 KiB
Python
Raw Normal View History

"""OPC 创业基因测评 · 计分逻辑(移植自 website/server/opcTest.js"""
from . import opc_data
QUESTIONS = opc_data.QUESTIONS
RULES = opc_data.RULES
PROFILES = opc_data.PROFILES
RULE_MAP = {r["id"]: r for r in RULES}
ADAPT_DIMS = ["IND", "RISK", "DRIVE", "SOLO", "AI", "STABLE"]
ADAPT_LABELS = {"IND": "独立自主", "RISK": "风险承受", "DRIVE": "自驱动力", "SOLO": "单兵多面", "AI": "AI 意愿", "STABLE": "安全垫"}
AXIS_PAIRS = [("E", "I"), ("V", "G"), ("R", "T"), ("P", "F")]
AXIS_NAMES = {"EI": "能量", "VG": "视野", "RT": "价值", "PF": "节奏"}
SECTIONS = {
"P1": "第一部分 · 创业内核(独立 / 风险 / 自驱 / 单兵 / AI / 安全垫)",
"P2": "第二部分 · 特质倾向(能量 / 视野 / 价值 / 节奏)",
"P3": "第三部分 · 赛道偏好(文旅 / 内容IP / 咨询 / 电商 / 跨境 / 本地)",
"P4": "第四部分 · 角色偏向(产品 / 内容 / 商务 / 运营 / 架构)",
"P5": "第五部分 · 人机协作(对话 / 自动化 / 智能体 / 外包)",
}
def current_questions(version):
return [q for q in QUESTIONS if q["quick"]] if version == "quick" else QUESTIONS
def _get_ans(answers, qid):
"""兼容:answers 键可能是字符串或数字"""
if qid in answers:
return answers[qid]
if str(qid) in answers:
return answers[str(qid)]
return None
def calculate(answers, version="full"):
ids = [q["id"] for q in current_questions(version)]
counts = {}
for qid in ids:
ans = _get_ans(answers, qid)
if ans not in ("A", "B"):
continue
rule = RULE_MAP.get(qid)
if not rule:
continue
code = rule["A"] if ans == "A" else rule["B"]
if code:
counts[code] = counts.get(code, 0) + 1
adapt_dims = []
adapt_sum = 0
for dim in ADAPT_DIMS:
mx = sum(1 for qid in ids for r in [RULE_MAP.get(qid)] if r and (r.get("A") == dim or r.get("B") == dim))
votes = counts.get(dim, 0)
score = round(votes / mx * 100) if mx > 0 else 0
adapt_sum += score
adapt_dims.append({"code": dim, "label": ADAPT_LABELS[dim], "score": score, "votes": votes, "max": mx})
adapt_index = round(adapt_sum / len(ADAPT_DIMS))
adapt_level = next((lv for lv in PROFILES["adaptLevels"] if lv["range"][0] <= adapt_index <= lv["range"][1]), None)
weakest_dims = sorted(adapt_dims, key=lambda d: d["score"])[:2]
type_code = ""
axes_detail = []
for left, right in AXIS_PAIRS:
l = counts.get(left, 0)
r = counts.get(right, 0)
total = l + r
letter = left if l >= r else right
type_code += letter
axes_detail.append({
"pair": left + right, "left": l, "right": r,
"leftPct": round(l / total * 100) if total > 0 else 50,
"rightPct": round(r / total * 100) if total > 0 else 50,
"winner": letter, "lCount": l, "rCount": r,
})
persona = next((p for p in PROFILES["personas"] if p["code"] == type_code), None)
def rank(dims):
total = sum(counts.get(d, 0) for d in dims)
res = []
for d in dims:
v = counts.get(d, 0)
res.append({"code": d, "votes": v, "pct": round(v / total * 100) if total > 0 else 0})
res.sort(key=lambda x: (-x["votes"], x["code"]))
return res
tracks = [t for t in rank([t["code"] for t in PROFILES["tracks"]]) if t["pct"] >= 10][:3]
roles = [x for x in rank([x["code"] for x in PROFILES["roles"]]) if x["pct"] >= 5][:2]
tools = [t for t in rank([t["code"] for t in PROFILES["toolModes"]]) if t["pct"] >= 5][:2]
answered_count = sum(1 for qid in ids if _get_ans(answers, qid) in ("A", "B"))
return {
"version": version, "answeredCount": answered_count, "typeCode": type_code, "persona": persona,
"adaptIndex": adapt_index, "adaptLevel": adapt_level, "adaptDims": adapt_dims, "weakestDims": weakest_dims,
"axesDetail": axes_detail, "tracks": tracks, "roles": roles, "tools": tools,
}
def expand_result(r):
def find(lst, code):
return next((x for x in lst if x["code"] == code), {})
return {
**r,
"tracks": [{**t, **find(PROFILES["tracks"], t["code"])} for t in r["tracks"]],
"roles": [{**x, **find(PROFILES["roles"], x["code"])} for x in r["roles"]],
"tools": [{**x, **find(PROFILES["toolModes"], x["code"])} for x in r["tools"]],
}