包含前端(Vue3 + VueFlow 画布)、后端(Go)、文档体系。 - 工作台画布:节点拖放、连线模式、右键菜单、AI 助手 - 后端:连接器 API、专员种子数据 - 导航:左侧导航、工坊、市场、控制台
17 KiB
pj0231-eaisalestraining 完整分析报告(对比 + 差距 + 路线图 + 设计稿)
版本:V1.0 | 日期:2026-08-16 对比对象:pj006-zhilianyuan2(智练猿) 范围:知识库能力、考试能力、测试/自测能力、岗位与知识对应能力 原则:以实际代码为准(本项目 PRD 文档存在过时,见 §1.3)
注: 本文基于 2026-08-16 代码快照(后端 Go + Gin + GORM,检索为 brute-force 余弦 + bge-m3);数据库与向量栈已决策迁移至 MySQL 8.0 + FAISS,见
docs/db_schema.md/docs/deploy.md。
目录
- Part A —— 现状对比
- Part B —— 差距汇总与反向差异
- Part C —— 改进路线图
- Part D —— 设计稿:岗位与知识对应能力(核心)
- Part E —— 设计要点:其余改进项
- Part F —— 待拍板决策点与下一步
Part A —— 现状对比
A1. 一句话结论
两个项目不是同一量级:
- pj0231 是一个极简单租户、内部专属培训平台——Go 后端约 4,900 行,11 张表,2 个角色,聚焦「看资料 → AI 答疑 → 考试验收」的窄闭环。
- pj006 是一个多租户、多角色、带完整 AI 对练 / 岗位能力模型的通用销售培训平台——Python 后端约 1.7 万行 + 独立 engine 库,23 版 schema、40+ 张表,4 个前端。
在四个对比维度上,pj006 全部能力更全、更深;但 pj0231 在个别点上有「更实」的实现(见 §B3)。
A2. 系统定位与技术架构
| 维度 | pj0231 | pj006 |
|---|---|---|
| 定位 | 博昇内部培训(单企业) | 通用 AI 销售培训平台(多企业 SaaS) |
| 后端 | Go + Gin + GORM + SQLite | Python 3.10 + FastAPI + SQLite(per-org 分库) |
| 前端 | 1 个:Vue3 + Vite + Element Plus | 4 个:app / orgadmin / admin / platformadmin |
| 角色 | 2(employee / admin) | 多级(学员 / 组织管理员 oadmin / 平台管理员 padmin / 教练 trainer) |
| 租户 | 单租户 | 多租户(orgs 注册表 + 每 org 一个 <org>.db) |
| 代码量 | ~4,900 行 Go | ~17,200 行 Python + zlylibs/engine |
| 数据表 | 11 张 | 40+ 张(schema v23) |
| AI 底座 | 内网 OpenAI 兼容接口(Ollama LLM + bge-m3) | OpenAI 兼容路由(trainee/coach/eval 三 slot) |
A3. 重要前提:pj0231 文档已过时
docs/PRD.md写「FastAPI + MySQL + 不做向量检索」;实际代码是 Go + Gin + GORM + SQLite,检索已实现向量(brute-force 余弦 + bge-m3)。- 本项目权威信息以
eaisalestrain_app/CLAUDE.md与实际代码为准。
A4. 知识库能力对比
| 能力 | pj0231 | pj006 |
|---|---|---|
| 素材摄入 | ✅ 上传→审批→转换→切片(真实跑通) | ⚠️ 知识工厂骨架完备,Phase 1 解析抽取「不真跑、停 pending」 |
| 文档转换 | LibreOffice + pdftotext,切片入 knowledge_chunk |
设计 parse/structured/extraction_blocks 多阶段管线,未实跑 |
| 审批流 | ✅ pending/approved/rejected,审批前置 |
✅ 更细:working_knowledge_points 待复核 + kb_publish_records 发布链 |
| 知识组织 | 扁平 knowledge_chunk + knowledge_source |
多层:knowledge_points → knowledge_meta → knowledge_units(统一主干)→ knowledge_chunks(带 embedding_status) |
| 术语/规则 | ❌ 无 | ✅ knowledge_terms、industry_rules |
| 检索 | ✅ 混合检索(向量余弦 + 关键词兜底) | ⚠️ 实跑 jieba 分词 + LIKE;向量仅文档宣称 |
| 自动出题 | ❌ 无 | ✅ 设计:working_questions 自动生成(待复核) |
| 知识-题目/场景关联 | 仅题目挂 course_id |
✅ question_knowledge_units、scene_knowledge_units、knowledge_unit_links |
A5. 考试能力对比
| 能力 | pj0231 | pj006 |
|---|---|---|
| 题型 | 单选 / 多选 / 判断 | 单选 / 多选 / 判断 / 简答 essay |
| 简答评分 | ❌ 无 | ✅ LLM 按 rubric 评分,得分率持久化 exam_session_grades |
| 组卷 | ✅ 按知识域 + 随机抽题 | ⚠️ 固定 question_ids 列表;蓝图组卷未接考试服务 |
| 考试次数 | 正式考只允许一次 | ✅ max_attempts + deadline |
| 自测 | ✅ 自测(即时对错、不存记录) | ✅ 练习走独立 practice 模块 |
| 判分 | 确定性比对 | 选择题确定性 + 简答 LLM |
| 成绩分析 | 仅存得分/正确率/明细 | ✅ 维度能力分析(weak/ok/strong)+ 薄弱题 + 改进建议 |
| 错题联动 | ❌ 无 | ✅ 交卷错题自动入 mistake_records |
A6. 测试 / 自测 / 评估能力对比
A6.1 面向学员(产品能力)
| 能力 | pj0231 | pj006 |
|---|---|---|
| 自测练习 | ✅ 自测(不存记录) | ✅ 每日一练 practice(游戏化:段位/星/连击/经验/积分) |
| 错题本 | ❌ 无 | ✅ mistake_records(练习+考试聚合) |
| 笔记/收藏 | ❌ 无 | ✅ study_notes + 收藏 |
| 学习档案/学情 | ❌ PRD 明确禁止 | ✅ 学习档案 dashboard + 能力对标 |
| AI 对练复盘评分 | ❌ 无 | ✅ dialogue_review_scores + ReviewScoreService |
A6.2 面向软件工程(自动化测试)
| 维度 | pj0231 | pj006 |
|---|---|---|
| 后端测试 | ❌ 0 个 _test.go |
✅ 31 个 pytest 文件 |
| 引擎测试 | 无 engine 层 | ✅ zlylibs/engine/tests 约 20 个 |
| 测试策略文档 | ❌ 无 | ✅ docs/12_Test_Strategy |
| CI/依赖管理 | 无 | ✅ pyproject dev deps |
A7. 岗位与知识对应能力(差距最大维度)
pj0231 是结构性缺失:用户只有 role,完全没有岗位概念,也没有岗位→知识→考试的能力链。
| 能力 | pj0231 | pj006 |
|---|---|---|
| 岗位表 | ❌ 无 | ✅ positions |
| 岗位↔用户绑定 | ❌ 无 | ✅ user_positions |
| 岗位能力模型(洋葱) | ❌ 无 | ✅ competency_layers + competency_levels(L1-L4)+ competency_details |
| 能力模型自动生成 | ❌ 无 | ✅ generate_competency_model |
| 岗位↔知识映射 | ❌ 无 | ✅ position_knowledge_units + auto_match |
| 岗位考试蓝图 | ❌ 无 | ✅ position_exam_blueprints |
| 标准岗位/职级代码 | ❌ 无 | ✅ SD01_Standard_Job_Codes |
| 岗位驱动学练考 | ❌ 无 | ✅ A11 学习 / A12 练习 / A13 蓝图组卷 |
Part B —— 差距汇总与反向差异
B1. 差距严重度总表
| 维度 | pj0231 现状 | 差距严重度 | 是否 pj0231 定位内该补 |
|---|---|---|---|
| 岗位与知识对应 | 完全缺失 | 🔴 高 | ✅ 是(本次核心诉求) |
| 错题本 / 学习闭环 | 完全缺失 | 🟠 中高 | ✅ 是(考试「考完即止」无闭环) |
| 简答题 + LLM 评分 | 缺失 | 🟠 中 | ✅ 是(LLM 底座已有,成本低) |
| 自动化测试 | 0 测试 | 🟠 中高(工程风险) | ✅ 是 |
| 多租户 / NPC 人格 / KPI | 缺失 | 🔵 低 | ❌ 否(与「极简内网单企业」冲突) |
| 向量检索 | ✅ 已实现 | — | —(反而领先 pj006) |
| 随机 + 按域组卷 | ✅ 已实现 | — | —(反而领先 pj006) |
B2. 反向差异:pj0231 反而更「实」的点
- 向量检索是真实现:pj0231 混合检索端到端可用;pj006 实跑 jieba 关键词,向量仅字段预留。
- 随机 + 按知识域自动抽题组卷:pj0231 支持;pj006 考试为固定题目列表。
- 文档→文本→检索链路真实跑通:pj0231 LibreOffice/pdftotext 切片入库完整;pj006 知识工厂 Phase 1 未实跑。
- AI 算力点计费 + 审计日志:pj0231 有
ai_call_log;pj006 未见同等计费。
Part C —— 改进路线图
C1. 优先级(按 ROI)
| 优先级 | 项 | 理由 |
|---|---|---|
| P0 | 岗位模型 + 岗位-知识映射 + 岗位驱动考试 | 最本质差距,本次核心诉求 |
| P1 | 学员「我的岗位应学清单」+ 成绩按岗位聚合 | P0 的自然延伸 |
| P2 | 错题本 + 轻量成绩档案 | 补考试闭环 |
| P2 | 简答题 + LLM 评分 | 复用 LLM 底座,成本低 |
| P2 | 后端自动化测试 | 消除 0 测试隐患 |
| 不做 | 多租户 / NPC 人格 / HEP 引擎 / KPI | 与极简定位冲突 |
C2. 分阶段总览
P0(本次落地):岗位 + 映射 + 岗位驱动考试组卷
↓
P1:我的岗位应学清单 · 成绩按岗位聚合 · 岗位考试蓝图
↓
P2:错题本 · 简答题 LLM 评分 · 自动化测试
Part D —— 设计稿:岗位与知识对应能力(核心)
完整设计见《设计稿_岗位与知识对应能力.md》,此处为完整版正文。
D1. 目标与原则
目标:落地最小岗位驱动闭环 —— 岗位 → 用户绑定 → 岗位知识映射 → 岗位驱动考试组卷。
原则:保持极简;复用现有 AutoMigrate / RequireAdmin / pickQuestions;级别词汇对齐 pj006 L1-L4 但不建独立级别表;单岗位优先。
D2. 改动点清单
| 现有资产 | 位置 | 改动 |
|---|---|---|
User |
internal/model/user.go |
加列 PositionID *uint(可空) |
ExamPaper |
internal/model/exam_paper.go |
加列 PositionID *uint(可空) |
| 迁移 | internal/store/db.go |
AutoMigrate 追加 2 张新表 |
| 路由 | internal/api/router.go |
新增岗位端点(admin 组) |
| 抽题 | internal/api/exam.go pickQuestions |
增加岗位知识映射过滤分支 |
| 种子 | internal/store/seed.go |
可选预置默认岗位 |
D3. 数据模型
3.1 position(岗位表)
type Position struct {
ID uint `gorm:"primaryKey" json:"id"`
Code string `gorm:"size:32;uniqueIndex;not null" json:"code"`
Name string `gorm:"size:64;not null" json:"name"`
Description string `gorm:"type:text" json:"description"`
Status string `gorm:"size:16;not null;default:active;index" json:"status"` // active / inactive
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (Position) TableName() string { return "position" }
3.2 position_knowledge(岗位知识映射,核心)
type PositionKnowledge struct {
ID uint `gorm:"primaryKey" json:"id"`
PositionID uint `gorm:"not null;index" json:"position_id"`
Domain string `gorm:"size:16;not null;index" json:"domain"` // company / product / sales
CourseID *uint `gorm:"index" json:"course_id"` // 可空
ProductID *uint `gorm:"index" json:"product_id"` // 可空
RequiredLevel string `gorm:"size:16;not null;default:L1" json:"required_level"` // L1/L2/L3/L4
Weight float64 `gorm:"not null;default:1" json:"weight"`
IsMandatory bool `gorm:"not null;default:true" json:"is_mandatory"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (PositionKnowledge) TableName() string { return "position_knowledge" }
3.3 级别常量
const (
LevelL1 = "L1" // 入门级
LevelL2 = "L2" // 稳定执行级
LevelL3 = "L3" // 复杂处理级
LevelL4 = "L4" // 体系策略级
)
var validLevels = map[string]bool{LevelL1: true, LevelL2: true, LevelL3: true, LevelL4: true}
D4. API 设计
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/positions |
admin | 岗位列表 |
| POST | /api/positions |
admin | 新建岗位 |
| PUT | /api/positions/:id |
admin | 编辑岗位 |
| DELETE | /api/positions/:id |
admin | 停用(软删) |
| GET | /api/positions/:id/knowledge |
admin | 岗位知识映射列表 |
| PUT | /api/positions/:id/knowledge |
admin | 保存映射(整表覆盖) |
| PUT | /api/users/:id/position |
admin | 设置/清除用户岗位 |
| GET | /api/my/position |
员工 | 我的岗位 + 应学范围(P1) |
D5. 岗位驱动考试组卷(核心算法改动)
修改 pickQuestions,在 PositionID != nil 时按岗位知识映射圈定题池,否则回退原 domain 抽题:
func pickQuestions(p model.ExamPaper) ([]model.Question, error) {
pool := store.DB.Where("status = ?", "active")
if p.PositionID != nil {
var pks []model.PositionKnowledge
if err := store.DB.Where("position_id = ?", *p.PositionID).Find(&pks).Error; err != nil {
return nil, err
}
domains := make([]string, 0, len(pks))
courseIDs := make([]uint, 0, len(pks))
for _, pk := range pks {
domains = append(domains, pk.Domain)
if pk.CourseID != nil {
courseIDs = append(courseIDs, *pk.CourseID)
}
}
domains = dedupe(domains)
if len(domains) > 0 {
pool = pool.Where("domain IN ?", domains)
}
if len(courseIDs) > 0 {
pool = pool.Where("course_id IN ? OR course_id IS NULL", courseIDs)
}
} else {
domains := splitDomains(p.Domain)
if len(domains) > 0 {
pool = pool.Where("domain IN ?", domains)
}
}
var qs []model.Question
if err := pool.Order("id ASC").Find(&qs).Error; err != nil {
return nil, err
}
if len(qs) < p.QuestionCount {
return nil, fmt.Errorf("题库题目不足:需要 %d,可用 %d", p.QuestionCount, len(qs))
}
if p.Randomize {
rand.Shuffle(len(qs), func(i, j int) { qs[i], qs[j] = qs[j], qs[i] })
}
return qs[:p.QuestionCount], nil
}
配套:CreatePaper / UpdatePaper 追加校验——岗位考试必须已配置至少一条知识映射,否则拒绝。
D6. 前端设计
- 导航:管理员「系统管理」下拉新增 「岗位管理」。
- 页面:岗位列表(CRUD)+ 岗位知识映射编辑面板(domain 下拉 + 课程/产品下拉 + L1-L4 + 权重 + 必学开关)。
- 改造:用户管理页加「岗位」下拉;考试配置表单加「关联岗位」下拉。
D7. 分阶段
- P0:表 + API + 岗位 CRUD + 用户设岗 + 岗位驱动组卷 + 前端岗位管理页。
- P1:学员「我的岗位应学清单」+ 成绩按岗位聚合 + 岗位考试蓝图。
- P2:见 Part E。
D8. 明确不做
❌ 不建洋葱三层模型;❌ 不引入多租户;❌ 不做 LLM 自动生成能力模型;❌ 不做多岗位绑定;❌ 不做学情分析。
Part E —— 设计要点:其余改进项
E1. 错题本 + 轻量成绩档案(P2)
⚠️ 与 PRD 冲突需先拍板:PRD 第 7 节「无学情分析」。错题本是学员自助工具(不做管理层分析),可不冲突;学习档案/能力雷达属「学情分析」范畴,需产品重新确认是否放开。
数据模型
// MistakeRecord 错题本
type MistakeRecord struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `gorm:"not null;index" json:"user_id"`
QuestionID uint `gorm:"not null;index" json:"question_id"`
Source string `gorm:"size:16;not null" json:"source"` // self_test / formal
UserAnswer string `gorm:"type:text" json:"user_answer"`
CorrectAnswer string `gorm:"type:text" json:"correct_answer"`
Resolved bool `gorm:"not null;default:false;index" json:"resolved"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (MistakeRecord) TableName() string { return "mistake_record" }
联动点
- 正式考
ExamSubmit判分循环里,错题INSERT进mistake_record(source=formal);自测可选不写。 - 学员端「我的错题本」页:列表 + 「已掌握」标记(
resolved=true)。
E2. 简答题 + LLM 评分(P2)
数据模型改动
question.type增加essay;answer存 rubric 评分要点文本;options空。exam_record.detail_json已存答题明细,可直接承载简答文本。
评分流程
- 复用
internal/ai/llm.go,新增「评分」能力路由(或在ai_call_log.capability加essay_grade)。 - 交卷时对
essay题调 LLM 按 rubric 打分(0~1 得分率),持久化到detail_json。 - 选择题仍确定性判分;总分 = Σ得分率/题数×100(对齐 pj006 算法)。
E3. 自动化测试(P2)
- 后端:新增
_test.go,优先覆盖纯逻辑函数:isCorrect(判分)、splitDomains、pickQuestions(组卷)、ai.Retrieve的splitTerms/cosine。 - 命令:
go test ./...;接入start_dev或 CI 脚本。 - 前端:可选,暂缓。
Part F —— 待拍板决策点与下一步
F1. 需产品/技术拍板
- 岗位考试课程过滤语义(D5):岗位绑定具体课程时,抽题用
course_id IN ?(严格)还是course_id IN ? OR course_id IS NULL(宽松含域匹配)? - 错题本 vs 学情分析边界(E1):PRD 禁止「学情分析」,错题本是否属于允许范围?
- 岗位知识映射去重(D3.2):是否加唯一约束防重复?
- 简答题是否本期做:P0 还是 P2?
F2. 建议的下一步
- 确认 F1 四个决策点;
- 进入 P0 编码(改动文件已列于 D2,共 6 处);
- P0 完成后补
isCorrect/pickQuestions单测(借 P0 一并补测试债)。
本文件为整合版:Part A/B/C 来自《对比分析_pj0231_vs_pj006-zhilianyuan2.md》,Part D 整合自《设计稿_岗位与知识对应能力.md》,Part E 为新增其余改进项设计要点。