答辩级 UI 改造:指挥台驾驶舱/证据地图/演化生命线/成长路线图/候选审核台

- 驾驶舱改'人才能力变化指挥台':变化事件流主图+可信度卡+行动卡+播报卡
- 图谱子图同心分层布局、技能变化角标、系统发现折叠入右栏、主操作收敛
- 演化页双 Tab(岗位报告/全岗位扫描)+ 能力生命线 Swimlane + 更替流
- 学习路径成长路线图:概要条/示范路线/预计投入/标记完成联动
- 新增候选审核台抽屉 + GET /api/stats/credibility
- 简历裸姓名/专硕提取修复(桌面真实简历验收通过)+ BGE-M3 加载提示
This commit is contained in:
RoyirinRosmontis
2026-08-19 12:14:39 +08:00
parent ac6d08cdf2
commit 0b198e9976
9 changed files with 1002 additions and 238 deletions
+14
View File
@@ -38,6 +38,20 @@ def test_graph_endpoint(client):
assert any(e.get("rel") == "BELONGS_TO" for e in data["edges"])
def test_credibility_stats(client):
"""数据可信度卡:有效 JD / 独立企业 / 来源类型 / 人工复核率 / 异常剔除率。"""
r = client.get("/api/stats/credibility")
assert r.status_code == 200
d = r.json()["data"]
assert d["jd_total"] >= d["jd_valid"] > 0
assert d["companies"] >= 5
assert d["sources"] >= 1 and len(d["source_names"]) == d["sources"]
assert 0.0 <= d["excluded_rate"] <= 1.0
assert d["reviewed"] >= 0 and d["pending"] >= 0
if d["review_rate"] is not None:
assert 0.0 <= d["review_rate"] <= 1.0
def test_jobs_list_and_filter(client):
r = client.get("/api/jobs")
assert r.status_code == 200
+23
View File
@@ -70,5 +70,28 @@ const heat = Array.from({ length: 30 }, (_, i) => [`20${10 + i}Q1`, i]);
eq(heat.slice(-8).length, 8, "近8季切片");
eq(heat.slice(-Infinity).length, 30, "全部范围");
/* candidateTrust:候选可信等级四指标(JD≥15 / 企业≥5 / 增长趋势 / 差异度≥0.40) */
const candidateTrust = loadFn("candidateTrust");
const stageEffortText = loadFn("stageEffortText");
const existJobs = [{ title: "Java开发工程师", required_skills: ["Java", "MySQL"], preferred_skills: ["Redis"] }];
const strong = candidateTrust({
source_count: 31, company_count: 12,
support_windows: ["2025Q1", "2025Q2", "2025Q3", "2025Q4", "2026Q1", "2026Q2"],
required_skills: ["RAG", "向量检索"], preferred_skills: [],
}, existJobs);
eq(strong.dots.length, 4, "四指标点");
eq(strong.level, "高", "四指标全达标为高");
const weak = candidateTrust({
source_count: 3, company_count: 1, support_windows: ["2026Q2"],
required_skills: ["Java", "MySQL"], preferred_skills: ["Redis"],
}, existJobs);
eq(weak.level, "低", "弱证据且与既有岗位重合为低");
eq(weak.nearest.title, "Java开发工程师", "识别出最相近既有岗位");
eq(weak.distinctness, 0, "技能完全重合时差异度为 0");
/* stageEffortText:阶段预计投入(每周约 8 小时) */
eq(stageEffortText(2), "2 周 / 约 16 小时", "阶段投入文案");
eq(stageEffortText(1), "1 周 / 约 8 小时", "单周投入文案");
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);
+14
View File
@@ -91,3 +91,17 @@ def test_resume_parse_skills_and_years():
assert profile.years == 5.0
assert set(profile.skills) == {"Java", "MySQL", "Docker"}
assert "138" not in profile.raw_masked_text
def test_resume_bare_name_and_edu_alias():
"""真实简历排版:首行裸姓名(无"姓名:"前缀)可识别并脱敏;专硕归一为硕士。"""
from app.match.resume import parse_resume
text = (" 王伟航 \n出生年月:2003年10月 现居地:辽宁大连\n"
"联系电话:19293819303 电子邮件:wwh@example.com\n"
"东北财经大学 金融科技(专硕) 2025.09-至今\n"
"职业技能:熟练掌握Python、Hadoop等数据分析技能。")
p = parse_resume(text)
assert p.name == "王**" and "王伟航" not in p.raw_masked_text # 裸姓名识别+脱敏
assert p.education == "硕士" # 专硕→硕士
assert p.address.startswith("辽宁大连")
assert "Python" in p.skills