Services: - api: FastAPI gateway with JWT auth, async endpoints, WebSocket - worker-gpu: CUDA sentence-transformers, FAISS IVFFlat, Ollama LLM - worker-indexer: Winnowing+MinHash plagiarism detection, PDF/DOCX extraction - worker-notifier: SMTP email notifications - worker-gost: GOST 7.1-2003 and GOST R 7.0.5-2008 formatting Infrastructure: - docker-compose.yml (production) + docker-compose.dev.yml (hot reload) - Nginx reverse proxy + WebSocket support - PostgreSQL 16 with Alembic migrations - Elasticsearch 8 with Russian/English analyzers - MinIO, RabbitMQ, Redis, Ollama Frontend: - React 18 + Vite + TypeScript + TailwindCSS + Zustand + React Query v5 - 9 pages: Home, Search, Cabinet, Task, Check, Bibliography, Pricing, Login, Register Scripts: - Parser stubs: OpenAlex, КиберЛенинка, arXiv (Phase 0 - to be filled) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
"""Celery задача форматирования библиографии по ГОСТ."""
|
||
|
||
import logging
|
||
from typing import Any
|
||
|
||
from celery.utils.log import get_task_logger
|
||
from sqlalchemy import select
|
||
|
||
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__)
|
||
|
||
|
||
@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}")
|
||
|
||
# Выбрать форматтер
|
||
formatter = GOST71Formatter() if style == "7.1" else GOST705Formatter()
|
||
|
||
# Преобразовать 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,
|
||
})
|
||
|
||
# Сортировать: кириллица (рус. авторы) → латиница (иностр.)
|
||
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),
|
||
}
|
||
|
||
# Сохранить результат
|
||
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"{len(bibliography)} записей по ГОСТ {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)
|