diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/ai/HotakeAiCommonToolsController.java b/vetti-admin/src/main/java/com/vetti/web/controller/ai/HotakeAiCommonToolsController.java index 0bbedc6..359a8d0 100644 --- a/vetti-admin/src/main/java/com/vetti/web/controller/ai/HotakeAiCommonToolsController.java +++ b/vetti-admin/src/main/java/com/vetti/web/controller/ai/HotakeAiCommonToolsController.java @@ -187,19 +187,19 @@ public class HotakeAiCommonToolsController extends BaseController { */ @ApiOperation("候选人AI面试分析") @PostMapping(value = "/candidateAiInterviewAnalysis") - public R candidateAiInterviewAnalysis(@RequestBody HotakeCandidateAiInterviewAnalysisVo analysisVo) + public R candidateAiInterviewAnalysis(@RequestBody HotakeCandidateAiInterviewAnalysisVo analysisVo) { return R.ok(hotakeAiCommonToolsService.handleCandidateAiInterviewAnalysis(analysisVo)); } /** - * 招聘者AI简历评分和排名系统 + * 招聘者 AI简历评分和排名系统 */ - @ApiOperation("招聘者AI简历评分和排名系统") + @ApiOperation("招聘者 AI简历评分和排名系统") @PostMapping(value = "/aiCvScoringRanking") - public R aiCvScoringRanking(@RequestBody HotakeAiCvScoringRankingVo scoringRankingVo) + public R aiCvScoringRanking(@RequestBody HotakeAiCvScoringRankingRoleApplyVo roleApplyVo) { - return R.ok(hotakeAiCommonToolsService.handleAiCvScoringRanking(scoringRankingVo)); + return R.ok(hotakeAiCommonToolsService.handleAiCvScoringRanking(roleApplyVo)); } /** diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/hotake/HotakeRolesApplyInfoController.java b/vetti-admin/src/main/java/com/vetti/web/controller/hotake/HotakeRolesApplyInfoController.java index 944dde5..09ac240 100644 --- a/vetti-admin/src/main/java/com/vetti/web/controller/hotake/HotakeRolesApplyInfoController.java +++ b/vetti-admin/src/main/java/com/vetti/web/controller/hotake/HotakeRolesApplyInfoController.java @@ -109,4 +109,6 @@ public class HotakeRolesApplyInfoController extends BaseController hotakeRolesApplyInfoService.updateBatchEditStage(stageVoList); return R.ok(); } + + } diff --git a/vetti-admin/src/main/java/com/vetti/web/entity/vo/HotakeAiInterviewScoringVo.java b/vetti-admin/src/main/java/com/vetti/web/entity/vo/HotakeAiInterviewScoringVo.java index a877289..68f70ec 100644 --- a/vetti-admin/src/main/java/com/vetti/web/entity/vo/HotakeAiInterviewScoringVo.java +++ b/vetti-admin/src/main/java/com/vetti/web/entity/vo/HotakeAiInterviewScoringVo.java @@ -17,6 +17,9 @@ import java.util.List; @Accessors(chain = true) public class HotakeAiInterviewScoringVo { + @ApiModelProperty("岗位申请ID") + private Long roleApplyId; + @ApiModelProperty("AI 面试问题记录 数据集合") private List aiInterviewQuestionRecordVoList; } diff --git a/vetti-admin/target/classes/application-druid.yml b/vetti-admin/target/classes/application-druid.yml index aa24259..1463f6f 100644 --- a/vetti-admin/target/classes/application-druid.yml +++ b/vetti-admin/target/classes/application-druid.yml @@ -187,6 +187,9 @@ chatGpt: modelPpg: gpt-4o-mini modelRLinkAl: gpt-4o-mini modelRLinkAl_1: gpt-4o-mini + modelAiIntPf: gpt-4o-mini + modelAiCvSr: gpt-4o-mini + modelAiCac: gpt-4o-mini role: system http: diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeRolesApplyInfo.java b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeRolesApplyInfo.java index 4fcec06..5990bc2 100644 --- a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeRolesApplyInfo.java +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/HotakeRolesApplyInfo.java @@ -130,4 +130,7 @@ public class HotakeRolesApplyInfo extends BaseEntity @ApiModelProperty("岗位申请操作记录数据集合") private List applyOperRecords; + @ApiModelProperty("岗位申请ID数据集合") + private List applyRoleIdList; + } diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/dto/HotakeCandidateAiInterviewAnalysisAnalysisDto.java b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/dto/HotakeCandidateAiInterviewAnalysisAnalysisDto.java new file mode 100644 index 0000000..e3a9cb5 --- /dev/null +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/dto/HotakeCandidateAiInterviewAnalysisAnalysisDto.java @@ -0,0 +1,26 @@ +package com.vetti.hotake.domain.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; + + +import java.util.List; + +/** + * 候选人AI面试分析-优势点列表以及改进列表 返回对象 + * + * @author ID + * @date 2025-09-06 + */ +@Data +@Accessors(chain = true) +public class HotakeCandidateAiInterviewAnalysisAnalysisDto { + + @ApiModelProperty("优势点列表") + private List strengths; + + @ApiModelProperty("改进建议列表") + private List improvements; + +} diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/dto/HotakeCandidateAiInterviewAnalysisDto.java b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/dto/HotakeCandidateAiInterviewAnalysisDto.java index 02b081a..75ce3ab 100644 --- a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/dto/HotakeCandidateAiInterviewAnalysisDto.java +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/dto/HotakeCandidateAiInterviewAnalysisDto.java @@ -1,8 +1,12 @@ package com.vetti.hotake.domain.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.experimental.Accessors; +import java.math.BigDecimal; +import java.util.List; + /** * 候选人AI面试分析 返回对象 * @@ -13,6 +17,25 @@ import lombok.experimental.Accessors; @Accessors(chain = true) public class HotakeCandidateAiInterviewAnalysisDto { + @ApiModelProperty("技术能力评分") + private BigDecimal technical; + @ApiModelProperty("沟通能力评分") + private BigDecimal communication; + + @ApiModelProperty("文化契合评分") + private BigDecimal cultural; + + @ApiModelProperty("技术能力-优势点列表以及改进列表") + private HotakeCandidateAiInterviewAnalysisAnalysisDto technicalDto; + + @ApiModelProperty("沟通能力-优势点列表以及改进列表") + private HotakeCandidateAiInterviewAnalysisAnalysisDto communicationDto; + + @ApiModelProperty("文化契合-优势点列表以及改进列表") + private HotakeCandidateAiInterviewAnalysisAnalysisDto culturalDto; + + @ApiModelProperty("具体描述列表") + private List notesDtoList; } diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/dto/HotakeCandidateAiInterviewAnalysisNotesDto.java b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/dto/HotakeCandidateAiInterviewAnalysisNotesDto.java new file mode 100644 index 0000000..85d0940 --- /dev/null +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/dto/HotakeCandidateAiInterviewAnalysisNotesDto.java @@ -0,0 +1,23 @@ +package com.vetti.hotake.domain.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 候选人AI面试分析-具体描述 返回对象 + * + * @author ID + * @date 2025-09-06 + */ +@Data +@Accessors(chain = true) +public class HotakeCandidateAiInterviewAnalysisNotesDto { + + @ApiModelProperty("笔记类型") + private String type; + + @ApiModelProperty("笔记内容 ") + private String content; + +} diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/vo/HotakeAiCvScoringRankingRoleApplyVo.java b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/vo/HotakeAiCvScoringRankingRoleApplyVo.java new file mode 100644 index 0000000..f255b39 --- /dev/null +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/vo/HotakeAiCvScoringRankingRoleApplyVo.java @@ -0,0 +1,21 @@ +package com.vetti.hotake.domain.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 招聘者AI简历评分和排名系统-岗位申请 请求对象 + * + * @author wangxiangshun + * @date 2026-01-17 + */ +@Data +@Accessors(chain = true) +public class HotakeAiCvScoringRankingRoleApplyVo { + + @ApiModelProperty("岗位申请ID集合") + private List roleApplyIds; +} diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/vo/HotakeCandidateAiInterviewAnalysisVo.java b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/vo/HotakeCandidateAiInterviewAnalysisVo.java index 20093bc..70dc820 100644 --- a/vetti-hotakes/src/main/java/com/vetti/hotake/domain/vo/HotakeCandidateAiInterviewAnalysisVo.java +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/domain/vo/HotakeCandidateAiInterviewAnalysisVo.java @@ -16,6 +16,9 @@ import java.util.List; @Accessors(chain = true) public class HotakeCandidateAiInterviewAnalysisVo { + @ApiModelProperty("岗位申请ID") + private Long roleApplyId; + @ApiModelProperty("面试岗位") private String position; diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeAiCommonToolsService.java b/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeAiCommonToolsService.java index 92af690..e1c0769 100644 --- a/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeAiCommonToolsService.java +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/service/IHotakeAiCommonToolsService.java @@ -133,15 +133,15 @@ public interface IHotakeAiCommonToolsService { * @param analysisVo 候选人AI面试分析信息 * @return */ - public String handleCandidateAiInterviewAnalysis(HotakeCandidateAiInterviewAnalysisVo analysisVo); + public HotakeCandidateAiInterviewAnalysisDto handleCandidateAiInterviewAnalysis(HotakeCandidateAiInterviewAnalysisVo analysisVo); /** * 招聘者AI简历评分和排名系统 - * @param scoringRankingVo 招聘者AI简历评分和排名系统输入信息 + * @param roleApplyVo 岗位申请数据对象 * @return */ - public String handleAiCvScoringRanking(HotakeAiCvScoringRankingVo scoringRankingVo); + public String handleAiCvScoringRanking(HotakeAiCvScoringRankingRoleApplyVo roleApplyVo); /** diff --git a/vetti-hotakes/src/main/java/com/vetti/hotake/service/impl/HotakeAiCommonToolsServiceImpl.java b/vetti-hotakes/src/main/java/com/vetti/hotake/service/impl/HotakeAiCommonToolsServiceImpl.java index f07c913..3edc123 100644 --- a/vetti-hotakes/src/main/java/com/vetti/hotake/service/impl/HotakeAiCommonToolsServiceImpl.java +++ b/vetti-hotakes/src/main/java/com/vetti/hotake/service/impl/HotakeAiCommonToolsServiceImpl.java @@ -2,6 +2,7 @@ package com.vetti.hotake.service.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.vetti.common.ai.gpt.ChatGPTClient; import com.vetti.common.constant.AiCommonPromptConstants; @@ -9,10 +10,12 @@ import com.vetti.common.core.domain.entity.SysUser; import com.vetti.common.core.service.BaseServiceImpl; import com.vetti.common.enums.FillTypeEnum; import com.vetti.common.enums.RoleBenefitsEnum; +import com.vetti.common.exception.ServiceException; import com.vetti.common.utils.SecurityUtils; import com.vetti.common.utils.html.ReadHtmlByOkHttp; import com.vetti.hotake.domain.HotakeInitScreQuestionsReplyRecordInfo; import com.vetti.hotake.domain.HotakeInitialScreeningQuestionsInfo; +import com.vetti.hotake.domain.HotakeRolesApplyInfo; import com.vetti.hotake.domain.HotakeRolesInfo; import com.vetti.hotake.domain.dto.*; import com.vetti.hotake.domain.dto.VcDto.*; @@ -30,6 +33,7 @@ import org.springframework.stereotype.Service; import java.io.IOException; import java.math.BigDecimal; import java.util.*; +import java.util.stream.Collectors; /** * AI共通工具 信息Service业务层处理 @@ -994,10 +998,9 @@ public class HotakeAiCommonToolsServiceImpl extends BaseServiceImpl implements I * @return */ @Override - public String handleCandidateAiInterviewAnalysis(HotakeCandidateAiInterviewAnalysisVo analysisVo) { - + public HotakeCandidateAiInterviewAnalysisDto handleCandidateAiInterviewAnalysis(HotakeCandidateAiInterviewAnalysisVo analysisVo) { + HotakeCandidateAiInterviewAnalysisDto analysisDto = new HotakeCandidateAiInterviewAnalysisDto(); String prompt = AiCommonPromptConstants.initializationCandidateAiInterviewAnalysisPrompt(); - String qa = ""; if(CollectionUtil.isNotEmpty(analysisVo.getAiInterviewQuestionRecordVoList())){ for(HotakeAiInterviewQuestionRecordVo recordVo : analysisVo.getAiInterviewQuestionRecordVoList()){ @@ -1009,7 +1012,6 @@ public class HotakeAiCommonToolsServiceImpl extends BaseServiceImpl implements I } } } - String userPrompt_1 = "Please conduct an interview analysis based on the questions answered by the following candidates:\n" + "\n" + "**Job Information**:\n" + @@ -1032,8 +1034,57 @@ public class HotakeAiCommonToolsServiceImpl extends BaseServiceImpl implements I String resultStrOne = chatGPTClient.handleAiChat(promptJsonOne,"AIINTPF"); String resultJsonOne = resultStrOne.replaceAll("```json","").replaceAll("```",""); log.info("候选人AI面试分析结果:{}",resultJsonOne); + if(StrUtil.isNotEmpty(resultJsonOne)){ + try { + Map dataMap = JSONUtil.toBean(resultJsonOne,Map.class); + Map scoresMap = (Map)dataMap.get("scores"); + analysisDto.setTechnical(new BigDecimal(scoresMap.get("technical").toString())); + analysisDto.setCommunication(new BigDecimal(scoresMap.get("communication").toString())); + analysisDto.setCultural(new BigDecimal(scoresMap.get("cultural").toString())); - return resultJsonOne; + Map analysisMap = (Map)dataMap.get("analysis"); + Map technicalMap = (Map)analysisMap.get("technical"); + HotakeCandidateAiInterviewAnalysisAnalysisDto technicalDto = new HotakeCandidateAiInterviewAnalysisAnalysisDto(); + List improvements = (List)technicalMap.get("improvements"); + List strengths = (List)technicalMap.get("strengths"); + technicalDto.setImprovements(improvements); + technicalDto.setStrengths(strengths); + analysisDto.setTechnicalDto(technicalDto); + + Map communicationMap = (Map)analysisMap.get("communication"); + HotakeCandidateAiInterviewAnalysisAnalysisDto communicationDto = new HotakeCandidateAiInterviewAnalysisAnalysisDto(); + List improvements1 = (List)communicationMap.get("improvements"); + List strengths1 = (List)communicationMap.get("strengths"); + communicationDto.setImprovements(improvements1); + communicationDto.setStrengths(strengths1); + analysisDto.setCommunicationDto(communicationDto); + + Map culturalMap = (Map)analysisMap.get("cultural"); + HotakeCandidateAiInterviewAnalysisAnalysisDto culturalDto = new HotakeCandidateAiInterviewAnalysisAnalysisDto(); + List improvements2 = (List)culturalMap.get("improvements"); + List strengths2 = (List)culturalMap.get("strengths"); + culturalDto.setImprovements(improvements2); + culturalDto.setStrengths(strengths2); + analysisDto.setCulturalDto(culturalDto); + List notesDtoList = new ArrayList<>(); + List notesMapList = (List)dataMap.get("notes"); + if(CollectionUtil.isNotEmpty(notesMapList)){ + for(Map noteMap : notesMapList){ + HotakeCandidateAiInterviewAnalysisNotesDto analysisNotesDto = new HotakeCandidateAiInterviewAnalysisNotesDto(); + analysisNotesDto.setType(noteMap.get("type").toString()); + analysisNotesDto.setContent(noteMap.get("content").toString()); + notesDtoList.add(analysisNotesDto); + } + } + analysisDto.setNotesDtoList(notesDtoList); + + }catch (Exception e){ + throw new ServiceException("No rating"); + } + } + //todo 把面试结果分析保存到记录表中 + + return analysisDto; } /** @@ -1042,7 +1093,68 @@ public class HotakeAiCommonToolsServiceImpl extends BaseServiceImpl implements I * @return */ @Override - public String handleAiCvScoringRanking(HotakeAiCvScoringRankingVo scoringRankingVo) { + public String handleAiCvScoringRanking(HotakeAiCvScoringRankingRoleApplyVo roleApplyVo) { + if(CollectionUtil.isEmpty(roleApplyVo.getRoleApplyIds()) || roleApplyVo.getRoleApplyIds().size() < 2){ + throw new ServiceException("Please select at least two pieces of data"); + } + //根据岗位申请Id查询岗位申请数据信息列表 + HotakeRolesApplyInfo queryApplyInfo = new HotakeRolesApplyInfo(); + queryApplyInfo.setApplyRoleIdList(roleApplyVo.getRoleApplyIds()); + List applyInfoList = hotakeRolesApplyInfoMapper.selectHotakeRolesApplyInfoList(queryApplyInfo); + + List roleIds = applyInfoList.stream().map(HotakeRolesApplyInfo::getRoleId).collect(Collectors.toList());; + Set setDatas = new HashSet<>(); + setDatas.addAll(roleIds); + if(setDatas.size() > 1){ + throw new ServiceException("Only data from the same position can be selected"); + } + HotakeRolesInfo rolesInfo = hotakeRolesInfoMapper.selectHotakeRolesInfoById(roleIds.get(0)); + + HotakeAiCvScoringRankingVo scoringRankingVo = new HotakeAiCvScoringRankingVo(); + scoringRankingVo.setTitle(rolesInfo.getRoleName()); + scoringRankingVo.setDescription(rolesInfo.getAboutRole()); + + List requiredSkillsList = JSONUtil.toList(rolesInfo.getRequiredSkillsJson(),RequiredSkillsDto.class); + List requiredSkills = new ArrayList<>(); + if(CollectionUtil.isNotEmpty(requiredSkillsList)){ + for (RequiredSkillsDto requiredSkillsDto : requiredSkillsList) { + requiredSkills.add(requiredSkillsDto.getKeyValue()); + } + } + List niceToHaveSkillsList = JSONUtil.toList(rolesInfo.getNiceToHaveSkillsJson(),NiceToHaveSkillsDto.class); + List niceToHaveSkills = new ArrayList<>(); + if(CollectionUtil.isNotEmpty(niceToHaveSkillsList)){ + for (NiceToHaveSkillsDto haveSkillsDto : niceToHaveSkillsList) { + niceToHaveSkills.add(haveSkillsDto.getKeyValue()); + } + } + scoringRankingVo.setRequiredSkills(requiredSkills); + scoringRankingVo.setNiceToHaveSkills(niceToHaveSkills); + scoringRankingVo.setExperienceRequired(rolesInfo.getJobExperience()); + EducationRequirementsDto educationRequirements = JSONUtil.toBean(rolesInfo.getEducationRequirementsJson(),EducationRequirementsDto.class); + scoringRankingVo.setEducationRequired(educationRequirements.getDegree()+","+educationRequirements.getAcademicMajor()); + + List candidates = new ArrayList<>(); + for(HotakeRolesApplyInfo applyInfo : applyInfoList){ + HotakeCvInfoDto cvInfoDto = JSONUtil.toBean(applyInfo.getCvTemplateJson(), HotakeCvInfoDto.class); + HotakeCandidateVcInfoVo infoVo = new HotakeCandidateVcInfoVo(); + + infoVo.setName(cvInfoDto.getName()); + infoVo.setCurrentPosition(cvInfoDto.getPosition()); + infoVo.setWorkExperience(JSONUtil.toJsonStr(cvInfoDto.getExperience())); + infoVo.setEducation(JSONUtil.toJsonStr(cvInfoDto.getEducation())); + + List skills = new ArrayList<>(); + if(CollectionUtil.isNotEmpty(cvInfoDto.getSkillsTools())){ + for (VcSkillsToolsDto skillsToolsDto : cvInfoDto.getSkillsTools()) { + skills.add(skillsToolsDto.getContent()); + } + } + infoVo.setSkills(skills); + infoVo.setSummary(cvInfoDto.getAbout()); + candidates.add(infoVo); + } + scoringRankingVo.setCandidates(candidates); String prompt = AiCommonPromptConstants.initializationAiCvScoringRankingPrompt(); String userPrompt_1 = JSONUtil.toJsonStr(scoringRankingVo); @@ -1061,6 +1173,8 @@ public class HotakeAiCommonToolsServiceImpl extends BaseServiceImpl implements I String resultJsonOne = resultStrOne.replaceAll("```json","").replaceAll("```",""); log.info("招聘者AI简历评分和排名系统结果:{}",resultJsonOne); + + return resultJsonOne; }