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"]],
|
|||
|
|
}
|