Resume Screener

SkillAI & models

Implementation reference for a resume screening and scoring system. The LLM scores 4 dimensions for a final score (exp/skill/proj/major) + a script checks school_tier to calibrate edu + a formula recalculates the total.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Resume Screener skill

What this skill tells your AI

The instructions your AI receives, as published by 1xiaoyueryuer/boss-hr-agent-toolkit in resume-screener/SKILL.md and read by ahel’s review.

🔁 跨 run 评分去重(脚本自动执行)

score_resumes.py 自带去重,智能体不需要手工过滤已评分候选人

  • 入口:按 job_id:geek_idstate/scored_state.json,历史评过的自动跳过
  • 出口:本轮评分结果自动回写 scored_state.json
  • 逃生门--rescore 强制重评(换 JD、改评分口径时用)
# 正常评分(自动跳过历史已评人员)
python score_resumes.py --input _llm_scores.json --output screening_results.json \
  --job-name "<岗位名>" --encrypt-job-id "<id>" --run-id "$RUN_ID"
# → ⏭ 跳过 12 位历史已评分候选人:张三、李四...
# 缺 _llm_scores.json → SystemExit(26) + JSON 错误提示

# 换了 JD 要全部重评
python score_resumes.py --input _llm_scores.json --output screening_results.json \
  --job-name "<岗位名>" --encrypt-job-id "<id>" --run-id "$RUN_ID" --rescore

去重按 geek_id 而非姓名。BOSS 上「杨先生」「吕女士」这类匿名昵称会重名, 脚本对同一姓名维护 ID 列表:只要还有任一同名候选人未评分就放行 (宁可偶尔重复评分,也不把没评过的人误杀)。

姓名 → geek_id 的映射从 state/resumes_master.json 反查,所以 _llm_scores.json 里的 name 必须与简历原始姓名一致,改写姓名会导致匹配失败(脚本会告警)。


评分架构

  • LLM 评 4 维度最终分exp / skill / proj / major 全部由 LLM 真实分析完整简历后给出 0–100 的最终分(已综合考虑年限、对口度、实操深度、复杂度等)
  • 脚本只覆盖 1 维度:用 scripts/school_tier.py 查表覆盖 edu
  • 公式重算:5 维度 × 权重 = total(按 25/25/25/15/10)
  • Tier 判定:≥70 推荐 / 60-69 待定 / <60 不推荐
  • 通用:不限岗位(任一 JD 都能用)

LLM 不输出 industry_fit 之类的系数标签。exp 分本身已综合考虑对口度,脚本不会再乘任何系数。


核心不变量

5 维度权重edu 25% / exp 25% / skill 25% / proj 15% / major 10%
Tier 阈值推荐 ≥70 / 待定 60-69 / 不推荐 <60
公式total = Σ (raw × weight)
Tier 名称推荐 / 待定 / 不推荐
学校分档7 档(C9 / 985 / 211 / 双一流 / 一本公办 / 二本公办 / 民办)
评分主体LLM 评 4 维度最终分 + 脚本查 edu(仅此一套)

工具脚本

scripts/score_resumes.py

工具函数(agent 直接 import 调用):

函数作用
_extract_school_name(score)智能拆纯校名(优先 school_name,兜底从 school/·(( 拆分)
validate_score(score)LLM 评分收尾:用 school_tier 覆盖 edu + 重算 weighted + total + 判定 tier
calc_tier(total)≥70 推荐 / 60-69 待定 / <60 不推荐
calc_weighted(dims)5 维度 × 权重
calc_total(weighted)求和
candidate_to_report(c, rank)list 元素 → candidates[] 格式
build_actions(candidates)生成 actions 三段式(recommend/pending/reject)
build_meta(job_name, job_info)构造报告 meta

CLI

python score_resumes.py \
  --input <llm_scores.json> \
  --output <screening_results.json> \
  --job-name "<岗位名>" \
  --encrypt-job-id "<BOSS 的 encryptJobId>" \
  --job-info <JD JSON 字符串> \
  --run-id <run_id>

🚨 新接口必传 --encrypt-job-id:工作区目录名 = encryptJobId,与 Step 1/2/4 保持一致。也可以设 env BOSS_HR_ENCRYPT_JOB_ID 作为 fallback。缺则直接 ValueError 退出(严格模式,不静默回退)。

scripts/school_tier.py

from school_tier import lookup
info = lookup("辽宁工业大学")
# → {"tier": "二本公办", "score": 62, "matched": "辽宁工业大学", "fuzzy": False}
info = lookup("江南大学")
# → {"tier": "211", "score": 85, "matched": "江南大学", "fuzzy": False}

支持精确匹配 + 模糊匹配(输入校名是表内校的子串或父串时也能命中)。


完整工作流(2026-07-31 v3:LLM 每评一份立即落盘)

# 公共参数(5 步全流程同一个 encryptJobId)
export ENCRYPT_ID="9a7759badfd95d350nFz3d-_F1NX"
export JOB_NAME="线控底盘制动、转向工程师"
export RUN_ID="2026-07-29_150915"

# 假设已有 runs/<run_id>/process/new_resumes.json(来自 boss-recommend-downloader)

# 0. 简历净化层 —— 把 new_resumes.json 拆成每人一份(2026-07-31 v2)
#    输入:new_resumes.json(动辄几 MB,含 _meta/active_status/空字段等噪声)
#    输出:runs/<run_id>/process/scoring/
#      ├── manifest.json                     # 候选人清单 + status(pending/scored/missing)
#      ├── inputs/candidate_<geek_id>.json   # 净化输入(LLM 读这里)
#      ├── outputs/candidate_<geek_id>.json  # LLM 评分落盘点(每评一个立即写一份)
#      └── _skipped.json                     # 被跳过的简历
#    关键:不改变评分标准,只是把「一坨 JSON」拆成「每人一文件」
python scripts/prepare_scoring_inputs.py \
  --job-name "$JOB_NAME" --encrypt-job-id "$ENCRYPT_ID" --run-id "$RUN_ID"

# 1. LLM agent 读 scoring/manifest.json(拿到候选人清单 + status)
# 2. 对 status="pending" 的候选人循环:
#      a) 读 scoring/inputs/candidate_<geek_id>.json(一份精简简历)
#      b) 调 LLM API 评 4 维度(exp / skill / proj / major),产出评分 object
#      c) **立即落盘**到 scoring/outputs/candidate_<geek_id>.json(单个评分 object)
#      d) 中途崩了下次只需重跑循环里 status="pending" 的那批
# 3. 跑 collect_llm_scores.py:把 outputs/ 合并成 _llm_scores.json(幂等可重跑)
#    - 回写 manifest.status 为 scored / missing / invalid
#    - 不读简历、不做评分,只做文件收集 + 数组拼接
python scripts/collect_llm_scores.py \
  --job-name "$JOB_NAME" --encrypt-job-id "$ENCRYPT_ID" --run-id "$RUN_ID"

# 4. 跑 score_resumes.py 收尾(不读 outputs/,只接 _llm_scores.json)
#    - 用 school_tier 查 edu
#    - 加权 + total + tier 判定
#    - 跨 run 去重(state/scored_state.json)
python score_resumes.py \
  --job-name "$JOB_NAME" --encrypt-job-id "$ENCRYPT_ID" --run-id "$RUN_ID"

# Step 4: 生成 HTML 报告
python html-report/scripts/generate_html_report.py \
  --job-name "$JOB_NAME" \
  --encrypt-job-id "$ENCRYPT_ID" \
  --run-id <run_id>

关键设计点

  1. 断点续评:LLM agent 任何时候崩了,下次只需读 manifest.jsonstatus="pending" 的继续评,已评的 scored 直接跳过。
  2. 不污染评分标准score_resumes.py 的入参 schema 完全不变(仍是 _llm_scores.json 数组);collect_llm_scores.py 只做文件收集 + 拼接,不做任何评分/打分/校准。
  3. 校验兜底collect_llm_scores.py 校验每个 outputs 文件的 schema(必含 name / dims.{exp,skill,proj,major} 且 0-100),不合法的标 status="invalid" 不入数组,错误信息打印在 stdout。
  4. geek_id / job_id 兜底:如果 LLM 在 outputs 文件里漏写 geek_id,collect_llm_scores.py 从 manifest 自动补(避免 score_resumes.py 拒绝评分)。

净化层字段规则(白名单 + 必保留)

prepare_scoring_inputs.pynew_resumes.json[i] 净化出 <index>_<name>.json,规则:

只删

  • ok / age / expectation / active_status(接口包装 / 平台状态 / 与评分无关)
  • _meta 整层(包装字段;encrypt_geek_id / encrypt_job_id 抽到顶层)
  • work_experience[].performance / work_experience[].keywords / work_experience[].department(历年空 / BOSS 内部字段)
  • project_experience[] 中的全空字段(保留 name 作为骨架)
  • 空字段(null / "")在 work_experience / project_experience 内做最小保留(让 LLM 看到"无技能"是证据,不是字段缺失)

必保留(评分主体证据):

  • name / degree / work_years(脚本硬门槛过滤依据)
  • work_experience[].company / position / start / end / duration / responsibility
  • project_experience[].name / role / start / end / duration / description / achievement
  • education[](school_tier 查表 + 专业匹配)
  • certifications[](英语 / 计算机证书是辅助证据)
  • skills(JD 关键词命中证据;空串也保留)
  • 顶层 geek_id / job_id(从 _meta 抽出,供 score_resumes.py 去重)

额外

  • 顶层加 __meta__ 块标 source / source_index / generated_at,方便反查
  • _manifest.json(文件清单 + 字节数 + geek_id)和 _skipped.json(被跳过的 ok=false / 缺 name 的简历)

工作区路径约定(新设计 · 2026-07-29+)

所有数据统一存放在 ~/Desktop/boss-hr-output/<encryptJobId>/目录名直接用 BOSS 的 encryptJobId(不再用中文岗位名)。job_name 仅作为 jobs.json 里的可读元数据。

~/Desktop/boss-hr-output/
├── jobs.json                               # JobRegistry:encryptJobId → {name, company}
└── <encryptJobId>/                         # 目录名 = BOSS 的 encryptJobId
    ├── state/                              # 跨 run 保留(不覆盖)
    │   ├── candidate_pool.json
    │   ├── download_state.json
    │   ├── resumes_master.json             # 累计成功简历(含 _meta)
    │   ├── collection_state.json
    │   ├── scored_state.json
    └── runs/
        └── <run_id>/                       # 一次筛选任务
            ├── <run_id>_screening_report.html
            └── process/
                ├── job_detail.json              ← Step 1: boss_jd.py 输出
                ├── recommend_geek_ids.json      ← Step 2a: list 输出
                ├── new_resumes.json             ← Step 2b: download 输出
                ├── scoring/                     ← Step 3: prepare_scoring_inputs.py 输出
                │   ├── manifest.json             # 候选人清单 + status(pending/scored/missing)
                │   ├── _skipped.json             # 被跳过的简历
                │   ├── inputs/candidate_<geek_id>.json   # LLM 读这里
                │   └── outputs/candidate_<geek_id>.json  # LLM 落盘点(每评一个立即写一份)
                ├── _llm_scores.json             ← Step 3: collect_llm_scores.py 合并产物
                ├── screening_results.json       ← Step 3: score_resumes.py 输出
                ├── failed_resumes.json
                └── greet_log.json               ← Step 5: auto_greet.py 输出

上游 boss-job-detail / boss-recommend-downloader 已自动写入此目录,下游脚本(score_resumes.py / generate_html_report.py / auto_greet.py)通过 --input/--output 读取此目录。

JobOutputManager 提供的标准路径属性:jd_path / recommend_geek_ids_path / new_resumes_path / screening_results_path / report_path / run_summary_path

同一 run 必须传同一个 --run-id 给所有脚本(list / download / score / HTML / greet),产物才落在同一个 runs/<run_id>/

同一 job 必须传同一个 --encrypt-job-id 给所有脚本,工作区目录才一致。


LLM 评分输入 schema

[
  {
    "name": "陈瀚",
    "school": "辽宁工业大学/车辆工程/本科",
    "work_years": "3 年",
    "match_type": "山东浩信 · 汽车零部件三维设计",
    "dims": {
      "exp": 80,
      "skill": 65,
      "proj": 60,
      "major": 100
    },
    "highlights": ["3 年 CATIA", "2 项发明专利"],
    "concerns": ["做的是轮端非车架"],
    "advice": "强烈建议电话沟通..."
  }
]

字段说明

字段必填说明
name候选人姓名
school校名(脚本自动拆出纯校名)
work_years工作年限
match_type当前岗位 / 业务方向
dims.expLLM 评 0-100 最终分(工作经验:综合年限 + 对口度 + 实操深度)
dims.skillLLM 评 0-100 最终分(专业技能)
dims.projLLM 评 0-100 最终分(项目经历)
dims.majorLLM 评 0-100 最终分(专业匹配)
dims.edu会被 validate_score 强制覆盖
school_name备选字段,优先级高于 school 拆分
highlights候选人亮点(推荐动作背景)
concerns候选人顾虑(不推荐原因 / 待确认问题)
advice个性化建议(推荐/待定动作方向)

注意

  • 4 个 LLM 评分维度全部是最终分(0-100),脚本不会再做任何乘法 / 折扣
  • school 字段允许任意格式,脚本智能拆出纯校名(支持 / · ( ( 等分隔符)
  • LLM 不要评 edu,会强制被 school_tier 覆盖

5 维度评分方法(理工类通用)

适用于一切工程/技术岗(机械、车辆、电子、材料、自动化、化工、土木、软件等)。LLM 评分时结合具体 JD 的核心技能清单与方向,按本表锚点给 0–100。所有维度给的都直接是最终分

维度权重评分方评分主体
学历25%school_tier 查表(校名→档次分);可选本/硕/博层次加成脚本(强制)
工作经验25%综合考虑工作年限 + 与 JD 对口度 + 实操深度,给最终分LLM
专业技能25%JD 核心技能覆盖度 × 实操深度LLM
项目经历15%项目与岗位的相关度 × 复杂度 × 角色LLM
专业匹配10%专业与 JD 的对口度(优先/相关/弱相关/无关)LLM

维度评分锚点

① 学历 edu(脚本查表,LLM 不评)

  • 分数取 school_tier:C9=100 / 985=92 / 211=85 / 双一流=77 / 一本公办=71 / 二本公办=62 / 民办=53
  • 表外学校:edu 显示"缺失",建议人工复核(可给默认 60 并标"需复核")
  • (可选扩展)层次加成:硕士 +8、博士 +12,封顶 100——由脚本读 degree 字段实现

② 工作经验 exp(LLM 评最终分 0–100)

LLM 在评 exp 时综合以下 3 个因素,给一个最终分

  • 工作年限(参考锚点):
    • ≥8 年 = 95 / 5–7 年 = 85 / 3–4 年 = 75 / 1–2 年 = 60 / <1 年 = 45
  • 与 JD 对口度(参考锚点,已并入最终分):
    • 精准对口(与 JD 核心方向完全一致)= 不折扣
    • 行业相关但非精准(测试/工艺/验证/零部件/相近子领域)= 视情况打 0.7×基础分附近
    • 完全跨行业(纯销售/行政/文科背景)= 视情况打 0.4×基础分附近
  • 实操深度(参考锚点):
    • 能独立负责核心模块 + 有可验证产出 = 锚点 +5
    • 仅参与执行 / 无量化成果 = 锚点 −5

以上三因素LLM 在打分时一次性综合考虑,直接给出最终分。脚本不参与任何乘法。

③ 专业技能 skill(LLM 评最终分 0–100)

  • 按 JD 列出的核心技能清单评估"覆盖度 × 熟练度":
    • 全覆盖且能独立负责核心模块 = 85–100
    • 核心技能 70%+ 且较熟 = 70–84
    • 覆盖 40-70% 或仅部分了解 = 55-69
    • 覆盖 <40% 或仅字面提及 = 40-54
    • 有 JD 硬性要求但不会的"硬伤":该档下限再 −10

④ 项目经历 proj(LLM 评最终分 0–100)

  • 按"与 JD 岗位的相关度 + 复杂度 + 担任角色":
    • 主导本岗核心项目(0–1 设计/量产落地)= 85–100
    • 重要角色参与核心项目 = 70–84
    • 边缘/支持角色、或项目仅部分相关 = 55–69
    • 非相关但体现工程能力 = 40–54
    • 无可验证项目 / 完全无关 = <40

⑤ 专业匹配 major(LLM 评最终分 0–100)

  • 以 JD 要求的对口专业为"优先"基准,按专业大类映射(理工类通用):
    • 优先=100:JD 直接点名的对口专业(随岗而定,如机械设计/车辆工程/材料成型/自动化/电子/化工等)
    • 相关=80:同大类工科(机械类、车辆类、材料类、自动化/机电、土木、化工、计算机偏硬等相近领域)
    • 弱相关=60:基础/交叉学科(力学、数学、物理、计算机偏软、工业工程等)
    • 无关=40:文科、经管、艺术、生化医药等非工程背景
    • 跨专业但有扎实对口课程 + 项目经历的,按"相关"档处理

评分公式

单维度加权分 = 原始分 (0-100) × 该维度权重
总分 = Σ(所有维度加权分)

示例 A(精准对口,3 年结构设计)

edu=62, exp=80, skill=65, proj=60, major=100
→ 62×0.25 + 80×0.25 + 65×0.25 + 60×0.15 + 100×0.10
→ 15.5 + 20.0 + 16.25 + 9.0 + 10.0 = 70.75 → "推荐"(≥70)

示例 B(行业相关非精准对口,3 年结构测试)

LLM 综合评估后直接给 exp 最终分 = 56(已包含"年限 75 × 对口度 0.7 ≈ 53" 的折扣判断)。

edu=62, exp=56, skill=65, proj=60, major=100
→ 62×0.25 + 56×0.25 + 65×0.25 + 60×0.15 + 100×0.10
→ 15.5 + 14.0 + 16.25 + 9.0 + 10.0 = 64.75 → "待定"(≥60)

Tier 判定

def calc_tier(total):
    if total >= 70: return "推荐"
    if total >= 60: return "待定"
    return "不推荐"

学校分档表(脚本内置,500+ 学校)

档次分数示例学校
C9100清华、北大、复旦、交大、浙大、中科大、南大、哈工大、西交
98592其他 30 所 985 高校
21185非 985 的 211 高校(如江南大学、苏州大学、华东理工)
双一流77南方科技大学、上海科技大学、中国科学院大学
一本公办71深圳大学、广东工业大学、西安理工大学、杭州电子科技大学
二本公办62辽宁工业大学、信阳农林学院、太原工业学院、滁州学院、长江师范学院
民办/独立学院53燕京理工学院、黄河科技学院、郑州工商学院、四川工业科技学院

完整 500+ 学校列表见 scripts/school_tier.py。 不在表内的学校:返回 score=None,edu 维度显示"缺失(XX 不在学校表)"。


LLM 评分调用示例

from score_resumes import validate_score

score = {
    "name": "陈瀚",
    "school": "辽宁工业大学/车辆工程/本科",
    "work_years": "3 年",
    "match_type": "山东浩信 · 汽车零部件三维设计",
    "dims": {
        "exp": 80,        # LLM 评最终分
        "skill": 65,      # LLM 评最终分
        "proj": 60,       # LLM 评最终分
        "major": 100      # LLM 评最终分
        # edu 不需要填
    },
    "highlights": ["3 年 CATIA 三维设计", "2 项发明专利"],
    "concerns": ["做的是轮端非车架本体", "无 CAE 仿真经验", "无焊接/工艺经验"],
    "advice": "专业对口 + 设计经验真实,建议电话沟通是否有车架/底盘结构项目经验"
}
score = validate_score(score)
# → score["dims"]["edu"] = 62(二本公办,school_tier 查表)
# → score["total"] = 69.8
# → score["tier"] = "待定"
# → score["dims_edu_reason"] = "二本公办(school_tier 查询:辽宁工业大学)"

输出 schema(screening_results.json)

{
  "job_name": "车架工程师",
  "meta": {
    "title": "车架工程师 · 简历筛选报告",
    "subtitle": "LLM 主导评分 + school_tier 学历分档校准",
    "job": {
      "name": "...",
      "company": "...",
      "location": "...",
      "salary": "...",
      "experience_required": "...",
      "degree_required": "..."
    },
    "type_judgment": {"type": "技术岗", "reason": "..."},
    "core_requirements": ["..."]
  },
  "summary": {"total": 32, "recommend": 0, "pending": 2, "reject": 30},
  "dimension_labels": ["学历", "工作经验", "专业技能", "项目经历", "专业匹配"],
  "candidates": [
    {
      "rank": 1,
      "name": "陈瀚",
      "tier": "待定",
      "total": 69.8,
      "school": "辽宁工业大学/车辆工程/本科",
      "work_years": "3年",
      "current_role": "汽车零部件三维设计(轮端)",
      "dimensions": [
        {
          "pct": 62,
          "weighted": 15.5,
          "weight": 25,
          "reason": "二本公办(school_tier 查询:辽宁工业大学)"
        }
      ],
      "highlights": ["..."],
      "concerns": ["..."]
    }
  ],
  "actions": {
    "recommend": [{"name", "score", "background", "action"}],
    "pending":   [{"name", "score", "strengths", "action"}],
    "reject":    [{"name", "score", "concerns"}]
  }
}

注意事项

  • LLM 评 4 维度时务必读完完整简历再打分,避免"看一半就判断"
  • 测试 / 设计 / 验证 / 仿真等职能差异要识别(如底盘电控功能测试 ≠ 车架结构设计)
  • 学校档次 / tier 阈值 / 公式权重都由脚本控制,LLM 不要尝试覆盖
  • exp 维度给的分数就是最终分,不要再额外标 industry_fit / 对口度系数,这些已经综合在 exp 分里了
  • highlights / concerns / advice 是个性化建议的核心,必须基于候选人实际经历

Signals

GitHub stars
47
Forks
4
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
resume-screener
Source
github.com/1xiaoyueryuer/boss-hr-agent-toolkit