jze9 c152fafa70
All checks were successful
Deploy / test (push) Successful in 3m23s
Deploy / deploy (push) Successful in 13s
feat(deploy): переключить .env на проде на Infisical вместо ручного файла
deploy.sh теперь на каждом запуске логинится в Infisical (Machine Identity
Universal Auth) и генерирует .env из окружения prod перед синком кода и
поднятием контейнеров. Если Infisical недоступен или вернул подозрительно
мало ключей — деплой падает раньше, не трогая рабочий .env на сервере.

Добавлен шаг compose up -d без --build для всех бэкенд-сервисов после
сборки изменившихся — иначе правка секрета без изменения кода не попадала
бы в уже запущенные контейнеры (docker compose пересоздаёт только то, чей
эффективный конфиг реально изменился, остальное не трогает).
2026-08-27 16:25:28 +05:00

Академический помощник

Микросервисная система антиплагиата и поиска академических источников.

Студент вводит тему → система ищет источники → проверяет плагиат → форматирует ГОСТ-библиографию. Всё асинхронно: студент закрыл браузер, получил email когда готово.

📐 Полное описание системы — docs/ARCHITECTURE.md. Отказоустойчивость и восстановление — docs/DR-HA.md.

Архитектура

                    ┌─────────────────┐
                    │   Frontend       │  React + Vite + TypeScript
                    │  (React SPA)     │
                    └────────┬────────┘
                             │ HTTPS
                    ┌────────▼────────┐
                    │   API Gateway    │  FastAPI, порт 8000
                    │  (FastAPI)       │  JWT, Rate Limit, WebSocket
                    └──┬─────────┬───┘
                       │ RabbitMQ│ Celery задачи
          ┌────────────▼──┐  ┌──▼──────────────┐
          │  worker-gpu    │  │ worker-indexer   │
          │  (CUDA/FAISS)  │  │ (PDF/DOCX parse) │
          │  Sem. search   │  │ Winnowing/MinHash │
          │  LLM paraphrase│  └──────────────────┘
          └────────────────┘
                    │
          ┌─────────▼──────────┐  ┌────────────────────┐
          │  worker-notifier    │  │  worker-gost        │
          │  (SMTP email)       │  │  (ГОСТ 7.1/7.0.5)  │
          └────────────────────┘  └────────────────────┘

Инфраструктура:
  PostgreSQL 16 · Redis 7 · RabbitMQ 3 · Elasticsearch 8
  MinIO (S3) · Ollama (qwen2.5:7b) · отдельный GPU-сервер

Быстрый старт

# 1. Клонировать репозиторий
git clone https://github.com/jze9/anti-plagiarism.git
cd anti-plagiarism

# 2. Создать файл конфигурации
cp .env.example .env
# Отредактировать .env — сменить пароли и ключи!

# 3. Запустить в режиме разработки
make dev

# 4. Применить миграции базы данных
make migrate-dev

# 5. Создать ES индекс
make es-init

# 6. Открыть браузер
# Frontend: http://localhost:5173
# API docs: http://localhost:8000/api/docs
# RabbitMQ: http://localhost:15672 (guest/guest)
# Flower:   http://localhost:5555
# MinIO:    http://localhost:9001

Команды

make dev          # Запуск в dev режиме (hot reload)
make build        # Сборка Docker образов
make up           # Запуск в продакшн режиме
make down         # Остановить все сервисы
make migrate      # Применить Alembic миграции
make logs         # Логи всех сервисов
make shell-api    # Shell в контейнере API
make shell-gpu    # Shell в контейнере GPU воркера
make lint         # ruff + mypy в контейнере (тот же гейт, что в CI)
make test         # юнит-тесты всех сервисов в контейнерах
make clean        # Удалить контейнеры и volumes

Стек технологий

Компонент Технологии
API Gateway FastAPI 0.111, Python 3.11, SQLAlchemy 2.0, Alembic
GPU Worker sentence-transformers, FAISS-CPU (IndexIDMap2 · IndexFlatIP), Ollama (qwen2.5:7b)
Indexer PyMuPDF, python-docx, Winnowing, MinHash LSH
Очереди RabbitMQ (брокер) + Celery 5 (воркеры) + Redis (результаты)
База данных PostgreSQL 16
Поиск Elasticsearch 8 (BM25) + FAISS (cosine, IndexFlatIP)
Хранилище MinIO (S3-совместимый)
Frontend React 18, Vite, TypeScript, TailwindCSS, Zustand, React Query v5

Проверка плагиата (4 уровня)

  1. Winnowing — точные/частичные совпадения по fingerprint'ам (xxHash + скользящее окно)
  2. MinHash LSH — нечёткие совпадения (шинглы + Jaccard, индекс в общем Redis)
  3. FAISS cosine — семантическое сходство (порог 0.75; IndexFlatIP на нормированных эмбеддингах)
  4. Ollama qwen2.5:7b — LLM-анализ парафраза (порог confidence 0.7)

Тарифные планы

Тариф Цена Поиск/день Изложений/мес Плагиат/мес Одновременно
Бесплатный 0₽ 10 3 1 1
Студенческий 199₽ безлимит 30 10 2
Премиум 499₽ безлимит безлимит 50 5
Научный 999₽ безлимит безлимит безлимит 10

Парсеры источников

# Запарсить OpenAlex
python scripts/run_parser.py openalex \
    --query "машинное обучение" \
    --limit 10000 \
    --output /data/processed

# КиберЛенинка
python scripts/run_parser.py cyberleninka \
    --query "нейронные сети" \
    --limit 1000

# arXiv
python scripts/run_parser.py arxiv \
    --query "deep learning" \
    --categories cs.AI cs.LG \
    --limit 5000

Переменные окружения

Смотри .env.example для полного списка переменных. Обязательно смените SECRET_KEY, POSTGRES_PASSWORD, MINIO_SECRET_KEY.

Три docker-compose файла

Файл Назначение
docker-compose.prod.yml Реальный прод. app-сервисы + nginx (собранный фронтенд + TLS) + локальный Elasticsearch. Postgres/Redis/RabbitMQ/MinIO/Ollama — уже существующие общие серверы, адреса в .env.
docker-compose.test.yml Повседневная разработка — hot reload против той же общей инфры, что и прод (make test-up).
docker-compose.selfhosted.yml.example Не используется. Полностью автономный вариант (свои Postgres/Redis/RabbitMQ/ES/MinIO/Ollama + GPU passthrough) — на случай отдельного выделенного сервера в будущем.

Продакшн деплой

# На сервере
cp .env.example .env
nano .env  # Настроить все пароли и ключи, указать реальные адреса общих Postgres/Redis/RabbitMQ/MinIO/Ollama

# Сертификат ДО первого запуска nginx-контейнера (standalone, порт 80 должен быть свободен)
certbot certonly --standalone -d academic.jze9.ru

make build
make up
make migrate

Nginx работает внутри контейнера (docker-compose.prod.yml, сервис nginx, Dockerfile в infra/nginx/Dockerfile) — собирает services/frontend в статику и отдаёт её вместе с проксированием /api/, /ws/ на api:8000 по конфигу infra/nginx/nginx.conf. Контейнер монтирует /etc/letsencrypt с хоста как read-only — сертификат обновляется на хосте (certbot renew), контейнер просто читает его.

Векторный бэкенд (FAISS / Qdrant)

Семантический индекс (уровень 3) спрятан за app.vector_store.get_backend() и переключается настройкой VECTOR_BACKEND — без изменения кода:

  • faiss (по умолчанию) — файловый IndexIDMap2(IndexFlatIP) в RAM воркера. Просто, но это единая точка отказа и без конкурентной записи.
  • qdrant — сетевой сервис: снимает SPOF, допускает конкурентный upsert из нескольких воркеров, переживает рестарт, масштабируется горизонтально.

Переключение на Qdrant (аддитивно, ничего не ломает до шага 2):

# 1. Поднять Qdrant (профиль qdrant в docker-compose.prod.yml)
docker compose -f docker-compose.prod.yml --profile qdrant up -d qdrant

# 2. В .env выставить VECTOR_BACKEND=qdrant (QDRANT_URL по умолчанию http://qdrant:6333)

# 3. Перелить существующие векторы FAISS → Qdrant (идемпотентно)
docker compose -f docker-compose.prod.yml exec worker-gpu python -m app.migrate_faiss_to_qdrant

# 4. Перезапустить GPU-воркер
docker compose -f docker-compose.prod.yml up -d worker-gpu

Наблюдаемость (Prometheus + Grafana)

Опционально (профиль observability, по умолчанию не поднимается):

docker compose -f docker-compose.prod.yml --profile observability up -d prometheus grafana
  • API отдаёт HTTP-метрики на /metrics (кол-во и латентность запросов по хендлерам).
  • Prometheus (infra/prometheus/prometheus.yml) скрейпит API и Flower — из Flower приходят метрики Celery (задачи, время выполнения, воркеры) без доп. кода.
  • Grafana с автоподключённым источником Prometheus (infra/grafana/provisioning/); пароль admin — через GRAFANA_ADMIN_PASSWORD в .env (по умолчанию admin).

Тестирование и качество кода

Перед деплоем CI (.gitea/workflows/deploy.yml, job test) прогоняет два гейта, и deploy стартует, только если оба зелёные — кривой код в прод не уезжает:

  1. Линт — ruff (весь Python) + mypy (чистая доменная логика). Конфиги: ruff.toml, mypy.ini.
  2. Юнит-тесты — pytest по сервисам: 122 теста на ядро детекции, скоринга, парсеров, форматирования и OAuth, без внешней инфры (БД/Redis/GPU/Ollama замоканы либо не нужны).
make lint                        # ruff + mypy в изолированном контейнере
make lint-fix                    # авто-исправления ruff
make test                        # все юнит-тесты в контейнерах
make test-one SVC=worker-gost    # тесты одного сервиса

Всё гоняется в python:3.11-slim (не засоряя хост) через scripts/run_lint.sh и scripts/run_tests.sh.

Что покрыто. Чистая логика вынесена из Celery-задач в отдельные тестируемые модули (доменная логика отдельно от оркестрации):

Слой Модуль Тестов
L1 — точные совпадения worker-indexer/app/algorithms/winnowing.py 13
L2 — нечёткие (MinHash LSH) worker-indexer/app/algorithms/minhash.py 6
Разбиение на фрагменты worker-indexer/app/fragments.py 5
Автопополнение корпуса worker-indexer/app/staging.py 6
Извлечение текста из PDF worker-indexer/app/extractors/pdf.py 4
L3 — семантический индекс (FAISS) worker-gpu/app/faiss_manager.py 5
L3 — векторный бэкенд (Qdrant + выбор) worker-gpu/app/qdrant_manager.py, vector_store.py 9
L4 — LLM-парафраз worker-gpu/app/ollama_client.py 12
Итоговый % плагиата + цитаты worker-gpu/app/scoring.py 18
ГОСТ 7.1 / 7.0.5 worker-gost/app/formatters/ 17
Список литературы worker-gost/app/bibliography.py 7
Парсеры источников (CyberLeninka, PMC) scripts/parsers/ 14
OAuth-ссылки (Google/Яндекс) api/app/core/oauth.py 6

Лицензия

MIT

Description
No description provided
Readme 382 KiB
Languages
Python 72.6%
TypeScript 23.7%
Shell 2.2%
Makefile 0.8%
Dockerfile 0.5%