fix: E-коды без карточки в связке 112 → ДДС, идемпотентная ручная отметка, ответ по строке после commit
This commit is contained in:
parent
db8787c6b3
commit
da740dce7f
1 changed files with 30 additions and 6 deletions
|
|
@ -46,9 +46,9 @@ from app.session.store import (
|
||||||
FindingAdded,
|
FindingAdded,
|
||||||
FindingReviewed,
|
FindingReviewed,
|
||||||
ScoreOverridden,
|
ScoreOverridden,
|
||||||
apply_finding_change,
|
|
||||||
apply_score_override,
|
apply_score_override,
|
||||||
)
|
)
|
||||||
|
from app.scoring.review import has_finding
|
||||||
from app.voice.recording import recording_path
|
from app.voice.recording import recording_path
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/sessions", tags=["sessions"])
|
router = APIRouter(prefix="/api/sessions", tags=["sessions"])
|
||||||
|
|
@ -624,6 +624,8 @@ class FindingIn(BaseModel):
|
||||||
norm: str = Field(min_length=1, max_length=1000)
|
norm: str = Field(min_length=1, max_length=1000)
|
||||||
#: Номер карточки очереди ДДС (с 1); без него отметка относится к занятию.
|
#: Номер карточки очереди ДДС (с 1); без него отметка относится к занятию.
|
||||||
card: int | None = Field(default=None, ge=1)
|
card: int | None = Field(default=None, ge=1)
|
||||||
|
#: Ключ идемпотентности с клиента: повтор запроса не добавит отметку второй раз.
|
||||||
|
client_id: UUID | None = None
|
||||||
|
|
||||||
_fact = field_validator("fact")(_required_text)
|
_fact = field_validator("fact")(_required_text)
|
||||||
_norm = field_validator("norm")(_required_text)
|
_norm = field_validator("norm")(_required_text)
|
||||||
|
|
@ -648,6 +650,7 @@ async def _change_findings(
|
||||||
raise HTTPException(status_code=409, detail="scenario_not_found")
|
raise HTTPException(status_code=409, detail="scenario_not_found")
|
||||||
change = make(who, state.score)
|
change = make(who, state.score)
|
||||||
async with hub.operation(session_id):
|
async with hub.operation(session_id):
|
||||||
|
if not _repeated(state.score, change):
|
||||||
change_findings(state, change)
|
change_findings(state, change)
|
||||||
return build_report(session_id, state, scenario)
|
return build_report(session_id, state, scenario)
|
||||||
|
|
||||||
|
|
@ -662,11 +665,24 @@ async def _change_findings(
|
||||||
raise HTTPException(status_code=409, detail="score_not_ready")
|
raise HTTPException(status_code=409, detail="score_not_ready")
|
||||||
if (score.report or {}).get("full_report") is None:
|
if (score.report or {}).get("full_report") is None:
|
||||||
raise HTTPException(status_code=409, detail="report_not_archived")
|
raise HTTPException(status_code=409, detail="report_not_archived")
|
||||||
change = make(who, dict(score.report))
|
current = dict(score.report)
|
||||||
report = apply_finding_change(dict(score.report), change)
|
change = make(who, current)
|
||||||
await db.rollback() # чтение закончено; запись — одной транзакцией хранилища
|
await db.rollback() # чтение закончено; запись — одной транзакцией хранилища
|
||||||
|
if not _repeated(current, change):
|
||||||
await hub.store.commit_archived(session_id, [change])
|
await hub.store.commit_archived(session_id, [change])
|
||||||
return SessionReport.model_validate(report["full_report"])
|
# Ответ — по строке после commit: пересчёт шёл под блокировкой и мог учесть
|
||||||
|
# решение из соседней вкладки, которого не было в прочитанном выше отчёте.
|
||||||
|
score = await db.scalar(
|
||||||
|
select(Score).where(Score.session_id == session_id)
|
||||||
|
.execution_options(populate_existing=True)
|
||||||
|
)
|
||||||
|
report = SessionReport.model_validate(score.report["full_report"])
|
||||||
|
await db.rollback()
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
def _repeated(report: dict, change: FindingReviewed | FindingAdded) -> bool:
|
||||||
|
return isinstance(change, FindingAdded) and has_finding(report, change.finding.client_id)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{session_id}/findings/{index}/review", response_model=SessionReport)
|
@router.post("/{session_id}/findings/{index}/review", response_model=SessionReport)
|
||||||
|
|
@ -703,6 +719,13 @@ async def add_finding(
|
||||||
def make(who, score: dict) -> FindingAdded:
|
def make(who, score: dict) -> FindingAdded:
|
||||||
service = None
|
service = None
|
||||||
prefix = ""
|
prefix = ""
|
||||||
|
exercise = (score.get("full_report") or {}).get("exercise")
|
||||||
|
# E1–E6 в связке 112 → ДДС — ошибки приёма вызова: их метрики у занятия,
|
||||||
|
# а не у карточки очереди. С номером карточки отметка не нашла бы свою
|
||||||
|
# метрику 112 и штрафовала бы отдельной метрикой преподавателя.
|
||||||
|
if (body.card is not None and body.code.value.startswith("E")
|
||||||
|
and exercise != Exercise.DDS.value):
|
||||||
|
raise HTTPException(status_code=422, detail="call_code_without_card")
|
||||||
if body.card is not None:
|
if body.card is not None:
|
||||||
cards = score.get("card_results") or []
|
cards = score.get("card_results") or []
|
||||||
if body.card > len(cards):
|
if body.card > len(cards):
|
||||||
|
|
@ -722,6 +745,7 @@ async def add_finding(
|
||||||
card=body.card,
|
card=body.card,
|
||||||
service=service,
|
service=service,
|
||||||
author=who.login,
|
author=who.login,
|
||||||
|
client_id=body.client_id,
|
||||||
))
|
))
|
||||||
|
|
||||||
return await _change_findings(session_id, who, db, make)
|
return await _change_findings(session_id, who, db, make)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue