fix: preserve concurrent teacher decisions when model scoring fails
This commit is contained in:
@@ -373,6 +373,35 @@ def test_model_declining_score_records_original_gap(sqlite_db):
|
||||
assert score.evidence_gaps[0]["reason"] == "只有装置介绍"
|
||||
|
||||
|
||||
def test_failed_model_call_cannot_overwrite_a_concurrent_teacher_review(sqlite_db, monkeypatch):
|
||||
from wisegrade import scoring
|
||||
db = sqlite_db
|
||||
item = project(db)
|
||||
created = services.create_batch(SimpleNamespace(course_version_id=1, rubric_version_id=1), db)
|
||||
batch = db.get(ScoringBatch, created["id"])
|
||||
criterion = batch.rubric_snapshot["criteria"][0]
|
||||
batch.rubric_snapshot = {**batch.rubric_snapshot, "criteria": [criterion]}
|
||||
batch.status, batch.progress_total, batch.progress_done = "running", 1, 0
|
||||
score = CriterionScore(batch_id=batch.id, project_id=item.id, criterion_id=criterion["criterion_id"], status="pending")
|
||||
db.add(score)
|
||||
db.commit()
|
||||
factory = sessionmaker(bind=db.get_bind(), expire_on_commit=False)
|
||||
def teacher_then_failure(*args, **kwargs):
|
||||
with factory() as teacher_db:
|
||||
services.review_score(score.id, SimpleNamespace(score=0, comment="教师核定零分", reason="已核对"), teacher_db)
|
||||
raise ModelError("模型响应超时")
|
||||
monkeypatch.setattr(scoring, "_project_facts", lambda *args, **kwargs: {})
|
||||
monkeypatch.setattr(scoring, "_score_criterion", teacher_then_failure)
|
||||
failures, reviews = scoring._run_project(batch.id, item.id, factory, lambda *args: None, FLAT, lambda *a, **k: [])
|
||||
db.expire_all()
|
||||
saved = db.get(CriterionScore, score.id)
|
||||
assert saved.status == "reviewed"
|
||||
assert saved.teacher_score == 0 and saved.teacher_comment == "教师核定零分"
|
||||
assert saved.error == ""
|
||||
assert failures == reviews == 0
|
||||
assert db.get(ScoringBatch, batch.id).progress_done == 1
|
||||
|
||||
|
||||
def test_unrebuilt_course_document_does_not_complete_task(sqlite_db):
|
||||
db = sqlite_db
|
||||
doc = KnowledgeDocument(course_version_id=1, title="无页面资料", document_type="course",
|
||||
|
||||
@@ -387,13 +387,17 @@ def _run_project(batch_id, project_id, session_factory, client_factory, embedder
|
||||
except Exception as exc:
|
||||
db.rollback()
|
||||
score = db.scalar(select(CriterionScore).where(CriterionScore.batch_id == batch.id,
|
||||
CriterionScore.project_id == project.id, CriterionScore.criterion_id == criterion["criterion_id"]))
|
||||
CriterionScore.project_id == project.id, CriterionScore.criterion_id == criterion["criterion_id"])
|
||||
.with_for_update().execution_options(populate_existing=True))
|
||||
if not score:
|
||||
score = CriterionScore(batch_id=batch.id, project_id=project.id, criterion_id=criterion["criterion_id"])
|
||||
db.add(score)
|
||||
score.status, score.error = STATUS_FAILED, str(exc)[:500]
|
||||
db.commit()
|
||||
failures += 1
|
||||
# A teacher may have committed a decision while the request was in
|
||||
# flight. The exception path must honour it just like success does.
|
||||
if score.status != STATUS_REVIEWED and score.teacher_score is None:
|
||||
score.status, score.error = STATUS_FAILED, str(exc)[:500]
|
||||
db.commit()
|
||||
failures += 1
|
||||
db.execute(update(ScoringBatch).where(ScoringBatch.id == batch_id).values(progress_done=ScoringBatch.progress_done + 1))
|
||||
db.commit()
|
||||
return failures, reviews
|
||||
|
||||
Reference in New Issue
Block a user