docs: route production operations through MIEM infra-platform
This commit is contained in:
1 parent
22e1bd3f2e
commit
201e4dd299
1 file changed
+91
-91
@@ -1,48 +1,48 @@
|
||||
# MIEM Employees Server
|
||||
|
||||
# MIEM Employees Server
|
||||
|
||||
Сервис собирает сотрудников МИЭМ с сайта ВШЭ, хранит карточки и историю обновлений в Postgres и показывает минимальную админку.
|
||||
|
||||
## Архитектура
|
||||
|
||||
|
||||
## Архитектура
|
||||
|
||||
- `api`: FastAPI, REST API, HTML-админка и healthcheck.
|
||||
- `worker`: weekly scheduler, который запускает парсинг по `CRAWL_CRON`.
|
||||
- `postgres`: основная БД.
|
||||
|
||||
Парсер использует фиксированный источник сотрудников, по умолчанию `https://miem.hse.ru/persons`. Для каждой карточки сохраняются ФИО, должности, год начала работы, контакты, идентификаторы, вкладки профиля, секции, публикации, курсы, ВКР, новости, JSON-снапшот и сжатый HTML-снапшот. Детальные публикации дополнительно нормализуются в отдельную таблицу `employee_publications`, а новости из блока «В новостях» — в `employee_news_links`. Ссылки обходятся только из меню профиля самого сотрудника (`person-menu`), например `#sci`, `#teaching`, `#main`.
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
Скопируйте `.env.example` в `.env` и поменяйте секреты:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Основные настройки:
|
||||
|
||||
- `DATABASE_URL`: строка подключения SQLAlchemy.
|
||||
- `postgres`: основная БД.
|
||||
|
||||
Парсер использует фиксированный источник сотрудников, по умолчанию `https://miem.hse.ru/persons`. Для каждой карточки сохраняются ФИО, должности, год начала работы, контакты, идентификаторы, вкладки профиля, секции, публикации, курсы, ВКР, новости, JSON-снапшот и сжатый HTML-снапшот. Детальные публикации дополнительно нормализуются в отдельную таблицу `employee_publications`, а новости из блока «В новостях» — в `employee_news_links`. Ссылки обходятся только из меню профиля самого сотрудника (`person-menu`), например `#sci`, `#teaching`, `#main`.
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
Скопируйте `.env.example` в `.env` и поменяйте секреты:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Основные настройки:
|
||||
|
||||
- `DATABASE_URL`: строка подключения SQLAlchemy.
|
||||
- `SOURCE_URL`: список сотрудников МИЭМ.
|
||||
- `CRAWL_CRON`: расписание в формате crontab, по умолчанию `0 3 * * 1`.
|
||||
- `CRAWL_LIMIT`: опциональный лимит профилей для тестового запуска.
|
||||
- `CRAWL_LIMIT`: опциональный лимит профилей для тестового запуска.
|
||||
- `ADMIN_USERNAME`, `ADMIN_PASSWORD`: начальные данные администратора для создания учётной записи в БД. После смены пароля используется пароль из БД; значения окружения его не перезаписывают.
|
||||
- `SESSION_SECRET`: секрет подписи cookie.
|
||||
- `PARSER_USE_PLAYWRIGHT`: включение Playwright-рендера динамических вкладок.
|
||||
- `DISMISSAL_CONFIRMATION_RUNS`: сколько последовательных проверок недоступности нужно для увольнения, по умолчанию `3`.
|
||||
- `MAX_AUTO_DISMISSALS_PER_RUN`: защитный лимит массовых автоматических увольнений за один запуск, по умолчанию `25`.
|
||||
|
||||
## Локальный запуск
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
|
||||
Админка: `http://localhost:8000/admin`.
|
||||
|
||||
В админке доступны:
|
||||
|
||||
- `SESSION_SECRET`: секрет подписи cookie.
|
||||
- `PARSER_USE_PLAYWRIGHT`: включение Playwright-рендера динамических вкладок.
|
||||
- `DISMISSAL_CONFIRMATION_RUNS`: сколько последовательных проверок недоступности нужно для увольнения, по умолчанию `3`.
|
||||
- `MAX_AUTO_DISMISSALS_PER_RUN`: защитный лимит массовых автоматических увольнений за один запуск, по умолчанию `25`.
|
||||
|
||||
## Локальный запуск
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
|
||||
Админка: `http://localhost:8000/admin`.
|
||||
|
||||
В админке доступны:
|
||||
|
||||
- «Обзор»: статистика, последний добавленный сотрудник, прогресс парсинга и ручной запуск.
|
||||
- «Сотрудники»: поиск, фильтры, сортировка, пагинация и выбор колонок. По умолчанию показаны ФИО, статус, должности, дата последнего обнаружения и внешний профиль. В диалоге колонок доступны наборы «Проверка», «Контакты» и «Все поля»; ранее сохранённый выбор сохраняется.
|
||||
- «Запуски»: история обходов, ошибки и доступный индикатор прогресса.
|
||||
@@ -52,7 +52,7 @@ uvicorn app.main:app --reload
|
||||
Каталог поддерживает навигацию с клавиатуры и системный диалог колонок с Escape и возвратом фокуса. На узких экранах таблица прокручивается внутри страницы, колонка ФИО закреплена. Используется системный шрифт без внешних загрузок.
|
||||
|
||||
Каталог формируется сервером: отдельная клиентская загрузка списка не нужна, переход показывает браузер. Пустой результат предлагает сбросить фильтры, пустая база — запустить парсинг. При ошибке базы каталог возвращает HTTP 503 и предлагает повторить загрузку с теми же фильтрами. При обновлении прогресса показывается состояние загрузки; при ошибке сохраняются последние значения с предупреждением об их актуальности и кнопкой «Повторить». Автоматические попытки продолжаются каждые 4 секунды, запрос ограничен 15 секундами; параллельные запросы не запускаются.
|
||||
|
||||
|
||||
## Пользователи и личный кабинет
|
||||
|
||||
`/admin/users` доступен только администратору: создание учётных записей с ролями «Администратор»/«Пользователь», включение и отключение доступа, сброс пароля, первый и последний вход, дата смены пароля пользователем и выдачи временного пароля. Пользователь может читать служебные страницы и редактировать карточки, но не запускает парсинг (включая обновление одного сотрудника) и не управляет учётными записями. Проверки выполняются на сервере для HTML и API. Публичный просмотр диссовета и XLSX сохранён.
|
||||
@@ -71,37 +71,37 @@ uvicorn app.main:app --reload
|
||||
|
||||
Проверки: `python -m pytest -q`, `python tests/browser_users.py`. Браузерный сценарий использует только синтетическую SQLite-БД и временный локальный HTTP-сервер без запуска миграций рабочего приложения; временные пароли на скриншотах скрыты. Для native PostgreSQL-проверок тестов пользователей задайте USERS_TEST_DATABASE_URL на отдельную одноразовую БД **miem_users_test**: эти тесты создают и удаляют в ней схему и не должны запускаться на рабочей БД.
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```bash
|
||||
## Docker Compose
|
||||
|
||||
```bash
|
||||
docker compose up -d --build --remove-orphans
|
||||
```
|
||||
|
||||
По умолчанию:
|
||||
|
||||
- API и админка: `http://localhost:8000`
|
||||
```
|
||||
|
||||
По умолчанию:
|
||||
|
||||
- API и админка: `http://localhost:8000`
|
||||
- PostgreSQL: `postgres:5432` внутри сети Compose; порт на хост не опубликован.
|
||||
|
||||
Compose запускает `api`, `worker` и `postgres`. API привязан к localhost; для внешнего доступа нужен настроенный reverse proxy. REST API данных требует сессию администратора.
|
||||
|
||||
Таблицы создаются приложением при старте. При обновлении существующей базы приложение также добавляет недостающие runtime-колонки, например `crawl_runs.skipped_count`. SQL-миграции для ручного применения лежат в `migrations/`.
|
||||
|
||||
## Наполнение БД
|
||||
|
||||
Основная карточка сотрудника хранится в `employees`: профиль, статус, даты обнаружения/увольнения, текущий JSON `current_data`, checksum и версия парсера. История успешных изменений сохраняется в `employee_snapshots` вместе с JSON-снимком и сжатым HTML профиля.
|
||||
|
||||
Публикации теперь хранятся в двух видах:
|
||||
|
||||
- краткий список остается внутри `employees.current_data.sections[].publications` для обратной совместимости;
|
||||
- детальные записи сохраняются в `employee_publications` и связываются с сотрудником через `employee_id`.
|
||||
|
||||
|
||||
Таблицы создаются приложением при старте. При обновлении существующей базы приложение также добавляет недостающие runtime-колонки, например `crawl_runs.skipped_count`. SQL-миграции для ручного применения лежат в `migrations/`.
|
||||
|
||||
## Наполнение БД
|
||||
|
||||
Основная карточка сотрудника хранится в `employees`: профиль, статус, даты обнаружения/увольнения, текущий JSON `current_data`, checksum и версия парсера. История успешных изменений сохраняется в `employee_snapshots` вместе с JSON-снимком и сжатым HTML профиля.
|
||||
|
||||
Публикации теперь хранятся в двух видах:
|
||||
|
||||
- краткий список остается внутри `employees.current_data.sections[].publications` для обратной совместимости;
|
||||
- детальные записи сохраняются в `employee_publications` и связываются с сотрудником через `employee_id`.
|
||||
|
||||
`employee_publications` содержит `publication_id`, название, год, тип публикации, язык, статус, ссылку на карточку HSE Publications, DOI, внешние/document-ссылки, citation text, аннотацию, описание, авторов, raw JSON ответа `searchPubs` и `source_hash` для безопасного повторного upsert. Уникальность поддерживается по `(employee_id, publication_id)` и `(employee_id, source_hash)`, поэтому повторный crawl не должен создавать дубликаты.
|
||||
|
||||
Новости сотрудников также хранятся в двух видах:
|
||||
|
||||
- краткий список остается внутри `employees.current_data.sections[].news_links`;
|
||||
- нормализованные карточки из вкладки «В новостях» сохраняются в `employee_news_links`.
|
||||
|
||||
|
||||
- краткий список остается внутри `employees.current_data.sections[].news_links`;
|
||||
- нормализованные карточки из вкладки «В новостях» сохраняются в `employee_news_links`.
|
||||
|
||||
`employee_news_links` содержит название новости, ссылку, краткое описание, дату публикации, год публикации, raw JSON карточки и `source_hash`. Уникальность поддерживается по `(employee_id, url)` и `(employee_id, source_hash)`, поэтому повторный crawl не создает дубликаты.
|
||||
|
||||
## Кандидаты в диссертационный совет
|
||||
@@ -140,45 +140,45 @@ Compose запускает `api`, `worker` и `postgres`. API привязан
|
||||
|
||||
После изменения JSON/PDF пересоберите образ и пересоздайте API: данные включены в образ и кешируются в процессе.
|
||||
|
||||
## Парсинг
|
||||
|
||||
## Парсинг
|
||||
|
||||
Worker запускает обход по `CRAWL_CRON`. Ручной запуск также доступен в админке на `Dashboard` и странице `Runs` или через REST:
|
||||
|
||||
```bash
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/crawl-runs --cookie "miem_admin_session=..." -H "X-CSRF-Token: ..."
|
||||
```
|
||||
|
||||
Алгоритм обновления:
|
||||
|
||||
- найденные сотрудники получают статус `active` и обновленный `last_seen_at`;
|
||||
- новые сотрудники добавляются в `employees`;
|
||||
- если профиль перенесен на другой URL, он сопоставляется с прежней записью по единственному точному совпадению ФИО;
|
||||
- старые URL сохраняются в истории `employee_profile_urls`;
|
||||
- количество новых сотрудников за запуск сохраняется в `crawl_runs.new_count`;
|
||||
- публикации из HSE Publications записываются в `employee_publications`, а краткий список остается в JSON профиля;
|
||||
- новости из блока «В новостях» записываются в `employee_news_links`, а краткий список остается в JSON профиля;
|
||||
- один `404` старого профиля переводит сотрудника в статус `verification_required`, а не в `dismissed`;
|
||||
- статус `dismissed` устанавливается только после нескольких последовательных проверок `404`/`410`;
|
||||
- сетевые ошибки и ответы `5xx` не считаются подтверждением увольнения;
|
||||
```
|
||||
|
||||
Алгоритм обновления:
|
||||
|
||||
- найденные сотрудники получают статус `active` и обновленный `last_seen_at`;
|
||||
- новые сотрудники добавляются в `employees`;
|
||||
- если профиль перенесен на другой URL, он сопоставляется с прежней записью по единственному точному совпадению ФИО;
|
||||
- старые URL сохраняются в истории `employee_profile_urls`;
|
||||
- количество новых сотрудников за запуск сохраняется в `crawl_runs.new_count`;
|
||||
- публикации из HSE Publications записываются в `employee_publications`, а краткий список остается в JSON профиля;
|
||||
- новости из блока «В новостях» записываются в `employee_news_links`, а краткий список остается в JSON профиля;
|
||||
- один `404` старого профиля переводит сотрудника в статус `verification_required`, а не в `dismissed`;
|
||||
- статус `dismissed` устанавливается только после нескольких последовательных проверок `404`/`410`;
|
||||
- сетевые ошибки и ответы `5xx` не считаются подтверждением увольнения;
|
||||
- если число кандидатов на увольнение превышает защитный лимит, автоматическое увольнение приостанавливается;
|
||||
- кнопка «Проверить уволенных» сверяет только их profile_key с текущим списком источника и возвращает найденных сотрудников в `active` без обновления содержимого профиля;
|
||||
- каждый успешный новый или измененный разбор сохраняет запись в `employee_snapshots`;
|
||||
- неизмененные профили учитываются в `crawl_runs.skipped_count` и не получают новый snapshot.
|
||||
|
||||
Во время выполнения парсинга `found_count`, `parsed_count`, `skipped_count` и `error_count` обновляются в базе. Админка опрашивает `/api/crawl-runs/latest` и показывает прогресс как `(parsed_count + skipped_count + error_count) / found_count`.
|
||||
|
||||
- неизмененные профили учитываются в `crawl_runs.skipped_count` и не получают новый snapshot.
|
||||
|
||||
Во время выполнения парсинга `found_count`, `parsed_count`, `skipped_count` и `error_count` обновляются в базе. Админка опрашивает `/api/crawl-runs/latest` и показывает прогресс как `(parsed_count + skipped_count + error_count) / found_count`.
|
||||
|
||||
## Обслуживание
|
||||
|
||||
```bash
|
||||
|
||||
```bash
|
||||
docker compose logs -f api
|
||||
docker compose logs -f worker
|
||||
docker compose exec postgres pg_dump -U miem miem_workers > backup.sql
|
||||
docker compose exec postgres pg_dump -U miem miem_workers > backup.sql
|
||||
docker compose down
|
||||
```
|
||||
|
||||
Production deployment временно отключён до установки отдельного deployment-доступа Workers. Workflow `CI` запускает тесты на push в `main` и pull_request в `main`; шаги Upload release и Deploy and verify безусловно отключены для всех событий и значений input. Стандартные имена deployment secrets: `PROD_HOST`, `PROD_USER`, `PROD_SSH_KEY`, `PROD_KNOWN_HOSTS`; `.env` и ключи в репозиторий не добавляются. PostgreSQL backup выполняется на VPS раз в неделю user cron deploy-пользователя через `infra/postgres-backup.sh`: `pg_dumpall` сохраняет весь кластер в `~/backups/postgres/postgres-latest.sql`. Новый dump сначала создаётся во временный файл и заменяет старый только после успешного завершения; предыдущие dump-файлы `miem_workers` удаляются после успешного создания нового. Эта правка не меняет существующий backup cron.
|
||||
Развёртывание на новом VPS ведётся через общую платформу МИЭМ. Канонический домен `workers.miem.space`. Выбор образа, восстановление PostgreSQL, доступ, healthcheck и откат описаны в [процедуре первого запуска](https://git.vakanaut.ru/admin/infra-platform/src/branch/main/docs/runbooks/REPLACEMENT_VPS_FIRST_ROLLOUT.md). Фактические статусы публикуются в Infrastructure Registry. [Карточка проекта](https://git.vakanaut.ru/admin/infrastructure-registry/src/branch/main/environments/miem/projects/miem_workers/index.md).
|
||||
|
||||
Ручной запуск с `diagnostic_only=true` (по умолчанию) выводит только SHA256 fingerprint ключа `PROD_SSH_KEY`, без подключения к VPS. Публичный ключ передаётся через pipe, не сохраняется в файл или artifacts; shell tracing отключён. При `diagnostic_only=false` диагностический job пропускается: это значение также не разрешает deployment. Возврат production deployment требует отдельной reviewed правки после установки нового доступа.
|
||||
Ручной запуск с `diagnostic_only=true` (по умолчанию) выводит только SHA256 fingerprint ключа `PROD_SSH_KEY`, без подключения к VPS. Публичный ключ передаётся через pipe, не сохраняется в файл или artifacts; shell tracing отключён. При `diagnostic_only=false` диагностический job пропускается: это значение также не разрешает deployment. Этот job относится к диагностике ключа. Выбор и публикация production-релиза выполняются в infra-platform.
|
||||
|
||||
## Проверки
|
||||
|
||||
|
||||
Reference in new issue
Block a user