172 lines
6.4 KiB
Python
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",
|
|
}
|