From 871832d5ca1523ddd011d7bb6edea625a5e8353a Mon Sep 17 00:00:00 2001 From: GGlamer <52128225+Gamer201760@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:41:27 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B1=D0=B0=D0=BB=D0=BB=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=20=D1=80=D0=B0=D0=B7=D0=B1=D0=BE=D1=80=D0=B0?= =?UTF-8?q?=20=D0=B2=20PDF,=20=D0=B2=D1=81=D0=B5=20=D1=80=D0=B5=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=20=D0=BE=D1=82=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=BA=D0=B5=20=D0=B2=20=D1=8D=D0=BA=D1=81=D0=BF?= =?UTF-8?q?=D0=BE=D1=80=D1=82=D0=B5,=20=D0=B2=D0=B5=D1=81=20=D0=BE=D1=82?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D0=BA=D0=B8=20=D0=BF=D1=80=D0=B5=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F=20=D0=B2=20?= =?UTF-8?q?=D0=BA=D1=80=D0=B8=D1=82=D0=B5=D1=80=D0=B8=D1=8F=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/domain/events.py | 6 ++++-- backend/app/scoring/export.py | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/backend/app/domain/events.py b/backend/app/domain/events.py index 3cd3790..0d5f931 100644 --- a/backend/app/domain/events.py +++ b/backend/app/domain/events.py @@ -23,7 +23,7 @@ from app.domain.roles import Role from app.domain.statuses import ServiceStatus, StationSnapshot from app.domain.taxonomy import Finding 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): @@ -60,7 +60,7 @@ class LessonCriteria(BaseModel): @field_validator("score_weights") @classmethod 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: raise ValueError(f"неизвестные метрики весов: {', '.join(sorted(unknown))}") if any(not 0 <= weight <= 10 for weight in weights.values()): @@ -747,6 +747,8 @@ class DdsCardReport(BaseModel): card_id: UUID scenario_id: str score_auto: float + #: Балл карточки после решений преподавателя по отметкам; None — решений не было. + score_reviewed: float | None = None reply_text: str metrics: list[Metric] findings: list[Finding] diff --git a/backend/app/scoring/export.py b/backend/app/scoring/export.py index 9ee879b..2ea4cf6 100644 --- a/backend/app/scoring/export.py +++ b/backend/app/scoring/export.py @@ -19,7 +19,7 @@ def _cell(value: object) -> str: if value is None: return "" 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")): return "'" + result return result @@ -32,12 +32,12 @@ def _finding_origin(item: Finding) -> str: def _finding_decision(item: Finding) -> str: - """Решение по отметке рядом с ней: снятая отметка видна вместе с причиной.""" - if item.review is None: - return "" - verdict = ("снята" if item.review.decision is FindingDecision.DISMISSED - else "подтверждена") - return f"; {verdict} преподавателем {item.review.author}: {item.review.reason}" + """Решения по отметке рядом с ней, все по порядку: действует последнее.""" + return "".join( + f"; {'снята' if review.decision is FindingDecision.DISMISSED else 'подтверждена'} " + f"преподавателем {review.author}: {review.reason}" + for review in item.reviews + ) def to_csv(report: SessionReport) -> bytes: @@ -96,7 +96,7 @@ def to_csv(report: SessionReport) -> bytes: row("Сверка самооценки", "", label, ", ".join(values)) for number, item in enumerate(report.notes, 1): 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]: @@ -179,6 +179,9 @@ def to_pdf(report: SessionReport) -> bytes: ("Итоговая оценка", report.score_final), ): story.append(Paragraph(f"{escape(label)}: {escape(str(value))}", base)) + # Когда итог правили вручную, без этой строки не понять, какой балл дал разбор. + if report.score_reviewed is not None: + story.append(Paragraph(f"После разбора отметок: {report.score_reviewed}", base)) if report.overridden_by: story.append(Paragraph(f"Оценка изменена: {escape(report.overridden_by)}", base)) if report.override_comment: