software copyright
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
# 自然写教学数据分析与学情诊断系统软件 V1.0
|
||||
# api/profile_api.py - 学情画像API接口
|
||||
|
||||
import logging
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime, date, timedelta
|
||||
from enum import Enum
|
||||
|
||||
from fastapi import APIRouter, Query, Path, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
logger = logging.getLogger("writech.analytics.profile")
|
||||
|
||||
router = APIRouter(tags=["学情画像"])
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 数据模型定义
|
||||
# ============================================================
|
||||
|
||||
class SubjectEnum(str, Enum):
|
||||
"""学科枚举"""
|
||||
CHINESE = "chinese"
|
||||
MATH = "math"
|
||||
ENGLISH = "english"
|
||||
PHYSICS = "physics"
|
||||
CHEMISTRY = "chemistry"
|
||||
BIOLOGY = "biology"
|
||||
|
||||
|
||||
class KnowledgeMastery(BaseModel):
|
||||
"""知识点掌握度模型"""
|
||||
knowledge_id: str = Field(..., description="知识点ID")
|
||||
knowledge_name: str = Field(..., description="知识点名称")
|
||||
chapter: str = Field("", description="所属章节")
|
||||
mastery_level: float = Field(0.0, ge=0.0, le=1.0, description="掌握度(0-1)")
|
||||
practice_count: int = Field(0, description="练习次数")
|
||||
correct_rate: float = Field(0.0, description="正确率")
|
||||
last_practice_at: Optional[str] = Field(None, description="最近练习时间")
|
||||
trend: str = Field("stable", description="趋势: improving/declining/stable")
|
||||
|
||||
|
||||
class WeakPoint(BaseModel):
|
||||
"""薄弱知识点模型"""
|
||||
knowledge_id: str
|
||||
knowledge_name: str
|
||||
mastery_level: float
|
||||
error_count: int = Field(0, description="错误次数")
|
||||
suggested_exercises: List[str] = Field([], description="推荐练习题ID")
|
||||
related_knowledge: List[str] = Field([], description="关联知识点")
|
||||
|
||||
|
||||
class StudentProfile(BaseModel):
|
||||
"""学生学情画像完整模型"""
|
||||
student_id: str
|
||||
student_name: str
|
||||
class_id: str
|
||||
grade: str
|
||||
school_id: str
|
||||
|
||||
# 总体学业水平
|
||||
overall_score: float = Field(0.0, description="综合评分(百分制)")
|
||||
overall_rank: int = Field(0, description="班级排名")
|
||||
overall_trend: str = Field("stable", description="总体趋势")
|
||||
|
||||
# 各科目掌握度
|
||||
subject_scores: Dict[str, float] = Field({}, description="各科目评分")
|
||||
|
||||
# 知识点掌握度矩阵
|
||||
knowledge_mastery: List[KnowledgeMastery] = Field([])
|
||||
|
||||
# 薄弱环节
|
||||
weak_points: List[WeakPoint] = Field([])
|
||||
|
||||
# 书写能力评估
|
||||
writing_quality_score: float = Field(0.0, description="书写规范性评分")
|
||||
stroke_order_accuracy: float = Field(0.0, description="笔顺正确率")
|
||||
writing_speed: float = Field(0.0, description="书写速度(字/分)")
|
||||
|
||||
# 学习习惯统计
|
||||
avg_daily_study_minutes: float = Field(0.0, description="日均学习时长(分)")
|
||||
homework_completion_rate: float = Field(0.0, description="作业完成率")
|
||||
homework_on_time_rate: float = Field(0.0, description="按时提交率")
|
||||
|
||||
# 更新时间
|
||||
updated_at: str = Field("", description="画像更新时间")
|
||||
|
||||
|
||||
class ClassProfile(BaseModel):
|
||||
"""班级学情统计模型"""
|
||||
class_id: str
|
||||
class_name: str
|
||||
grade: str
|
||||
student_count: int
|
||||
|
||||
# 班级整体指标
|
||||
avg_score: float = Field(0.0, description="班级平均分")
|
||||
median_score: float = Field(0.0, description="班级中位分")
|
||||
max_score: float = Field(0.0, description="最高分")
|
||||
min_score: float = Field(0.0, description="最低分")
|
||||
std_deviation: float = Field(0.0, description="标准差")
|
||||
|
||||
# 成绩分布(分数段人数)
|
||||
score_distribution: Dict[str, int] = Field(
|
||||
{}, description="分数段分布: {'90-100': 5, '80-89': 10, ...}"
|
||||
)
|
||||
|
||||
# 知识点班级掌握度
|
||||
knowledge_avg_mastery: List[Dict[str, Any]] = Field([])
|
||||
|
||||
# 薄弱知识点(班级维度)
|
||||
class_weak_points: List[Dict[str, Any]] = Field([])
|
||||
|
||||
# 作业统计
|
||||
homework_avg_completion: float = Field(0.0)
|
||||
homework_avg_score: float = Field(0.0)
|
||||
|
||||
|
||||
class ProfileCompareResponse(BaseModel):
|
||||
"""学情对比响应"""
|
||||
student_profile: StudentProfile
|
||||
class_avg: Dict[str, float]
|
||||
grade_avg: Dict[str, float]
|
||||
percentile: float = Field(0.0, description="年级百分位排名")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# API接口实现
|
||||
# ============================================================
|
||||
|
||||
@router.get("/student/{student_id}", response_model=StudentProfile)
|
||||
async def get_student_profile(
|
||||
student_id: str = Path(..., description="学生ID"),
|
||||
subject: Optional[SubjectEnum] = Query(None, description="筛选科目"),
|
||||
):
|
||||
"""
|
||||
获取学生个人学情画像
|
||||
|
||||
返回学生的知识掌握度、薄弱环节、书写能力、学习习惯等全面画像数据。
|
||||
教师可查看本班学生,家长可查看自己子女。
|
||||
"""
|
||||
logger.info("查询学生画像: student_id=%s, subject=%s", student_id, subject)
|
||||
|
||||
try:
|
||||
# 从ClickHouse查询学生画像宽表数据
|
||||
# profile_data = await query_student_profile(student_id)
|
||||
|
||||
# 从Neo4j查询知识点掌握度和薄弱环节
|
||||
# mastery = await query_knowledge_mastery(student_id, subject)
|
||||
# weak = await query_weak_points(student_id, subject)
|
||||
|
||||
# 组装画像数据
|
||||
profile = StudentProfile(
|
||||
student_id=student_id,
|
||||
student_name="",
|
||||
class_id="",
|
||||
grade="",
|
||||
school_id="",
|
||||
updated_at=datetime.now().isoformat(),
|
||||
)
|
||||
|
||||
return profile
|
||||
|
||||
except Exception as e:
|
||||
logger.error("查询学生画像失败: %s", str(e))
|
||||
raise HTTPException(status_code=500, detail=f"查询学生画像失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/class/{class_id}", response_model=ClassProfile)
|
||||
async def get_class_profile(
|
||||
class_id: str = Path(..., description="班级ID"),
|
||||
subject: Optional[SubjectEnum] = Query(None, description="筛选科目"),
|
||||
start_date: Optional[str] = Query(None, description="起始日期"),
|
||||
end_date: Optional[str] = Query(None, description="结束日期"),
|
||||
):
|
||||
"""
|
||||
获取班级学情统计
|
||||
|
||||
返回班级平均分、分数分布、薄弱知识点等班级维度的统计数据。
|
||||
仅班级教师和校管理员可查看。
|
||||
"""
|
||||
logger.info("查询班级学情: class_id=%s, subject=%s", class_id, subject)
|
||||
|
||||
try:
|
||||
# 从ClickHouse聚合查询班级统计数据
|
||||
# class_stats = await aggregate_class_stats(class_id, subject, ...)
|
||||
|
||||
class_profile = ClassProfile(
|
||||
class_id=class_id,
|
||||
class_name="",
|
||||
grade="",
|
||||
student_count=0,
|
||||
)
|
||||
|
||||
return class_profile
|
||||
|
||||
except Exception as e:
|
||||
logger.error("查询班级学情失败: %s", str(e))
|
||||
raise HTTPException(status_code=500, detail=f"查询班级学情失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/compare/{student_id}", response_model=ProfileCompareResponse)
|
||||
async def compare_student_with_class(
|
||||
student_id: str = Path(..., description="学生ID"),
|
||||
subject: Optional[SubjectEnum] = Query(None),
|
||||
):
|
||||
"""
|
||||
学生与班级/年级对比分析
|
||||
|
||||
将学生各项指标与班级平均和年级平均对比,计算百分位排名。
|
||||
"""
|
||||
logger.info("学情对比分析: student_id=%s", student_id)
|
||||
|
||||
try:
|
||||
# 查询学生个人画像
|
||||
# student = await query_student_profile(student_id)
|
||||
|
||||
# 查询班级和年级平均值
|
||||
# class_avg = await query_class_avg(student.class_id, subject)
|
||||
# grade_avg = await query_grade_avg(student.grade, subject)
|
||||
|
||||
# 计算百分位排名
|
||||
# percentile = await calc_percentile(student_id, student.grade)
|
||||
|
||||
return ProfileCompareResponse(
|
||||
student_profile=StudentProfile(
|
||||
student_id=student_id,
|
||||
student_name="",
|
||||
class_id="",
|
||||
grade="",
|
||||
school_id="",
|
||||
),
|
||||
class_avg={},
|
||||
grade_avg={},
|
||||
percentile=0.0,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("学情对比失败: %s", str(e))
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/knowledge-map/{student_id}")
|
||||
async def get_knowledge_map(
|
||||
student_id: str = Path(..., description="学生ID"),
|
||||
subject: SubjectEnum = Query(..., description="科目"),
|
||||
):
|
||||
"""
|
||||
获取知识图谱掌握度可视化数据
|
||||
|
||||
从Neo4j查询该科目知识图谱结构,叠加学生个人掌握度,
|
||||
生成可供前端ECharts渲染的图谱JSON数据。
|
||||
"""
|
||||
logger.info(
|
||||
"查询知识图谱: student_id=%s, subject=%s", student_id, subject
|
||||
)
|
||||
|
||||
try:
|
||||
# 从Neo4j查询知识点节点和边
|
||||
# nodes = await neo4j_query_knowledge_nodes(subject)
|
||||
# edges = await neo4j_query_knowledge_edges(subject)
|
||||
|
||||
# 查询学生对各知识点的掌握度
|
||||
# mastery_map = await query_mastery_map(student_id, subject)
|
||||
|
||||
# 组装ECharts图谱数据格式
|
||||
graph_data = {
|
||||
"nodes": [], # [{id, name, mastery, category, ...}]
|
||||
"edges": [], # [{source, target, relation_type}]
|
||||
"categories": [
|
||||
{"name": "已掌握"},
|
||||
{"name": "部分掌握"},
|
||||
{"name": "未掌握"},
|
||||
{"name": "未学习"},
|
||||
],
|
||||
}
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": graph_data,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("查询知识图谱失败: %s", str(e))
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/weak-analysis/{student_id}")
|
||||
async def analyze_weak_points(
|
||||
student_id: str = Path(..., description="学生ID"),
|
||||
subject: Optional[SubjectEnum] = Query(None),
|
||||
top_n: int = Query(10, ge=1, le=50, description="返回前N个薄弱点"),
|
||||
):
|
||||
"""
|
||||
薄弱知识点深度分析
|
||||
|
||||
结合错题归因和知识图谱前驱关系,分析薄弱根因并给出学习建议。
|
||||
"""
|
||||
logger.info(
|
||||
"薄弱分析: student_id=%s, subject=%s, top=%d",
|
||||
student_id, subject, top_n,
|
||||
)
|
||||
|
||||
try:
|
||||
# 查询错题记录及关联知识点
|
||||
# errors = await query_error_records(student_id, subject)
|
||||
|
||||
# 利用Neo4j知识图谱进行根因分析
|
||||
# 如果某知识点正确率低,检查其前驱知识点是否也未掌握
|
||||
# root_causes = await trace_knowledge_prerequisites(errors)
|
||||
|
||||
# 生成学习建议
|
||||
weak_analysis = {
|
||||
"weak_points": [], # 薄弱知识点列表
|
||||
"root_causes": [], # 根因知识点
|
||||
"suggestions": [], # 学习建议
|
||||
"recommended_exercises": [], # 推荐练习
|
||||
}
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": weak_analysis,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("薄弱分析失败: %s", str(e))
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Reference in New Issue
Block a user