Files
anti-plagiarism/services/worker-gost/app/formatters/gost_7_1.py
jze9 7758315632 feat: initial microservices project structure
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>
2026-05-24 19:42:39 +05:00

268 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""ГОСТ 7.1-2003 — Библиографическая запись. Библиографическое описание.
Полная запись для списка литературы.
Формат для статьи в журнале:
Автор(ы). Название // Журнал. — Год. — Т. X, № X. — С. XX.
Формат для книги:
Автор(ы). Название. — Город : Издательство, Год. — X с.
Правила по ГОСТ 7.1-2003:
- До 3 авторов: перечислить всех
- 4+ авторов: первые 3 + "и др." (рус.) / "et al." (англ.)
- Разделитель смысловых частей: ""
- Разделитель авторов: ", "
- Место публикации через "/"
"""
import re
def _format_author(author: dict) -> str:
"""
Форматировать одного автора.
Args:
author: dict с полями last_name, first_name, initials (опционально)
Returns:
Строка вида "Иванов И. И." или "Иванов И."
"""
last_name = author.get("last_name", "").strip()
initials = author.get("initials", "").strip()
first_name = author.get("first_name", "").strip()
if not last_name:
return ""
if initials:
# Нормализовать инициалы: обеспечить точки
if not initials.endswith("."):
initials += "."
return f"{last_name} {initials}"
elif first_name:
# Извлечь инициалы из полного имени
parts = first_name.split()
inits = "".join(p[0].upper() + "." for p in parts if p)
return f"{last_name} {inits}"
else:
return last_name
def _format_authors(authors: list[dict], lang: str = "ru") -> str:
"""
Форматировать список авторов по правилам ГОСТ 7.1-2003.
Args:
authors: Список словарей с авторами
lang: Язык для "и др." / "et al."
Returns:
Строка с отформатированными авторами
"""
if not authors:
return ""
et_al = "и др." if lang == "ru" else "et al."
formatted = [_format_author(a) for a in authors if a.get("last_name")]
formatted = [f for f in formatted if f]
if not formatted:
return ""
if len(formatted) <= 3:
return ", ".join(formatted)
else:
return ", ".join(formatted[:3]) + f", {et_al}"
class GOST71Formatter:
"""Форматтер полных библиографических записей по ГОСТ 7.1-2003."""
def format_full(self, doc: dict) -> str:
"""
Форматировать полную библиографическую запись.
Args:
doc: Словарь с полями документа
Returns:
Отформатированная строка библиографической записи
"""
doc_type = self._detect_type(doc)
if doc_type == "article":
return self._format_article(doc)
elif doc_type == "book":
return self._format_book(doc)
elif doc_type == "web":
return self._format_web(doc)
else:
return self._format_generic(doc)
def _detect_type(self, doc: dict) -> str:
"""Определить тип документа."""
if doc.get("journal"):
return "article"
if doc.get("url") and not doc.get("journal"):
return "web"
return "generic"
def _format_article(self, doc: dict) -> str:
"""
Форматировать статью в журнале.
Формат: Автор(ы). Название // Журнал. — Год. — Т. X, № X. — С. XX.
"""
lang = doc.get("lang", "ru") or "ru"
authors = doc.get("authors", [])
author_str = _format_authors(authors, lang=lang)
title = (doc.get("title") or "").strip()
journal = (doc.get("journal") or "").strip()
year = doc.get("year", "")
volume = doc.get("volume", "")
issue = doc.get("issue", "")
pages = doc.get("pages", "")
doi = doc.get("doi", "")
parts = []
# Авторы и название
if author_str:
parts.append(f"{author_str}. {title}")
else:
parts.append(title)
# Место публикации
location = f"// {journal}"
if year:
location += f". — {year}"
if volume:
location += f". — Т. {volume}"
if issue:
location += f", № {issue}"
if pages:
location += f". — С. {pages}"
parts.append(location)
result = " ".join(parts)
# DOI
if doi:
result += f". — DOI: {doi}"
return result.strip()
def _format_book(self, doc: dict) -> str:
"""
Форматировать книгу / монографию.
Формат: Автор(ы). Название. — Город : Издательство, Год. — X с.
"""
lang = doc.get("lang", "ru") or "ru"
authors = doc.get("authors", [])
author_str = _format_authors(authors, lang=lang)
title = (doc.get("title") or "").strip()
year = doc.get("year", "б. г.")
pages = doc.get("pages", "")
parts = []
if author_str:
parts.append(f"{author_str}.")
parts.append(f"{title}.")
parts.append(f"{year}.")
if pages:
# Предполагаем, что pages содержит количество страниц
parts.append(f"{pages} с.")
return " ".join(parts).strip()
def _format_web(self, doc: dict) -> str:
"""
Форматировать электронный ресурс.
Формат: Автор(ы). Название [Электронный ресурс]. — URL: ... (дата обращения: ...)
"""
lang = doc.get("lang", "ru") or "ru"
authors = doc.get("authors", [])
author_str = _format_authors(authors, lang=lang)
title = (doc.get("title") or "").strip()
url = (doc.get("url") or "").strip()
year = doc.get("year", "")
parts = []
if author_str:
parts.append(f"{author_str}.")
# Для ГОСТ — обозначение электронного ресурса
parts.append(f"{title} [Электронный ресурс].")
if year:
parts.append(f"{year}.")
if url:
parts.append(f"— URL: {url}")
return " ".join(parts).strip()
def _format_generic(self, doc: dict) -> str:
"""Базовый форматтер для неопределённых типов."""
lang = doc.get("lang", "ru") or "ru"
author_str = _format_authors(doc.get("authors", []), lang=lang)
title = (doc.get("title") or "").strip()
year = doc.get("year", "")
result = ""
if author_str:
result += f"{author_str}. "
result += title
if year:
result += f". — {year}"
return result.strip()
def _get_sort_key(doc: dict) -> str:
"""
Получить ключ сортировки для документа.
Кириллица идёт перед латиницей (для русских традиций):
сначала русскоязычные источники, затем иностранные.
Args:
doc: Словарь с метаданными
Returns:
Строка для сортировки
"""
authors = doc.get("authors", [])
if authors:
last_name = authors[0].get("last_name", "")
else:
last_name = doc.get("title", "")
if not last_name:
return "яяя" # В конец
# Кириллица < латиница (традиция: русские источники первыми)
first_char = last_name[0].lower()
is_latin = ord(first_char) < 256 and first_char.isalpha()
# Префикс для сортировки: 0 для кириллицы, 1 для латиницы
prefix = "1" if is_latin else "0"
return f"{prefix}{last_name.lower()}"
def format_full(doc: dict) -> str:
"""Удобная функция для быстрого форматирования."""
return GOST71Formatter().format_full(doc)