fix: improve import, evidence indexing and material-scoped grading

Preserve document revisions and evidence, harden indexing and export behavior, add explicit material selection and direct-folder import, validate model JSON schemas with safe error messages, and retain student selection while loading scoring data.
This commit is contained in:
2026-10-14 13:52:56 +08:00
parent 29c95ce5ad
commit ebe33392ca
42 changed files with 2332 additions and 304 deletions
+23 -7
View File
@@ -4,11 +4,11 @@ from pathlib import Path
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill
from sqlalchemy import select
from weasyprint import HTML
from .config import get_settings
from .evidence import POLICY_VERSION
from .knowledge_pages import evidence_links
from .models import (CriterionScore, ExportTask, Project, ScoreEvidence, ScoringBatch, Student, SubmissionFile)
from .models import (CriterionScore, ExportTask, KnowledgeDocument, KnowledgePage, Project, ScoreEvidence, ScoringBatch, Student, SubmissionFile)
from .rules import totals
#: Teacher-facing wording for a criterion status, shared by exports and the API.
@@ -43,6 +43,20 @@ def project_result(db, batch: ScoringBatch, project: Project):
CriterionScore.project_id == project.id)).all()
by_id = {score.criterion_id: score for score in scores}
criteria = batch.rubric_snapshot["criteria"]
all_evidence = db.scalars(select(ScoreEvidence).where(ScoreEvidence.score_id.in_([s.id for s in scores]))).all() if scores else []
evidence_by_score = {}
for item in all_evidence:
evidence_by_score.setdefault(item.score_id, []).append(item)
file_ids = {item.submission_file_id for item in all_evidence if item.submission_file_id}
file_names = dict(db.execute(select(SubmissionFile.id, SubmissionFile.relative_path).where(SubmissionFile.id.in_(file_ids))).all()) if file_ids else {}
# Keep canonical source entities alive for this result, so page_links uses the
# session identity map instead of issuing a query for every quotation.
doc_ids = {item.document_id for item in all_evidence if item.document_id}
documents = db.scalars(select(KnowledgeDocument).where(KnowledgeDocument.id.in_(doc_ids))).all() if doc_ids else []
retired_ids = {doc.id for doc in documents if doc.status == "superseded"}
cited_pages = {item.page for item in all_evidence}
pages = db.scalars(select(KnowledgePage).where(KnowledgePage.document_id.in_(doc_ids),
KnowledgePage.page.in_(cited_pages))).all() if doc_ids else []
totals_ = totals({key: score.final_score for key, score in by_id.items()}, criteria, batch.rubric_snapshot["weights"])
rows = []
def evidence_dict(item, links):
@@ -53,12 +67,13 @@ def project_result(db, batch: ScoringBatch, project: Project):
"source_role": item.source_role or "content",
# A quotation saved before the evidence policy existed is labelled as a
# historical citation and never presented as report body evidence.
"historical": bool(item.historical),
"document": db.get(SubmissionFile, item.submission_file_id).relative_path if item.submission_file_id else "课程资料",
**links}
"historical": bool(item.historical) or item.document_id in retired_ids
or batch.rubric_snapshot.get("evidence_plan", {}).get("version") != POLICY_VERSION,
"document": file_names.get(item.submission_file_id, "来源材料") if item.submission_file_id else "课程资料",
"url": links["url"], "image_url": links["image_url"]}
for criterion in criteria:
score = by_id.get(criterion["criterion_id"])
evidence = db.scalars(select(ScoreEvidence).where(ScoreEvidence.score_id == score.id)).all() if score else []
evidence = evidence_by_score.get(score.id, []) if score else []
rows.append({"criterion": criterion, "score_id": score.id if score else None,
"model_score": score.model_score if score else None, "teacher_score": score.teacher_score if score else None,
"final_score": score.final_score if score else None,
@@ -124,7 +139,7 @@ def export_excel(db, batch_id: int):
def _html_report(result, batch):
esc = lambda value: escape(str(value or ""))
esc = lambda value: escape("" if value is None else str(value))
rows = []
for row in result["rows"]:
criterion = row["criterion"]
@@ -170,6 +185,7 @@ def export_project_pdf(db, batch_id: int, project_id: int):
db.flush()
path = root / f"batch-{batch_id}-project-{project_id}-{task.id}.pdf"
try:
from weasyprint import HTML
HTML(string=_html_report(project_result(db, batch, project), batch)).write_pdf(path)
task.path, task.status = str(path), "completed"
except Exception as exc: