Files
offerpai_python_ai/.kiro/specs/resume-diagnose.md
T
2026-04-07 20:15:43 +08:00

19 KiB
Raw Blame History

简历诊断功能 - 技术方案

一、功能概述

对用户已有简历的描述文本进行 AI 诊断,找出问题、给出改进建议和 AI 改写版本,生成诊断报告。

诊断范围(只诊断描述文本)

来源表 字段 module_type
bg_user_resume summary(个人概述) summary
bg_user_resume_education description education
bg_user_resume_work description work
bg_user_resume_internship description internship
bg_user_resume_project description project
bg_user_resume_competition description competition

执行策略:先分后合

第一阶段:所有模块记录并行 AI 诊断(asyncio.gather
第二阶段:汇总 issues → 代码算评级 → AI 生成整体评价 → 写入数据库

AI 模型:LLM.DOUBAO_SEED_PRO,暂不接入功能权限校验。


二、问题分类与诊断判断思路

2.1 紧急修复(urgent

typo — 错别字 / 语病

判断思路:

  • 检查文本中是否存在明显的错别字(如"功则"应为"功能"、"负责"写成"付责"
  • 检查是否存在语病:主谓搭配不当、语序混乱、成分残缺
  • 检查标点符号使用错误:中英文标点混用、缺少句号、逗号过多造成长句
  • 检查用词不当:近义词误用、口语化表达出现在正式简历中

AI 判断信号:

  • 同音字替换(的/得/地 混用)
  • 句子读不通顺,需要反复阅读才能理解
  • 专业术语拼写错误
  • 一句话中出现多个逗号,缺少句号断句

2.2 重点优化(important

no_result — 缺少成果

判断思路:

  • 描述只写了"做了什么"(任务/职责),没有写"做出了什么结果"
  • 典型的流水账式描述:只有动作没有产出
  • 缺少对业务/团队/项目的实际贡献影响

AI 判断信号:

  • 只有动词+宾语("负责XX系统开发"、"参与XX项目"),没有后续的结果说明
  • 全是过程描述,找不到"提升了"、"优化了"、"实现了"、"完成了"等结果性表述
  • 对比 STAR 法则:有 Situation + Task + Action,但缺 Result

正面示例(有成果):

负责用户中心系统重构,将接口响应时间从 800ms 降至 200ms,用户投诉率下降 60%

反面示例(缺成果):

负责用户中心系统重构,使用 Spring Boot + Redis 实现了新的架构

no_quantify — 缺少量化

判断思路:

  • 有成果描述但没有具体数字支撑
  • 使用了模糊表达:"大幅提升"、"显著改善"、"大量用户",但没有具体数值
  • 缺少以下任何维度的量化:人数、金额、百分比、时间周期、覆盖范围、处理规模

AI 判断信号:

  • 出现"大幅"、"显著"、"有效"、"极大"等模糊程度副词,但没有跟随数字
  • 提到了结果但只是定性描述,没有定量数据
  • 可以合理推断应该有数据但未提供(如"提升了性能"没说提升多少)

正面示例(有量化):

优化数据库查询,将平均响应时间从 2s 降至 200ms,日处理订单量从 5 万提升至 20 万

反面示例(缺量化):

优化数据库查询,显著提升了系统性能,改善了用户体验

weak_relevance — 岗位相关性弱

前置条件:仅在 target_position 有值时才判断,未填目标岗位则跳过此维度,计数为 0。

判断思路:

  • 需要结合 target_position(目标岗位)进行判断
  • 描述内容与目标岗位的核心职责关联度低
  • 花大量篇幅描述与目标岗位无关的技能或经历
  • 对于目标岗位来说,这段描述无法体现匹配度

AI 判断信号:

  • 目标岗位是"Java后端工程师",但描述中全是前端或运营内容
  • 描述的技能/工具与目标岗位的 JD 常见要求差距大
  • 可转移技能存在但未被强调,反而突出了无关内容

2.3 表达提升(expression

not_concise — 表述不精炼

判断思路:

  • 句子偏长(单句超过 50 字),信息密度低
  • 存在赘词和重复表达:"进行了开发"可简化为"开发了"
  • 信息堆叠:一句话塞了太多内容,应拆分为多个要点
  • 使用了空泛的修饰词"充分"、"积极"、"认真"等无实质信息

AI 判断信号:

  • 单个描述段落超过 80 字但核心信息只有一个
  • 出现"进行了"、"完成了对...的"、"负责了...的工作"等冗余句式
  • 同一段落中重复表达相似的意思
  • 可以删去一半文字而不损失关键信息

正面示例(精炼):

设计并实现分布式缓存方案,QPS 从 1000 提升至 8000,缓存命中率 95%

反面示例(不精炼):

在项目中,我积极主动地参与了分布式缓存方案的设计与实现工作,通过对缓存策略的深入研究和反复优化,最终成功地将系统的 QPS 从原来的 1000 提升到了 8000

format_inconsistent — 格式不统一

判断思路:

  • 同一份简历中时间格式不统一(有的写"2023.06",有的写"2023年6月",有的写"2023/06"
  • 标点风格不统一:有的段落用分号结尾,有的用句号,有的不加标点
  • 数字写法不统一:有的用阿拉伯数字,有的用中文数字
  • 项目符号不统一:有的用"•",有的用"-",有的用"1."
  • 人称不统一:有的用"我",有的用第三人称,有的省略主语

AI 判断信号:

  • 同一段描述中出现两种以上的格式风格
  • 与模块上下文中的时间格式不一致
  • 段落之间的排版结构差异明显

三、综合评级规则

由代码硬算,不依赖 AI 判断:

评级 条件 评语
A(优秀) urgent=0, important<=1, expression<=1 您的简历相当出彩,在求职市场中格外抢眼,能清晰展现您的优势与经历,已经超越绝大多数候选人了。
B(良好) urgent=0, important 2-3, expression<=2 简历已经很棒了,但还有提升的潜力。再调整一下细节,会有更具有竞争力!
C(一般) urgent=1, 或 important 3-4 你的简历还有打磨空间,多推敲细节、补充些具体内容,整体会更出彩。
D(待提升) urgent>=2, 或 (important>=4 且 has_weak_relevance) 您的简历目前还有较大提升空间,建议尽快补充关键经历、完善内容表达,并优化整体结构。

判断优先级:从 D → C → B → A 依次判断,命中即返回。


四、数据库表设计

bg_resume_diagnosis_report

字段 类型 说明
id BigInteger 主键,雪花ID
resume_id BigInteger 关联 bg_user_resume.id
user_id BigInteger 用户ID
grade VARCHAR(1) 评级 A/B/C/D
summary TEXT AI 生成的整体评价
urgent_total Integer 紧急修复总数
important_total Integer 重点优化总数
expression_total Integer 表达提升总数
create_time DateTime 创建时间
update_time DateTime 更新时间

bg_resume_diagnosis_issue

字段 类型 说明
id BigInteger 主键,雪花ID
report_id BigInteger 关联 report.id
resume_id BigInteger 关联 bg_user_resume.id
user_id BigInteger 用户ID
module_type VARCHAR(32) summary/education/work/internship/project/competition
module_record_id BigInteger 模块记录IDsummary 时为 resume_id
finding TEXT 诊断发现
importance TEXT 为什么重要
suggestion TEXT 改进建议
urgent_issues JSON {"typo": 0}
important_issues JSON {"no_result": 0, "no_quantify": 0, "weak_relevance": 0}
expression_issues JSON {"not_concise": 0, "format_inconsistent": 0}
optimized_content JSON AI改写后的内容。子表模块(education/work/internship/project/competition)与原 description 格式一致 [{id, text}],保持原 id 不变只改写 text;summary 模块为纯文本字符串
status Integer 0=待处理 1=已处理
user_feedback Integer 0=未评价 1=符合 2=不符合
create_time DateTime 创建时间
update_time DateTime 更新时间

建表 SQL

CREATE TABLE `bg_resume_diagnosis_report` (
  `id` bigint NOT NULL COMMENT '主键,雪花ID',
  `resume_id` bigint NOT NULL COMMENT '关联bg_user_resume.id',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `grade` varchar(1) DEFAULT NULL COMMENT '评级 A/B/C/D',
  `summary` text COMMENT 'AI生成的整体评价',
  `urgent_total` int NOT NULL DEFAULT '0' COMMENT '紧急修复总数',
  `important_total` int NOT NULL DEFAULT '0' COMMENT '重点优化总数',
  `expression_total` int NOT NULL DEFAULT '0' COMMENT '表达提升总数',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_resume_id` (`resume_id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='简历诊断报告表';

CREATE TABLE `bg_resume_diagnosis_issue` (
  `id` bigint NOT NULL COMMENT '主键,雪花ID',
  `report_id` bigint NOT NULL COMMENT '关联report.id',
  `resume_id` bigint NOT NULL COMMENT '关联bg_user_resume.id',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `module_type` varchar(32) NOT NULL COMMENT '模块类型: summary/education/work/internship/project/competition',
  `module_record_id` bigint NOT NULL COMMENT '模块记录IDsummary时为resume_id',
  `finding` text COMMENT '诊断发现',
  `importance` text COMMENT '为什么重要',
  `suggestion` text COMMENT '改进建议',
  `urgent_issues` json DEFAULT NULL COMMENT '紧急修复子类型计数 {"typo": 0}',
  `important_issues` json DEFAULT NULL COMMENT '重点优化子类型计数 {"no_result": 0, "no_quantify": 0, "weak_relevance": 0}',
  `expression_issues` json DEFAULT NULL COMMENT '表达提升子类型计数 {"not_concise": 0, "format_inconsistent": 0}',
  `optimized_content` json DEFAULT NULL COMMENT 'AI改写后的内容,子表模块与原description格式一致[{id,text}]保持原id只改写textsummary模块为纯文本字符串',
  `status` int NOT NULL DEFAULT '0' COMMENT '0=待处理 1=已处理',
  `user_feedback` int NOT NULL DEFAULT '0' COMMENT '0=未评价 1=符合 2=不符合',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_report_id` (`report_id`),
  KEY `idx_resume_id` (`resume_id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='简历诊断问题表';

五、API 设计

1. POST /resume/diagnose — 触发诊断

请求体: {"resume_id": 123}

执行流程:

┌──────────────────────────────────────────────────┐
│ 1. RequestContext.user_id.get() 获取当前用户        │
├──────────────────────────────────────────────────┤
│ 2. 短事务1(只读)                                  │
│    async for session in get_db():                  │
│      service = ResumeDiagnoseService(session)      │
│      resume, tasks = await service                 │
│          .load_resume_data(resume_id, user_id)     │
│    → 加载主表 + 5 张子表数据                         │
│    → 组装 AI 任务列表                                │
│    → 事务结束,释放数据库连接                          │
├──────────────────────────────────────────────────┤
│ 3. 校验 tasks 非空                                  │
│    → 空则 raise ValueError("无可诊断内容")           │
├──────────────────────────────────────────────────┤
│ 4. 并行 AI 诊断(不持有数据库连接)                   │
│    ai_results = await diagnose_all(tasks)          │
│    → asyncio.gather 并行调用 N 条诊断链              │
│    → 每条链独立容错,失败返回空结果                    │
├──────────────────────────────────────────────────┤
│ 5. 汇总统计 + 评级(纯计算,无 IO)                  │
│    → 遍历 ai_results 统计问题数量                    │
│    → 过滤无问题的记录(所有计数都为 0 则跳过)         │
│    → 代码硬算评级(_calc_grade                     │
├──────────────────────────────────────────────────┤
│ 6. AI 生成整体评价(不持有数据库连接)                │
│    summary = await generate_summary(...)           │
│    → 传入评级、统计、all_findings                    │
│    → 返回纯文本评价                                  │
├──────────────────────────────────────────────────┤
│ 7. 短事务2(纯写入,无 AI 调用)                     │
│    async for session in get_db():                  │
│      service = ResumeDiagnoseService(session)      │
│      report_id = await service.save_report(...)    │
│    → 写入 report + issues 表                        │
│    → 事务提交                                       │
└──────────────────────────────────────────────────┘

响应: {"reportId": 456}

2. GET /resume/diagnose/{resume_id} — 查询最近一次报告

执行流程:

  1. 获取 user_id
  2. 查 report 表(ORDER BY create_time DESC LIMIT 1
  3. 查该 report 下所有 issues
  4. 返回 {"report": {...}, "issues": [...]}

响应示例:

{
  "report": {
    "id": "123",
    "resumeId": "456",
    "grade": "B",
    "summary": "您的简历整体质量良好...",
    "urgentTotal": 0,
    "importantTotal": 3,
    "expressionTotal": 1,
    "createTime": "2026-04-07 10:30:00"
  },
  "issues": [
    {
      "id": "789",
      "moduleType": "work",
      "moduleRecordId": "1001",
      "finding": "工作描述缺少量化数据...",
      "importance": "量化数据能让招聘者...",
      "suggestion": "建议在描述中添加...",
      "urgentIssues": {"typo": 0},
      "importantIssues": {"no_result": 0, "no_quantify": 1, "weak_relevance": 0},
      "expressionIssues": {"not_concise": 0, "format_inconsistent": 0},
      "optimizedContent": [{"id": "abc123", "text": "负责XX系统后端开发,日均处理100万+请求..."}],
      "status": 0,
      "userFeedback": 0
    }
  ]
}

3. PUT /resume/diagnose/issue/{issue_id}/resolve — 标记已处理

请求体: {"user_feedback": 1} 1=符合 2=不符合)

执行流程:

  1. 获取 user_id
  2. 查 issue 记录(校验 user_id
  3. 设置 status=1, user_feedback
  4. 返回 null

六、文件变更清单

新增 7 个文件

app/models/resume_diagnosis_report.py      — 诊断报告 ORM
app/models/resume_diagnosis_issue.py       — 诊断问题 ORM
app/ai/resume_diagnoser/__init__.py        — 包初始化(空文件)
app/ai/resume_diagnoser/prompts.py         — Prompt 模板
app/ai/resume_diagnoser/diagnoser.py       — AI 诊断引擎
app/services/resume_diagnose_service.py    — 业务逻辑
app/api/resume_diagnose.py                 — API 路由

修改 1 个文件

app/main.py                                — 注册新路由(加 2 行)

七、模块设计

AI 诊断模块 (app/ai/resume_diagnoser/)

prompts.py — 两个 Prompt 模板:

  • DIAGNOSE_MODULE_PROMPT:第一阶段,单条记录诊断,输入 module_type/target_position/context/description_text(子表传原始 JSON [{id,text}],summary 传纯文本),输出 JSON
  • SUMMARY_PROMPT:第二阶段,汇总评价,输入统计数据 + 所有 findings,输出纯文本

diagnoser.py — 参照 app/ai/resume_extractor/extractor.py 的模式:

  • 诊断链:ChatPromptTemplate → DOUBAO_SEED_PRO(temperature=0) → JsonOutputParser
  • 汇总链:ChatPromptTemplate → DOUBAO_SEED_PRO(temperature=0.3) → StrOutputParser
  • diagnose_all(tasks) — asyncio.gather 并行
  • generate_summary(...) — AI 生成整体评价
  • _safe_invoke() — 容错

Service 层 (app/services/resume_diagnose_service.py)

ResumeDiagnoseService(session: AsyncSession)
  ├─ load_resume_data(resume_id, user_id) → (resume, tasks)
  │    加载主表 + 5 张子表,组装 AI 任务列表
  │
  ├─ save_report(resume_id, user_id, grade, summary, stats, tasks, ai_results) → report_id
  │    纯写入:接收已算好的 grade、summary、统计数据,写入 report + issues
  │    跳过无问题的记录(所有计数为 0 则不创建 issue 行)
  │
  ├─ get_latest_report(resume_id, user_id) → dict | None
  └─ resolve_issue(issue_id, user_id, user_feedback) → None

工具函数(无状态,路由层或 Service 外部可调用):
  ├─ _build_description_text(description) — 子表传原始 JSON 字符串 [{id,text}]summary 传纯文本
  ├─ _calc_grade(urgent, important, expression, has_weak_relevance) — 评级硬算
  ├─ _aggregate_results(tasks, ai_results) — 统计汇总 + 过滤无问题记录
  └─ _issue_to_dict(issue) — ORM → camelCase dict

description_text 传入格式:

  • 子表模块:传原始 JSON 字符串 [{"id": "abc123", "text": "负责XX系统..."}, ...]AI 能看到每个段落的 id
  • summary 模块:传纯文本(summary 字段本身就是 VARCHAR

optimized_content 返回格式:

  • 子表模块:与原始 description 格式一致 [{id, text}],保持原 id 不变,只改写 text
  • summary 模块:纯文本字符串(与原始 summary 字段格式一致)

API 路由 (app/api/resume_diagnose.py)

router = APIRouter(prefix="/resume/diagnose", tags=["简历诊断"])
  ├─ POST ""              — 触发诊断
  ├─ GET "/{resume_id}"   — 查询报告
  └─ PUT "/issue/{issue_id}/resolve" — 标记已处理

八、实施顺序

  1. ORM 模型(resume_diagnosis_report.py + resume_diagnosis_issue.py
  2. AI 模块(__init__.py + prompts.py + diagnoser.py
  3. Service 层(resume_diagnose_service.py
  4. API 路由(resume_diagnose.py+ 修改 main.py
  5. 更新项目结构文档