- 全链路:多源JD接入/清洗交叉验证/结构化抽取(星火+离线双通道)/时序知识图谱/新岗位发现/演化分析/人岗匹配/学习路径 - WebUI 五视图:驾驶舱/图谱工作台/岗位演化/匹配(单人+批量)/学习路径 - 设置中心(config.json)/Token鉴权/SQLite状态库/语义向量通道(BGE-M3本地或联网API) - 71 个 pytest 用例;数据目录仅保留结构(.gitkeep),数据由 scripts/generate_dataset.py 复现
213 lines
12 KiB
Markdown
213 lines
12 KiB
Markdown
# CareerNexus 职联
|
||
|
||
多源异构数据驱动的**岗位与能力图谱构建与动态演化分析**平台 —— 挑战杯“揭榜挂帅”赛题 XH-202621 参赛作品。
|
||
|
||
核心链路:多源 JD 接入 → 清洗交叉验证(时滞/噪声/抄袭)→ 结构化抽取(离线规则 / 星火大模型双通道,JSON Schema 强校验)→ 时序知识图谱(Temporal KG)→ 新岗位发现与能力演化分析 → 简历解析、人岗匹配、差距分析与学习路径。
|
||
|
||
## 环境准备
|
||
|
||
```bash
|
||
python -m venv .venv
|
||
# Windows: .venv\Scripts\activate Linux: source .venv/bin/activate
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
## 一键启动
|
||
|
||
```bash
|
||
python main.py # 自动构建/加载图谱,启动服务并打开浏览器
|
||
# 浏览器访问 http://127.0.0.1:9008
|
||
```
|
||
|
||
## 系统设置(WebUI 右上角 ⚙)
|
||
|
||
设置保存在 `config.json`(优先级高于 config.yaml),包含:
|
||
|
||
- **服务监听**:`127.0.0.1`(仅本机)或 `0.0.0.0`(对外开放)+ 端口,修改后需重启(面板提供"保存并重启");
|
||
- **访问令牌**:设置后 `/api/*` 需携带 `X-Access-Token` 头(health 与只读 GET /api/config 豁免);
|
||
- **LLM API**:星火/OpenAI 兼容的 base_url、api_key、model,保存即热生效——配置后抽取走大模型通道,未配置时显式回退离线确定性抽取,前端有醒目的"离线回退模式"横幅提示。
|
||
|
||
## 数据生成(已随工程提交,可复现重建)
|
||
|
||
```bash
|
||
python scripts/generate_dataset.py
|
||
# random.seed(2026) 固定:1375 条 JD(2019-2026,18 个岗位,15 城市,7 行业,8% 抄袭、噪声植入)、
|
||
# 161 项技能词表(含 AIOps/微前端等新技能)、144 条测试集+标准答案、36 对人岗匹配标注、
|
||
# 7 份简历样例(txt,另附 resume_full.docx / resume_full.pdf 演示上传解析)、3 份行业报告样例、7 条审核留痕种子记录
|
||
```
|
||
|
||
简历上传支持 .txt / .docx / .pdf(`POST /api/resume/parse/file`,≤5MB,解析即脱敏,临时文件自动清理)。
|
||
|
||
## 真实数据集(data/real/,与合成集分开报告)
|
||
|
||
```bash
|
||
python scripts/build_real_dataset.py
|
||
# random.seed(2026) 固定,从 datasets/XH-202621/data/(只读)加工:
|
||
# autoweaks_jd/ 200 条 AutoWeakS 真实 JD(腾讯160/京东40,去分词+技术岗过滤)+ silver gold(初标+抽检30条)
|
||
# aistudio_resumes/ 50 份 AIStudio CC0 简历(保留 PII 验证脱敏链路)+ 字段级 gold(抽检10条)
|
||
# batch_demo_resumes/ 12 份 chinese_resume 渲染后经 mask_text 脱敏的批量演示素材(原始 PII 不落盘)
|
||
# esco_reference/ ISCO/ESCO 职业分类锚点(4 个关键 labels 文件)
|
||
```
|
||
|
||
来源/授权/处理口径/抽检一致率详见 `data/real/README.md`。
|
||
|
||
## 指标复现
|
||
|
||
```bash
|
||
python scripts/evaluate.py
|
||
# 输出 reports/metrics.json 与控制台报告
|
||
```
|
||
|
||
实测(从文本重新抽取再与 gold 比对,非硬编码):
|
||
|
||
| 指标 | 实测 | 目标 |
|
||
|---|---|---|
|
||
| JD 解析准确率 | 100% | ≥90% |
|
||
| 简历提取准确率 | 100% (7 份简历) | ≥90% |
|
||
| 人岗匹配准确率 | 100% (36 对标注) | ≥90% |
|
||
| 真实 JD(AutoWeakS)技能 F1 | 99.94% (200 条) | ≥80% |
|
||
| 真实简历(AIStudio)姓名/电话命中 | 100% / 100% (50 份) | 100% |
|
||
| 真实简历(AIStudio)技能 F1 | 100% (50 份) | — |
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
python -m pytest tests/ --cov=app --cov-report=term-missing
|
||
# 71 passed
|
||
```
|
||
|
||
端到端冒烟(不启动服务):
|
||
|
||
```bash
|
||
python scripts/run_demo.py
|
||
```
|
||
|
||
## Docker
|
||
|
||
```bash
|
||
docker build -t careernexus .
|
||
docker run -p 9008:9008 careernexus
|
||
# 或 docker compose up(可选 Neo4j 增强见下节,默认不启动)
|
||
```
|
||
|
||
## 可选高级组件
|
||
|
||
### SQLite 应用状态库(默认启用,零配置)
|
||
|
||
审核留痕、待审核候选、异步任务状态统一存储在 SQLite(`data/careernexus.db`,
|
||
配置键 `state_db_path`),仅用标准库 sqlite3、**零新依赖**:WAL 模式、幂等建表,
|
||
服务启动时自动把旧的 `data/review/audit.jsonl` / `data/review/pending.json`
|
||
一次性迁移入库(原文件保留不删除)。表结构:`audit_log`(留痕)、
|
||
`review_candidates`(待审核候选)、`tasks`(异步任务,重启后可恢复查询)、
|
||
`job_overrides`(人工状态覆写层,见下节)。
|
||
|
||
### 人工状态持久化(跨图谱重建)
|
||
|
||
`job_overrides` 表保存两类人工操作:审核通过的岗位定义(含支撑 JD 来源与时间窗)、
|
||
图谱节点编辑。审核 `accept` 时用候选簇的真实统计入库——REQUIRES 边的 `intervals`
|
||
自首个支持窗口起、`sources` 为真实支撑 JD 的 source_id 列表,并补
|
||
`(Job)-[:APPLIES_TO]->(Scenario)` / `(Job)-[:BELONGS_TO]->(Industry)` 边与
|
||
"新增"演化事件;节点编辑同步 `kg.jobs` 的 JobDefinition(title/category/级别,
|
||
级别编辑同步回期望年限)。pipeline 全量重建图谱后自动重放覆写层:人工入库岗位
|
||
**强制纳入正式图谱**(不受"2023 年前出现"规则限制),节点编辑一并恢复;
|
||
候选被驳回时相应覆写记录自动清理。
|
||
|
||
### JD 手动导入
|
||
|
||
`POST /api/jd/import` 支持三种用法(均触发异步 pipeline 重建,任务 ID + 轮询):
|
||
|
||
- **文件上传**:multipart 表单 `file` 字段,支持 `.jsonl` / `.csv` / `.txt`
|
||
(UTF-8,≤10MB;CSV 需含 `jid,job_title,company,publish_date,text` 表头);
|
||
- **文本粘贴**:JSON 请求体 `{"text": "..."}`,每行一条 JSON 记录;
|
||
- **空请求**:仅触发全量重建(旧行为)。
|
||
|
||
逐行校验:合格记录写入 `data/jd_raw/import_YYYYmmdd_HHMMSS.jsonl`
|
||
(source_id 按 `文件名#行号` 重新分配,保留来源标记);坏行进入响应的
|
||
`bad_lines` 清单并写入 `logs/import_bad_时间戳.jsonl`。响应含
|
||
`imported / skipped / bad_lines / file / task_id`。WebUI 驾驶舱"导入 JD 数据"
|
||
按钮提供上传/粘贴对话框。
|
||
|
||
### Neo4j 图数据库(profile: advanced)
|
||
|
||
平台默认使用内置 TemporalKG + JSON 快照即可完整运行;需要企业级图库时可选启用:
|
||
|
||
```bash
|
||
pip install neo4j # 官方驱动为可选依赖
|
||
NEO4J_PASSWORD=你的密码 docker compose --profile advanced up -d neo4j
|
||
python scripts/sync_neo4j.py # MERGE 幂等同步 data/graph.json
|
||
python scripts/sync_neo4j.py --dry-run # 只打印节点/边统计,不连接
|
||
# 浏览器打开 http://localhost:7474 查看(Bolt 端口 7687)
|
||
```
|
||
|
||
### 为什么 BGE-M3 / FAISS / reranker / GLiNER / PaddleOCR 不做进容器
|
||
|
||
这些组件定位为**生产部署的可选增强,而非竞赛交付**:
|
||
|
||
- **模型权重 GB 级**(BGE-M3、reranker、GLiNER、PaddleOCR 均为深度模型),
|
||
镜像体积膨胀到数 GB,构建与分发成本高;
|
||
- **评审环境离线**,容器内无法下载模型权重,打进镜像又会进一步放大体积;
|
||
- 与赛题要求的**“普通笔记本 2 分钟启动”**目标直接冲突——默认链路用纯 Python
|
||
TF-IDF / 规则抽取即可达到实测指标(见上表),深度模型只在生产侧追求
|
||
长尾召回/版式鲁棒性时才有增量价值。
|
||
|
||
因此交付物默认链路保持轻量确定性实现,上述模型以接口预留方式作为生产增强路线。
|
||
|
||
## 大模型抽取通道(可选)
|
||
|
||
默认 `mode: demo` 全程离线,使用离线确定性抽取器。配置密钥并改 `mode: live` 即启用星火/OpenAI 兼容通道(JSON 模式、Schema 校验、失败重试 2 次、超时自动降级离线):
|
||
|
||
```bash
|
||
cp .env.example .env # 填入 SPARK_API_KEY(密钥只从环境变量读取)
|
||
```
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
├── main.py # 唯一入口:加载配置→构建/加载图谱→启动服务→自动开浏览器
|
||
├── config.yaml # 端口/数据路径/运行模式 demo|live/匹配权重等
|
||
├── app/
|
||
│ ├── models.py # pydantic v2 全链路模型
|
||
│ ├── ingest/loader.py # jsonl 接入,source_id=文件#行号,坏行错误清单
|
||
│ ├── cleaning/ # MinHash(128哈希,5-gram)+LSH 近重复、噪声过滤、exp(-λ·age) 时间衰减
|
||
│ ├── extract/ # Extractor 协议+多源投票(base) / 离线规则(offline) / 大模型(llm) / 提示词(prompts)
|
||
│ ├── graph/tkg.py # TemporalKG:时间区间 + 事件追加不覆盖 + snapshot 切片 + JSON 持久化
|
||
│ ├── discover/cluster.py # TF-IDF+凝聚聚类新岗位发现 + 待审核分区 + 审核留痕
|
||
│ ├── storage/db.py # SQLite 应用状态库:audit_log/review_candidates/tasks/job_overrides + 旧文件一次性迁移
|
||
│ ├── evolution/analyzer.py# 季度窗口聚合,新增/增强/减弱/退出事件 + 中文说明
|
||
│ ├── match/ # 简历解析脱敏(resume) / 四维加权匹配+学习路径(engine)
|
||
│ ├── services/ # pipeline 端到端编排 / overrides 人工状态覆写层(跨重建重放)
|
||
│ └── api/routes.py # FastAPI:统一响应{code,message,data,request_id},任务ID+轮询
|
||
├── webui/ # 原生 HTML/JS 单页应用(无构建、完全离线):驾驶舱/图谱工作台/演化/匹配/学习路径
|
||
│ ├── index.html # 侧栏+顶栏外壳与五视图结构
|
||
│ ├── style.css # 设计系统(token/按钮/卡片/弹窗/表格等)
|
||
│ ├── app.js # 公共库 + 驾驶舱/演化/匹配/学习路径
|
||
│ └── graph.js # 图谱工作台:力导向画布/小地图/详情面板/编辑/备注/审核
|
||
├── scripts/ # 数据生成、真实数据集构建(build_real_dataset)、指标复现、端到端冒烟、sync_neo4j(可选 Neo4j 同步)
|
||
├── tests/ # pytest:cleaning/extract/tkg/evolution/match/api/real_data
|
||
└── data/ # 词表、JD 原始数据、测试集+gold、简历、报告、图谱快照、real/(真实公开数据集,见其 README)
|
||
```
|
||
|
||
## 人机协同审核(防“幻觉”)
|
||
|
||
新岗位簇与低置信抽取不直接入库:先进入“待审核”分区(SQLite 状态库 `review_candidates` 表),
|
||
人工 `accept / modify / reject` 后才写入正式图谱,全程留痕(操作人/时间/before/after/理由,
|
||
`audit_log` 表)。抽取结果每个字段附原文证据片段供核对。
|
||
|
||
## WebUI 图谱工作台与节点编辑
|
||
|
||
全景图谱页为“岗位能力图谱工作台”:canvas 力导向(缩放/平移/拖拽节点/小地图/筛选/关系标签开关),
|
||
节点类型覆盖 岗位/技能/行业/场景((Job)-[:BELONGS_TO]->(Industry) 行业归属边,可按类型筛选),
|
||
点击节点打开右侧详情面板(基础信息/关联跳转/证据来源/演化 sparkline/备注/审核留痕),
|
||
待审核候选以虚线节点并入画布,可直接确认/驳回。
|
||
|
||
配套接口(统一响应格式,全部留痕):
|
||
|
||
| 接口 | 说明 |
|
||
|---|---|
|
||
| `POST /api/graph/node/update` | 编辑节点展示属性(title/level/importance/weight 白名单字段),写图谱并在状态库 audit_log 表追加 `kind=node_update` 留痕;Job 节点同步 JobDefinition,编辑写入 job_overrides 覆写层(重建后自动重放);只改属性不动结构 |
|
||
| `GET /api/jd?sid=<文件#行号>` | 按 source_id 回查 JD 原文(证据来源弹窗),文件名/行号强校验防路径穿越 |
|
||
| `GET /api/audit?node_id=&limit=` | 查询审核/编辑留痕(新→旧),可按节点过滤 |
|
||
| `GET /api/graph` | 追加返回 `events`(演化事件列表),供前端趋势图与智能建议 |
|
||
|
||
节点备注保存在浏览器 localStorage(`cn_notes`),图谱视图布局保存在 `cn_graph_view`,均为纯前端持久化。
|