398 lines
11 KiB
Python
398 lines
11 KiB
Python
# 自然写教学数据分析与学情诊断系统软件 V1.0
|
||
# api/report_api.py - 报告导出与查询API
|
||
# api/growth_api.py - 成长轨迹API
|
||
# model/data_models.py - 核心数据模型定义
|
||
|
||
import logging
|
||
from typing import Optional, List, Dict, Any
|
||
from datetime import datetime, date
|
||
from enum import Enum
|
||
|
||
from fastapi import APIRouter, Query, Path, HTTPException, BackgroundTasks
|
||
from pydantic import BaseModel, Field
|
||
|
||
logger = logging.getLogger("writech.analytics.api")
|
||
|
||
|
||
# ============================================================
|
||
# 报告导出API路由
|
||
# ============================================================
|
||
|
||
report_router = APIRouter(tags=["报告导出"])
|
||
|
||
|
||
class ExportRequest(BaseModel):
|
||
"""报告导出请求"""
|
||
report_type: str = Field(..., description="报告类型")
|
||
target_id: str = Field(..., description="目标ID(学生/班级)")
|
||
start_date: str = Field(..., description="开始日期")
|
||
end_date: str = Field(..., description="结束日期")
|
||
format: str = Field("pdf", description="输出格式: json/pdf/html")
|
||
include_charts: bool = Field(True, description="是否包含图表")
|
||
|
||
|
||
class ExportResponse(BaseModel):
|
||
"""报告导出响应"""
|
||
task_id: str
|
||
status: str
|
||
download_url: Optional[str] = None
|
||
estimated_seconds: int = 0
|
||
|
||
|
||
@report_router.post("/export", response_model=ExportResponse)
|
||
async def export_report(
|
||
request: ExportRequest,
|
||
background_tasks: BackgroundTasks,
|
||
):
|
||
"""
|
||
生成并导出学情报告
|
||
|
||
异步生成报告,返回任务ID。
|
||
客户端可通过任务ID轮询状态或等待WebSocket通知。
|
||
"""
|
||
logger.info(
|
||
"报告导出请求: type=%s, target=%s, format=%s",
|
||
request.report_type,
|
||
request.target_id,
|
||
request.format,
|
||
)
|
||
|
||
# 生成任务ID
|
||
task_id = f"rpt_{datetime.now().strftime('%Y%m%d%H%M%S')}_{request.target_id[:8]}"
|
||
|
||
# 将报告生成任务加入后台队列
|
||
# background_tasks.add_task(
|
||
# generate_report_task,
|
||
# task_id=task_id,
|
||
# config=request,
|
||
# )
|
||
|
||
return ExportResponse(
|
||
task_id=task_id,
|
||
status="processing",
|
||
estimated_seconds=30,
|
||
)
|
||
|
||
|
||
@report_router.get("/status/{task_id}")
|
||
async def get_export_status(task_id: str = Path(...)):
|
||
"""查询报告导出任务状态"""
|
||
# status = await query_task_status(task_id)
|
||
return {
|
||
"task_id": task_id,
|
||
"status": "completed",
|
||
"download_url": None,
|
||
}
|
||
|
||
|
||
@report_router.get("/class/{class_id}")
|
||
async def get_class_report(
|
||
class_id: str = Path(..., description="班级ID"),
|
||
subject: Optional[str] = Query(None),
|
||
start_date: Optional[str] = Query(None),
|
||
end_date: Optional[str] = Query(None),
|
||
):
|
||
"""
|
||
获取班级学情统计报告
|
||
|
||
返回班级平均分、分数分布、薄弱知识点等统计数据。
|
||
仅班级教师和校管理员有权限查看。
|
||
"""
|
||
logger.info("班级报告查询: class=%s, subject=%s", class_id, subject)
|
||
|
||
# 权限校验:教师仅可查看本班数据
|
||
# verify_class_permission(current_user, class_id)
|
||
|
||
# 从ClickHouse查询班级统计数据
|
||
# stats = await aggregate_class_report(class_id, subject, ...)
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"class_id": class_id,
|
||
"student_count": 0,
|
||
"avg_score": 0,
|
||
"score_distribution": {},
|
||
"weak_points": [],
|
||
"top_students": [],
|
||
},
|
||
}
|
||
|
||
|
||
@report_router.get("/history")
|
||
async def list_report_history(
|
||
target_id: str = Query(..., description="目标ID"),
|
||
report_type: Optional[str] = Query(None),
|
||
page: int = Query(1, ge=1),
|
||
page_size: int = Query(20, ge=1, le=100),
|
||
):
|
||
"""查询历史报告列表"""
|
||
# reports = await query_report_history(target_id, report_type, ...)
|
||
return {
|
||
"code": 0,
|
||
"data": {
|
||
"total": 0,
|
||
"page": page,
|
||
"items": [],
|
||
},
|
||
}
|
||
|
||
|
||
# ============================================================
|
||
# 成长轨迹API路由
|
||
# ============================================================
|
||
|
||
growth_router = APIRouter(tags=["成长轨迹"])
|
||
|
||
|
||
@growth_router.get("/{student_id}")
|
||
async def get_growth_trajectory(
|
||
student_id: str = Path(..., description="学生ID"),
|
||
subject: Optional[str] = Query(None, description="科目"),
|
||
start_date: Optional[str] = Query(None),
|
||
end_date: Optional[str] = Query(None),
|
||
granularity: str = Query("weekly", description="粒度: daily/weekly/monthly"),
|
||
):
|
||
"""
|
||
获取学生成长轨迹
|
||
|
||
返回学生在指定时间范围内的各项指标时序数据,
|
||
包括成绩趋势、书写能力变化、学习习惯变化等。
|
||
家长仅可查看自己子女的数据。
|
||
"""
|
||
logger.info(
|
||
"成长轨迹查询: student=%s, subject=%s, granularity=%s",
|
||
student_id, subject, granularity,
|
||
)
|
||
|
||
# 权限校验
|
||
# verify_student_access(current_user, student_id)
|
||
|
||
# 从ClickHouse查询时序数据
|
||
# trend_data = await query_growth_trend(student_id, subject, ...)
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"student_id": student_id,
|
||
"period": f"{start_date} ~ {end_date}",
|
||
"score_trend": [], # 成绩趋势
|
||
"writing_trend": [], # 书写能力趋势
|
||
"habit_trend": [], # 学习习惯趋势
|
||
"milestones": [], # 里程碑事件
|
||
},
|
||
}
|
||
|
||
|
||
@growth_router.get("/writing/{student_id}")
|
||
async def get_writing_growth(
|
||
student_id: str = Path(..., description="学生ID"),
|
||
start_date: str = Query(..., description="开始日期"),
|
||
end_date: str = Query(..., description="结束日期"),
|
||
):
|
||
"""
|
||
获取书写能力成长报告
|
||
|
||
返回笔顺准确率、书写规范性、书写速度等维度的成长趋势。
|
||
"""
|
||
logger.info(
|
||
"书写成长查询: student=%s, %s~%s",
|
||
student_id, start_date, end_date,
|
||
)
|
||
|
||
# 调用书写成长分析引擎
|
||
# from analytics.writing_growth import WritingGrowthAnalyzer
|
||
# analyzer = WritingGrowthAnalyzer()
|
||
# report = await analyzer.analyze_growth(
|
||
# student_id, start_date, end_date
|
||
# )
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"student_id": student_id,
|
||
"overall_level": "",
|
||
"overall_score": 0,
|
||
"dimensions": {
|
||
"stroke_order": {"score": 0, "trend": "stable"},
|
||
"quality": {"score": 0, "trend": "stable"},
|
||
"speed": {"score": 0, "trend": "stable"},
|
||
"structure": {"score": 0, "trend": "stable"},
|
||
},
|
||
"snapshots": [],
|
||
"most_improved_chars": [],
|
||
"needs_practice_chars": [],
|
||
},
|
||
}
|
||
|
||
|
||
@growth_router.get("/error/analysis/{student_id}")
|
||
async def get_error_analysis(
|
||
student_id: str = Path(..., description="学生ID"),
|
||
subject: Optional[str] = Query(None),
|
||
top_n: int = Query(20, ge=1, le=100),
|
||
):
|
||
"""
|
||
错题归因分析
|
||
|
||
返回学生的错题统计、知识点薄弱分析、错因归类。
|
||
结合知识图谱进行根因分析。
|
||
"""
|
||
logger.info(
|
||
"错题分析: student=%s, subject=%s", student_id, subject
|
||
)
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"student_id": student_id,
|
||
"total_errors": 0,
|
||
"by_subject": {}, # 按科目分组
|
||
"by_knowledge": [], # 按知识点排序
|
||
"error_types": {}, # 错因分类
|
||
"root_causes": [], # 根因分析(知识图谱)
|
||
"recommendations": [], # 学习建议
|
||
},
|
||
}
|
||
|
||
|
||
@growth_router.post("/push/parent")
|
||
async def push_to_parent(
|
||
student_id: str = Query(..., description="学生ID"),
|
||
report_type: str = Query("weekly", description="推送报告类型"),
|
||
background_tasks: BackgroundTasks = None,
|
||
):
|
||
"""
|
||
触发学情报告推送至家长端
|
||
|
||
通过WebSocket或APP推送通知家长查看学情报告。
|
||
家长端展示简化版本的学情摘要。
|
||
"""
|
||
logger.info("家长推送: student=%s, type=%s", student_id, report_type)
|
||
|
||
# 生成家长版报告
|
||
# background_tasks.add_task(
|
||
# generate_and_push_parent_report,
|
||
# student_id=student_id,
|
||
# report_type=report_type,
|
||
# )
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "推送任务已提交",
|
||
"data": {"student_id": student_id},
|
||
}
|
||
|
||
|
||
# ============================================================
|
||
# 核心数据模型定义(model/data_models.py)
|
||
# ============================================================
|
||
|
||
class GradeLevel(str, Enum):
|
||
"""年级枚举"""
|
||
GRADE_1 = "grade_1"
|
||
GRADE_2 = "grade_2"
|
||
GRADE_3 = "grade_3"
|
||
GRADE_4 = "grade_4"
|
||
GRADE_5 = "grade_5"
|
||
GRADE_6 = "grade_6"
|
||
GRADE_7 = "grade_7"
|
||
GRADE_8 = "grade_8"
|
||
GRADE_9 = "grade_9"
|
||
|
||
|
||
class StudentInfo(BaseModel):
|
||
"""学生基本信息"""
|
||
student_id: str
|
||
name: str
|
||
class_id: str
|
||
grade: GradeLevel
|
||
school_id: str
|
||
gender: Optional[str] = None
|
||
created_at: Optional[str] = None
|
||
|
||
|
||
class ClassInfo(BaseModel):
|
||
"""班级基本信息"""
|
||
class_id: str
|
||
class_name: str
|
||
grade: GradeLevel
|
||
school_id: str
|
||
teacher_id: str
|
||
student_count: int = 0
|
||
|
||
|
||
class SchoolInfo(BaseModel):
|
||
"""学校信息"""
|
||
school_id: str
|
||
school_name: str
|
||
region: str
|
||
district: str
|
||
|
||
|
||
class ErrorRecord(BaseModel):
|
||
"""错题记录模型(MySQL)"""
|
||
id: Optional[int] = None
|
||
student_id: str
|
||
homework_id: str
|
||
question_id: str
|
||
subject: str
|
||
knowledge_point: str = ""
|
||
error_type: str = "" # 计算错误/概念混淆/审题不清/粗心
|
||
student_answer: str = ""
|
||
correct_answer: str = ""
|
||
created_at: str = ""
|
||
|
||
|
||
class ExamAnalysis(BaseModel):
|
||
"""考试分析结果模型(ClickHouse)"""
|
||
exam_id: str
|
||
class_id: str
|
||
subject: str
|
||
exam_date: str
|
||
avg_score: float = 0.0
|
||
median_score: float = 0.0
|
||
max_score: float = 0.0
|
||
min_score: float = 0.0
|
||
std_deviation: float = 0.0
|
||
pass_rate: float = 0.0
|
||
excellent_rate: float = 0.0
|
||
score_distribution: Dict[str, int] = {}
|
||
difficulty_coefficient: float = 0.0
|
||
discrimination_index: float = 0.0
|
||
|
||
|
||
class KafkaEventSchema(BaseModel):
|
||
"""Kafka事件消息Schema"""
|
||
event_id: str
|
||
event_type: str
|
||
student_id: str
|
||
class_id: str = ""
|
||
school_id: str = ""
|
||
timestamp: str
|
||
source: str = ""
|
||
payload: Dict[str, Any] = {}
|
||
|
||
class Config:
|
||
json_schema_extra = {
|
||
"example": {
|
||
"event_id": "evt_20240101_001",
|
||
"event_type": "grade_result",
|
||
"student_id": "stu_001",
|
||
"class_id": "cls_001",
|
||
"school_id": "sch_001",
|
||
"timestamp": "2024-01-01T10:00:00+08:00",
|
||
"source": "pad",
|
||
"payload": {
|
||
"homework_id": "hw_001",
|
||
"subject": "chinese",
|
||
"score": 85,
|
||
"total_score": 100,
|
||
},
|
||
}
|
||
}
|