Files
xsk-education-management/app/app/routers/records.py
T
2026-06-25 11:22:31 +08:00

172 lines
6.4 KiB
Python

from __future__ import annotations
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Query
from ..api_utils import load_accounts, load_records, load_teachers
from ..auth import verify_records_auth
from ..config import (
ADMIN_TASKS_PATH,
COURSE_SUMMARIES_ROOT,
COURSE_SUMMARY_STATE_PATH,
OPERATION_LOGS_PATH,
write_lock,
)
from ..data import (
account_to_dict,
append_operation_log,
course_summary_semantic_key,
duration_minutes_from_time_range,
find_record_by_identity,
query_public_records,
read_course_summary_state,
save_course_summary_markdown,
sha1_text,
submit_public_correction_tasks,
submit_public_deletion_tasks,
write_course_summary_state,
)
from ..schemas import CorrectionSubmitPayload, CourseSummarySupplementPayload, DeletionSubmitPayload
router = APIRouter()
@router.get("/api/records")
def records(
q: str = Query(..., min_length=1, description="自然语言查询,例如:王鑫鹏5月数学课"),
limit: int = Query(30, ge=1, le=1000),
offset: int = Query(0, ge=0),
_user: str = Depends(verify_records_auth),
):
return query_public_records(
load_records(),
load_teachers(),
q,
limit=limit,
offset=offset,
summaries_root=COURSE_SUMMARIES_ROOT,
)
@router.get("/api/student-account/{student}")
def record_student_account(student: str, _user: str = Depends(verify_records_auth)):
for account in load_accounts():
if account.student == student or account.student_id == student:
return account_to_dict(account)
raise HTTPException(status_code=404, detail=f"未找到学生账户: {student}")
@router.post("/api/corrections")
def submit_corrections(payload: CorrectionSubmitPayload, _user: str = Depends(verify_records_auth)):
try:
result = submit_public_correction_tasks(
ADMIN_TASKS_PATH,
load_records(),
load_teachers(),
[item.dict() for item in payload.items],
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {"ok": True, **result}
@router.post("/api/deletions")
def submit_deletions(payload: DeletionSubmitPayload, _user: str = Depends(verify_records_auth)):
try:
result = submit_public_deletion_tasks(
ADMIN_TASKS_PATH,
load_records(),
[item.dict() for item in payload.items],
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {"ok": True, **result}
@router.post("/api/course-summaries/supplement")
def supplement_course_summary(payload: CourseSummarySupplementPayload, _user: str = Depends(verify_records_auth)):
body = payload.body.strip()
if not body:
raise HTTPException(status_code=400, detail="课程小结正文不能为空")
try:
records = load_records()
record = find_record_by_identity(records, payload.record_id)
date_iso = record.date.replace(".", "-")
source_id = f"supplement:{payload.record_id}:{sha1_text(body, 12)}"
summary = {
"source_id": source_id,
"student": record.student,
"date_iso": date_iso,
"time_range": record.time,
"duration_minutes": duration_minutes_from_time_range(record.time),
"teacher": record.teacher,
"subject": record.subject,
"group": "",
"sender": "课程记录页补充",
"message_time": "",
"body": body,
}
semantic_key = course_summary_semantic_key(summary)
with write_lock:
state = read_course_summary_state(COURSE_SUMMARY_STATE_PATH)
seen_source_ids = set(str(item) for item in state.get("seen_source_ids", []))
seen_semantic_keys = set(str(item) for item in state.get("seen_semantic_keys", []))
if source_id in seen_source_ids or semantic_key in seen_semantic_keys:
log_id = append_operation_log(
OPERATION_LOGS_PATH,
"课程小结补充",
"重复",
record_id=payload.record_id,
student=record.student,
teacher=record.teacher,
subject=record.subject,
source_id=source_id,
)
return {"ok": True, "log_id": log_id, "submitted": 0, "status": "duplicate"}
saved = save_course_summary_markdown(COURSE_SUMMARIES_ROOT, summary)
status_value = "完成" if saved.get("added") else "重复"
seen_source_ids.add(source_id)
seen_semantic_keys.add(semantic_key)
state["seen_source_ids"] = sorted(seen_source_ids)
state["seen_semantic_keys"] = sorted(seen_semantic_keys)
state.setdefault("batches", []).append(
{
"batch_id": f"supplement-{payload.record_id}-{sha1_text(body, 8)}",
"received_at": datetime.now().isoformat(timespec="seconds"),
"window": {"source": "records_page_supplement"},
"students": [record.student],
"result": {
"received": 1,
"saved": 1 if saved.get("added") else 0,
"auto_registered": 0,
"review_pending": 0,
"duplicates": 0 if saved.get("added") else 1,
"rejected": 0,
},
}
)
state["batches"] = state["batches"][-200:]
write_course_summary_state(COURSE_SUMMARY_STATE_PATH, state)
log_id = append_operation_log(
OPERATION_LOGS_PATH,
"课程小结补充",
status_value,
record_id=payload.record_id,
student=record.student,
teacher=record.teacher,
subject=record.subject,
source_id=source_id,
saved_path=str(saved.get("path") or ""),
heading=str(saved.get("heading") or ""),
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {
"ok": True,
"log_id": log_id,
"submitted": 1 if status_value == "完成" else 0,
"status": "saved" if status_value == "完成" else "duplicate",
}