software copyright
This commit is contained in:
@@ -0,0 +1,397 @@
|
||||
# 自然写教学数据分析与学情诊断系统软件 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user