Files
RoyirinRosmontis a10442d78c CareerNexus 职联:岗位能力图谱构建与动态演化分析平台(挑战杯 XH-202621)
- 全链路:多源JD接入/清洗交叉验证/结构化抽取(星火+离线双通道)/时序知识图谱/新岗位发现/演化分析/人岗匹配/学习路径
- WebUI 五视图:驾驶舱/图谱工作台/岗位演化/匹配(单人+批量)/学习路径
- 设置中心(config.json)/Token鉴权/SQLite状态库/语义向量通道(BGE-M3本地或联网API)
- 71 个 pytest 用例;数据目录仅保留结构(.gitkeep),数据由 scripts/generate_dataset.py 复现
2026-08-03 13:15:35 +08:00

69 lines
3.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""演化分析测试:构造两季度(多窗口)数据验证 新增/增强/减弱/退出 事件。"""
from __future__ import annotations
from app.evolution.analyzer import analyze
from app.graph.tkg import TemporalKG
from app.models import GraphObservation, JobDefinition
def _obs(job: str, skill: str, window: str, n: int) -> list[GraphObservation]:
"""生成 n 条同窗口观察(模拟窗口内 n 条 JD 提到该技能)。"""
return [GraphObservation(job_id=job, skill=skill, importance="required",
weight=1.0, window=window, source_ids=[f"{window}#{i}"])
for i in range(n)]
def test_quarter_window_events():
kg = TemporalKG()
kg.add_job(JobDefinition(job_id="job_A", title="岗位A"))
observations: list[GraphObservation] = []
# 老技能 Legacy:2024Q1-Q3 稳定出现(3 窗口),Q4 与次年 Q1 连续消失 → 退出
for w in ["2024Q1", "2024Q2", "2024Q3"]:
observations += _obs("job_A", "Legacy技能", w, 3)
# 新技能 NewSkill:2024Q4 首次出现且支撑 >=2 → 新增
observations += _obs("job_A", "NewSkill", "2024Q4", 3)
# 增强技能:3 → 9(>=1.5 倍且差 >=3)
observations += _obs("job_A", "GrowSkill", "2024Q3", 3)
observations += _obs("job_A", "GrowSkill", "2024Q4", 9)
# 减弱技能:8 → 3(<=0.5 倍且差 >=3)
observations += _obs("job_A", "ShrinkSkill", "2024Q3", 8)
observations += _obs("job_A", "ShrinkSkill", "2024Q4", 3)
for o in observations: # 与 pipeline 一致:观察先写入图谱,再分析演化
kg.observe(o)
events = analyze(kg, observations=observations)
by_type = {}
for e in events:
by_type.setdefault(e.change_type, []).append(e)
# 新增:LegacySkill 首现 + NewSkill 首现
new_skills = {e.skill for e in by_type.get("新增", [])}
assert "NewSkill" in new_skills and "Legacy技能" in new_skills
# 退出:Legacy 技能在 2024Q4 判定(Q4、次年 Q1 连续为 0,且已稳定 3 窗口)
exits = [e for e in by_type.get("退出", []) if e.skill == "Legacy技能"]
assert exits and exits[0].window == "2024Q4"
assert exits[0].prev_weight > 0 and exits[0].curr_weight == 0
assert "数据源" in exits[0].description # 中文说明注明数据源
# 增强 / 减弱
assert any(e.skill == "GrowSkill" and e.window == "2024Q4"
for e in by_type.get("增强", []))
assert any(e.skill == "ShrinkSkill" and e.window == "2024Q4"
for e in by_type.get("减弱", []))
# 退出事件关闭了图谱区间
assert kg.g["job_A"]["Legacy技能"]["intervals"][-1][1] == "2024Q4"
# 事件保存前后权重、时间窗、来源
grow = next(e for e in by_type["增强"] if e.skill == "GrowSkill")
assert grow.prev_weight == 3.0 and grow.curr_weight == 9.0
assert grow.source_ids
def test_no_spurious_exit_for_new_skill():
"""只出现 1-2 个窗口的技能消失不报“退出”(抑制稀疏噪声)。"""
kg = TemporalKG()
kg.add_job(JobDefinition(job_id="job_B", title="岗位B"))
observations = _obs("job_B", "一次性技能", "2024Q1", 3) # 只出现 1 个窗口
observations += _obs("job_B", "常驻技能", "2024Q1", 2)
observations += _obs("job_B", "常驻技能", "2024Q2", 2)
events = analyze(kg, observations=observations)
assert not [e for e in events if e.change_type == "退出" and e.skill == "一次性技能"]