diff --git a/README.md b/README.md index 891bb90..9d91cb8 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,22 @@ python main.py # 自动构建/加载图谱,启动服务并打开浏 ```bash python scripts/generate_dataset.py -# random.seed(2026) 固定:1375 条 JD(2019-2026,18 个岗位,15 城市,7 行业,8% 抄袭、噪声植入)、 -# 161 项技能词表(含 AIOps/微前端等新技能)、144 条测试集+标准答案、36 对人岗匹配标注、 +# random.seed(2026) 固定:1720 条 JD(2019-2026,26 个岗位,15 城市,7 行业,8% 抄袭、噪声植入)、 +# 179 项技能词表(含 AIOps/微前端/ROS/SLAM/联邦学习/零信任等新技能)、176 条测试集+标准答案、 +# 52 对人岗匹配标注、 # 7 份简历样例(txt,另附 resume_full.docx / resume_full.pdf 演示上传解析)、3 份行业报告样例、7 条审核留痕种子记录 ``` +岗位清单(26 个,对齐人工智能/大数据/智能系统/物联网等新一代信息技术领域): + +- 后端与前端:Java开发工程师、Python开发工程师、Go开发工程师、前端工程师 +- 大数据:数据工程师、大数据开发工程师、数据治理工程师 +- AI 与算法:算法工程师、机器学习工程师、NLP工程师、CV工程师、智能机器人算法工程师、隐私计算工程师 +- 智能系统与云原生:云原生工程师、智能系统架构师、边缘计算工程师 +- 物联网与嵌入式:嵌入式工程师、物联网工程师、物联网安全工程师 +- 测试/运维/分析/产品:测试开发工程师、运维工程师(SRE)、数据分析师、AI产品经理 +- 新岗位发现入库(2023 年后出现):大模型应用工程师、RAG工程师、提示词工程师 + 简历上传支持 .txt / .docx / .pdf(`POST /api/resume/parse/file`,≤5MB,解析即脱敏,临时文件自动清理)。 ## 真实数据集(data/real/,与合成集分开报告) @@ -62,18 +73,18 @@ python scripts/evaluate.py | 指标 | 实测 | 目标 | |---|---|---| -| JD 解析准确率 | 100% | ≥90% | +| JD 解析准确率 | 100% (176 条) | ≥90% | | 简历提取准确率 | 100% (7 份简历) | ≥90% | -| 人岗匹配准确率 | 100% (36 对标注) | ≥90% | -| 真实 JD(AutoWeakS)技能 F1 | 99.94% (200 条) | ≥80% | -| 真实简历(AIStudio)姓名/电话命中 | 100% / 100% (50 份) | 100% | -| 真实简历(AIStudio)技能 F1 | 100% (50 份) | — | +| 人岗匹配准确率 | 100% (52 对标注) | ≥90% | +| 真实 JD(AutoWeakS)技能 F1 | 99.80% (200 条) / 99.82% (500 条分层) | ≥80% | +| 真实简历(AIStudio)姓名/电话命中 | 100% / 100% (200 份) | 100% | +| 真实简历(AIStudio)技能 F1 | 100% (200 份) | — | ## 测试 ```bash python -m pytest tests/ --cov=app --cov-report=term-missing -# 71 passed +# 74 passed ``` 端到端冒烟(不启动服务): diff --git a/reports/metrics.json b/reports/metrics.json index f5e5baa..7676cc5 100644 --- a/reports/metrics.json +++ b/reports/metrics.json @@ -27,7 +27,7 @@ "f1": 1.0 } }, - "samples": 144, + "samples": 176, "bad_lines": 0 }, "resume_detail": { @@ -262,6 +262,102 @@ "score": 6.67, "ok": true }, + { + "job": "大数据开发工程师", + "expected": true, + "score": 75.0, + "ok": true + }, + { + "job": "大数据开发工程师", + "expected": false, + "score": 6.67, + "ok": true + }, + { + "job": "机器学习工程师", + "expected": true, + "score": 75.0, + "ok": true + }, + { + "job": "机器学习工程师", + "expected": false, + "score": 6.67, + "ok": true + }, + { + "job": "智能机器人算法工程师", + "expected": true, + "score": 75.0, + "ok": true + }, + { + "job": "智能机器人算法工程师", + "expected": false, + "score": 6.67, + "ok": true + }, + { + "job": "数据治理工程师", + "expected": true, + "score": 75.0, + "ok": true + }, + { + "job": "数据治理工程师", + "expected": false, + "score": 6.67, + "ok": true + }, + { + "job": "边缘计算工程师", + "expected": true, + "score": 75.0, + "ok": true + }, + { + "job": "边缘计算工程师", + "expected": false, + "score": 6.67, + "ok": true + }, + { + "job": "物联网安全工程师", + "expected": true, + "score": 75.0, + "ok": true + }, + { + "job": "物联网安全工程师", + "expected": false, + "score": 6.67, + "ok": true + }, + { + "job": "智能系统架构师", + "expected": true, + "score": 75.0, + "ok": true + }, + { + "job": "智能系统架构师", + "expected": false, + "score": 4.0, + "ok": true + }, + { + "job": "隐私计算工程师", + "expected": true, + "score": 75.0, + "ok": true + }, + { + "job": "隐私计算工程师", + "expected": false, + "score": 5.0, + "ok": true + }, { "job": "前端工程师", "expected": true, @@ -300,13 +396,13 @@ } ], "real_jd": { - "accuracy": 0.9995, + "accuracy": 0.9984, "title_accuracy": 1.0, "fields": { "required_skills": { - "precision": 1.0, + "precision": 0.9976, "recall": 0.9988, - "f1": 0.9994 + "f1": 0.9982 }, "preferred_skills": null, "scenarios": { @@ -319,13 +415,13 @@ "bad_lines": 0 }, "real_jd_500": { - "accuracy": 0.9991, + "accuracy": 0.9983, "title_accuracy": 1.0, "fields": { "required_skills": { - "precision": 1.0, + "precision": 0.998, "recall": 0.998, - "f1": 0.999 + "f1": 0.998 }, "preferred_skills": null, "scenarios": { diff --git a/scripts/generate_dataset.py b/scripts/generate_dataset.py index b73c306..0ae8aa4 100644 --- a/scripts/generate_dataset.py +++ b/scripts/generate_dataset.py @@ -57,10 +57,10 @@ SKILLS = [ ("Struts", "后端框架", [], "L1", ["Java"]), ("Servlet", "后端框架", [], "L1", ["Java"]), ("JSP", "后端框架", [], "L1", ["Java"]), ("SSM", "后端框架", [], "L2", ["Spring", "MyBatis"]), # 前端 - ("Vue", "前端", ["Vue.js"], "L2", ["JavaScript"]), ("React", "前端", ["React.js"], "L2", ["JavaScript"]), + ("Vue", "前端", ["Vue.js"], "L2", ["JavaScript"]), ("React", "前端", ["React.js", "ReactJS"], "L2", ["JavaScript"]), ("Angular", "前端", ["AngularJS"], "L2", ["TypeScript"]), ("jQuery", "前端", [], "L1", ["JavaScript"]), ("Webpack", "前端", [], "L2", ["JavaScript"]), ("Vite", "前端", [], "L2", ["JavaScript"]), - ("Node.js", "前端", ["Node"], "L2", ["JavaScript"]), ("小程序开发", "前端", ["微信小程序"], "L2", ["JavaScript"]), + ("Node.js", "前端", ["Node", "NodeJS"], "L2", ["JavaScript"]), ("小程序开发", "前端", ["微信小程序"], "L2", ["JavaScript"]), ("Element UI", "前端", [], "L1", ["Vue"]), ("Ant Design", "前端", [], "L1", ["React"]), ("HTML", "前端", ["HTML5"], "L1", []), ("CSS", "前端", ["CSS3"], "L1", []), ("Tailwind CSS", "前端", [], "L2", ["CSS"]), ("低代码平台", "前端", [], "L2", []), @@ -141,6 +141,28 @@ SKILLS = [ ("Tableau", "数据分析", [], "L2", ["数据可视化"]), ("Power BI", "数据分析", ["PowerBI"], "L2", ["数据可视化"]), ("SPSS", "数据分析", [], "L2", []), ("数据可视化", "数据分析", [], "L2", ["数据分析"]), ("A/B测试", "数据分析", ["AB测试"], "L2", ["数据分析"]), + # 机器人与智能系统(岗位覆盖扩展) + ("ROS", "AI算法", ["机器人操作系统"], "L2", ["C++", "Linux"]), + ("SLAM", "AI算法", ["同步定位与建图"], "L3", ["计算机视觉"]), + ("路径规划", "AI算法", [], "L3", ["机器学习"]), + # 数据治理 + ("数据治理", "大数据", [], "L2", ["SQL"]), + ("数据脱敏", "大数据", ["数据遮蔽"], "L2", ["SQL"]), + ("元数据管理", "大数据", ["元数据"], "L2", ["数据治理"]), + ("数据血缘", "大数据", [], "L3", ["元数据管理"]), + ("湖仓一体", "大数据", ["实时湖仓"], "L3", ["Hive"]), + # 边缘计算 + ("边缘推理", "物联网", ["边缘推理框架"], "L3", ["边缘计算", "深度学习"]), + ("TensorRT", "AI算法", [], "L3", ["深度学习"]), + ("KubeEdge", "云原生", [], "L3", ["Kubernetes", "边缘计算"]), + # 安全与隐私计算 + ("零信任", "安全", ["零信任架构"], "L3", []), + ("入侵检测", "安全", [], "L2", ["Linux"]), + ("等保合规", "安全", [], "L2", []), + ("联邦学习", "AI算法", [], "L3", ["机器学习"]), + ("多方安全计算", "安全", ["MPC"], "L3", []), + ("同态加密", "安全", [], "L3", []), + ("差分隐私", "安全", [], "L3", ["机器学习"]), ] SCENARIOS = ["电商", "金融", "物流", "社交", "教育", "医疗", "政务", "游戏", @@ -152,11 +174,11 @@ INDUSTRIES = ["互联网/软件", "人工智能", "金融科技", "智能制造" # ---------------------------------------------------------------- 岗位原型 def arc(title, category, years, salary, scenarios, industries, - duty_tmpls, duty_objs, timeline, volumes): + duty_tmpls, duty_objs, timeline, volumes, test_n=8): return dict(title=title, category=category, years=years, salary=salary, scenarios=scenarios, industries=industries, duty_tmpls=duty_tmpls, duty_objs=duty_objs, - timeline=timeline, volumes=volumes) + timeline=timeline, volumes=volumes, test_n=test_n) BACKEND_DUTIES = ["负责{o}的设计与开发", "参与{o}的性能优化与重构", "配合产品团队完成{o}的需求评审", "编写{o}的技术文档与接口文档", "承担{o}的线上问题排查与修复", "推动{o}的代码评审与质量改进"] @@ -325,6 +347,69 @@ ARCHETYPES = [ ["营销文案生成", "智能客服话术", "内容创作", "教学辅导", "商品描述", "对话流程"], [(2024, 2026, ["提示词工程", "大模型应用开发", "Python", "数据分析"], ["LangChain", "RAG", "AI产品"])], {2024: 2, 2025: 6, 2026: 10}), + # —— 岗位覆盖扩展(8 个既有岗位族,2019 年前已存在,支撑赛题领域覆盖) —— + # 大数据开发:2019-2021 Hadoop 离线系 → 2022+ Flink 实时/湖仓一体(演化信号) + arc("大数据开发工程师", "大数据", 3, "16K-32K", ["电商", "金融", "短视频"], + ["互联网/软件", "金融科技"], + ["负责{o}的离线计算任务开发", "搭建{o}的实时计算链路", "参与{o}的数据模型设计", + "优化{o}的任务性能与资源消耗", "维护{o}的调度依赖与数据质量"], + ["离线数仓", "实时数仓", "用户行为日志", "流量数据", "交易数据", "数据集市"], + [(2019, 2021, ["SQL", "Hadoop", "Hive", "Spark", "HDFS"], ["Sqoop", "HBase", "Scala"]), + (2022, 2026, ["SQL", "Spark", "Flink", "Kafka", "Hive"], ["ClickHouse", "Doris", "湖仓一体"])], + {2019: 4, 2020: 4, 2021: 4, 2022: 5, 2023: 5, 2024: 6, 2025: 6, 2026: 6}, test_n=4), + # 机器学习工程师:2023+ 加分项出现"模型微调/大模型应用开发"(演化信号) + arc("机器学习工程师", "AI算法", 3, "18K-35K", ["金融", "电商", "广告"], + ["人工智能"], + ["负责{o}的特征工程与模型训练", "参与{o}的离线评估与线上实验", "优化{o}的推理性能与资源占用", + "建设{o}的样本与特征流水线", "跟进{o}的算法迭代与复盘"], + ["风控模型", "搜索排序", "召回策略", "销量预测", "异常检测", "用户分层"], + [(2019, 2022, ["Python", "机器学习", "Scikit-learn", "SQL", "XGBoost"], ["LightGBM", "TensorFlow", "Spark"]), + (2023, 2026, ["Python", "机器学习", "深度学习", "PyTorch", "XGBoost"], ["模型微调", "大模型应用开发", "LightGBM"])], + {2019: 4, 2020: 4, 2021: 4, 2022: 5, 2023: 5, 2024: 6, 2025: 6, 2026: 6}, test_n=4), + arc("智能机器人算法工程师", "AI算法", 3, "20K-40K", ["智能制造", "物流", "出行"], + ["人工智能", "智能制造"], + ["负责{o}的算法研发与调优", "参与{o}的仿真验证与实机部署", "优化{o}的实时性与鲁棒性", + "建设{o}的数据闭环与评测体系", "跟进{o}的前沿方案并落地"], + ["SLAM 建图", "定位导航", "运动规划", "多传感器融合", "机械臂控制", "避障策略"], + [(2019, 2026, ["C++", "Python", "ROS", "SLAM", "路径规划"], ["深度学习", "强化学习", "OpenCV"])], + {2019: 4, 2020: 4, 2021: 4, 2022: 5, 2023: 5, 2024: 6, 2025: 6, 2026: 6}, test_n=4), + arc("数据治理工程师", "大数据", 3, "15K-30K", ["金融", "政务", "医疗"], + ["金融科技", "互联网/软件"], + ["负责{o}的标准制定与落地", "建设{o}的元数据与血缘体系", "推进{o}的质量监控与整改", + "制定{o}的分级分类与脱敏策略", "组织{o}的盘点与确权工作"], + ["数据资产", "主数据", "指标口径", "敏感数据", "数据目录", "质量规则"], + [(2019, 2026, ["SQL", "数据治理", "元数据管理", "数据脱敏", "Python"], ["数据血缘", "Hive", "Spark"])], + {2019: 4, 2020: 4, 2021: 4, 2022: 5, 2023: 5, 2024: 6, 2025: 6, 2026: 6}, test_n=4), + arc("边缘计算工程师", "物联网", 3, "16K-30K", ["智能制造", "安防", "物流"], + ["物联网", "智能制造"], + ["负责{o}的部署与调优", "参与{o}的模型压缩与推理加速", "建设{o}的云边协同链路", + "优化{o}的资源占用与稳定性", "编写{o}的技术文档"], + ["边缘网关", "边缘节点", "端侧推理", "视频分析", "设备接入", "边云协同"], + [(2019, 2021, ["C++", "Python", "Linux", "MQTT", "嵌入式开发"], ["Docker", "边缘计算", "OpenCV"]), + (2022, 2026, ["Python", "边缘计算", "Docker", "Linux", "MQTT"], ["边缘推理", "TensorRT", "KubeEdge"])], + {2019: 4, 2020: 4, 2021: 4, 2022: 5, 2023: 5, 2024: 6, 2025: 6, 2026: 6}, test_n=4), + arc("物联网安全工程师", "物联网", 3, "16K-32K", ["智能制造", "安防", "政务"], + ["物联网", "智慧城市"], + ["负责{o}的安全评估与加固", "建设{o}的入侵检测与响应体系", "参与{o}的固件安全审计", + "制定{o}的接入认证策略", "跟踪{o}的漏洞情报与处置"], + ["设备接入", "设备网络", "边缘网关", "固件", "通信协议", "密钥体系"], + [(2019, 2026, ["Linux", "Python", "入侵检测", "零信任", "MQTT"], ["等保合规", "嵌入式开发", "云计算"])], + {2019: 4, 2020: 4, 2021: 4, 2022: 5, 2023: 5, 2024: 6, 2025: 6, 2026: 6}, test_n=4), + arc("智能系统架构师", "云原生", 5, "30K-60K·14薪", ["金融", "政务", "智能制造"], + ["互联网/软件", "人工智能"], + ["负责{o}的整体架构设计", "主导{o}的技术选型与评审", "制定{o}的容量与容灾方案", + "推动{o}的架构演进与落地", "沉淀{o}的架构规范与最佳实践"], + ["智能平台", "业务中台", "云边端协同", "高可用体系", "多云架构", "技术路线"], + [(2019, 2021, ["分布式系统", "微服务", "云计算", "高并发", "Linux"], ["Docker", "Kubernetes", "Service Mesh"]), + (2022, 2026, ["分布式系统", "微服务", "Kubernetes", "云计算", "高并发"], ["Service Mesh", "边缘计算", "物联网平台"])], + {2019: 4, 2020: 4, 2021: 4, 2022: 5, 2023: 5, 2024: 6, 2025: 6, 2026: 6}, test_n=4), + arc("隐私计算工程师", "AI算法", 4, "25K-45K", ["金融", "政务", "医疗"], + ["人工智能", "金融科技"], + ["负责{o}的方案设计与实现", "参与{o}的性能优化与工程化", "建设{o}的协同计算链路", + "评估{o}的安全性与合规性", "跟进{o}的最新研究成果"], + ["联邦建模", "多方联合统计", "安全求交", "数据协作", "密文计算", "隐私保护"], + [(2019, 2026, ["Python", "机器学习", "联邦学习", "同态加密", "多方安全计算"], ["差分隐私", "模型微调", "Spark"])], + {2019: 4, 2020: 4, 2021: 4, 2022: 5, 2023: 5, 2024: 6, 2025: 6, 2026: 6}, test_n=4), ] COMPANY_PRE = ["星澜", "云启", "数联", "智汇", "蓝湾", "极光", "恒宇", "锐思", "博远", "辰安", @@ -779,8 +864,8 @@ def main() -> None: # 5. 测试集(与 raw 不同 jid 命名空间)+ 标准答案 testset, gold = [], {} tseq = 0 - per_arc = 8 # 18 岗位 × 8 = 144 条 >= 120 for a in ARCHETYPES: + per_arc = a["test_n"] # 原 18 岗位 × 8 + 扩展 8 岗位 × 4 = 176 条 >= 120 years = sorted(a["volumes"]) for _ in range(per_arc): tseq += 1 diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..5cc1e50 --- /dev/null +++ b/start.bat @@ -0,0 +1,19 @@ +@echo off +chcp 65001 >nul +REM ============================================================ +REM CareerNexus 职联 启动脚本(Windows) +REM 直接通过项目内 .venv 启动服务,自动打开浏览器 +REM 首次使用请先运行 init.bat +REM ============================================================ +cd /d "%~dp0" + +if not exist ".venv\Scripts\python.exe" ( + echo [错误] 未找到 .venv,请先运行 init.bat 完成初始化 + pause + exit /b 1 +) + +echo 启动 CareerNexus 职联 ... +echo 服务地址 http://127.0.0.1:9008 (关闭本窗口即停止服务) +.venv\Scripts\python.exe main.py +pause diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..f5910f3 --- /dev/null +++ b/start.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# ============================================================ +# CareerNexus 职联 启动脚本(Linux / macOS / Git Bash) +# 直接通过项目内 .venv 启动服务,自动打开浏览器 +# 首次使用请先运行 bash init.sh +# ============================================================ +set -e +cd "$(dirname "$0")" + +# Windows venv 与 POSIX venv 路径兼容 +if [ -f ".venv/bin/python" ]; then + VENV_PY=.venv/bin/python +elif [ -f ".venv/Scripts/python.exe" ]; then + VENV_PY=.venv/Scripts/python.exe +else + echo "[错误] 未找到 .venv,请先运行 bash init.sh 完成初始化" + exit 1 +fi + +echo "启动 CareerNexus 职联 ..." +echo "服务地址 http://127.0.0.1:9008 (Ctrl+C 停止服务)" +exec "$VENV_PY" main.py diff --git a/webui/graph.js b/webui/graph.js index 0634ac7..0ab3882 100644 --- a/webui/graph.js +++ b/webui/graph.js @@ -14,6 +14,7 @@ filters: { types: { Job: true, Skill: true, Industry: true, Scenario: true }, category: "", level: "", edges: { required: true, preferred: true, applies: true, belongs: true } }, selected: null, // 选中节点 id + soloJob: null, // 岗位子图模式:只看该岗位及其一跳邻居 hover: null, navStack: [], // 详情面板导航栈 drag: null, // {mode:'pan'|'node', ...} @@ -161,6 +162,9 @@ } function visible(n) { if (!G.filters.types[n.type]) return false; + if (G.soloJob) { // 岗位子图:只保留该岗位与一跳邻居 + if (n.id !== G.soloJob && !neighborsOf(G.soloJob).has(n.id)) return false; + } if (n.type === "Job") { if (G.filters.category && n.category !== G.filters.category) return false; if (G.filters.level && jobLevel(n) !== G.filters.level) return false; @@ -597,6 +601,18 @@ } CN.gotoNode = locateNode; + function updateSoloBadge() { + const badge = $("#solo-badge"); + if (!badge) return; + badge.classList.toggle("hidden", !G.soloJob); + if (G.soloJob) { + const n = G.nodes[G.idx[G.soloJob]]; + $("#solo-badge-text").textContent = `岗位子图:${(n && (n.title || n.id)) || G.soloJob}`; + } + } + const soloExit = $("#solo-exit"); + if (soloExit) soloExit.onclick = () => { G.soloJob = null; updateSoloBadge(); requestDraw(); }; + /* ================= 备注(localStorage) ================= */ const getNotes = () => JSON.parse(localStorage.getItem(NOTES_KEY) || "{}"); function nodeNotes(id) { return getNotes()[id] || []; } @@ -720,6 +736,7 @@ `} ${n.type === "Job" && !isPending ? ` + ` : ""} @@ -765,6 +782,13 @@ $("#evo-job-select").value = id; $("#evo-job-select").dispatchEvent(new Event("change")); }; + const soloBtn = $("#dt-solo"); + if (soloBtn) soloBtn.onclick = () => { + G.soloJob = G.soloJob === id ? null : id; + updateSoloBadge(); + requestDraw(); + if (G.soloJob) toast(`已进入「${n.title || id}」岗位子图(只看该岗位与一跳技能/场景/行业)`, "success"); + }; const learnBtn = $("#dt-learn"); if (learnBtn) learnBtn.onclick = () => { CN.switchView("path"); diff --git a/webui/index.html b/webui/index.html index 41ffce4..f12c94d 100644 --- a/webui/index.html +++ b/webui/index.html @@ -135,6 +135,12 @@