CareerNexus 职联:岗位能力图谱构建与动态演化分析平台(挑战杯 XH-202621)

- 全链路:多源JD接入/清洗交叉验证/结构化抽取(星火+离线双通道)/时序知识图谱/新岗位发现/演化分析/人岗匹配/学习路径
- WebUI 五视图:驾驶舱/图谱工作台/岗位演化/匹配(单人+批量)/学习路径
- 设置中心(config.json)/Token鉴权/SQLite状态库/语义向量通道(BGE-M3本地或联网API)
- 71 个 pytest 用例;数据目录仅保留结构(.gitkeep),数据由 scripts/generate_dataset.py 复现
This commit is contained in:
RoyirinRosmontis
2026-08-03 13:15:35 +08:00
commit a10442d78c
78 changed files with 12860 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
"""CareerNexus 全链路数据模型(pydantic v2)。
覆盖:JD 原始记录、结构化抽取结果、岗位定义、图谱观察、
变更事件、人岗匹配结果、简历画像与人机协同审核记录。
"""
from __future__ import annotations
from datetime import date
from typing import Literal, Optional
from pydantic import BaseModel, Field
class JDRecord(BaseModel):
"""一条招聘 JD 原始记录。source_id 由接入层分配(文件名#行号),不可变。"""
jid: str
job_title: str
company: str
city: str = ""
salary: str = ""
industry: str = "" # 招聘平台标注的所属行业
publish_date: date
source: str = ""
url: str = ""
text: str
source_id: str = "" # 形如 batch_2023.jsonl#17
weight: float = 1.0 # 清洗阶段赋予的时间衰减权重
is_duplicate: bool = False # 近重复(抄袭)降权标记
dup_of: Optional[str] = None # 指向被保留的最早来源 jid
is_noise: bool = False # 噪声(广告/无关条款)标记
class ExtractionResult(BaseModel):
"""从单条 JD 文本抽取出的结构化岗位定义。"""
jid: str = ""
job_title: str = ""
responsibilities: list[str] = Field(default_factory=list)
required_skills: list[str] = Field(default_factory=list)
preferred_skills: list[str] = Field(default_factory=list)
scenarios: list[str] = Field(default_factory=list)
evidence: dict[str, str] = Field(default_factory=dict) # 字段 -> 证据片段
confidence: float = 0.0
extractor: str = "offline"
prompt_version: str = "offline-v1"
class JobDefinition(BaseModel):
"""聚合后的岗位定义(图谱中的 Job 节点属性)。"""
job_id: str
title: str
category: str = ""
industries: list[str] = Field(default_factory=list) # 归属行业(1-2 个)
required_skills: list[str] = Field(default_factory=list)
preferred_skills: list[str] = Field(default_factory=list)
responsibilities: list[str] = Field(default_factory=list)
scenarios: list[str] = Field(default_factory=list)
expected_years: int = 3
status: Literal["active", "pending", "rejected"] = "active"
source_count: int = 0 # 支撑 JD 数
company_count: int = 0 # 独立企业数
# 候选新岗位的支撑证据(新岗位发现时填写):支撑 JD 的 source_id 与出现季度窗口,
# 审核入库时用于生成真实的 REQUIRES 区间、来源列表与演化事件。
support_source_ids: list[str] = Field(default_factory=list)
support_windows: list[str] = Field(default_factory=list)
class GraphObservation(BaseModel):
"""一次岗位-技能关系观察(带时间窗,追加而不覆盖)。"""
job_id: str
skill: str
importance: Literal["required", "preferred"]
weight: float = 1.0
window: str = "" # 季度窗口,如 2022Q1
source_ids: list[str] = Field(default_factory=list)
class ChangeEvent(BaseModel):
"""岗位能力演化事件:新增 / 增强 / 减弱 / 退出。"""
event_id: str
job_id: str
skill: str
change_type: Literal["新增", "增强", "减弱", "退出"]
window: str
prev_weight: float = 0.0
curr_weight: float = 0.0
source_ids: list[str] = Field(default_factory=list)
description: str = ""
created_at: str = ""
class ResumeProfile(BaseModel):
"""脱敏后的简历画像。"""
name: str = ""
phone: str = ""
email: str = ""
years: float = 0.0
skills: list[str] = Field(default_factory=list)
scenarios: list[str] = Field(default_factory=list)
raw_masked_text: str = ""
# 结构化信息(规范简历模板用)
gender: str = "" # 性别(不脱敏,人口统计学字段)
education: str = "" # 最高学历(本科/硕士/博士…)
address: str = "" # 家庭住址/现居地(脱敏到城市)
photo_b64: str = "" # 证件照 base64(data URI,无照片为空)
sections: list[dict] = Field(default_factory=list) # [{title, text}] 分节正文
class MatchResult(BaseModel):
"""人岗匹配结果:总分、四维分、命中/缺失/可迁移技能与学习路径。"""
job_id: str
job_title: str = ""
total_score: float = 0.0
dimension_scores: dict[str, float] = Field(default_factory=dict)
matched_required: list[str] = Field(default_factory=list)
missing_required: list[str] = Field(default_factory=list)
matched_preferred: list[str] = Field(default_factory=list)
missing_preferred: list[str] = Field(default_factory=list)
transferable_skills: list[str] = Field(default_factory=list)
learning_path: list[dict] = Field(default_factory=list)
is_match: bool = False
class ReviewRecord(BaseModel):
"""人机协同审核留痕。"""
candidate_id: str
action: Literal["accept", "modify", "reject"]
operator: str
reason: str = ""
before: dict = Field(default_factory=dict)
after: dict = Field(default_factory=dict)
time: str = ""