大版本修改前的方案改造 实体类修改

This commit is contained in:
zk
2026-03-26 16:46:13 +08:00
parent ec6c2d9579
commit 271d43a5a5
7 changed files with 266 additions and 9 deletions
@@ -0,0 +1,163 @@
# 评分体系改造方案
## 核心公式
`MatchScore = Sedu × 30% + Sexp × 40% + Sskill × 30%`
---
## 一、表结构改动
### 1. 新建表:bg_major_category(专业分类,三级树形)
```sql
create table bg_major_category (
id bigint not null comment 'ID' primary key,
name varchar(50) not null comment '类型名称',
root_id bigint not null comment '根节点ID,顶级=自身ID',
parent_id bigint not null comment '父级ID0=顶级',
level int not null comment '层级 1=一级 2=二级 3=三级'
) comment '专业分类' row_format = DYNAMIC;
```
基于《教育部普通高等学校本科专业目录》建立层级索引,AI 将简历/岗位专业归一化到标准 ID。
### 2. bg_skill_tag — 改造
| 操作 | 字段 | 说明 |
|------|------|------|
| 删除 | `category_id` | 不再挂岗位分类 |
| 新增 | `name` 唯一索引 | 去重用 |
定位从"人工预定义"变为"AI 提取自动入库"。提取技能时先查表去重,不存在则 insert,拿 ID 写关联表。
### 3. bg_job — 新增字段
| 字段 | 类型 | 备注 |
|------|------|------|
| `required_major_ids` | json | 要求专业ID数组,关联 bg_major_category |
| `major_sensitivity` | tinyint | 专业敏感度 0=不限 1=优先 2=强制 |
### 4. bg_user_profile — 新增字段
| 字段 | 类型 | 备注 |
|------|------|------|
| `major_ids` | json | 用户专业ID数组,关联 bg_major_category |
| `school_rank` | tinyint | 学校等级 1=C9/985/QS前50 2=211/双一流/QS前200 3=普通一本/QS前500 4=其他 |
| `company_prestige` | tinyint | 公司背书 1=名企 2=普通实习 3=校内活动 4=无 |
| `experience_duration` | tinyint | 经历时长 1=≥3月 2=1-3月 3=≤1月 |
| `role_depth` | tinyint | 职责深度 1=主导/创新 2=执行/应用 3=辅助/学习 |
| `output_quality` | tinyint | 量化产出 1=有量化结果 2=有具体产出 3=纯描述 |
| `honors` | json | 荣誉/竞赛/论文,格式:{"national":[],"provincial":[],"school":[],"paper":[]} |
以上字段全部不在前端展示,纯用于评分计算,由 AI 分析简历后写入。
### 5. bg_user_profile — 废弃字段
| 字段 | 说明 |
|------|------|
| `work_years` | 被经历质量维度替代 |
| `experience_industry_ids` | 行业前置过滤废除 |
### 6. 不变的表
- `bg_job_skill_tag_relation` — 结构不变,数据来源从预定义标签变为 AI 自动入库标签
- `bg_user_profile_skill_tag_relation` — 同上
---
## 二、评分算法
### 维度1:教育背景(Sedu)— 权重 30%
学校等级分(Srank)与专业相关度(Smajor)的权重由专业敏感度决定:
| 专业敏感度 | 公式 | 特殊规则 |
|-----------|------|---------|
| 强制(2 | Sedu = Srank × 0.4 + Smajor × 0.6 | 若 Smajor < 40Sedu = Sedu × 0.5 |
| 优先(1 | Sedu = Srank × 0.6 + Smajor × 0.4 | 无 |
| 不限(0 | Sedu = Srank × 1.0 | Smajor 默认100,不减分 |
学校等级映射:1→100, 2→80, 3→60, 4→40
专业相关度(基于 bg_major_category 树形匹配):
- 完全匹配(同三级专业)→ 100
- 相关对口(同二级专业类)→ 70
- 跨专业相关(同一级学科门类)→ 30
- 不相关 → 0
### 维度2:经历质量(Sexp)— 权重 40%
`Sexp = 公司背书 × 30% + 时长 × 10% + 经历深度 × 60%`
公司背书映射:1→100, 2→60, 3→30, 4→0
经历时长映射:1→100, 2→60, 3→30
经历深度(60%)内部拆解:
| 子项 | 占比 | 说明 |
|------|------|------|
| 语义相关度 | 40% | AI 判断经历与岗位方向的相关性 |
| 职责深度 | 25% | 映射:1→100, 2→80, 3→40 |
| 量化产出 | 20% | 映射:1→100, 2→70, 3→40 |
| 荣誉加分 | 15% | 封顶累加制,总分不超过100 |
荣誉分值规则:
- 国奖级(national):每项 +15-20
- 省奖/大厂赛(provincial):每项 +8-10
- 院校级/专业证(school):每项 +3-5
- 顶刊顶会论文:每项 +15-20,普通论文:每项 +8-10
### 维度3:技能匹配(Sskill)— 权重 30%
废除产品方案中的向量距离、技能近亲表、分层技能池。
采用简化方案:
- AI 自由提取技能 + prompt demo 约束颗粒度
- 提取结果入 bg_skill_tag(去重),写关联表
- 匹配时通过 skill_tag_id 做集合碰撞
- 公式:`Sskill = (匹配数量 / 岗位要求数量) × 100`
---
## 三、数据清洗改造
### 岗位清洗(JobCleanService
改动:
- 第二次 AI 调用从"预定义标签匹配"改为"自由提取 + prompt demo 约束"
- 新增:提取要求专业 → 归一化到 bg_major_category ID
- 新增:提取专业敏感度
- 技能入库逻辑:查 bg_skill_tag 去重 → insert → 写关联表
### 用户简历分析(UserSkillTagMatchService
改动:
- 可能简化为一次 AI 调用,直接从简历全文提取
- 新增:识别学校等级、公司背书、经历时长、职责深度、量化产出、荣誉
- 新增:提取用户专业 → 归一化到 bg_major_category ID
- 技能入库逻辑同上
### DictCacheService
- 删掉 skillTagMap、getSkillTagText()、getSkillTagIds()
- 新增:加载 bg_major_category 缓存(供专业归一化使用)
---
## 四、影响文件汇总
| 文件 | 改动 |
|------|------|
| SkillTag.java | 删 categoryId |
| Job.java | 加 requiredMajorIds、majorSensitivity |
| UserProfile.java | 加 7 个新字段,删 workYears、experienceIndustryIds |
| MajorCategory.java | 新建 PO |
| MajorCategoryMapper.java | 新建 Mapper |
| DictCacheService | 删技能缓存,加专业缓存 |
| JobCleanService | 改技能提取 + 新增专业/敏感度提取 |
| JobCleanTransactionService | 改技能入库逻辑 |
| UserSkillTagMatchService | 大改,新增多维度识别 |
| JobMatchService | 重写,三维度算法全换 |
| JobMatchScoreDto | 改字段(教育/经历/技能) |
| JobDto / JobDetailDto | matchScore 范围可能变化 |
@@ -0,0 +1,13 @@
package org.jiayunet.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.jiayunet.pojo.po.MajorCategory;
/**
* 专业分类Mapper
*
* @author zk
*/
@Mapper
public interface MajorCategoryMapper extends CommonMapper<MajorCategory> {
}
@@ -63,6 +63,13 @@ public class Job {
/** 要求的行业经验ID,关联bg_industry */ /** 要求的行业经验ID,关联bg_industry */
private Long requiredIndustryId; private Long requiredIndustryId;
/** 要求专业ID数组,关联bg_major_category */
@TableField(typeHandler = JacksonTypeHandler.class)
private List<Long> requiredMajorIds;
/** 专业敏感度 0=不限 1=优先 2=强制 */
private Integer majorSensitivity;
/** 来源链接 */ /** 来源链接 */
private String sourceUrl; private String sourceUrl;
@@ -0,0 +1,32 @@
package org.jiayunet.pojo.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 专业分类表(bg_major_category
* <p>基于教育部专业目录,三级树形结构</p>
*
* @author zk
*/
@Data
@TableName(value = "bg_major_category")
public class MajorCategory {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/** 类型名称 */
private String name;
/** 根节点ID,顶级=自身ID */
private Long rootId;
/** 父级ID0=顶级 */
private Long parentId;
/** 层级 1=一级 2=二级 3=三级 */
private Integer level;
}
@@ -18,9 +18,6 @@ public class SkillTag {
@TableId(type = IdType.ASSIGN_ID) @TableId(type = IdType.ASSIGN_ID)
private Long id; private Long id;
/** 标签名称 */ /** 标签名称(唯一索引) */
private String name; private String name;
/** 所属岗位类型ID */
private Long categoryId;
} }
@@ -7,6 +7,8 @@ import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data; import lombok.Data;
import org.jiayunet.pojo.vo.UserHonorsVo;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
@@ -46,12 +48,28 @@ public class UserProfile {
/** 作品集链接 */ /** 作品集链接 */
private String portfolioUrl; private String portfolioUrl;
/** 工作年限(不展示,用于适配度计算) */ /** 用户专业ID数组,关联bg_major_category(不展示,用于适配度计算) */
private Integer workYears;
/** 拥有经验的行业ID列表(不展示,用于适配度计算) */
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = JacksonTypeHandler.class)
private List<Long> experienceIndustryIds; private List<Long> majorIds;
/** 学校等级 1=C9/985/QS前50 2=211/双一流/QS前200 3=普通一本/QS前500 4=其他(不展示,用于适配度计算) */
private Integer schoolRank;
/** 公司背书 1=名企 2=普通实习 3=校内活动 4=无(不展示,用于适配度计算) */
private Integer companyPrestige;
/** 经历时长 1=≥3月 2=1-3月 3=≤1月(不展示,用于适配度计算) */
private Integer experienceDuration;
/** 职责深度 1=主导/创新 2=执行/应用 3=辅助/学习(不展示,用于适配度计算) */
private Integer roleDepth;
/** 量化产出 1=有量化结果 2=有具体产出 3=纯描述(不展示,用于适配度计算) */
private Integer outputQuality;
/** 荣誉/竞赛/论文(不展示,用于适配度计算) */
@TableField(typeHandler = JacksonTypeHandler.class)
private UserHonorsVo honors;
/** 技能标签列表 */ /** 技能标签列表 */
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = JacksonTypeHandler.class)
@@ -0,0 +1,27 @@
package org.jiayunet.pojo.vo;
import lombok.Data;
import java.util.List;
/**
* 用户荣誉/竞赛/论文VO
* <p>存储在 bg_user_profile.honors JSON 字段中</p>
*
* @author zk
*/
@Data
public class UserHonorsVo {
/** 国奖级:ACM-ICPC、数学建模国赛、互联网+金奖、挑战杯国赛、国家奖学金等 */
private List<String> national;
/** 省奖/大厂赛:省赛一/二等奖、蓝桥杯、阿里/腾讯专项赛等 */
private List<String> provincial;
/** 院校级/专业证:优秀毕业生、院系奖学金、CPA/CFA、软考高级等 */
private List<String> school;
/** 论文:顶刊顶会/普通论文 */
private List<String> paper;
}