initial commit
This commit is contained in:
20
.env.example
Normal file
20
.env.example
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
POSTGRES_DB=miem_workers
|
||||||
|
POSTGRES_USER=miem
|
||||||
|
POSTGRES_PASSWORD=change-me
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
|
||||||
|
DATABASE_URL=postgresql+psycopg://miem:change-me@postgres:5432/miem_workers
|
||||||
|
SOURCE_URL=https://miem.hse.ru/persons
|
||||||
|
CRAWL_CRON=0 3 * * 1
|
||||||
|
CRAWL_LIMIT=
|
||||||
|
REQUEST_TIMEOUT=30
|
||||||
|
REQUEST_DELAY_SECONDS=1
|
||||||
|
PARSER_USE_PLAYWRIGHT=false
|
||||||
|
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=change-me
|
||||||
|
SESSION_SECRET=change-me-session-secret
|
||||||
|
MCP_TOKEN=change-me-mcp-token
|
||||||
|
|
||||||
|
API_PORT=8000
|
||||||
|
MCP_PORT=8001
|
||||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.env
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.db
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
postgres_data/
|
||||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends gcc libpq-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
103
README.md
Normal file
103
README.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# MIEM Employees Server
|
||||||
|
|
||||||
|
Сервис собирает сотрудников МИЭМ с сайта ВШЭ, хранит карточки и историю обновлений в Postgres, показывает минимальную админку и отдает read-only MCP endpoint для ИИ-агентов.
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
- `api`: FastAPI, REST API, HTML-админка, healthcheck.
|
||||||
|
- `worker`: weekly scheduler, который запускает парсинг по `CRAWL_CRON`.
|
||||||
|
- `mcp`: HTTP MCP endpoint с bearer token.
|
||||||
|
- `postgres`: основная БД.
|
||||||
|
|
||||||
|
Парсер использует фиксированный источник сотрудников, по умолчанию `https://miem.hse.ru/persons`. Для каждой карточки сохраняются ФИО, должности, год начала работы, контакты, идентификаторы, вкладки профиля, секции, публикации, курсы, ВКР, JSON-снапшот и сжатый HTML-снапшот. Ссылки обходятся только из меню профиля самого сотрудника (`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`: опциональный лимит профилей для тестового запуска.
|
||||||
|
- `ADMIN_USERNAME`, `ADMIN_PASSWORD`: логин и пароль админки.
|
||||||
|
- `SESSION_SECRET`: секрет подписи cookie.
|
||||||
|
- `MCP_TOKEN`: bearer token для `/mcp`.
|
||||||
|
- `PARSER_USE_PLAYWRIGHT`: включение Playwright-рендера динамических вкладок.
|
||||||
|
|
||||||
|
## Локальный запуск
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv .venv
|
||||||
|
.venv\Scripts\activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
uvicorn app.main:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
Админка: `http://localhost:8000/admin`.
|
||||||
|
|
||||||
|
## Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
По умолчанию:
|
||||||
|
|
||||||
|
- API и админка: `http://localhost:8000`
|
||||||
|
- MCP: `http://localhost:8001/mcp`
|
||||||
|
- Postgres: `localhost:5432`
|
||||||
|
|
||||||
|
Таблицы создаются приложением при старте. SQL-миграция для ручного применения лежит в `migrations/001_init.sql`.
|
||||||
|
|
||||||
|
## Парсинг
|
||||||
|
|
||||||
|
Weekly worker запускается по `CRAWL_CRON`. Ручной запуск доступен в админке на странице `Runs` или через REST:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8000/api/crawl-runs --cookie "miem_admin_session=..."
|
||||||
|
```
|
||||||
|
|
||||||
|
Алгоритм обновления:
|
||||||
|
|
||||||
|
- найденные сотрудники получают статус `active` и обновленный `last_seen_at`;
|
||||||
|
- новые сотрудники добавляются в `employees`;
|
||||||
|
- активные сотрудники, исчезнувшие из текущего списка источника, получают статус `dismissed` и `dismissed_at`;
|
||||||
|
- каждый успешный разбор сохраняет запись в `employee_snapshots`.
|
||||||
|
|
||||||
|
## MCP
|
||||||
|
|
||||||
|
Endpoint: `POST /mcp`, авторизация `Authorization: Bearer <MCP_TOKEN>`.
|
||||||
|
|
||||||
|
Поддерживаемые tools:
|
||||||
|
|
||||||
|
- `search_employees(query, status?, limit?)`
|
||||||
|
- `get_employee(profile_id_or_url)`
|
||||||
|
- `list_employee_publications(profile_id_or_url)`
|
||||||
|
- `list_employee_courses(profile_id_or_url)`
|
||||||
|
- `get_crawl_status()`
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8001/mcp \
|
||||||
|
-H "Authorization: Bearer change-me-mcp-token" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обслуживание
|
||||||
|
|
||||||
|
```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 down
|
||||||
|
```
|
||||||
|
|
||||||
|
Версия сервиса: `0.1.0`. Админка всегда показывает версии backend и frontend в footer.
|
||||||
53
docker-compose.yml
Normal file
53
docker-compose.yml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB:-miem_workers}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER:-miem}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-miem_password}
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "${POSTGRES_PORT:-5432}:5432"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-miem} -d ${POSTGRES_DB:-miem_workers}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
api:
|
||||||
|
build: .
|
||||||
|
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-miem}:${POSTGRES_PASSWORD:-miem_password}@postgres:5432/${POSTGRES_DB:-miem_workers}
|
||||||
|
ports:
|
||||||
|
- "${API_PORT:-8000}:8000"
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
worker:
|
||||||
|
build: .
|
||||||
|
command: python -m app.worker
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-miem}:${POSTGRES_PASSWORD:-miem_password}@postgres:5432/${POSTGRES_DB:-miem_workers}
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
mcp:
|
||||||
|
build: .
|
||||||
|
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-miem}:${POSTGRES_PASSWORD:-miem_password}@postgres:5432/${POSTGRES_DB:-miem_workers}
|
||||||
|
ports:
|
||||||
|
- "${MCP_PORT:-8001}:8000"
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
28
pyproject.toml
Normal file
28
pyproject.toml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[project]
|
||||||
|
name = "miem-workers"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "MIEM employees parser, admin API, and MCP server"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"apscheduler>=3.10.4",
|
||||||
|
"beautifulsoup4>=4.12.3",
|
||||||
|
"fastapi>=0.115.0",
|
||||||
|
"httpx>=0.27.0",
|
||||||
|
"jinja2>=3.1.4",
|
||||||
|
"lxml>=5.2.0",
|
||||||
|
"psycopg[binary]>=3.2.0",
|
||||||
|
"pydantic-settings>=2.4.0",
|
||||||
|
"python-multipart>=0.0.9",
|
||||||
|
"requests>=2.32.0",
|
||||||
|
"sqlalchemy>=2.0.32",
|
||||||
|
"uvicorn[standard]>=0.30.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=8.3.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
pythonpath = ["."]
|
||||||
13
requirements.txt
Normal file
13
requirements.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apscheduler>=3.10.4
|
||||||
|
beautifulsoup4>=4.12.3
|
||||||
|
fastapi>=0.115.0
|
||||||
|
httpx>=0.27.0
|
||||||
|
jinja2>=3.1.4
|
||||||
|
lxml>=5.2.0
|
||||||
|
psycopg[binary]>=3.2.0
|
||||||
|
pydantic-settings>=2.4.0
|
||||||
|
python-multipart>=0.0.9
|
||||||
|
requests>=2.32.0
|
||||||
|
sqlalchemy>=2.0.32
|
||||||
|
uvicorn[standard]>=0.30.0
|
||||||
|
pytest>=8.3.0
|
||||||
Reference in New Issue
Block a user