diff --git a/README.md b/README.md index 8cb5d96..dfc41cd 100644 --- a/README.md +++ b/README.md @@ -4,45 +4,256 @@ Сервис приёма, нормализации и ИИ-обогащения данных об объектах недвижимости. -- Принимает полусырые данные от парсеров через REST API -- Валидирует: AI определяет, является ли payload объявлением о недвижимости -- Нормализует: приводит неструктурированные данные к единому формату -- Обогащает: анализ изображений (vision) + текстовый анализ (NER, summary, оценка цены) -- Сохраняет в PostgreSQL с полной историей изменений (snapshots) +- **Приём**: Принимает полусырые данные от парсеров через REST API +- **Валидация**: AI определяет, является ли payload объявлением о недвижимости +- **Нормализация**: Приводит неструктурированные данные к единому формату +- **Обогащение**: Анализ изображений (vision) + текстовый анализ (NER, summary, оценка цены) +- **Хранение**: Сохраняет в PostgreSQL с полной историей изменений (snapshots) -## Быстрый старт +--- + +## Быстрый старт (Docker) ```bash -# 1. Запуск PostgreSQL и Ollama (опционально) -docker compose up -d +# 1. Клонировать и перейти в директорию +cd vmk_data_collector + +# 2. Скопировать конфиг +# Отредактируй .env под себя (OLLAMA_BASE_URL, порты и т.д.) +cp .env.example .env + +# 3. Запуск всего стека (PostgreSQL + FastAPI) +docker compose up -d --build + +# 4. Проверка +# Документация API: http://localhost:8020/docs +# Health check: http://localhost:8020/api/v1/health +``` + +### Стоп/очистка + +```bash +docker compose down # остановить +docker compose down -v # остановить + удалить данные БД +``` + +--- + +## Ручной старт (для разработки) + +```bash +# 1. PostgreSQL должен быть доступен (локально или в Docker) +# Порт по умолчанию: 5432 (хост) / 5433 (если через docker-compose) # 2. Установка зависимостей pip install -e ".[dev]" -# 3. Копирование .env -cp .env.example .env - -# 4. Применение миграций +# 3. Применение миграций alembic upgrade head -# 5. Запуск приложения -uvicorn vmk_data_collector.main:app --reload --port 8000 +# 4. Запуск приложения +uvicorn vmk_data_collector.main:app --reload --port 8020 ``` -## API +--- -- `POST /api/v1/properties/ingest` — приём сырых данных от парсеров +## API для парсеров + +### `POST /api/v1/ingest` + +Принимает сырые данные от парсеров, валидирует payload и ставит задачу в очередь на обработку. + +#### Заголовки + +| Заголовок | Обязательный | Значение | +|---|---|---| +| `Content-Type` | Да | `application/json` | + +#### Тело запроса + +```json +{ + "source_slug": "avito", + "external_id": "avito-12345678", + "payload": { + "title": "2-комнатная квартира, 65 м², 5/25 этаж", + "description": "Продается просторная двухкомнатная квартира в новостройке. Рядом метро.", + "price": 8500000, + "url": "https://avito.ru/item/12345678", + "images": [ + "https://avito.ru/img1.jpg", + "https://avito.ru/img2.jpg" + ], + "contact_phone": "+7 (999) 123-45-67", + "address": "Москва, Тверская ул., 1", + "area": 65.5, + "rooms": 2, + "floor": 5 + } +} +``` + +#### Поля `payload` + +| Поле | Тип | Обязательное | Описание | +|---|---|---|---| +| `title` | `string` | Нет | Заголовок объявления | +| `description` | `string` | Нет | Описание объявления | +| `price` | `number / string` | Нет | Цена (руб.) | +| `url` | `string` | Нет | Ссылка на источник | +| `images` | `string[]` | Нет | Массив URL изображений | +| `contact_phone` | `string` | Нет | Телефон продавца | +| `address` | `string` | Нет | Адрес объекта | +| `area` | `number / string` | Нет | Площадь (м²) | +| `rooms` | `integer / string` | Нет | Количество комнат | +| `floor` | `integer / string` | Нет | Этаж | + +**Важно:** хотя бы одно из полей `title` или `description` должно присутствовать. + +#### Ответ `202 Accepted` (успех) + +```json +{ + "job_id": 42, + "property_id": null, + "status": "pending", + "reason": null, + "message": "Queued for processing", + "snapshot_id": null +} +``` + +#### Ответ `422 Unprocessable Entity` (ошибка валидации) + +```json +{ + "detail": "Invalid payload: 1 validation error for PayloadSchema..." +} +``` + +#### Статусы обработки + +После приёма задача (`job_id`) проходит через pipeline: + +| Статус | Значение | +|---|---| +| `pending` | Задача поставлена в очередь | +| `processing` | AI нормализует данные | +| `completed` | Объект сохранён в БД | +| `invalid` | AI определил, что это не недвижимость | +| `failed` | Ошибка на стадии обработки (Ollama недоступен и т.д.) | + +--- + +## Примеры (curl) + +### Успешный ingest + +```bash +curl -X POST http://localhost:8020/api/v1/ingest \ + -H "Content-Type: application/json" \ + -d '{ + "source_slug": "avito", + "external_id": "avito-99999", + "payload": { + "title": "3-комнатная квартира, 120 м²", + "description": "Элитная квартира в центре", + "price": 25000000, + "url": "https://avito.ru/item/99999", + "address": "Москва, Сити" + } + }' +``` + +### Проверка архивации (404/410 = объявление снято) + +```bash +curl -X POST http://localhost:8020/api/v1/listings/1/archive-check +``` + +--- + +## Конфигурация + +Все настройки задаются через переменные окружения (`.env` или `docker-compose.yml`): + +| Переменная | Дефолт | Описание | +|---|---|---| +| `APP_PORT` | `8020` | Порт приложения (хост) | +| `DATABASE_URL` | `postgresql+asyncpg://postgres:postgres@postgres:5432/vmk_data` | PostgreSQL (async) | +| `OLLAMA_BASE_URL` | `http://192.168.1.75:11434` | Ollama API | +| `OLLAMA_TEXT_MODEL` | `gemma4:e2b-it-q4_K_M` | Модель для текстовых задач | +| `OLLAMA_VISION_MODEL` | `gemma4:e2b-it-q4_K_M` | Модель для анализа изображений | +| `OLLAMA_TIMEOUT` | `120` | Таймаут запроса к Ollama (сек) | +| `LOG_LEVEL` | `INFO` | Уровень логирования | + +--- ## Архитектура -Сервис построен по слоистой архитектуре: -- **API Layer** — FastAPI, Pydantic валидация -- **Service Layer** — PropertyPipeline, AI-нормализация, обогащение -- **Repository Layer** — абстракция доступа к PostgreSQL +``` +┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ +│ Парсеры │────▶│ FastAPI │────▶│ PostgreSQL │ +│ (curl/HTTP) │ │ /api/v1/ │ │ (raw_data + │ +└──────────────┘ │ ingest │ │ property_...) │ + └──────┬───────┘ └──────────────────┘ + │ + ▼ + ┌──────────────┐ + │ QueueWorker │ + │ (async bg) │ + └──────┬───────┘ + │ + ┌─────────────┼─────────────┐ + ▼ ▼ ▼ + ┌─────────┐ ┌─────────┐ ┌─────────┐ + │AI Норма-│ │AI Анализ│ │AI Обога-│ + │лизатор │ │изображ. │ │щение │ + └────┬────┘ └────┬────┘ └────┬────┘ + │ │ │ + └─────────────┴─────────────┘ + │ + ▼ + ┌──────────────┐ + │ Ollama │ + │ 192.168.1.75 │ + └──────────────┘ +``` + +- **API Layer** — FastAPI, Pydantic валидация, structured logging (structlog) +- **Service Layer** — `PropertyPipeline`, AI-нормализация, обогащение, `QueueWorker` +- **Repository Layer** — абстракция доступа к PostgreSQL (async SQLAlchemy) - **Domain Layer** — чистые сущности недвижимости -- **Infrastructure Layer** — Ollama client, image downloader, логирование +- **Infrastructure Layer** — Ollama client, image downloader, rate limiter + +--- ## Документация - [Техническое задание](docs/SPECIFICATION.md) — полное описание модели данных, API, AI-слоя - [Архитектура](docs/ARCHITECTURE.md) — диаграммы, потоки данных, компоненты +- **OpenAPI (Swagger)**: `http://localhost:8020/docs` +- **ReDoc**: `http://localhost:8020/redoc` + +--- + +## Логирование + +Все логи выводятся в stdout в формате JSON (structlog), что удобно для сбора через `docker compose logs`: + +```bash +# Следить за логами в реальном времени +docker compose logs -f app + +# Посмотреть последние 50 строк +docker compose logs --tail=50 app +``` + +Ключевые события для мониторинга: +- `ingest_request` — новый запрос от парсера +- `ingest_accepted` — задача поставлена в очередь +- `ingest_validation_failed` — ошибка валидации payload +- `pipeline_start` — начало обработки задачи +- `pipeline_completed` — задача успешно завершена +- `pipeline_not_real_estate` — AI отбросил объект +- `ollama_chat_request/response` — запросы к Ollama