"""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)