feat: 初始化考培练系统项目
- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
272
backend/app/services/ability_assessment_service.py
Normal file
272
backend/app/services/ability_assessment_service.py
Normal file
@@ -0,0 +1,272 @@
|
||||
"""
|
||||
能力评估服务
|
||||
用于分析用户对话数据,生成能力评估报告和课程推荐
|
||||
|
||||
使用 Python 原生实现
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, Any, List, Literal
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.models.ability import AbilityAssessment
|
||||
from app.services.ai import ability_analysis_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AbilityAssessmentService:
|
||||
"""能力评估服务类"""
|
||||
|
||||
async def analyze_yanji_conversations(
|
||||
self,
|
||||
user_id: int,
|
||||
phone: str,
|
||||
db: AsyncSession,
|
||||
yanji_service,
|
||||
engine: Literal["v2"] = "v2"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
分析言迹对话并生成能力评估及课程推荐
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
phone: 用户手机号(用于获取言迹数据)
|
||||
db: 数据库会话
|
||||
yanji_service: 言迹服务实例
|
||||
engine: 引擎类型(v2=Python原生)
|
||||
|
||||
Returns:
|
||||
评估结果字典,包含:
|
||||
- assessment_id: 评估记录ID
|
||||
- total_score: 综合评分
|
||||
- dimensions: 能力维度列表
|
||||
- recommended_courses: 推荐课程列表
|
||||
- conversation_count: 分析的对话数量
|
||||
|
||||
Raises:
|
||||
ValueError: 未找到员工的录音记录
|
||||
Exception: API调用失败或其他错误
|
||||
"""
|
||||
logger.info(f"开始分析言迹对话: user_id={user_id}, phone={phone}, engine={engine}")
|
||||
|
||||
# 1. 获取员工对话数据(最多10条录音)
|
||||
conversations = await yanji_service.get_employee_conversations_for_analysis(
|
||||
phone=phone,
|
||||
limit=10
|
||||
)
|
||||
|
||||
if not conversations:
|
||||
logger.warning(f"未找到员工的录音记录: user_id={user_id}, phone={phone}")
|
||||
raise ValueError("未找到该员工的录音记录")
|
||||
|
||||
# 2. 合并所有对话历史
|
||||
all_dialogues = []
|
||||
for conv in conversations:
|
||||
all_dialogues.extend(conv['dialogue_history'])
|
||||
|
||||
logger.info(
|
||||
f"准备分析: user_id={user_id}, "
|
||||
f"对话数={len(conversations)}, "
|
||||
f"总轮次={len(all_dialogues)}"
|
||||
)
|
||||
|
||||
used_engine = "v2"
|
||||
|
||||
# Python 原生实现
|
||||
logger.info(f"调用原生能力分析服务")
|
||||
|
||||
# 将对话历史格式化为文本
|
||||
dialogue_text = self._format_dialogues_for_analysis(all_dialogues)
|
||||
|
||||
# 调用原生服务
|
||||
result = await ability_analysis_service.analyze(
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
dialogue_history=dialogue_text
|
||||
)
|
||||
|
||||
if not result.success:
|
||||
raise Exception(f"能力分析失败: {result.error}")
|
||||
|
||||
# 转换为兼容格式
|
||||
analysis_result = {
|
||||
"analysis": {
|
||||
"total_score": result.total_score,
|
||||
"ability_dimensions": [
|
||||
{"name": d.name, "score": d.score, "feedback": d.feedback}
|
||||
for d in result.ability_dimensions
|
||||
],
|
||||
"course_recommendations": [
|
||||
{
|
||||
"course_id": c.course_id,
|
||||
"course_name": c.course_name,
|
||||
"recommendation_reason": c.recommendation_reason,
|
||||
"priority": c.priority,
|
||||
"match_score": c.match_score,
|
||||
}
|
||||
for c in result.course_recommendations
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
f"能力分析完成 - total_score: {result.total_score}, "
|
||||
f"provider: {result.ai_provider}, latency: {result.ai_latency_ms}ms"
|
||||
)
|
||||
|
||||
# 4. 提取结果
|
||||
analysis = analysis_result.get('analysis', {})
|
||||
ability_dims = analysis.get('ability_dimensions', [])
|
||||
course_recs = analysis.get('course_recommendations', [])
|
||||
total_score = analysis.get('total_score')
|
||||
|
||||
logger.info(
|
||||
f"分析完成 (engine={used_engine}): total_score={total_score}, "
|
||||
f"dimensions={len(ability_dims)}, courses={len(course_recs)}"
|
||||
)
|
||||
|
||||
# 5. 保存能力评估记录到数据库
|
||||
assessment = AbilityAssessment(
|
||||
user_id=user_id,
|
||||
source_type='yanji_badge',
|
||||
source_id=','.join([str(c['audio_id']) for c in conversations]),
|
||||
total_score=total_score,
|
||||
ability_dimensions=ability_dims,
|
||||
recommended_courses=course_recs,
|
||||
conversation_count=len(conversations)
|
||||
)
|
||||
|
||||
db.add(assessment)
|
||||
await db.commit()
|
||||
await db.refresh(assessment)
|
||||
|
||||
logger.info(
|
||||
f"评估记录已保存: assessment_id={assessment.id}, "
|
||||
f"user_id={user_id}, total_score={total_score}"
|
||||
)
|
||||
|
||||
# 6. 返回评估结果
|
||||
return {
|
||||
"assessment_id": assessment.id,
|
||||
"total_score": total_score,
|
||||
"dimensions": ability_dims,
|
||||
"recommended_courses": course_recs,
|
||||
"conversation_count": len(conversations),
|
||||
"analyzed_at": assessment.analyzed_at,
|
||||
"engine": used_engine,
|
||||
}
|
||||
|
||||
def _format_dialogues_for_analysis(self, dialogues: List[Dict[str, Any]]) -> str:
|
||||
"""
|
||||
将对话历史列表格式化为文本
|
||||
|
||||
Args:
|
||||
dialogues: 对话历史列表,每项包含 speaker, content 等字段
|
||||
|
||||
Returns:
|
||||
格式化后的对话文本
|
||||
"""
|
||||
lines = []
|
||||
for i, d in enumerate(dialogues, 1):
|
||||
speaker = d.get('speaker', 'unknown')
|
||||
content = d.get('content', '')
|
||||
|
||||
# 统一说话者标识
|
||||
if speaker in ['consultant', 'employee', 'user', '员工']:
|
||||
speaker_label = '员工'
|
||||
elif speaker in ['customer', 'client', '顾客', '客户']:
|
||||
speaker_label = '顾客'
|
||||
else:
|
||||
speaker_label = speaker
|
||||
|
||||
lines.append(f"[{i}] {speaker_label}: {content}")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
async def get_user_assessment_history(
|
||||
self,
|
||||
user_id: int,
|
||||
db: AsyncSession,
|
||||
limit: int = 10
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取用户的能力评估历史记录
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
db: 数据库会话
|
||||
limit: 返回记录数量限制
|
||||
|
||||
Returns:
|
||||
评估历史记录列表
|
||||
"""
|
||||
stmt = (
|
||||
select(AbilityAssessment)
|
||||
.where(AbilityAssessment.user_id == user_id)
|
||||
.order_by(AbilityAssessment.analyzed_at.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
assessments = result.scalars().all()
|
||||
|
||||
history = []
|
||||
for assessment in assessments:
|
||||
history.append({
|
||||
"id": assessment.id,
|
||||
"source_type": assessment.source_type,
|
||||
"total_score": assessment.total_score,
|
||||
"ability_dimensions": assessment.ability_dimensions,
|
||||
"recommended_courses": assessment.recommended_courses,
|
||||
"conversation_count": assessment.conversation_count,
|
||||
"analyzed_at": assessment.analyzed_at.isoformat() if assessment.analyzed_at else None,
|
||||
"created_at": assessment.created_at.isoformat() if assessment.created_at else None
|
||||
})
|
||||
|
||||
logger.info(f"获取评估历史: user_id={user_id}, count={len(history)}")
|
||||
return history
|
||||
|
||||
async def get_assessment_detail(
|
||||
self,
|
||||
assessment_id: int,
|
||||
db: AsyncSession
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
获取单个评估记录的详细信息
|
||||
|
||||
Args:
|
||||
assessment_id: 评估记录ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
评估详细信息
|
||||
|
||||
Raises:
|
||||
ValueError: 评估记录不存在
|
||||
"""
|
||||
stmt = select(AbilityAssessment).where(AbilityAssessment.id == assessment_id)
|
||||
result = await db.execute(stmt)
|
||||
assessment = result.scalar_one_or_none()
|
||||
|
||||
if not assessment:
|
||||
raise ValueError(f"评估记录不存在: assessment_id={assessment_id}")
|
||||
|
||||
return {
|
||||
"id": assessment.id,
|
||||
"user_id": assessment.user_id,
|
||||
"source_type": assessment.source_type,
|
||||
"source_id": assessment.source_id,
|
||||
"total_score": assessment.total_score,
|
||||
"ability_dimensions": assessment.ability_dimensions,
|
||||
"recommended_courses": assessment.recommended_courses,
|
||||
"conversation_count": assessment.conversation_count,
|
||||
"analyzed_at": assessment.analyzed_at.isoformat() if assessment.analyzed_at else None,
|
||||
"created_at": assessment.created_at.isoformat() if assessment.created_at else None
|
||||
}
|
||||
|
||||
|
||||
def get_ability_assessment_service() -> AbilityAssessmentService:
|
||||
"""获取能力评估服务实例(依赖注入)"""
|
||||
return AbilityAssessmentService()
|
||||
Reference in New Issue
Block a user