Files
training/website/server/opcTest.js
T

93 lines
31 KiB
JavaScript
Raw Normal View History

2026-08-23 22:32:42 +08:00
// OPC 𥕢瘚贝唳旿銝舘恣唳旿皞琜 OPC𥕢瘚贝/scripts/*.json
// 𡁏𧋦閫OPC𥕢瘚贝/scripts/_generate_opcTest.js嚗𧋦甈⊥
export const QUESTIONS = [{"id":1,"part":"P1","quick":true,"question":"W笆銝銝芣瓷鈭箇雿𣳇𡁜虜","A":"芸楛摰𡁏嚗𣬚凒","B":"撣峕劐犖銝芣"},{"id":2,"part":"P1","quick":false,"question":"𤑳緵銝銝芸末雿䭾凒","A":"銝𠹺隡港韏瑁隡啜韏瑕","B":"䌊撌勗僕箔葵"},{"id":3,"part":"P1","quick":false,"question":"芣䔉雿䭾凒賣𦻖堒𪑛蝘","A":"銝芣銝滨迅摰𡄯韏瑟䔉","B":"銝憪见停閬蝔喳摨閙𤣰"},{"id":4,"part":"P1","quick":true,"question":"銝蝚𥪜虾兝嚗䔶","A":"撠賡摮条峕凒摰厩迅","B":"踹枂銝銝芸虾圈★"},{"id":5,"part":"P1","quick":true,"question":"瘝∩犖雿删雿惩","A":"靘萘隡𡁏芸楛憟𤩺蝏剜綫餈","B":"摰寞㗇⏛甇X𠯫滚𢆡"},{"id":6,"part":"P1","quick":false,"question":"摮虫憿寞鰵賣𧒄嚗䔶游虜","A":"霂曄鈭箏蒂滚郎敺𡑒","B":"芸楛銝餃𢆡朞器"},{"id":7,"part":"P1","quick":false,"question":"憒𥕢蝚砌撟游蘨銝芯犖嚗䔶游虾","A":"芸楛銝𦠜滚僎銋𣂼銁嗡葉","B":"閫匧澈銋𤩺钟𠗕隞交𦻖"},{"id":8,"part":"P1","quick":true,"question":"峕𧒄閬摰X鈭支嚗䔶","A":"閫匧劐犖","B":"銝銝芯犖銝𧢲䔉嚗諹器朞器摮"},{"id":9,"part":"P1","quick":true,"question":"撖孵 AI 撌亙嚗䔶","A":"銝餃𢆡撠肽 AI 蝏躰䌊撌望","B":"閫匧啁鍂撌亙憭毺鍂嚗暻餌"},{"id":10,"part":"P1","quick":false,"question":"祈秩𣬚鍂 AI 憟𡑒䌊蝔贝瘥誩𪂹 10 撠𤩺𧒄雿牐","A":"閫匧憭芸𦆮銝","B":"蝡见喟䰻𤘪𦒘撽砌刻絲"},{"id":11,"part":"P1","quick":false,"question":"挽雿䭾𦻖銝𧢲䔉𠰴僑瘝⊥蝔喳嚗䔶","A":"甈暹𨅯嚗諹憿嗡","B":"隡𡁶駁萅亥揣∠揮撘"},{"id":12,"part":"P1","quick":true,"question":"雿䭾蝔喳𥕢𧒄","A":"日妟蝣擧𧒄𠗕靽肽","B":"𤐄摰𡁶憭批園𡢿嚗瘥誩予 2 撠𤩺𧒄隞乩"},{"id":13,"part":"P2","quick":true,"question":"雿䭾凒鈭怠","A":"銵䎚煺犖嚗䔶銝剛繮敺𡑒","B":"祈䌊銝𤘪釣梶ㄗ銝銝芯瘝㗇絡刻䌊撌梁銝𣇉"},{"id":14,"part":"P2","quick":true,"question":"𡁜摰寞𧒄雿䭾凒憿箸","A":"湔偘𢒰撖寥𢒰嚗𣬚凒亥挈箸䔉","B":"䠷鵭瘛勗漲𠉛弦嚗峕"},{"id":15,"part":"P2","quick":false,"question":"𡁏蝚砌𧶏霈拐撠望湔糓","A":"敶㯄𢒰𠰴恥拍倌銝","B":"鈭支𤾸恥霂𡁶霈文虾"},{"id":16,"part":"P2","quick":true,"question":"W笆銝銝芾瘝∩犖蝐鳴雿牐","A":"敺憟页喟洵銝銝芸餈𥕦縧霂閗","B":"敺𠬍𧢲瘝⊥鈭粹"},{"id":17,"part":"P2","quick":true,"question":"雿䭾凒羓移𥟇𦆮","A":"餈䀹瓷怨絲艙嚗銝箔銝芷","B":"撌脩鋡恍蝔喳㦤"},{"id":18,"part":"P2","quick":false,"question":"厰★格𧒄雿䭾凒","A":"鞾鵭蝛粹𡢿憭改銝钅銝齿","B":"啣銁撠望𡒊&犖隞䁅晶嚗𪑛鈭㗇"},{"id":19,"part":"P2","quick":true,"question":"𡁶𣂼停凒憭𡁏䔉","A":"撣桐銝芸恥窖敺𦯀縑隞","B":"𦠜蝔见敹怒"},{"id":20,"part":"P2","quick":true,"question":"雿䭾凒","A":"㗇萱摨衣撖嫣滚𦛚嚗倮霂//摰𡁜","B":"𣇉鈭批𤥁紋蝔页甈~虾憭滚"},{"id":21,"part":"P2","quick":false,"question":"摰X𧒄嚗䔶","A":"瘛勗圾隞𤥁銝芯犖嚗峕隞𣇉摰𡁜","B":"折䔮憸矋𡁏憭滚"},{"id":22,"part":"P2","quick":true,"question":"雿删","A":"
export const RULES = [{"id":1,"A":"IND","B":null},{"id":2,"A":null,"B":"IND"},{"id":3,"A":"RISK","B":null},{"id":4,"A":null,"B":"RISK"},{"id":5,"A":"DRIVE","B":null},{"id":6,"A":null,"B":"DRIVE"},{"id":7,"A":"SOLO","B":null},{"id":8,"A":null,"B":"SOLO"},{"id":9,"A":"AI","B":null},{"id":10,"A":null,"B":"AI"},{"id":11,"A":"STABLE","B":null},{"id":12,"A":null,"B":"STABLE"},{"id":13,"A":"E","B":"I"},{"id":14,"A":"E","B":"I"},{"id":15,"A":"E","B":"I"},{"id":16,"A":"V","B":"G"},{"id":17,"A":"V","B":"G"},{"id":18,"A":"V","B":"G"},{"id":19,"A":"R","B":"T"},{"id":20,"A":"R","B":"T"},{"id":21,"A":"R","B":"T"},{"id":22,"A":"P","B":"F"},{"id":23,"A":"F","B":"P"},{"id":24,"A":"P","B":"F"},{"id":25,"A":"TOUR","B":"CONTENT"},{"id":26,"A":"TOUR","B":"DIGITAL"},{"id":27,"A":"CONTENT","B":"ECOMM"},{"id":28,"A":"CONTENT","B":"CROSS"},{"id":29,"A":"DIGITAL","B":"LOCAL"},{"id":30,"A":"DIGITAL","B":"TOUR"},{"id":31,"A":"ECOMM","B":"CONTENT"},{"id":32,"A":"ECOMM","B":"DIGITAL"},{"id":33,"A":"CROSS","B":"LOCAL"},{"id":34,"A":"CROSS","B":"TOUR"},{"id":35,"A":"LOCAL","B":"ECOMM"},{"id":36,"A":"LOCAL","B":"DIGITAL"},{"id":37,"A":"MAKER","B":"CREATOR"},{"id":38,"A":"MAKER","B":"HUNTER"},{"id":39,"A":"MAKER","B":"RUNNER"},{"id":40,"A":"CREATOR","B":"MAKER"},{"id":41,"A":"CREATOR","B":"ARCH"},{"id":42,"A":"CREATOR","B":"RUNNER"},{"id":43,"A":"HUNTER","B":"MAKER"},{"id":44,"A":"HUNTER","B":"RUNNER"},{"id":45,"A":"RUNNER","B":"HUNTER"},{"id":46,"A":"RUNNER","B":"CREATOR"},{"id":47,"A":"ARCH","B":"MAKER"},{"id":48,"A":"ARCH","B":"RUNNER"},{"id":49,"A":"DIALOG","B":"AGENT"},{"id":50,"A":"DIALOG","B":"AUTOCON"},{"id":51,"A":"DIALOG","B":"OUTSOURCE"},{"id":52,"A":"AUTOCON","B":"DIALOG"},{"id":53,"A":"AUTOCON","B":"AGENT"},{"id":54,"A":"AUTOCON","B":"OUTSOURCE"},{"id":55,"A":"AGENT","B":"AUTOCON"},{"id":56,"A":"AGENT","B":"DIALOG"},{"id":57,"A":"AGENT","B":"OUTSOURCE"},{"id":58,"A":"OUTSOURCE","B":"AGENT"},{"id":59,"A":"OUTSOURCE","B":"DIALOG"},{"id":60,"A":"OUTSOURCE","B":"AUTOCON"}];
export const PROFILES = {"meta":{"name":"OPC 𥕢瘚贝唳旿","version":"1.0","description":"韏偦/閫坿𠧧/撌亙/蝑厩漣/16鈭箄挽/寡捶頧渡摰峕㟲讛膩唳旿𥁒𦠜葡韏𡝗𧋦","pairs":{"EI":["E","I"],"VG":["V","G"],"RT":["R","T"],"PF":["P","F"]}},"axes":[{"code":"EI","name":"","left":"E","right":"I","leftLabel":"撖孵餈墧𦻖","rightLabel":"撖孵瘛梯","leftDesc":"隞𦒘鈭箸𠾼蓥葉嚗偦瑕恥銝舘","rightDesc":"隞𡒊𡠺瘜具楛摨虫漣箔葉嚗偦𥕢銝𦒘漣"},{"code":"VG","name":"","left":"V","right":"G","leftLabel":"Y揣唳㦤隡","rightLabel":"𦠜𨘥","leftDesc":"𨀣洽蝐颯鰵璁憌𤾸藁嚗偦捆IP/頝典/鈭批","rightDesc":"訾縑撌脤蝔喳㦤嚗偦寡𠧧/砍𧑐滚𦛚/"},{"code":"RT","name":"隞瑕潸蓬","left":"R","right":"T","leftLabel":"喟頂滚𦛚","rightLabel":"鈭斗","leftDesc":"滢犖摰𡁜𡝗刻砭//砍𧑐滚𦛚","rightDesc":"齿憭滚嚗偦/捆IP/頝典"},{"code":"PF","name":"","left":"P","right":"F","leftLabel":"蝟餌","rightLabel":"菜暑摨𥪜","leftDesc":" SOP蝔卝虾憭滚嚗偦砍𧑐滚𦛚//鈭批","rightDesc":"撉䕘捆IP//頝典"}],"personas":[{"code":"EVRP","name":"","tagline":"","desc":"餈墧𦻖 + 唳㦤隡 + 滢犖滢縑隞 + 厩頂蝏麄銝滚皞鞉僼撘删獢䔶𡁜像","openPath":"雿删 OPC 嚗𡁜𡃏皞鞟撟喳蝱𣇉𧶏霈拍頂蝏𤜯雿㰘蓮"},{"code":"EVRF","name":"憌𤾸藁","tagline":"餈賣鰵","desc":"餈墧𦻖 + 唳㦤隡 + 滢犖滢縑隞 + 摨𥪜敹怒撖寞鰵鵭撣血𢆡鈭綽蝷曄黎鰵韏偦","openPath":"雿删 OPC 嚗𡁜甇亙翰頝穃蝷曄黎銝𦒘撉䕘蝟餃剖漲皛𡁜𢆡韏瑟䔉"},{"code":"EVTP","name":"条裦","tagline":"","desc":"餈墧𦻖 + 唳㦤隡 + 齿 + 厩頂蝏麄唳㦤隡𡁏𣂼虾憭滚頂蝏𡁜像唳沲曉之","openPath":"雿删 OPC 嚗𡁻挽霈 + 蝟餌憭滚嚗𣬚鍂雿枏𣪧"},{"code":"EVTF","name":"𤾸仍","tagline":"X貍","desc":"餈墧𦻖 + 唳㦤隡 + 齿 + 摨𥪜敹怒蝒堒藁𤘪X貍","openPath":"雿删 OPC 嚗𡁜甇亙翰頝㻫𤩺瘜𤏪蝒堒藁敹恍蠘翮隞"},{"code":"EGRP","name":"餈鮋摨烾鵭","tagline":"擧覔","desc":"餈墧𦻖 + 賢𧑐摮㗛 + 滢犖滢縑隞 + 厩頂蝏麄𠹺隞嗡典𧑐𡁏楛𡁻𧶏典藁蝣穃憭滚","openPath":"雿删 OPC 嚗𡁜銁砍𧑐𠹺摰嗅銝芯漣摰嗅振憭滚"},{"code":"EGRF","name":"啣㦤餈鞱𨯫摰","tagline":"","desc":"餈墧𦻖 + 賢𧑐摮㗛 + 滢犖滢縑隞 + 摨𥪜敹怒𠹺犖瘞𠉛交糓憭拍撘粹嚗屸砍𧑐瘣餃𢆡撉䎚冗蝢方","openPath":"雿删 OPC 嚗𡁏鈭箇鈭页箏㦤瘣餃𢆡瘝㗇銝𤾸韐准"},{"code":"EGTP","name":"蝟餌蝏讐","tagline":"𡁏","desc":"餈墧𦻖 + 賢𧑐摮㗛 + 齿 + 厩頂蝏麄砍𧑐𡁏OP ","openPath":"雿删 OPC 嚗𡁏滚𦛚 SOP 嚗𣬚鍂蝟餌凒憭𡁏𧋦摰嗚"},{"code":"EGTF","name":"皜𣳇桀恥","tagline":"刻蓮","desc":"餈墧𦻖 + 賢𧑐摮㗛 + 齿 + 摨𥪜敹怒鵭靽撌柴枏𪂹頧穿誨餈鞱𨯫"
const RULE_MAP = {};
RULES.forEach((r) => { RULE_MAP[r.id] = r; });
export const ADAPT_DIMS = ['IND','RISK','DRIVE','SOLO','AI','STABLE'];
export const ADAPT_LABELS = { IND:'芯蜓', RISK:'憌𡡞埯', DRIVE:'芷店', SOLO:'憭𡁻𢒰', AI:'AI 𤩺', STABLE:'摰匧' };
export const AXIS_PAIRS = [['E','I'],['V','G'],['R','T'],['P','F']];
export const AXIS_NAMES = { EI:'', VG:'閫', RT:'隞瑕', PF:'' };
export const SECTIONS = {
P1: '蝚砌𥕢瓲嚗𡠺蝡 / 憌𡡞埯 / 芷店 / / AI / 摰匧',
P2: '蝚砌寡捶 / 閫 / 隞瑕 / ',
P3: '蝚砌 繚 韏偦誩末嚗 / 捆IP / 刻砭 / / 頝典 / 砍𧑐嚗',
P4: '蝚砍 繚 閫坿𠧧 / 捆 / 𦛚 / 餈鞱𨯫 / ',
P5: '蝚砌 繚 鈭箸㦤笆霂 / 芸𢆡 / / 憭硋'
};
export function currentQuestions(version) {
return version === 'quick' ? QUESTIONS.filter((x) => x.quick) : QUESTIONS;
}
/** 霈∪嚗帋霈曇恣﹝銝 㺭(6蝏渡) + 蝐餃(4頧) + 韏偦/閫坿𠧧/撌亙(share of votes) */
export function calculate(answers, version = 'full') {
const ids = currentQuestions(version).map((x) => x.id);
const counts = {};
ids.forEach((id) => {
const ans = answers[id];
if (ans !== 'A' && ans !== 'B') return;
const rule = RULE_MAP[id];
if (!rule) return;
const code = ans === 'A' ? rule.A : rule.B;
if (code) counts[code] = (counts[code] || 0) + 1;
});
const adaptDims = [];
let adaptSum = 0;
ADAPT_DIMS.forEach((dim) => {
let max = 0;
ids.forEach((id) => { const r = RULE_MAP[id]; if ((r && r.A === dim) || (r && r.B === dim)) max++; });
const votes = counts[dim] || 0;
const score = max > 0 ? Math.round((votes / max) * 100) : 0;
adaptSum += score;
adaptDims.push({ code: dim, label: ADAPT_LABELS[dim], score, votes, max });
});
const adaptIndex = Math.round(adaptSum / ADAPT_DIMS.length);
let adaptLevel = null;
PROFILES.adaptLevels.forEach((lv) => { if (adaptIndex >= lv.range[0] && adaptIndex <= lv.range[1]) adaptLevel = lv; });
const weakestDims = [...adaptDims].sort((a, b) => a.score - b.score).slice(0, 2);
let typeCode = '';
const axesDetail = [];
AXIS_PAIRS.forEach(([left, right]) => {
const l = counts[left] || 0, r = counts[right] || 0, total = l + r;
const letter = l >= r ? left : right;
typeCode += letter;
axesDetail.push({ pair: left + right, left, right, leftPct: total > 0 ? Math.round((l / total) * 100) : 50, rightPct: total > 0 ? Math.round((r / total) * 100) : 50, winner: letter, lCount: l, rCount: r });
});
const persona = PROFILES.personas.find((x) => x.code === typeCode) || null;
function rank(dims) {
let total = 0;
dims.forEach((d) => { total += counts[d] || 0; });
return dims.map((d) => ({ code: d, votes: counts[d] || 0, pct: total > 0 ? Math.round(((counts[d] || 0) / total) * 100) : 0 }))
.sort((a, b) => b.votes - a.votes || a.code.localeCompare(b.code));
}
const tracks = rank(PROFILES.tracks.map((t) => t.code)).filter((t) => t.pct >= 10).slice(0, 3);
const roles = rank(PROFILES.roles.map((t) => t.code)).filter((t) => t.pct >= 5).slice(0, 2);
const tools = rank(PROFILES.toolModes.map((t) => t.code)).filter((t) => t.pct >= 5).slice(0, 2);
return { version, answeredCount: Object.keys(answers).filter((id) => ids.includes(Number(id))).length, typeCode, persona, adaptIndex, adaptLevel, adaptDims, weakestDims, axesDetail, tracks, roles, tools };
}
/**
* 撅訫嚗𡁏 tracks / roles / tools 隞汿稪ode + 蟡冽㺭滩‘其蛹祗/讛膩游笆鞊∴
* 餈蹱甅摰X蝡舀葡𤘪𥁒𦠜𧒄𣳇齿 PROFILES 砍𧑐舀𧋦嚗霂剖冽䔉蝡荔
*/
export function expandResult(r) {
const find = (list, code) => list.find((x) => x.code === code) || {};
return {
...r,
tracks: (r.tracks || []).map((t) => ({ ...t, ...find(PROFILES.tracks, t.code) })),
roles: (r.roles || []).map((x) => ({ ...x, ...find(PROFILES.roles, x.code) })),
tools: (r.tools || []).map((x) => ({ ...x, ...find(PROFILES.toolModes, x.code) }))
};
}