269 lines
7.2 KiB
JavaScript
269 lines
7.2 KiB
JavaScript
const fs = require('fs');
|
||
const path = require('path');
|
||
const portraitEngine = require('../portrait/portrait-engine');
|
||
|
||
// 加载规则配置
|
||
function loadRules() {
|
||
const rulesPath = path.join(__dirname, 'pca-rules.json');
|
||
return JSON.parse(fs.readFileSync(rulesPath));
|
||
}
|
||
|
||
// 维度1:EXE执行力计算
|
||
function calculateEXE(portrait, rules) {
|
||
const factors = rules.dimensions.EXE.factors;
|
||
const raw = portrait.raw_stats || {};
|
||
const recent = portrait.recent_snapshots?.[0] || {};
|
||
|
||
// 连胜率 (30%)
|
||
const streakRate = Math.min(100, (raw.streak || 0) * 10);
|
||
|
||
// 完成率 (30%)
|
||
const completionRate = raw.total_items ? (raw.completed_items / raw.total_items) * 100 : 100;
|
||
|
||
// 执行节奏得分 (20%)
|
||
const rhythmScore = {
|
||
'快': 100,
|
||
'稳': 80,
|
||
'慢': 50
|
||
}[recent.rhythm] || 60;
|
||
|
||
// 摩擦恢复得分 (20%)
|
||
const frictionScore = {
|
||
'无': 100,
|
||
'低': 85,
|
||
'中': 60,
|
||
'高': 30
|
||
}[recent.friction] || 50;
|
||
|
||
const total = (
|
||
streakRate * (factors.streak_rate / 100) +
|
||
completionRate * (factors.completion_rate / 100) +
|
||
rhythmScore * (factors.rhythm_score / 100) +
|
||
frictionScore * (factors.friction_recovery / 100)
|
||
);
|
||
|
||
return {
|
||
score: Math.round(total),
|
||
details: {
|
||
streakRate,
|
||
completionRate,
|
||
rhythmScore,
|
||
frictionScore
|
||
}
|
||
};
|
||
}
|
||
|
||
// 维度2:TEC技术纵深计算
|
||
function calculateTEC(portrait, rules) {
|
||
const factors = rules.dimensions.TEC.factors;
|
||
const raw = portrait.raw_stats || {};
|
||
|
||
// EL等级得分 (40%)
|
||
const elScore = raw.el_level ? parseInt(raw.el_level) * 10 : 80;
|
||
|
||
// 代码量得分 (30%)
|
||
const codeLines = raw.code_lines || 0;
|
||
const codeScore = Math.min(100, codeLines / 100);
|
||
|
||
// 技术栈广度 (30%) - 从最近快照中推断
|
||
const recentSnapshots = portrait.recent_snapshots || [];
|
||
const uniqueModules = new Set();
|
||
recentSnapshots.forEach(s => {
|
||
if (s.raw_stats?.module) uniqueModules.add(s.raw_stats.module);
|
||
});
|
||
const breadthScore = Math.min(100, uniqueModules.size * 25);
|
||
|
||
const total = (
|
||
elScore * (factors.el_level / 100) +
|
||
codeScore * (factors.code_lines / 100) +
|
||
breadthScore * (factors.tech_breadth / 100)
|
||
);
|
||
|
||
return {
|
||
score: Math.round(total),
|
||
details: {
|
||
elScore,
|
||
codeScore,
|
||
breadthScore
|
||
}
|
||
};
|
||
}
|
||
|
||
// 维度3:SYS系统理解力计算
|
||
function calculateSYS(portrait, rules) {
|
||
const factors = rules.dimensions.SYS.factors;
|
||
const recent = portrait.recent_snapshots?.[0] || {};
|
||
|
||
// 跨模块能力 (35%)
|
||
const crossModuleScore = portrait.total_sessions > 3 ? 100 : 60;
|
||
|
||
// 系统级任务 (35%)
|
||
const systemTasksScore = recent.raw_stats?.completed_items > 3 ? 100 : 70;
|
||
|
||
// 工程思维 (30%)
|
||
const engineeringScore = recent.growth?.signals?.includes('自主修复报错') ? 100 : 80;
|
||
|
||
const total = (
|
||
crossModuleScore * (factors.cross_module / 100) +
|
||
systemTasksScore * (factors.system_tasks / 100) +
|
||
engineeringScore * (factors.engineering_mind / 100)
|
||
);
|
||
|
||
return {
|
||
score: Math.round(total),
|
||
details: {
|
||
crossModuleScore,
|
||
systemTasksScore,
|
||
engineeringScore
|
||
}
|
||
};
|
||
}
|
||
|
||
// 维度4:COL协作力计算
|
||
function calculateCOL(portrait, rules) {
|
||
const factors = rules.dimensions.COL.factors;
|
||
const recent = portrait.recent_snapshots?.[0] || {};
|
||
|
||
// SYSLOG质量 (40%)
|
||
const syslogScore = recent.will === '强烈' ? 100 : 80;
|
||
|
||
// Git协作 (30%) - 从连胜推断
|
||
const gitScore = Math.min(100, (portrait.current_streak || 0) * 20);
|
||
|
||
// 人格体协同 (30%)
|
||
const personaScore = recent.mood === '正向' ? 100 : 70;
|
||
|
||
const total = (
|
||
syslogScore * (factors.syslog_quality / 100) +
|
||
gitScore * (factors.git_collab / 100) +
|
||
personaScore * (factors.persona_sync / 100)
|
||
);
|
||
|
||
return {
|
||
score: Math.round(total),
|
||
details: {
|
||
syslogScore,
|
||
gitScore,
|
||
personaScore
|
||
}
|
||
};
|
||
}
|
||
|
||
// 维度5:INI主动性计算
|
||
function calculateINI(portrait, rules) {
|
||
const factors = rules.dimensions.INI.factors;
|
||
const recent = portrait.recent_snapshots?.[0] || {};
|
||
|
||
// 意愿信号 (40%)
|
||
const willScore = {
|
||
'强烈': 100,
|
||
'正常': 80,
|
||
'低落': 40,
|
||
'待观察': 50
|
||
}[recent.will] || 60;
|
||
|
||
// 自发行为 (30%)
|
||
const selfScore = recent.growth?.signals?.includes('自主解决问题') ? 100 : 70;
|
||
|
||
// 72h响应 (30%)
|
||
const responseScore = portrait.current_streak > 0 ? 100 : 50;
|
||
|
||
const total = (
|
||
willScore * (factors.will_signal / 100) +
|
||
selfScore * (factors.self_initiative / 100) +
|
||
responseScore * (factors.response_72h / 100)
|
||
);
|
||
|
||
return {
|
||
score: Math.round(total),
|
||
details: {
|
||
willScore,
|
||
selfScore,
|
||
responseScore
|
||
}
|
||
};
|
||
}
|
||
|
||
// 主计算函数
|
||
function calculate(devId) {
|
||
// 获取画像数据
|
||
const portrait = portraitEngine.getPortrait(devId);
|
||
if (!portrait) {
|
||
throw new Error(`开发者 ${devId} 不存在`);
|
||
}
|
||
|
||
const rules = loadRules();
|
||
|
||
// 计算各维度
|
||
const exe = calculateEXE(portrait, rules);
|
||
const tec = calculateTEC(portrait, rules);
|
||
const sys = calculateSYS(portrait, rules);
|
||
const col = calculateCOL(portrait, rules);
|
||
const ini = calculateINI(portrait, rules);
|
||
|
||
// 加权汇总
|
||
const totalScore = Math.round(
|
||
exe.score * (rules.dimensions.EXE.weight / 100) +
|
||
tec.score * (rules.dimensions.TEC.weight / 100) +
|
||
sys.score * (rules.dimensions.SYS.weight / 100) +
|
||
col.score * (rules.dimensions.COL.weight / 100) +
|
||
ini.score * (rules.dimensions.INI.weight / 100)
|
||
);
|
||
|
||
// 等级判定
|
||
let grade = 'D';
|
||
for (const [g, threshold] of Object.entries(rules.grade_thresholds)) {
|
||
if (totalScore >= threshold) {
|
||
grade = g;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 维度摘要
|
||
const summary = `EXE:${exe.score} TEC:${tec.score} SYS:${sys.score} COL:${col.score} INI:${ini.score}`;
|
||
|
||
return {
|
||
dev_id: devId,
|
||
timestamp: new Date().toISOString(),
|
||
totalScore,
|
||
grade,
|
||
dimensions: {
|
||
EXE: exe.score,
|
||
TEC: tec.score,
|
||
SYS: sys.score,
|
||
COL: col.score,
|
||
INI: ini.score
|
||
},
|
||
details: {
|
||
EXE: exe.details,
|
||
TEC: tec.details,
|
||
SYS: sys.details,
|
||
COL: col.details,
|
||
INI: ini.details
|
||
},
|
||
summary
|
||
};
|
||
}
|
||
|
||
// 便捷方法:直接计算最新画像
|
||
function calculateLatest() {
|
||
const portraits = portraitEngine.getAllPortraits();
|
||
const results = {};
|
||
|
||
for (const devId in portraits) {
|
||
results[devId] = calculate(devId);
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
module.exports = {
|
||
calculate,
|
||
calculateLatest,
|
||
calculateEXE,
|
||
calculateTEC,
|
||
calculateSYS,
|
||
calculateCOL,
|
||
calculateINI
|
||
};
|