Files

153 lines
7.4 KiB
Python

"""Seed data for VoIdeaAI.
Called from app lifespan when DB tables are empty.
Creates default tariff plans, demo user, and sets system owner by email.
"""
import json
from datetime import datetime, timezone
from pathlib import Path
from uuid import uuid4
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import get_settings
from app.core.security import get_password_hash
from app.models.agent import AgentConfig
from app.models.tariff import TariffPlan
from app.models.user import User
from app.services.user_service import UserService
settings = get_settings()
_AGENT_VERSIONS_FILE = Path(__file__).resolve().parent.parent.parent / "AGENT_VERSIONS.json"
def _load_agent_versions() -> dict[str, str]:
try:
return json.loads(_AGENT_VERSIONS_FILE.read_text(encoding="utf-8"))
except (FileNotFoundError, json.JSONDecodeError, OSError):
return {}
AGENT_DESCRIPTIONS: dict[str, str] = {
"conductor": "Дирижёр — оркестратор, единственная точка входа. Маршрутизирует запросы к ролевым агентам, верифицирует ответы.",
"business_analyst": "Бизнес-аналитик — оценивает бизнес-метрики, ROI, конкурентную среду, окупаемость идеи.",
"task_organizer": "Организатор задач — разбивает идею на конкретные шаги реализации с оценкой сроков.",
"lawyer": "Юрист — проверяет идею на соответствие законодательству РФ (44-ФЗ, 152-ФЗ, 223-ФЗ).",
"financial_consultant": "Финансовый консультант — бюджет, прогноз выручки, точка безубыточности.",
"solution_architect": "Архитектор решений — предлагает 2 варианта архитектуры: монолит и микросервисы.",
"tester": "Тестировщик — описывает сценарии тестирования (позитивные и негативные кейсы).",
"ui_designer": "UI-дизайнер — предлагает 2 варианта визуального дизайна интерфейса.",
"smm_specialist": "SMM-специалист — разрабатывает контент-план для соцсетей.",
"life_coach": "Лайф-коуч — ставит SMART-цели, поквартальные вехи развития.",
"accessibility_expert": "Эксперт по доступности — проверяет на WCAG 2.1 AA соответствие.",
"critic": "Критик — конструктивная критика, поиск «слепых зон» идеи.",
"copywriter": "Копирайтер — упаковывает идею в продающий текст для разных аудиторий.",
"keeper": "Хранитель — сохраняет проработанную идею в базу данных.",
"doc_agent": "DocAgent — автоматическая генерация и обновление документации.",
"backlog_agent": "BacklogAgent — управление бэклогом, приоритизация задач.",
"spec_agent": "SpecAgent — написание формальных спецификаций и требований.",
"audit_agent": "AuditAgent — аудит кода, архитектуры, безопасности.",
"observer_agent": "ObserverAgent — мониторинг системы, сбор метрик, алерты.",
"evolution_agent": "EvolutionAgent — автоматическое улучшение кода, рефакторинг.",
"security_agent": "SecurityAgent — проверка безопасности, уязвимости, OWASP.",
"qa_tester_agent": "QATesterAgent — автоматизированное тестирование, регресс.",
"fix_agent": "FixAgent — автоматическое исправление ошибок на основе логов.",
"ui_test_agent": "UITestAgent — тестирование UI/UX, скриншотные тесты.",
"rollout_agent": "RolloutAgent — управление релизами, миграции, откаты.",
}
async def seed_database(db: AsyncSession) -> None:
"""Seed initial data if tables are empty."""
await _seed_tariff_plans(db)
await _seed_agent_descriptions(db)
await _seed_demo_user(db)
await _seed_owner(db)
async def _seed_tariff_plans(db: AsyncSession) -> None:
"""Create default Free tariff plan if none exist."""
result = await db.execute(select(TariffPlan).limit(1))
if result.scalar_one_or_none():
return
free_plan = TariffPlan(
name="Бесплатно",
code="free",
description="Базовый доступ ко всем функциям. Сейчас всё бесплатно 🎉",
price_monthly=0,
features={
"limit_ideas": None,
"limit_sessions_per_day": None,
"limit_agents": None,
"limit_team_members": None,
"limit_storage_mb": 100,
"has_voice": True,
"has_voice_commands": True,
"has_drive": True,
"has_advanced_analytics": True,
"has_api_access": True,
"has_priority_support": True,
"has_custom_branding": True,
"_promo": True,
},
is_active=True,
sort_order=0,
)
db.add(free_plan)
await db.commit()
async def _seed_agent_descriptions(db: AsyncSession) -> None:
"""Fill empty agent descriptions and update versions from AGENT_VERSIONS.json."""
agent_versions = _load_agent_versions()
result = await db.execute(select(AgentConfig))
agents = result.scalars().all()
updated = False
for agent in agents:
name = agent.agent_name.lower()
if name in AGENT_DESCRIPTIONS and (not agent.description or agent.description == ""):
agent.description = AGENT_DESCRIPTIONS[name]
updated = True
name_lower = agent.agent_name.lower()
expected_version = agent_versions.get(name_lower) or agent_versions.get(agent.agent_name)
if expected_version and agent.version != expected_version:
agent.version = expected_version
updated = True
if updated:
await db.commit()
async def _seed_demo_user(db: AsyncSession) -> None:
"""Create demo@voidea.app / demo1234 if not exists."""
result = await db.execute(select(User).where(User.email == "demo@voidea.app"))
if result.scalar_one_or_none():
return
demo = User(
id=uuid4(),
email="demo@voidea.app",
password_hash=get_password_hash("demo1234"),
display_name="Demo User",
is_active=True,
is_superuser=True,
role="admin",
accepted_terms_at=datetime.now(timezone.utc),
accepted_terms_version=settings.accepted_terms_version,
)
db.add(demo)
await db.commit()
async def _seed_owner(db: AsyncSession) -> None:
"""Set system owner by SYSTEM_OWNER_EMAIL if configured."""
if not settings.system_owner_email:
return
svc = UserService(db)
await svc.set_owner_by_email(settings.system_owner_email)