8b43d3df52
- 报名/测评/政策/调研,独立 data/opc.db - 源自原培训后端,路径调整至 server-core
111 lines
4.4 KiB
Python
111 lines
4.4 KiB
Python
"""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"]],
|
||
}
|