Files
anti-plagiarism/services/worker-indexer/app/config.py
jze9 78e27806b9
All checks were successful
Deploy / test (push) Successful in 3m56s
Deploy / deploy (push) Successful in 31s
feat(admin): шкала загрузки источников, отладка и загрузка работ в корпус
Заливка корпуса была чёрным ящиком: у источника только last_status
(idle/running/done/error), без «сколько из скольки», без причины падения и без
способа остановить начатое. Теперь каждый запуск создаёт строку parse_runs,
куда воркер раз в ~2с пишет стадию, счётчики и журнал событий.

Админка:
- шкала загрузки у каждого источника (0→50% выборка, 50→100% индексация),
  раскрытая строка — журнал прогона по шагам с таймингами;
- «Запустить всё» / «Остановить всё» и остановка по одному источнику
  (кооперативная отмена: воркер останавливается сам, не рвя запись в базу);
- пакетное добавление источников (тип + список тем), тип pmc в форме;
- загрузка PDF/DOCX/TXT прямо в базу сравнения (index.ingest_upload);
- страница «Отладка»: воркеры Celery и их текущие таски, очереди RabbitMQ,
  покрытие корпуса эмбеддингами, зависшие и упавшие прогоны, конфиг бэкендов.

Защита от краш-лупа по consumer_timeout RabbitMQ (docs/DR-HA.md §6), без неё
массовый запуск 170+ источников гарантированно ронял воркер:
- PARSER_TIME_BUDGET_S (1500с) — прогон закругляется сам и помечается partial;
- worker_prefetch_multiplier=1 — таймаут считается от ДОСТАВКИ сообщения, и с
  дефолтным префетчем очередь долгих run_parser убивала канал на задачах,
  которые ещё не начинались.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 17:40:23 +05:00

92 lines
5.0 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.
"""Конфигурация индексер-воркера."""
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Настройки индексер-воркера."""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
# PostgreSQL
POSTGRES_HOST: str = "postgres"
POSTGRES_PORT: int = 5432
POSTGRES_DB: str = "antiplagiator"
POSTGRES_USER: str = "antiplagiator"
POSTGRES_PASSWORD: str = "changeme"
# Redis
REDIS_URL: str = "redis://redis:6379/0"
# RabbitMQ
RABBITMQ_URL: str = "amqp://guest:guest@rabbitmq:5672/"
# MinIO
MINIO_ENDPOINT: str = "minio:9000"
MINIO_ACCESS_KEY: str = "minioadmin"
MINIO_SECRET_KEY: str = "changeme"
MINIO_BUCKET_DOCS: str = "documents"
MINIO_BUCKET_STAGING: str = "staging"
# Elasticsearch
ELASTICSEARCH_URL: str = "http://elasticsearch:9200"
# Настройки обработки текста
FRAGMENT_WINDOW_WORDS: int = 200 # Размер окна для фрагментов
FRAGMENT_OVERLAP_WORDS: int = 50 # Перекрытие фрагментов
# Максимум хэшей Winnowing на документ. Winnowing рассчитан на ПОЛНОЕ
# хранение отпечатков: обрезка теряет гарантию, что скопированный фрагмент
# разделит отпечатки с источником (при 500 хранилось ~14% отпечатков статьи,
# и локализованный поиск фрагментов почти не срабатывал). Держим высоким;
# цена — размер таблицы fingerprints (~3-4К строк на статью).
MAX_FINGERPRINTS_PER_DOC: int = 20000
# Порог уровня 1: % отпечатков фрагмента, найденных в источнике, чтобы
# пометить фрагмент как скопированный
EXACT_FRAGMENT_THRESHOLD: float = 40.0
# Скачивание полного текста статей (PDF по URL источника)
FETCH_FULL_TEXT: bool = False # Включить обогащение полным текстом при заливке корпуса
# Лить из OpenAlex только open-access работы (is_oa:true) — резко повышает
# долю статей с доступным PDF (покрытие full-text ~8% → ~70-80%)
INGEST_OPEN_ACCESS_ONLY: bool = True
FULL_TEXT_TIMEOUT: float = 30.0 # Таймаут скачивания одного документа, сек
FULL_TEXT_MAX_BYTES: int = 30 * 1024 * 1024 # Лимит размера скачиваемого файла (30 МБ)
FULL_TEXT_MIN_CHARS: int = 500 # Минимум символов, иначе считаем извлечение неудачным
EMBED_BATCH_SIZE: int = 64 # Размер пачки документов для диспатча эмбеддингов
# Бюджет времени одного прогона index.run_parser. RabbitMQ рвёт канал
# consumer'а, не сделавшего ack за consumer_timeout (по умолчанию 1800с):
# воркер падает, сообщение передоставляется, таск начинается заново —
# бесконечный краш-луп (docs/DR-HA.md §6). Прогон закругляется раньше и
# честно помечается partial: остаток дозаливается повторным запуском.
PARSER_TIME_BUDGET_S: float = 1500.0
# Автоматически добавлять проверенные работы студентов в базу для сравнения
# (как в коммерческих системах — Антиплагиат.ру/Turnitin ловят списывание у
# предыдущих потоков именно так). Выключено по умолчанию: без ручной
# модерации студент, перепроверивший тот же файл дважды, получал 100%
# "плагиата" — против собственной же более ранней загрузки. L1/L2/L3 теперь
# и так исключают source=user_submission из сравнения, так что включать это
# обратно есть смысл только вместе с реальной защитой от self/cross-match.
AUTO_APPROVE_SUBMISSIONS: bool = False
# App
ENVIRONMENT: str = "development"
DEBUG: bool = False
@property
def database_url_sync(self) -> str:
"""Синхронный URL для SQLAlchemy."""
return (
f"postgresql+psycopg2://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}"
f"@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"
)
settings = Settings()