Files
012-kaopeilian/backend/app/services/ability_assessment_service.py
111 998211c483 feat: 初始化考培练系统项目
- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
2026-01-24 19:33:28 +08:00

273 lines
9.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
能力评估服务
用于分析用户对话数据,生成能力评估报告和课程推荐
使用 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()