refactor(gost): вынести сборку списка литературы в app.bibliography + 7 тестов

Сортировка (кириллица→латиница), нумерация и выбор форматтера жили внутри
Celery-задачи с БД и не тестировались — хотя это порядок и вид готового списка
литературы, который видит студент. Вынес в чистый app.bibliography.build_bibliography:

- нумерация сквозная с 1; total = число записей;
- сортировка по фамилии первого автора, кириллица раньше латиницы;
- стиль 7.1 → полное описание (format_full), иначе 7.0.5 → краткая ссылка;
- doc_id сохраняется в каждой записи; пустой список → total 0.

ORM→dict конверсия осталась в задаче (она из БД), поведение сохранено 1:1.
Добавлен в mypy-гейт. Тестов всего: 73 (indexer 24, gost 24, gpu 25).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jze9
2026-08-11 20:20:01 +05:00
parent b2d2061a83
commit ccc3521e74
4 changed files with 106 additions and 28 deletions

View File

@@ -0,0 +1,42 @@
"""Сборка списка литературы по ГОСТ — чистая логика (без Celery/БД).
Сортирует источники (кириллица → латиница), нумерует и форматирует каждый по
выбранному стилю. Вынесено из Celery-задачи для изолированного тестирования
порядка и нумерации — того, что студент видит в готовом списке литературы.
"""
from collections.abc import Callable
from typing import Any
from app.formatters.gost_7_0_5 import GOST705Formatter
from app.formatters.gost_7_1 import GOST71Formatter, _get_sort_key
def build_bibliography(docs: list[dict[str, Any]], style: str = "7.1") -> dict[str, Any]:
"""Собрать нумерованный список литературы по ГОСТ.
Args:
docs: словари документов (title/authors/year/...); каждый должен иметь "id"
style: "7.1" (полное описание) или иначе — "7.0.5" (краткая ссылка)
Returns:
dict с полями:
bibliography: list of {"number", "citation", "doc_id"}
style: применённый стиль
total: число записей
"""
cite: Callable[[dict[str, Any]], str] = (
GOST71Formatter().format_full if style == "7.1" else GOST705Formatter().format_short
)
sorted_docs = sorted(docs, key=_get_sort_key)
bibliography = [
{"number": number, "citation": cite(doc), "doc_id": doc["id"]}
for number, doc in enumerate(sorted_docs, 1)
]
return {
"bibliography": bibliography,
"style": style,
"total": len(bibliography),
}

View File

@@ -5,10 +5,9 @@ from typing import Any
from celery.utils.log import get_task_logger
from sqlalchemy import select
from app.bibliography import build_bibliography
from app.celery_app import celery_app
from app.db import db_session
from app.formatters.gost_7_0_5 import GOST705Formatter
from app.formatters.gost_7_1 import GOST71Formatter, _get_sort_key
logger = get_task_logger(__name__)
@@ -59,9 +58,6 @@ def format_bibliography(
if not docs:
raise ValueError(f"Документы не найдены: {doc_ids}")
# Выбрать форматтер
formatter = GOST71Formatter() if style == "7.1" else GOST705Formatter()
# Преобразовать ORM объекты в словари для форматирования
docs_dicts = []
for doc in docs:
@@ -80,27 +76,8 @@ def format_bibliography(
"source": doc.source,
})
# Сортировать: кириллица (рус. авторы) → латиница (иностр.)
sorted_docs = sorted(docs_dicts, key=lambda d: _get_sort_key(d))
bibliography = []
for i, doc_dict in enumerate(sorted_docs, 1):
if style == "7.1":
citation = formatter.format_full(doc_dict)
else:
citation = formatter.format_short(doc_dict)
bibliography.append({
"number": i,
"citation": citation,
"doc_id": doc_dict["id"],
})
result = {
"bibliography": bibliography,
"style": style,
"total": len(bibliography),
}
# Сортировка + нумерация + форматирование — чистая логика в app.bibliography
result = build_bibliography(docs_dicts, style)
# Сохранить результат
task = session.get(Task, task_id)
@@ -112,7 +89,7 @@ def format_bibliography(
logger.info(
f"Библиография сформирована для задачи {task_id!r}: "
f"{len(bibliography)} записей по ГОСТ {style}"
f"{result['total']} записей по ГОСТ {style}"
)
# Уведомить пользователя