Commit Graph

9 Commits

Author SHA1 Message Date
jze9
df2dfcc456 fix(indexer): понятная ошибка, если загруженный "PDF" на самом деле не PDF
All checks were successful
Deploy / test (push) Successful in 2m41s
Deploy / deploy (push) Successful in 11s
Живой репорт: проверка плагиата упала с "Не удалось открыть PDF: code=7: no
objects found" — сырая внутренняя ошибка MuPDF. Разобрал файл из MinIO: это
HTML-страница (веб-интерфейс роутера D-Link DAP-400P), сохранённая с
расширением .pdf — 2049 байт, не PDF вообще. Система корректно отказалась
парсить мусор (это не баг пайплайна — реальный битый/не-PDF файл юзера), но
сообщение об ошибке было нечитаемым.

extract_text_from_pdf теперь проверяет magic-байты (%PDF-) ДО попытки
fitz.open() и даёт понятное "Файл повреждён или не является PDF-документом"
для этого частого случая; для настоящих PDF с внутренней порчей — прежнее
поведение (сырая ошибка MuPDF как detail, для диагностики).

4 юнит-теста (валидный PDF, HTML под видом PDF, пустые байты, битый заголовок
PDF). Тестов всего: 122 (было 118).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-24 18:55:24 +05:00
jze9
9f35ae8de1 feat(auth): вход через Google и Яндекс (OAuth2 authorization code flow)
All checks were successful
Deploy / test (push) Successful in 2m48s
Deploy / deploy (push) Successful in 23s
Реализовано без authlib, на голом httpx (AsyncClient — синхронный httpx
блокировал бы event loop API на время внешнего запроса), по образцу двух
провайдеров:

- Миграция 004: hashed_password → nullable (OAuth-юзеры без пароля),
  oauth_provider/oauth_id + уникальный индекс на пару.
- app/core/security.py: verify_password защищён от hashed=None (иначе TypeError
  при попытке OAuth-юзера войти по паролю — нашёл при ревью, не баг-репорт).
- app/core/oauth.py: get_authorize_url()/exchange_code() — единый интерфейс для
  google/yandex. Redirect URI: <APP_URL>/api/auth/<provider>/callback.
- app/api/auth.py: GET /auth/{provider}/login (редирект на согласие, state в
  httponly-cookie от CSRF) и /callback (обмен code, find-or-create юзера по
  oauth_id → по email для привязки существующего аккаунта → новый без пароля,
  is_verified=email_verified от провайдера). Токен фронту — через URL-фрагмент
  #token=..., не query (не уходит в логи/Referer).
- Фронтенд: OAuthButtons (Login/Register), страница /oauth/callback (читает
  фрагмент → GET /auth/me → setAuth → редирект в кабинет).
- 6 юнит-тестов чистой логики сборки ссылок (app/core/oauth.py) — первый тест-
  контур для api/ в этой сессии (pytest.ini/conftest/requirements-test по
  образцу остальных сервисов), добавлен в общий run_tests.sh + mypy-гейт.

GOOGLE_CLIENT_ID/SECRET уже в .env (юзер создал OAuth-клиент), YANDEX_* пусты —
эндпоинты в этом случае отвечают 503, не падают. .env.example документирует обе
пары. Тестов всего: 118 (было 112).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-24 17:59:10 +05:00
jze9
b471ec767a feat: две доработки в духе коммерческих систем — цитаты и авто-корпус
Some checks failed
Deploy / test (push) Failing after 4m15s
Deploy / deploy (push) Has been skipped
Ответ на "неужели больше нет" / "мы ведь можем это исправить": закрывает
две дыры относительно Антиплагиат.ру/Turnitin, о которых договорились.

1. Различение цитаты и голого плагиата (app.scoring.is_cited, worker-gpu):
   эвристика — фрагмент считается процитированным, если обрамлён кавычками
   («…», "…") либо сразу за ним (в пределах ~150 симв.) идёт скобочная ссылка
   с годом: [Иванов, 2023], (Smith, 2020) — совпадает и с нашим же форматом
   ГОСТ 7.0.5. aggregate_results теперь принимает full_text, размечает
   match["cited"] и считает uncited_similarity (доля БЕЗ похожих на цитаты —
   ближе к тому, что коммерческие системы называют "% некорректных
   заимствований") отдельно от overall_similarity (как было, для совместимости).
   Фронтенд: бейдж "Цитата" на совпадении + строка с разбивкой в отчёте
   (аддитивные опциональные поля в типах — старые задачи не ломаются).

2. Автопополнение корпуса проверенными работами (как у коммерческих систем —
   так ловится списывание у предыдущих потоков). Раньше загруженная на проверку
   работа складывалась в StagedWork и ждала РУЧНОГО одобрения админом — де-факто
   не пополняла базу для сравнения. Теперь index.auto_approve_submission
   (диспатчится из gpu.check_plagiarism ПОСЛЕ сохранения результата — чтобы
   работа не сматчилась сама с собой) добавляет её в documents автоматически,
   под настройкой AUTO_APPROVE_SUBMISSIONS (default True). Ручное
   approve/reject в админке остаётся рабочим (идемпотентно — auto-approve
   пропускает уже не-pending записи), пригодится при AUTO_APPROVE=False.
   Конвертация StagedWork→doc_data вынесена в чистый app/staging.py (без
   Celery/SQLAlchemy/MinIO) — тестируется изолированно, идентична ручному
   пути в admin.py (POST /admin/staging/{id}/approve).

Тестов добавлено 16 (scoring 8→18, новый staging.py — 6). Оба mypy-гейта
расширены (scoring.py, staging.py). Тестов всего: 112 (было 82 в последнем
подсчёте README — таблица давно отставала, заодно поправил на актуальные цифры
по всем сервисам, включая забытый в прошлый раз Qdrant).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-24 15:09:43 +05:00
jze9
e6c44f30dd docs: полное описание проекта — docs/ARCHITECTURE.md
Каноничная документация всей системы: назначение, схема, 6 сервисов и их зоны
ответственности, модель данных (все таблицы), Celery-очереди/задачи, ключевые
потоки (проверка плагиата, поиск, библиография, наполнение корпуса), 4 уровня
детекции, векторный бэкенд, инфра-топология (узлы/адреса), конфигурация, гейты
качества, наблюдаемость/эксплуатация, безопасность, раскладка репозитория.

README ссылается на ARCHITECTURE.md и DR-HA.md. Данные сверены с кодом (модели,
TaskType/TaskStatus, источники, тарифы, маршрутизация задач).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 20:45:23 +05:00
jze9
d2d99d8231 feat(obs): наблюдаемость Prometheus + Grafana под профилем observability
Раньше видимости в прод не было — только email-монитор (костыль). Добавлено
без нагрузки по умолчанию (профиль не поднимается, пока не попросят):

- API: /metrics через prometheus-fastapi-instrumentator (кол-во/латентность
  запросов по хендлерам);
- Prometheus (infra/prometheus/prometheus.yml) скрейпит API и Flower — из Flower
  приходят метрики Celery (задачи, время, воркеры) вообще без доп. кода;
- Grafana с автопровижном источника Prometheus (infra/grafana/provisioning),
  пароль через GRAFANA_ADMIN_PASSWORD;
- docker-compose.prod.yml: сервисы prometheus/grafana под profiles:[observability]
  + volumes; .env.example и README пополнены.

Запуск: docker compose --profile observability up -d prometheus grafana.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 20:37:47 +05:00
jze9
012b17304f feat(gpu): Qdrant как векторный бэкенд под флагом — снимает SPOF FAISS
FAISS-индекс — файловый синглтон в RAM одного воркера (save на каждую запись,
без блокировок, без HA, не горизонтален). Добавлен альтернативный бэкенд Qdrant
с тем же classmethod-интерфейсом, выбор через VECTOR_BACKEND — аддитивно и
безопасно: дефолт остаётся faiss, ничего не ломается, пока не переключат.

- app/qdrant_manager.py — search/add_vectors/save(no-op)/total_vectors поверх
  qdrant-client (коллекция Cosine, id точки = doc_id, upsert идемпотентен);
- app/vector_store.py — get_backend() по настройке; задачи search/plagiarism
  переведены на него (больше не импортируют FAISSManager напрямую);
- app/migrate_faiss_to_qdrant.py — перелив существующих векторов (reconstruct
  из IndexIDMap2 → upsert), идемпотентно;
- docker-compose.prod.yml — сервис qdrant под профилем `qdrant` (по умолчанию не
  поднимается, ресурсов не ест) + volume; README — раздел про переключение.

Тесты (9) гоняют QdrantManager против ВСТРОЕННОГО Qdrant (qdrant-client :memory:,
не моки) + диспетчеризацию бэкенда. Всего тестов: 82 (indexer 24, gost 24, gpu 34).
Плюсы Qdrant активируются только после явного переключения + миграции.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 20:34:43 +05:00
jze9
37ee18ac55 docs(readme): секция «Тестирование и качество» + фикс устаревших фактов
Добавлена секция про CI-гейты (ruff+mypy, 73 юнит-теста, make-команды,
таблица покрытия по слоям L1-L4/скоринг/фрагменты/ГОСТ/библиография).

Заодно поправлены устаревшие технические утверждения, разошедшиеся с кодом:
- FAISS-GPU (IVFFlat) → FAISS-CPU (IndexIDMap2 · IndexFlatIP) — реальный индекс;
- Ollama llama3:8b → qwen2.5:7b; убран RTX 2060 (LLM на отдельном GPU-сервере);
- уточнено описание 4 уровней проверки под фактическую реализацию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 20:22:45 +05:00
jze9
673e72a15a feat(infra): переход прод/test на общую инфраструктуру вместо self-hosted
Postgres/Redis/RabbitMQ/MinIO/Ollama теперь общие серверы сети (адреса в .env),
локально в докере остаётся только Elasticsearch + app-сервисы. Старый
docker-compose.yml (полностью автономный стек) сохранён как
docker-compose.selfhosted.yml.example на случай отдельного GPU-сервера в
будущем. Добавлен docker-compose.prod.yml (app + свой nginx с TLS,
собирающий фронтенд в статику). Makefile переписан под три режима
(dev/test-up/prod). Мелкая чистка неиспользуемых импортов во фронтенде.
2026-07-27 12:22:36 +05:00
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