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: