Сортировка (кириллица→латиница), нумерация и выбор форматтера жили внутри 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>
119 lines
4.0 KiB
Python
119 lines
4.0 KiB
Python
"""Celery задача форматирования библиографии по ГОСТ."""
|
||
|
||
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
|
||
|
||
logger = get_task_logger(__name__)
|
||
|
||
|
||
@celery_app.task(
|
||
name="gost.format_bibliography",
|
||
bind=True,
|
||
max_retries=3,
|
||
default_retry_delay=30,
|
||
)
|
||
def format_bibliography(
|
||
self,
|
||
task_id: str,
|
||
doc_ids: list[int],
|
||
style: str = "7.1",
|
||
) -> dict[str, Any]:
|
||
"""
|
||
Форматировать список литературы по ГОСТ для переданных doc_ids.
|
||
|
||
Args:
|
||
task_id: ID задачи в PostgreSQL
|
||
doc_ids: Список ID документов из PostgreSQL
|
||
style: Стиль форматирования — "7.1" (полная) или "7.0.5" (краткая)
|
||
|
||
Returns:
|
||
dict с отформатированной библиографией
|
||
"""
|
||
from app.models import Document, Task
|
||
|
||
logger.info(
|
||
f"Форматирование библиографии для задачи {task_id!r}: "
|
||
f"{len(doc_ids)} документов, стиль ГОСТ {style}"
|
||
)
|
||
|
||
try:
|
||
with db_session() as session:
|
||
# Обновить статус
|
||
task = session.get(Task, task_id)
|
||
if task:
|
||
task.status = "processing"
|
||
session.commit()
|
||
|
||
# Получить документы
|
||
docs = session.execute(
|
||
select(Document).where(Document.id.in_(doc_ids))
|
||
).scalars().all()
|
||
|
||
if not docs:
|
||
raise ValueError(f"Документы не найдены: {doc_ids}")
|
||
|
||
# Преобразовать ORM объекты в словари для форматирования
|
||
docs_dicts = []
|
||
for doc in docs:
|
||
docs_dicts.append({
|
||
"id": doc.id,
|
||
"title": doc.title,
|
||
"authors": doc.authors or [],
|
||
"year": doc.year,
|
||
"journal": doc.journal,
|
||
"volume": doc.volume,
|
||
"issue": doc.issue,
|
||
"pages": doc.pages,
|
||
"doi": doc.doi,
|
||
"url": doc.url,
|
||
"lang": doc.lang or "ru",
|
||
"source": doc.source,
|
||
})
|
||
|
||
# Сортировка + нумерация + форматирование — чистая логика в app.bibliography
|
||
result = build_bibliography(docs_dicts, style)
|
||
|
||
# Сохранить результат
|
||
task = session.get(Task, task_id)
|
||
if task:
|
||
task.result = result
|
||
task.status = "done"
|
||
task.queue_position = None
|
||
session.commit()
|
||
|
||
logger.info(
|
||
f"Библиография сформирована для задачи {task_id!r}: "
|
||
f"{result['total']} записей по ГОСТ {style}"
|
||
)
|
||
|
||
# Уведомить пользователя
|
||
celery_app.send_task(
|
||
"notify.send_task_done",
|
||
args=[task_id],
|
||
queue="queue.notify",
|
||
)
|
||
|
||
return result
|
||
|
||
except Exception as exc:
|
||
logger.error(f"Ошибка при форматировании библиографии для задачи {task_id!r}: {exc}", exc_info=True)
|
||
|
||
try:
|
||
from app.models import Task
|
||
with db_session() as session:
|
||
task = session.get(Task, task_id)
|
||
if task:
|
||
task.status = "failed"
|
||
task.error = str(exc)
|
||
session.commit()
|
||
except Exception as db_exc:
|
||
logger.error(f"Не удалось обновить статус задачи: {db_exc}")
|
||
|
||
raise self.retry(exc=exc, countdown=30) from exc
|