fix: балл после разбора в PDF, все решения по отметке в экспорте, вес отметки преподавателя в критериях

This commit is contained in:
GGlamer 2026-09-27 22:41:27 +03:00
commit 871832d5ca
2 changed files with 15 additions and 10 deletions

View file

@ -23,7 +23,7 @@ from app.domain.roles import Role
from app.domain.statuses import ServiceStatus, StationSnapshot from app.domain.statuses import ServiceStatus, StationSnapshot
from app.domain.taxonomy import Finding from app.domain.taxonomy import Finding
from app.domain.timers import TimerSnapshot from app.domain.timers import TimerSnapshot
from app.scoring.taxonomy import METRIC_MAP from app.scoring.taxonomy import INSTRUCTOR_METRIC, METRIC_MAP
class SessionMode(StrEnum): class SessionMode(StrEnum):
@ -60,7 +60,7 @@ class LessonCriteria(BaseModel):
@field_validator("score_weights") @field_validator("score_weights")
@classmethod @classmethod
def validate_score_weights(cls, weights: dict[str, float]) -> dict[str, float]: def validate_score_weights(cls, weights: dict[str, float]) -> dict[str, float]:
unknown = weights.keys() - METRIC_MAP.keys() unknown = weights.keys() - METRIC_MAP.keys() - {INSTRUCTOR_METRIC}
if unknown: if unknown:
raise ValueError(f"неизвестные метрики весов: {', '.join(sorted(unknown))}") raise ValueError(f"неизвестные метрики весов: {', '.join(sorted(unknown))}")
if any(not 0 <= weight <= 10 for weight in weights.values()): if any(not 0 <= weight <= 10 for weight in weights.values()):
@ -747,6 +747,8 @@ class DdsCardReport(BaseModel):
card_id: UUID card_id: UUID
scenario_id: str scenario_id: str
score_auto: float score_auto: float
#: Балл карточки после решений преподавателя по отметкам; None — решений не было.
score_reviewed: float | None = None
reply_text: str reply_text: str
metrics: list[Metric] metrics: list[Metric]
findings: list[Finding] findings: list[Finding]

View file

@ -19,7 +19,7 @@ def _cell(value: object) -> str:
if value is None: if value is None:
return "" return ""
result = str(value) result = str(value)
probe = result.lstrip(" \t\r\n\ufeff\u200b") probe = result.lstrip(" \t\r\n​")
if probe.startswith(("=", "+", "-", "@")) or result.startswith(("\t", "\r", "\n")): if probe.startswith(("=", "+", "-", "@")) or result.startswith(("\t", "\r", "\n")):
return "'" + result return "'" + result
return result return result
@ -32,12 +32,12 @@ def _finding_origin(item: Finding) -> str:
def _finding_decision(item: Finding) -> str: def _finding_decision(item: Finding) -> str:
"""Решение по отметке рядом с ней: снятая отметка видна вместе с причиной.""" """Решения по отметке рядом с ней, все по порядку: действует последнее."""
if item.review is None: return "".join(
return "" f"; {'снята' if review.decision is FindingDecision.DISMISSED else 'подтверждена'} "
verdict = ("снята" if item.review.decision is FindingDecision.DISMISSED f"преподавателем {review.author}: {review.reason}"
else "подтверждена") for review in item.reviews
return f"; {verdict} преподавателем {item.review.author}: {item.review.reason}" )
def to_csv(report: SessionReport) -> bytes: def to_csv(report: SessionReport) -> bytes:
@ -96,7 +96,7 @@ def to_csv(report: SessionReport) -> bytes:
row("Сверка самооценки", "", label, ", ".join(values)) row("Сверка самооценки", "", label, ", ".join(values))
for number, item in enumerate(report.notes, 1): for number, item in enumerate(report.notes, 1):
row("Пометки преподавателя", number, item.transcript_ref, item.text, item.author) row("Пометки преподавателя", number, item.transcript_ref, item.text, item.author)
return ("\ufeff" + output.getvalue()).encode("utf-8") return ("" + output.getvalue()).encode("utf-8")
def _font_paths() -> tuple[Path, Path | None]: def _font_paths() -> tuple[Path, Path | None]:
@ -179,6 +179,9 @@ def to_pdf(report: SessionReport) -> bytes:
("Итоговая оценка", report.score_final), ("Итоговая оценка", report.score_final),
): ):
story.append(Paragraph(f"<b>{escape(label)}:</b> {escape(str(value))}", base)) story.append(Paragraph(f"<b>{escape(label)}:</b> {escape(str(value))}", base))
# Когда итог правили вручную, без этой строки не понять, какой балл дал разбор.
if report.score_reviewed is not None:
story.append(Paragraph(f"<b>После разбора отметок:</b> {report.score_reviewed}", base))
if report.overridden_by: if report.overridden_by:
story.append(Paragraph(f"<b>Оценка изменена:</b> {escape(report.overridden_by)}", base)) story.append(Paragraph(f"<b>Оценка изменена:</b> {escape(report.overridden_by)}", base))
if report.override_comment: if report.override_comment: