图谱工作台功能扩充:职级视图改为岗位能力树(L1→L2→L3成长阶梯)、新增图谱搜索定位、岗位对比模式(三色交集/差集+转岗就绪度)、时序视图季度解说面板,纯函数单测83例全过

This commit is contained in:
FinStab
2026-08-28 11:44:50 +08:00
parent 3058fc8aca
commit 6d91df1a91
4 changed files with 597 additions and 47 deletions
+52
View File
@@ -183,5 +183,57 @@ eq(gf.levelBandOf({ type: "Skill", level: "L1" }), 0, "L1技能入带0");
eq(gf.levelBandOf({ type: "Skill", level: "L3" }), 2, "L3技能入带2");
eq(gf.levelBandOf({ type: "Skill" }), 1, "无级别技能默认中级带");
/* graph.js 纯逻辑:搜索排序 / 岗位对比 / 时序季度摘要 */
const gf2 = loadGraphFns(["searchRank", "compareJobs", "windowDigest"]);
const SN = [
{ id: "job_Java开发工程师", type: "Job", title: "Java开发工程师" },
{ id: "job_Java架构师", type: "Job", title: "Java架构师" },
{ id: "JavaScript", type: "Skill", title: "JavaScript" },
{ id: "Docker", type: "Skill", title: "Docker" },
];
eq(gf2.searchRank(SN, "").length, 0, "搜索:空串无结果");
eq(gf2.searchRank(SN, "架构").map((n) => n.id).join(","), "job_Java架构师", "搜索:中文子串命中");
eq(gf2.searchRank(SN, "javascript")[0].id, "JavaScript", "搜索:大小写不敏感");
eq(gf2.searchRank(SN, "java")[0].type, "Job", "搜索:并列时岗位优先于技能");
const CE = [
{ source: "A", target: "Java", rel: "REQUIRES", importance: "required" },
{ source: "A", target: "MySQL", rel: "REQUIRES", importance: "required" },
{ source: "B", target: "Java", rel: "REQUIRES", importance: "required" },
{ source: "B", target: "Go", rel: "REQUIRES", importance: "required" },
{ source: "B", target: "K8s", rel: "REQUIRES", importance: "preferred" },
{ source: "A", target: "X场景", rel: "APPLIES_TO" },
{ source: "B", target: "Y技能", rel: "REQUIRES", importance: "required", pending: true },
];
const cmp = gf2.compareJobs(CE, "A", "B");
eq(cmp.shared.map((x) => x.skill).join(","), "Java", "对比:共有技能(忽略非技能关系与待审核边)");
eq(cmp.onlyA.map((x) => x.skill).join(","), "MySQL", "对比:A 独有");
eq(cmp.onlyB.map((x) => x.skill).join(","), "Go,K8s", "对比:B 独有");
eq(cmp.gaps.join(","), "Go", "对比:转岗必备缺口只算 required");
eq(cmp.bReq, 2, "对比:目标必备总数");
eq(cmp.covered, 1, "对比:必备已覆盖数");
eq(cmp.ready, 0.5, "对比:就绪度=覆盖/必备");
const WE = [
{ source: "job_A", target: "Java", intervals: [["2025Q1", null]] },
{ source: "job_A", target: "K8s", intervals: [["2026Q1", null]] },
{ source: "job_B", target: "Go", intervals: [["2026Q1", null]] },
];
const WEV = [
{ window: "2026Q1", change_type: "新增", skill: "K8s", job_id: "job_A" },
{ window: "2026Q1", change_type: "退出", skill: "JSP", job_id: "job_A" },
{ window: "2025Q4", change_type: "增强", skill: "Java", job_id: "job_A" },
];
const WN = [
{ id: "job_A", type: "Job", title: "A工程师" },
{ id: "job_B", type: "Job", title: "B工程师" },
];
const dg = gf2.windowDigest(WE, WEV, WN, "2026Q1");
eq(dg.newEdges.length, 2, "摘要:当季新增关系数");
eq(dg.newJobs.join(","), "B工程师", "摘要:当季新晋岗位按首次出现窗口判定");
eq(dg.added.length, 1, "摘要:当季新增事件");
eq(dg.exited.length, 1, "摘要:当季退出事件");
eq(dg.boosted.length, 0, "摘要:非当季事件不计入");
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);