Архитектура Silent Meadow¶
Обзор системы¶
Silent Meadow - это standalone CRM-платформа для управления продажами недвижимости с фокусом на автоматизацию работы с площадками объявлений (Avito, Циан) и интеграцию с внешними источниками лидов.
Microservices Architecture¶
┌──────────────────────────┐
│ Leadgen Platform │ (Внешний сервис)
│ ───────────────── │
│ • Парсинг Telegram │
│ • AI-оценка лидов │
│ • Semantic search │
└──────────┬───────────────┘
│ REST API / Webhook
↓
┌──────────────────────────┐
│ Silent Meadow │ (Этот проект)
│ ───────────────── │
│ │
│ ┌────────────────────┐ │
│ │ Frontend │ │ React + TypeScript
│ │ (React SPA) │ │ • Карта участков
│ │ │ │ • Каталог домов
│ │ │ │ • Управление Avito/Циан
│ │ │ │ • CRM дашборд
│ └─────────┬──────────┘ │
│ │ HTTP/REST │
│ ↓ │
│ ┌────────────────────┐ │
│ │ Backend │ │ FastAPI + PostgreSQL
│ │ (FastAPI) │ │ • CRUD недвижимости
│ │ │ │ • Avito/Циан API
│ │ │ │ • CRM логика
│ │ │ │ • Аналитика
│ └─────────┬──────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ │ │
│ PostgreSQL Redis │
│ (БД) (Кэш) │
│ │
└────────┬─────────────────┘
│
└──→ External APIs:
• Avito API (публикация, чаты, автоответчик)
• Циан API (публикация объявлений)
• Yandex Maps API (геокодинг, карты)
• Telegram Bot API (CRM бот для менеджеров)
• OpenAI API (генерация описаний, автоответчик)
Разделение ответственности¶
| Сервис | Ответственность |
|---|---|
| Leadgen Platform | Парсинг Telegram, AI-оценка лидов, ChromaDB semantic search |
| Silent Meadow | Управление недвижимостью, Avito/Циан интеграции, CRM, аналитика |
Интеграция между сервисами¶
# Silent Meadow получает лиды из Leadgen Platform
# Вариант 1: REST API (polling)
GET https://leadgen.example.com/api/v1/leads?project_id=mdk&min_score=7
# Вариант 2: Webhook (push)
POST https://silent-meadow.example.com/api/webhooks/leadgen
{
"lead_id": "uuid",
"name": "Иван",
"telegram_username": "@ivan",
"message": "Ищу участок до 500К",
"score": 8.5,
"created_at": "2026-02-16T12:00:00Z"
}
Модели данных¶
1. Property (участки земли)¶
class Property(Base):
__tablename__ = "properties"
id: UUID = Field(primary_key=True)
number: str = Field(nullable=False) # "502", "503"
cadastral_number: str = Field(unique=True) # "50:18:0090613:670"
area_sotok: float = Field(nullable=False) # 10.0
price: int = Field(nullable=False) # 299000
original_price: int | None # 389000 (для скидки)
status: str = Field(default="available") # available, reserved, sold
latitude: float | None # 55.123456
longitude: float | None # 37.123456
settlement: str # "Тихие Луга", "Мечтаево"
description: str | None
images: list[str] = Field(default=[]) # URLs фото
created_at: datetime
updated_at: datetime
# Relations
avito_ads: list["AvitoAd"]
cian_ads: list["CianAd"]
leads: list["Lead"]
Индексы:
- cadastral_number (UNIQUE)
- status (для фильтрации)
- (latitude, longitude) (для пространственных запросов)
2. House (типовые дома)¶
class House(Base):
__tablename__ = "houses"
id: UUID = Field(primary_key=True)
name: str = Field(nullable=False) # "Шале", "Барнхаус", "Хай-тек"
type: str = Field(nullable=False) # "shale", "barnhouse", "hi_tech"
area_sqm: int = Field(nullable=False) # 40, 60, 80, 120
price_economy: int = Field(nullable=False) # Комплектация "Эконом"
price_comfort: int = Field(nullable=False) # Комплектация "Комфорт"
description: str | None
floor_plan_url: str | None # URL планировки
images: list[str] = Field(default=[]) # URLs фото
features: dict = Field(default={}) # {"bedrooms": 2, "bathrooms": 1}
created_at: datetime
updated_at: datetime
# Relations
avito_ads: list["AvitoAd"]
cian_ads: list["CianAd"]
leads: list["Lead"]
Возможные комбинации: - Шале: 40, 60, 80 м² - Барнхаус: 60, 80, 120 м² - Хай-тек: 80, 120 м²
3. AvitoAd (объявления на Avito)¶
class AvitoAd(Base):
__tablename__ = "avito_ads"
id: UUID = Field(primary_key=True)
property_id: UUID | None = Field(foreign_key="properties.id")
house_id: UUID | None = Field(foreign_key="houses.id")
avito_ad_id: str | None = Field(unique=True) # ID в системе Avito
title: str = Field(nullable=False) # "Участок 10 соток..."
description: str = Field(nullable=False)
price: int = Field(nullable=False)
status: str = Field(default="draft") # draft, active, paused, sold
views: int = Field(default=0)
contacts: int = Field(default=0) # Количество обращений
published_at: datetime | None
expires_at: datetime | None
created_at: datetime
updated_at: datetime
# Relations
property: "Property"
house: "House"
chats: list["AvitoChat"]
Статусы:
- draft - создано, не опубликовано
- active - опубликовано и активно
- paused - снято с публикации
- sold - продано
4. CianAd (объявления на Циан)¶
class CianAd(Base):
__tablename__ = "cian_ads"
id: UUID = Field(primary_key=True)
property_id: UUID | None = Field(foreign_key="properties.id")
house_id: UUID | None = Field(foreign_key="houses.id")
cian_ad_id: str | None = Field(unique=True) # ID в системе Циан
title: str = Field(nullable=False)
description: str = Field(nullable=False)
price: int = Field(nullable=False)
status: str = Field(default="draft") # draft, active, paused, sold
views: int = Field(default=0)
contacts: int = Field(default=0)
published_at: datetime | None
expires_at: datetime | None
created_at: datetime
updated_at: datetime
# Relations
property: "Property"
house: "House"
Статусы: аналогично AvitoAd
5. Lead (лиды из всех каналов)¶
class Lead(Base):
__tablename__ = "leads"
id: UUID = Field(primary_key=True)
name: str | None
contact: str | None # email или phone
telegram_username: str | None
phone: str | None
message: str # Исходное сообщение
# Источник лида
source: str = Field(nullable=False) # leadgen_platform, avito, cian, manual
source_lead_id: str | None # ID в исходной системе
score: float | None = Field(ge=0, le=10) # AI-оценка (только для leadgen)
# Связь с недвижимостью
property_id: UUID | None = Field(foreign_key="properties.id")
house_id: UUID | None = Field(foreign_key="houses.id")
# CRM статус
status: str = Field(default="new") # new, contacted, qualified, won, lost
assigned_to: UUID | None = Field(foreign_key="users.id")
contact_method: str | None # telegram, phone, email, avito_chat
notes: str | None
created_at: datetime
updated_at: datetime
# Relations
property: "Property"
house: "House"
manager: "User"
Источники лидов:
- leadgen_platform - лид из Telegram через Leadgen Platform API/webhook
- avito - обращение через Avito чат
- cian - обращение через Циан
- manual - вручную добавленный лид
5. AvitoChat (чаты с покупателями)¶
class AvitoChat(Base):
__tablename__ = "avito_chats"
id: UUID = Field(primary_key=True)
avito_ad_id: UUID = Field(foreign_key="avito_ads.id")
avito_chat_id: str = Field(unique=True) # ID чата в Avito
buyer_name: str | None
last_message: str | None
last_message_at: datetime | None
unread_count: int = Field(default=0)
created_at: datetime
# Relations
ad: "AvitoAd"
messages: list["AvitoMessage"]
API Endpoints¶
Properties API¶
GET /v1/properties # Список участков (с фильтрами)
POST /v1/properties # Создать участок
GET /v1/properties/{id} # Детали участка
PATCH /v1/properties/{id} # Обновить участок
DELETE /v1/properties/{id} # Удалить участок
GET /v1/map/properties # GeoJSON для карты
GET /v1/map/clusters # Кластеры участков
Пример запроса:
Пример ответа:
{
"items": [
{
"id": "uuid",
"number": "502",
"cadastral_number": "50:18:0090613:670",
"area_sotok": 10,
"price": 299000,
"original_price": 389000,
"status": "available",
"latitude": 55.123456,
"longitude": 37.123456,
"settlement": "Тихие Луга",
"images": ["url1", "url2"]
}
],
"total": 436,
"page": 1,
"per_page": 20
}
Houses API¶
GET /v1/houses # Каталог домов
POST /v1/houses # Добавить дом
GET /v1/houses/{id} # Детали дома
PATCH /v1/houses/{id} # Обновить дом
DELETE /v1/houses/{id} # Удалить дом
Avito API¶
POST /v1/avito/auth # Подключить Avito аккаунт (OAuth2)
GET /v1/avito/auth/status # Статус подключения
GET /v1/avito/auth/callback # OAuth callback
POST /v1/avito/ads # Создать объявление
GET /v1/avito/ads # Список объявлений
GET /v1/avito/ads/{id} # Детали объявления
PATCH /v1/avito/ads/{id} # Обновить объявление
DELETE /v1/avito/ads/{id} # Удалить объявление
POST /v1/avito/ads/{id}/publish # Опубликовать
POST /v1/avito/ads/{id}/pause # Приостановить
GET /v1/avito/chats # Чаты с покупателями
GET /v1/avito/chats/{id}/messages # История сообщений
POST /v1/avito/chats/{id}/message # Отправить сообщение
POST /v1/avito/webhook # Webhook от Avito
Циан API¶
POST /v1/cian/auth # Подключить Циан аккаунт
GET /v1/cian/auth/status # Статус подключения
POST /v1/cian/ads # Создать объявление
GET /v1/cian/ads # Список объявлений
GET /v1/cian/ads/{id} # Детали объявления
PATCH /v1/cian/ads/{id} # Обновить объявление
DELETE /v1/cian/ads/{id} # Удалить объявление
POST /v1/cian/ads/{id}/publish # Опубликовать
POST /v1/cian/ads/{id}/pause # Приостановить
GET /v1/cian/stats # Статистика по объявлениям
Leadgen Platform Integration¶
POST /v1/webhooks/leadgen # Webhook от Leadgen Platform
GET /v1/leadgen/status # Статус интеграции
GET /v1/leads # Все лиды (из всех источников)
GET /v1/leads/{id} # Детали лида
PATCH /v1/leads/{id} # Обновить лид (статус, заметки)
Telegram CRM Bot¶
POST /v1/telegram/notify # Отправить уведомление менеджеру
GET /v1/telegram/bot/status # Статус бота
Yandex Maps API¶
GET /v1/map/properties # Все участки в формате GeoJSON
GET /v1/map/clusters?zoom={level} # Кластеры для уровня zoom
GET /v1/map/geocode?address={addr} # Геокодинг адреса
Схема взаимодействия компонентов¶
1. Получение лидов из Leadgen Platform → CRM¶
[Leadgen Platform]
↓ (Webhook или REST API)
POST /v1/webhooks/leadgen
{
"lead_id": "uuid",
"name": "Иван",
"telegram_username": "@ivan",
"message": "Ищу участок до 500К",
"score": 8.5
}
↓
[Silent Meadow Backend]
↓ (создание лида в БД)
[Lead сохранен]
↓
[Telegram CRM бот]
↓
[Уведомление менеджера]
2. Создание объявления на Avito¶
[Manager UI] → POST /v1/avito/ads
↓
[Backend: AvitoService]
↓ (OAuth2 access token)
[Avito API: POST /items]
↓
[AvitoAd сохранен в БД]
↓ (webhook от Avito)
[Обновление статистики: views, contacts]
3. Автоответчик Avito¶
[Покупатель → сообщение в Avito]
↓ (webhook)
[POST /v1/avito/webhook]
↓
[AvitoAutoResponder]
↓ (GPT-4: анализ вопроса)
[Генерация ответа на базе данных об участке/доме]
↓
[Отправка через Avito API]
↓ (создание лида)
[Lead сохранен в БД]
↓
[Уведомление менеджера в Telegram]
Безопасность¶
Аутентификация¶
- JWT токены для API (access + refresh)
- Cookies для веб-сессий (httpOnly, secure)
- OAuth2 для интеграции с Avito
Авторизация¶
- RBAC: роли
admin,manager,viewer - Менеджеры видят только свои лиды
- Админы имеют полный доступ
Rate Limiting¶
# FastAPI middleware
@app.middleware("http")
async def rate_limit_middleware(request: Request, call_next):
# Redis-based rate limiting
# 100 requests per minute per IP
pass
CORS¶
app.add_middleware(
CORSMiddleware,
allow_origins=["https://your-domain.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Secrets Management¶
.envфайлы для разработки- Environment variables в production
- AWS Secrets Manager / HashiCorp Vault (опционально)
Кэширование¶
Redis Cache Strategy¶
# Кэш участков (TTL: 5 минут)
@cache(ttl=300, key="properties:all")
async def get_all_properties():
pass
# Кэш статистики Avito (TTL: 1 час)
@cache(ttl=3600, key="avito:stats:{ad_id}")
async def get_avito_ad_stats(ad_id: str):
pass
# Инвалидация при изменении
@invalidate_cache(pattern="properties:*")
async def update_property(property_id: UUID, data: dict):
pass
Масштабирование¶
Горизонтальное масштабирование Backend¶
- FastAPI поддерживает async/await
- Деплой нескольких инстансов за Load Balancer
- Stateless архитектура (сессии в Redis)
Database Optimization¶
- Индексы на часто запрашиваемых полях
- Connection pooling (SQLAlchemy)
- Read replicas для аналитики
CDN для статики¶
- Изображения участков/домов → S3 + CloudFront
- Frontend build → Vercel CDN
Мониторинг и логирование¶
Logging¶
import structlog
logger = structlog.get_logger()
logger.info("property_created", property_id=str(property.id), number=property.number)
Мониторинг¶
- Sentry - отслеживание ошибок
- Prometheus + Grafana - метрики (requests/s, latency, DB queries)
- Uptime monitoring - healthcheck endpoints
Healthcheck¶
GET /health
{
"status": "ok",
"database": "connected",
"redis": "connected",
"avito_api": "authenticated"
}
Технологический стек (детально)¶
Backend¶
- FastAPI 0.109 - веб-фреймворк
- SQLAlchemy 2.0 - ORM
- Alembic - миграции
- Pydantic - валидация данных
- Redis - кэширование
- PostgreSQL 15 - основная БД
Frontend¶
- React 18.2 - UI
- TypeScript 5.2 - типизация
- TailwindCSS 3.4 - стили
- React Query - управление серверным состоянием
- Zustand - локальное состояние
- React Hook Form - формы
- Yandex Maps React - карты
Infrastructure¶
- Docker + Docker Compose - контейнеризация
- GitHub Actions - CI/CD
- Render.com - хостинг backend
- Vercel - хостинг frontend
- AWS S3 - хранилище изображений
Диаграмма компонентов (детально)¶
┌──────────────────────────────────────────────────────────────┐
│ FRONTEND │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Properties│ │ Houses │ │ Avito │ │ Циан │ │
│ │ Page │ │ Page │ │ Page │ │ Page │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ └─────────────┴─────────────┴─────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ API Client │ │
│ └──────┬──────┘ │
└────────────────────────────┼────────────────────────────────┘
│ HTTP/REST
┌────────────────────────────┼────────────────────────────────┐
│ BACKEND (FastAPI) │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌──────────┐│
│ │Properties │ │ Avito │ │ Циан │ │Leadgen ││
│ │ Service │ │ Service │ │ Service │ │Integration││
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └────┬─────┘│
│ │ │ │ │ │
│ ┌─────┴──────────────┴──────────────┴──────────────┴────┐ │
│ │ Database Layer (SQLAlchemy) │ │
│ └───────────────────────┬──────────────────────────────┘ │
└──────────────────────────┼────────────────────────────────┘
│
┌─────────────────┴──────────────────┐
│ │
┌────┴────┐ ┌─────┴─────┐
│PostgreSQL│ │ Redis │
└──────────┘ └───────────┘
Внешние сервисы: - Leadgen Platform - получение лидов из Telegram - Avito API - публикация объявлений, чаты - Циан API - публикация объявлений - Yandex Maps API - геокодинг, карты - Telegram Bot API - CRM бот для менеджеров
Следующие шаги¶
- Инициализация проекта (FastAPI + PostgreSQL + React)
- Реализация моделей данных (Property, House, AvitoAd, CianAd, Lead)
- Создание REST API endpoints
- Интеграция с Avito API (публикация, чаты, автоответчик)
- Интеграция с Циан API
- Интеграция с Yandex Maps API
- Интеграция с Leadgen Platform (webhook/REST API)
- Telegram CRM бот для менеджеров
- Разработка Frontend (карта, каталоги, CRM дашборд)
- Тестирование и деплой
Подробности смотрите в docs/plan.md