Files
system-design/software-copyright/03-writech-learning-analytics/api/report_api.py
T
2026-03-22 15:24:40 +08:00

398 lines
11 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.
# 自然写教学数据分析与学情诊断系统软件 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,
},
}
}