抽象简历查询
This commit is contained in:
@@ -11,7 +11,7 @@ import json
|
||||
import random
|
||||
import string
|
||||
|
||||
from sqlalchemy import select, desc
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.ai.skill_gap_analyzer.analyzer import (
|
||||
@@ -26,11 +26,7 @@ from app.schemas.skill_gap import (
|
||||
)
|
||||
from app.models.job import Job
|
||||
from app.models.user_resume import UserResume
|
||||
from app.models.user_resume_competition import UserResumeCompetition
|
||||
from app.models.user_resume_education import UserResumeEducation
|
||||
from app.models.user_resume_internship import UserResumeInternship
|
||||
from app.models.user_resume_project import UserResumeProject
|
||||
from app.models.user_resume_work import UserResumeWork
|
||||
from app.services.resume_loader import ResumeDetail, load_resume_detail, load_default_resume_detail
|
||||
|
||||
# Redis 常量
|
||||
CUSTOMIZE_RESUME_KEY_PREFIX = "customize:resume:"
|
||||
@@ -63,24 +59,25 @@ def _build_paragraphs(description: list[dict] | None) -> list[Paragraph]:
|
||||
return [Paragraph(id=_rand_id(), text=item.get("text", "")) for item in description if isinstance(item, dict)]
|
||||
|
||||
|
||||
def _build_resume_json(resume: UserResume, edu_list, work_list, intern_list, proj_list, comp_list) -> str:
|
||||
def _build_resume_json(detail: ResumeDetail) -> str:
|
||||
"""拼装简历 JSON 字符串供 AI 使用"""
|
||||
resume = detail.resume
|
||||
data = {
|
||||
"skills": resume.skills or [],
|
||||
"certificates": resume.certificates or [],
|
||||
"summary": resume.summary or "",
|
||||
"targetPosition": resume.target_position or "",
|
||||
}
|
||||
if edu_list:
|
||||
data["education"] = [{"school": r.school, "major": r.major, "degree": r.degree, "description": r.description} for r in edu_list]
|
||||
if work_list:
|
||||
data["work"] = [{"companyName": r.company_name, "position": r.position, "description": r.description} for r in work_list]
|
||||
if intern_list:
|
||||
data["internship"] = [{"companyName": r.company_name, "position": r.position, "description": r.description} for r in intern_list]
|
||||
if proj_list:
|
||||
data["project"] = [{"companyName": r.company_name, "projectName": r.project_name, "role": r.role, "description": r.description} for r in proj_list]
|
||||
if comp_list:
|
||||
data["competition"] = [{"competitionName": r.competition_name, "award": r.award, "description": r.description} for r in comp_list]
|
||||
if detail.education:
|
||||
data["education"] = [{"school": r.school, "major": r.major, "degree": r.degree, "description": r.description} for r in detail.education]
|
||||
if detail.work:
|
||||
data["work"] = [{"companyName": r.company_name, "position": r.position, "description": r.description} for r in detail.work]
|
||||
if detail.internship:
|
||||
data["internship"] = [{"companyName": r.company_name, "position": r.position, "description": r.description} for r in detail.internship]
|
||||
if detail.project:
|
||||
data["project"] = [{"companyName": r.company_name, "projectName": r.project_name, "role": r.role, "description": r.description} for r in detail.project]
|
||||
if detail.competition:
|
||||
data["competition"] = [{"competitionName": r.competition_name, "award": r.award, "description": r.description} for r in detail.competition]
|
||||
return json.dumps(data, ensure_ascii=False)
|
||||
|
||||
|
||||
@@ -94,21 +91,20 @@ class SkillGapService:
|
||||
async def analyze_skill_gap(self, user_id: int, job_id: int) -> dict:
|
||||
"""差距分析完整流程:查简历 → 查岗位 → AI分析 → 计算匹配分"""
|
||||
# 1. 自动选择简历
|
||||
resume = await self._pick_resume(user_id)
|
||||
detail = await load_default_resume_detail(self.session, user_id)
|
||||
# 2. 查岗位
|
||||
job = await self._get_job(job_id)
|
||||
skill_tags: list[str] = job.skill_tags or []
|
||||
# 3. skill_tags 为空 → 满分
|
||||
if not skill_tags:
|
||||
return self._gap_result(10.0, job, resume, [])
|
||||
# 4. 查子表拼 AI 输入
|
||||
edu, work, intern, proj, comp = await self._load_sub_tables(resume.id)
|
||||
resume_json = _build_resume_json(resume, edu, work, intern, proj, comp)
|
||||
return self._gap_result(10.0, job, detail.resume, [])
|
||||
# 4. 拼 AI 输入
|
||||
resume_json = _build_resume_json(detail)
|
||||
# 5. AI 分析
|
||||
missing = await analyze_skill_gap(skill_tags, resume_json)
|
||||
# 6. 计算匹配分
|
||||
score = round((len(skill_tags) - len(missing)) / len(skill_tags) * 10, 1)
|
||||
return self._gap_result(score, job, resume, missing)
|
||||
return self._gap_result(score, job, detail.resume, missing)
|
||||
|
||||
@staticmethod
|
||||
def _gap_result(score: float, job: Job, resume: UserResume, missing: list[str]) -> dict:
|
||||
@@ -127,16 +123,15 @@ class SkillGapService:
|
||||
if not optimize_modules:
|
||||
raise ValueError("请至少选择一个优化模块")
|
||||
# 1. 查简历 + 岗位
|
||||
resume = await self._get_resume(resume_id, user_id)
|
||||
detail = await load_resume_detail(self.session, resume_id, user_id)
|
||||
job = await self._get_job(job_id)
|
||||
edu_rows, work_rows, intern_rows, proj_rows, comp_rows = await self._load_sub_tables(resume.id)
|
||||
# 2. 组装基础定制简历
|
||||
cr = self._build_customize_resume(resume, edu_rows, work_rows, intern_rows, proj_rows, comp_rows)
|
||||
cr = self._build_customize_resume(detail)
|
||||
# 3. 并发 AI 优化
|
||||
tasks = []
|
||||
job_desc = f"{job.description or ''}\n{job.requirement or ''}"
|
||||
if "summary" in optimize_modules:
|
||||
tasks.append(("summary", optimize_summary(job.title or "", add_skills, resume.summary or "")))
|
||||
tasks.append(("summary", optimize_summary(job.title or "", add_skills, detail.resume.summary or "")))
|
||||
if "experience" in optimize_modules:
|
||||
for module_name, rows_json in self._experience_tasks(cr, job.title or "", job_desc):
|
||||
tasks.append((module_name, optimize_module(job.title or "", job_desc, rows_json)))
|
||||
@@ -371,30 +366,6 @@ class SkillGapService:
|
||||
|
||||
# ===== 内部工具方法 =====
|
||||
|
||||
async def _pick_resume(self, user_id: int) -> UserResume:
|
||||
"""自动选择简历:先查默认,再查最新"""
|
||||
result = await self.session.execute(
|
||||
select(UserResume).where(UserResume.user_id == user_id, UserResume.is_default == 1)
|
||||
.order_by(desc(UserResume.update_time)).limit(1))
|
||||
resume = result.scalar_one_or_none()
|
||||
if not resume:
|
||||
result = await self.session.execute(
|
||||
select(UserResume).where(UserResume.user_id == user_id)
|
||||
.order_by(desc(UserResume.update_time)).limit(1))
|
||||
resume = result.scalar_one_or_none()
|
||||
if not resume:
|
||||
raise ValueError("请先创建简历")
|
||||
return resume
|
||||
|
||||
async def _get_resume(self, resume_id: int, user_id: int) -> UserResume:
|
||||
"""查指定简历"""
|
||||
result = await self.session.execute(
|
||||
select(UserResume).where(UserResume.id == resume_id, UserResume.user_id == user_id))
|
||||
resume = result.scalar_one_or_none()
|
||||
if not resume:
|
||||
raise ValueError("简历不存在")
|
||||
return resume
|
||||
|
||||
async def _get_job(self, job_id: int) -> Job:
|
||||
"""查岗位"""
|
||||
result = await self.session.execute(select(Job).where(Job.id == job_id))
|
||||
@@ -403,18 +374,9 @@ class SkillGapService:
|
||||
raise ValueError("岗位不存在")
|
||||
return job
|
||||
|
||||
async def _load_sub_tables(self, resume_id: int):
|
||||
"""查询简历5张子表"""
|
||||
edu = (await self.session.execute(select(UserResumeEducation).where(UserResumeEducation.resume_id == resume_id))).scalars().all()
|
||||
work = (await self.session.execute(select(UserResumeWork).where(UserResumeWork.resume_id == resume_id))).scalars().all()
|
||||
intern = (await self.session.execute(select(UserResumeInternship).where(UserResumeInternship.resume_id == resume_id))).scalars().all()
|
||||
proj = (await self.session.execute(select(UserResumeProject).where(UserResumeProject.resume_id == resume_id))).scalars().all()
|
||||
comp = (await self.session.execute(select(UserResumeCompetition).where(UserResumeCompetition.resume_id == resume_id))).scalars().all()
|
||||
return edu, work, intern, proj, comp
|
||||
|
||||
def _build_customize_resume(self, resume: UserResume, edu_rows, work_rows,
|
||||
intern_rows, proj_rows, comp_rows) -> CustomizeResume:
|
||||
"""从数据库记录组装 CustomizeResume"""
|
||||
def _build_customize_resume(self, detail: ResumeDetail) -> CustomizeResume:
|
||||
"""从 ResumeDetail 组装 CustomizeResume"""
|
||||
resume = detail.resume
|
||||
profile = ResumeProfile(
|
||||
avatarUrl=resume.avatar_url or "", name=resume.name or "", email=resume.email or "",
|
||||
mobileNumber=resume.mobile_number or "", city=resume.city or "",
|
||||
@@ -427,19 +389,19 @@ class SkillGapService:
|
||||
education=[Education(id=_rand_id(), school=r.school or "", major=r.major or "",
|
||||
degree=r.degree or "", studyType=r.study_type or "",
|
||||
startDate=r.start_date or "", endDate=r.end_date or "",
|
||||
description=_build_paragraphs(r.description)) for r in edu_rows],
|
||||
description=_build_paragraphs(r.description)) for r in detail.education],
|
||||
work=[Work(id=_rand_id(), companyName=r.company_name or "", position=r.position or "",
|
||||
startDate=r.start_date or "", endDate=r.end_date or "",
|
||||
description=_build_paragraphs(r.description)) for r in work_rows],
|
||||
description=_build_paragraphs(r.description)) for r in detail.work],
|
||||
internship=[Internship(id=_rand_id(), companyName=r.company_name or "", position=r.position or "",
|
||||
startDate=r.start_date or "", endDate=r.end_date or "",
|
||||
description=_build_paragraphs(r.description)) for r in intern_rows],
|
||||
description=_build_paragraphs(r.description)) for r in detail.internship],
|
||||
project=[Project(id=_rand_id(), companyName=r.company_name or "", projectName=r.project_name or "",
|
||||
role=r.role or "", startDate=r.start_date or "", endDate=r.end_date or "",
|
||||
description=_build_paragraphs(r.description)) for r in proj_rows],
|
||||
description=_build_paragraphs(r.description)) for r in detail.project],
|
||||
competition=[Competition(id=_rand_id(), competitionName=r.competition_name or "", award=r.award or "",
|
||||
awardDate=r.award_date or "",
|
||||
description=_build_paragraphs(r.description)) for r in comp_rows],
|
||||
description=_build_paragraphs(r.description)) for r in detail.competition],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user