116 lines
4.0 KiB
Python
116 lines
4.0 KiB
Python
from __future__ import annotations
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException
|
||
|
||
from ..auth import verify_ingest_token
|
||
from ..ai_register import preview_register
|
||
from ..config import (
|
||
ACCOUNTS_PATH,
|
||
ADMIN_TASKS_PATH,
|
||
CLASSNOTES_PATH,
|
||
COURSE_SUMMARIES_ROOT,
|
||
COURSE_SUMMARY_STATE_PATH,
|
||
OPERATION_LOGS_PATH,
|
||
write_lock,
|
||
)
|
||
from ..data import ingest_course_summaries
|
||
from ..schemas import CourseSummaryIngestPayload
|
||
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
def _summary_text(raw: dict) -> str:
|
||
parts = []
|
||
mapping = [
|
||
("student", "学生"),
|
||
("date_iso", "日期"),
|
||
("date", "日期"),
|
||
("time_range", "时间"),
|
||
("raw_time", "时间"),
|
||
("teacher", "老师"),
|
||
("subject", "科目"),
|
||
]
|
||
for key, label in mapping:
|
||
value = str(raw.get(key) or "").strip()
|
||
if value:
|
||
parts.append(f"{label}:{value}")
|
||
body = str(raw.get("body") or raw.get("content") or "").strip()
|
||
if body:
|
||
parts.append("小结:")
|
||
parts.append(body)
|
||
return "\n".join(parts)
|
||
|
||
|
||
def _needs_script_summary(raw: dict) -> bool:
|
||
required = ["student", "teacher", "subject"]
|
||
if any(not str(raw.get(key) or "").strip() for key in required):
|
||
return True
|
||
if not str(raw.get("date_iso") or raw.get("date") or raw.get("class_date") or "").strip():
|
||
return True
|
||
if not str(raw.get("time_range") or raw.get("raw_time") or raw.get("time") or "").strip():
|
||
return True
|
||
return False
|
||
|
||
|
||
def _standard_text_to_raw(text: str, original: dict) -> dict:
|
||
from ..data import extract_course_summary_from_text, normalize_course_summary
|
||
|
||
raw = extract_course_summary_from_text(text, known_students=[])
|
||
normalized = normalize_course_summary({**original, **raw})
|
||
return {**original, **normalized}
|
||
|
||
|
||
def _preprocess_summaries_locally(summaries: list[dict]) -> list[dict]:
|
||
processed: list[dict] = []
|
||
for raw in summaries:
|
||
if not _needs_script_summary(raw):
|
||
processed.append(raw)
|
||
continue
|
||
try:
|
||
preview = preview_register(register_type="course_summary", text=_summary_text(raw))
|
||
except ValueError as exc:
|
||
processed.append({**raw, "ai_used": False, "ai_questions": [str(exc)]})
|
||
continue
|
||
if preview.get("status") == "ready" and preview.get("standard_lines"):
|
||
try:
|
||
updated = _standard_text_to_raw(str(preview["standard_lines"][0]), raw)
|
||
processed.append({
|
||
**updated,
|
||
"ai_used": False,
|
||
"ai_summary": str(preview.get("summary") or ""),
|
||
})
|
||
except ValueError as exc:
|
||
processed.append({**raw, "ai_used": False, "ai_questions": [str(exc)]})
|
||
continue
|
||
questions = [str(item) for item in preview.get("questions") or [] if str(item)]
|
||
processed.append({
|
||
**raw,
|
||
"ai_used": False,
|
||
"ai_summary": str(preview.get("summary") or ""),
|
||
"ai_questions": questions or ["脚本未能补齐课程小结信息"],
|
||
})
|
||
return processed
|
||
|
||
|
||
@router.post("/api/ingest/course-summaries")
|
||
def ingest_course_summary_batch(payload: CourseSummaryIngestPayload, _user: str = Depends(verify_ingest_token)):
|
||
try:
|
||
summaries = _preprocess_summaries_locally([item.dict() for item in payload.summaries])
|
||
with write_lock:
|
||
result = ingest_course_summaries(
|
||
classnotes_path=CLASSNOTES_PATH,
|
||
accounts_path=ACCOUNTS_PATH,
|
||
tasks_path=ADMIN_TASKS_PATH,
|
||
summaries_root=COURSE_SUMMARIES_ROOT,
|
||
state_path=COURSE_SUMMARY_STATE_PATH,
|
||
operation_logs_path=OPERATION_LOGS_PATH,
|
||
batch_id=payload.batch_id,
|
||
window=payload.window,
|
||
students=payload.students,
|
||
summaries=summaries,
|
||
)
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||
return {"ok": True, **result}
|