Skill: suql82/resume-batch-screener (version 1.0.0)

--- name: resume-batch-screener description: 第一阶段批量筛选 - 通用岗位简历批量筛选工具，自动从JD提取评估维度，多候选人打分排序，生成筛选报告和面试邀请名单 trigger: 用户要求批量筛选简历、多候选人打分排序、批量生成筛选报告时使用本技能 --- # 第一阶段：批量筛选简历（通用版） ## 核心定位 **通用岗位简历批量筛选工具**。输入任意岗位JD和候选人简历文件夹，自动解析JD提取评估维度，对多候选人进行统一打分排序，输出标准化筛选报告。 --- ## 功能说明 批量解析多个候选人的简历，对照岗位JD进行多维度打分，输出： - 简历筛选报告.xlsx（所有候选人分数、关键信息、排序） - 面试邀请名单.xlsx（进入面试名单及邀请状态） --- ## 输入要求 ### 必要信息 1. **岗位JD文件**（docx/pdf/md/txt）— 支持任意行业/职能岗位 2. **简历文件夹**（多份简历，支持docx/pdf/jpg/png） ### 可选信息 - 筛选标准（如：综合得分≥60分进入面试） - 面试官安排 - 面试时间安排 --- ## 核心流程 ``` ┌─────────────────────────────────────────────────────────────┐ │ 步骤1：解析JD │ │ ↓ │ │ • 提取硬性要求（学历/年限/专业/证书等） │ │ • 提取评估维度（3-5个核心能力项） │ │ • 提取加分项（专利/证书/特定经验等） │ │ ↓ │ │ 步骤2：解析简历 │ │ ↓ │ │ • 提取基本信息（姓名/学历/工作年限） │ │ • 提取工作经历（公司/职位/时间/业绩） │ │ • 提取技能/项目/证书等信息 │ │ ↓ │ │ 步骤3：多维度打分 │ │ ↓ │ │ • 基础维度打分（6项通用维度） │ │ • JD专项维度打分（根据JD自动生成） │ │ ↓ │ │ 步骤4：排序输出 │ │ ↓ │ │ • 生成筛选报告.xlsx │ │ • 生成面试邀请名单.xlsx │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 评估维度体系 **总分：100分制** - 所有候选人按同一标准评分，确保跨岗位可比性。 ### 统一评分维度（所有岗位适用） | 维度 | 分值 | 评分标准 | |:---|:---:|:---| | 学历 | 15分 | 博士15/硕士12/本科8/大专4 | | 工作年限 | 10分 | 8年+满分10/5年+8分/3年+6分/1年+4分 | | 技能匹配 | 25分 | 与岗位核心技能关键词匹配度，按匹配数量计算 | | 项目/经验质量 | 25分 | 目标岗位相关项目经验质量，分4个等级 | | 职业稳定性 | 15分 | 8年+15分/5年+12分/3年+8分/1年+5分 | | 软性能力 | 10分 | 沟通协作、团队配合等，默认8分 | ### 技能匹配评分计算方法 ```python def calc_skill_match(skills, job_type): """计算技能匹配得分""" all_text = " ".join(skills).lower() if job_type == "tech": # 技术岗核心技能 tech_skills = ["CUDA", "OpenCL", "SIMT", "SIMD", "GPGPU", "GPU", "NPU", "RISC-V", "Verilog", "VHDL", "UVM", "RTL", "Gem5", "GPGPU-Sim", "SystemC", "SoC", "FPGA", "芯片架构", "微架构", "指令集", "AI", "深度学习", "流片", "量产"] matched = [s for s in tech_skills if s.lower() in all_text] return min(25, len(matched) * 3 + (5 if len(matched) >= 5 else 0)) elif job_type == "market": # 市场岗核心技能 market_skills = ["市场开拓", "客户关系", "项目管理", "PMP", "AEC-Q", "ISO26262", "车规", "上车", "Tier1", "整车"] matched = [s for s in market_skills if s.lower() in all_text] return min(25, len(matched) * 4 + (5 if len(matched) >= 4 else 0)) return 15 # 默认 ``` ### 项目经验评分标准 | 等级 | 技术岗分值 | 市场岗分值 | 条件 | |:---|:---:|:---:|:---| | 卓越级 | 22-25分 | 22-25分 | 有目标岗位同类产品主导经验，成果可量化 | | 良好级 | 16-21分 | 16-21分 | 有目标岗位同类项目完整参与经验 | | 中等级 | 10-15分 | 10-15分 | 有相近岗位项目经验 | | 基础级 | 5-9分 | 5-9分 | 有相关基础经验，无直接项目经历 | ### 筛选标准 | 分数区间 | 建议 | |:---:|:---| | ≥85分 | ✅ 强烈推荐 | | 70-84分 | ✅ 推荐 | | 60-69分 | ⚠️ 备选 | | <60分 | ❌ 不推荐 | ### 硬性门槛检测（先于打分） | 门槛项 | 要求 | 不满足处理 | |:---|:---|:---| | 学历 | JD要求的最低学历 | 直接淘汰或总分上限60分 | | 工作年限 | JD要求的最少年限 | 明显不足者直接淘汰 | | 必备技能 | JD要求的核心技能 | 无必备技能者直接淘汰 | | 行业经验 | JD要求的特定行业 | 无行业经验者降一档 | --- ## JD解析方法 ### 自动提取流程 ```python def parse_jd(jd_content): """从JD内容中自动提取评估维度""" # 1. 提取硬性要求 hard_requirements = { '学历': extract_education(jd_content), # 如：硕士及以上 '年限': extract_experience(jd_content), # 如：5年+ '专业': extract_major(jd_content), # 如：计算机相关 } # 2. 提取评估维度（3-5个核心能力） evaluation_dimensions = extract_core_abilities(jd_content) # 如：[架构设计, 算法开发, 团队管理, 客户对接] # 3. 提取加分项 bonus_items = extract_bonus_items(jd_content) # 如：[PMP认证, CPA, 行业排名前十公司经验] return hard_requirements, evaluation_dimensions, bonus_items ``` ### JD关键词识别 | 类别 | 识别关键词 | |:---|:---| | 学历要求 | "硕士及以上"、"本科985/211"、"全日制" | | 年限要求 | "5年+"、"8年+"、"10年以上" | | 硬性技能 | "熟练掌握"、"精通"、"必须具备" | | 加分技能 | "加分"、"优先"、"更佳" | | 行业经验 | "医疗行业"、"金融行业"、"汽车电子" | | 证书资质 | "CFA"、"CPA"、"PMP"、"司法考试" | ### 不同岗位类型的权重策略 | 岗位类型 | 权重调整策略 | |:---|:---| | 技术/研发类 | 技能匹配度25-30分，项目经验质量25-30分 | | 算法/AI类 | 技能匹配度30-40分，学历权重提高（15-18分） | | 管理类 | 软性能力15-20分，工作年限权重提高 | | 市场/销售类 | 行业经验权重提高，技能匹配度权重降低 | | 运营/产品类 | 项目经验权重提高，学历权重降低至8-10分 | | 职能/支持类 | 稳定性权重提高，技能匹配度权重降低 | --- ## 专业经验详细评分规则（20分） 根据JD提取的核心能力进行评估： | 等级 | 分值 | 条件 | |:---|:---:|:---| | 极强 | 18-20分 | 同时满足岗位JD要求的全部核心能力项 | | 强 | 14-17分 | 满足岗位JD要求的3项以上核心能力 | | 中 | 9-13分 | 满足岗位JD要求的2项核心能力 | | 弱 | 4-8分 | 满足岗位JD要求的1项核心能力 | | 极弱 | 0-3分 | 无相关经验 → 淘汰或降级 | --- ## 项目经验质量详细评分规则（25分） | 等级 | 分值 | 条件 | |:---|:---:|:---| | 卓越级 | 23-25分 | 有目标岗位同类产品/项目主导经验，成果可量化 | | 良好级 | 18-22分 | 有目标岗位同类项目完整参与经验 | | 中等级 | 12-17分 | 有相近岗位项目经验 | | 基础级 | 6-11分 | 有相关基础经验，但无直接项目经历 | | 无关级 | 0-5分 | 无目标岗位直接经验 → 降级处理 | **注**：卓越级指主导并产生明确成果的项目，良好级指完整参与但非主导，中等级指行业相近但岗位不同 --- ## 筛选标准 | 建议等级 | 分数区间 | 建议动作 | |:---|:---:|:---| | 强烈推荐 | ≥80分 | 优先安排面试（核心岗位/稀缺人才） | | 推荐 | 70-79分 | 安排面试（匹配度良好） | | 备选 | 60-69分 | 根据招聘数量决定（基本匹配） | | 不推荐 | <60分 | 不进入面试（存在明显短板） | **注**：分数区间可根据岗位紧急程度调整，如急招岗位可将"备选"区间提升至55-59分 --- ## 中文简历解析实现要点 ### 依赖库 ```python import pdfplumber # PDF文本提取 from docx import Document # Word文档解析 import re # 正则表达式 from pathlib import Path ``` ### PDF文本提取 ```python def read_pdf(path): text = "" with pdfplumber.open(path) as pdf: for page in pdf.pages: text += page.extract_text() or "" return text ``` ### Word文档解析 ```python def read_docx(path): doc = Document(path) return "\n".join([p.text for p in doc.paragraphs]) ``` ### 姓名提取（重要：多种格式兼容） ```python def extract_name(content, filename): """提取简历姓名，兼容多种格式""" # 1. 优先匹配 "姓 名：冯亮" 格式（中间有空格/制表符） name_match = re.search(r'姓\s*名[：:]\s*([\u4e00-\u9fa5]{2,4})', content) if name_match: return name_match.group(1) # 2. 备用：从文件名获取（当简历正文无标准姓名格式时） return filename.replace('.docx', '').replace('.pdf', '').strip() ``` ### 工作年限提取 ```python def extract_work_years(content): """提取工作年限""" # 优先匹配 "工作年限：9年" 格式 year_match = re.search(r"工作年限[：:]\s*(\d+)", content) if year_match: return int(year_match.group(1)) # 备用：从工作经历时间范围推算（取最早年份） dates = re.findall(r"(\d{4})[.\-/~]", content) if dates: try: years = [int(d) for d in dates if len(d) == 4] if years: return 2026 - min(years) except: pass return 5 # 默认值 ``` ### 学历提取 ```python def extract_education(content): """提取学历层次""" if "博士" in content: return "博士" elif "硕士" in content or "研究生" in content: return "硕士" elif "本科" in content: return "本科" else: return "大专" ``` ### 核心技能提取（大小写不敏感） ```python def extract_skills(content): """提取核心技能关键词""" skills = [] content_lower = content.lower() skill_pool = [ "CUDA", "OpenCL", "ROCm", "SIMT", "SIMD", "GPGPU", "GPU", "NPU", "TPU", "RISC-V", "Verilog", "VHDL", "SystemVerilog", "UVM", "RTL", "Gem5", "GPGPU-Sim", "SystemC", "SoC", "FPGA", "ASIC", "Linux", "KVM", "虚拟化", "AEC-Q", "ISO26262", "车规", "芯片架构", "微架构", "指令集", "ISA", "AI", "人工智能", "深度学习", "机器学习", "市场开拓", "客户关系", "渠道管理", "项目管理", "PMP", "流片", "量产", "EDA", "物理", "电路", "半导体", "器件", ] for skill in skill_pool: if skill.lower() in content_lower: skills.append(skill) return list(set(skills)) ``` ### 公司提取 ```python def extract_companies(content): """提取工作单位""" companies = re.findall(r'([\u4e00-\u9fa5]{2,15}(?:公司|集团|科技|技术|有限|股份|研究所))', content) return list(set(companies))[:5] ``` --- ## 输出文件 ### 简历筛选报告.xlsx **Sheet1: 筛选结果汇总（全部候选人）** | 序号 | 姓名 | 应聘岗位 | 综合得分 | 排名 | 学历 | 工作年限 | 技能匹配 | 项目经验 | 稳定性 | 软性 | 邀请状态 | 核心技能 | |:---:|:---|:---|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---| **数据要求**： - 按综合得分**降序排序** - 得分保留整数 - 邀请状态：✅强烈推荐/✅推荐/⚠️备选/❌不推荐 **Sheet2-N: 各岗位筛选结果（含评分依据）** 每个岗位一个Sheet，Sheet命名=岗位名称（最长20字符） 包含两部分内容： **第一部分：候选人得分汇总表** | 序号 | 姓名 | 综合得分 | 排名 | 学历 | 工作年限 | 技能匹配 | 项目经验 | 稳定性 | 软性 | 邀请状态 | |:---:|:---|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---| **第二部分：候选人详细评分依据（每人占用约15行）** 每位候选人包含以下评分明细： ``` 【候选人：XXX】 ■ 学历评分（满分15分，得X分） 评价依据：[学历层次]，[院校背景]（如适用） 结论：满足/不满足JD学历要求 ■ 工作年限评分（满分10分，得X分） 评价依据：工作年限X年，对应档位[X年+]，得X分 结论：满足/不满足JD年限要求 ■ 技能匹配评分（满分25分，得X分） 匹配技能：[列出所有匹配的技能关键词，用"/"分隔] 计算方法：匹配X个技能 × 3分 = X分（上限25分） 结论：技能匹配度[X/高/中/低] ■ 项目经验评分（满分25分，得X分） 评价依据：[简要描述项目经验质量] 等级认定：[卓越/良好/中等/基础级] 结论：项目经验与岗位要求[匹配度高/中/低] ■ 职业稳定性评分（满分15分，得X分） 评价依据：工作年限X年，平均每段工作时间约X年 结论：职业稳定性[高/中/低] ■ 软性能力评分（满分10分，得X分） 评价依据：[从简历中提取的软性能力描述] 结论：软性能力[强/中/弱]（默认8分） ■ 综合评价 总分：X分（学历X+年限X+技能X+项目X+稳定X+软性X） 建议：✅强烈推荐/✅推荐/⚠️备选/❌不推荐 ■ 核心优势 1. [优势1] 2. [优势2] ■ 风险点 1. [风险点1] 2. [风险点2] ■ 面试关注要点 1. [关注点1] 2. [关注点2] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` ### 面试邀请名单.xlsx **Sheet1: 邀请名单** | 序号 | 姓名 | 综合得分 | 学历 | 工作年限 | 核心优势 | 邀请状态 | 面试时间 | 面试形式 | 主面试官 | 备注 | |:---:|:---|:---:|:---|:---:|:---|:---|:---|:---|:---|:---:| **Sheet2: 候选人关键信息** 每个候选人一个区块： ``` 【候选人：姓名】 综合得分：XX分 排名：第X名 关键经历：━━━━━━━━━━━━━━━━━━━━ 核心技能：━━━━━━━━━━━━━━━━━━━━ 风险提示：━━━━━━━━━━━━━━━━━━━━ 面试关注点：━━━━━━━━━━━━━━━━━━ ``` ### 评分标准模板.xlsx **Sheet1: 评分维度总览** | 维度 | 最高分 | 评分说明 | |:---|:---:|:---| | 学历 | 15分 | 按院校层次打分 | | 工作年限 | 10分 | 按年限档位打分 | | 技能匹配度 | 20分 | 按与JD要求匹配度打分 | | 项目经验质量 | 25分 | 按项目规模/成果分级 | | 职业稳定性 | 10分 | 按平均每段工作时间打分 | | 软性能力 | 10分 | 按管理/协作/沟通打分 | | JD专项加分 | 10分 | 根据具体岗位JD提取 | **Sheet2: 筛选标准** | 分数区间 | 建议 | |:---:|:---| | ≥80分 | ✅ 强烈推荐 | | 70-79分 | ✅ 推荐 | | 60-69分 | ⚠️ 备选 | | <60分 | ❌ 不推荐 | **Sheet3: 学历评分细则** | 学历层次 | 分值 | |:---|:---:| | 博士（985/211） | 15分 | | 硕士（985/211） | 13分 | | 硕士（普通院校） | 11分 | | 本科（985/211） | 8分 | | 本科（普通院校） | 6分 | | 大专 | 3分 | **Sheet4: 各项评分细则** 详细列出其他维度的评分标准 ### 候选人详细评分依据.xlsx **Sheet1: 全部候选人汇总** | 候选人 | 学历 | 年限 | 技能匹配 | 项目质量 | 稳定性 | 软性 | JD专项 | 总分 | 排名 | 建议 | |:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| **Sheet2-N: 各候选人详细评分说明** | 维度 | 得分 | 评分依据 | |:---|:---:|:---| （每行一个维度的详细评分说明） **核心优势**：xxx **风险点**：xxx --- ## 排版格式 Excel格式要求： - 表头：加粗，背景色#E0E0E0，黑色边框 - 数据行：白底，黑色细线边框 - 综合得分列：黄色背景#FFFF00（可编辑），居中对齐 - 强烈推荐/推荐：绿色背景#C6EFCE - 备选：橙色背景#FFEB9C - 不推荐：红色背景#FFC7CE - 列宽：自动适应（建议width=14） 文件命名：`{岗位名称}-筛选报告-{日期}.xlsx` --- ## 文件写入注意事项（WSL环境） **权限问题处理**：WSL环境下直接写入/mnt/d/路径可能遇到PermissionError ```python # 解决方案：先写入/tmp，再复制到目标目录 import shutil output_path = '/tmp/简历筛选报告.xlsx' wb.save(output_path) # 复制到Downloads目录 shutil.copy(output_path, '/mnt/c/Users/{用户名}/Downloads/') ``` --- ## 注意事项 1. **通用性优先**：本Skill适用于任何行业/职能岗位，不预设任何特定行业词汇 2. 批量处理时保持每个候选人评估标准一致 3. 关键信息（核心技能、量化指标）需在备注中标注 4. 对不确定的信息在备注中标注"待核实" 5. 排序必须按综合得分降序 6. **中文文件名可能有空格**（如"周光迪 .pdf"），解析时需注意strip处理 7. PDF文件必须使用pdfplumber库提取文本，不要用PyPDF2（中文支持差） 8. JD解析时优先提取JD中的专有词汇作为评估维度，确保评分与岗位需求精准匹配 ## 重要：重新生成文件时的处理流程 当用户要求重新生成筛选报告时，必须遵循以下步骤避免数据不一致： ### 步骤1：删除旧文件 ```bash # 删除该目录下所有之前生成的筛选报告文件 rm -f /目标目录/简历筛选报告*.xlsx rm -f /目标目录/面试邀请名单*.xlsx rm -f /目标目录/候选人详细评分依据*.xlsx ``` ### 步骤2：使用验证过的评分数据 - 不要重新解析简历（解析结果可能因算法微调而变化） - 使用已验证的、用户确认过的正确评分数据 - 如无验证数据，需先与用户确认评分标准 ### 步骤3：生成后一致性检查 - 验证"筛选结果汇总"和"面试邀请名单"中的候选人数据一致 - 确保总分、排名、建议在所有sheet中保持一致 ### 错误示例（避免） ❌ 先生成文件A发现问题后修改评分再生成文件B ❌ 筛选结果汇总和面试邀请名单使用不同的评分结果 ❌ 保留旧的Excel文件只覆盖部分sheet ### 正确示例 ✅ 确认评分标准 → 删除旧文件 → 生成新文件 → 验证一致性


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)