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