""" 🎯 Главный менеджер для автоматизации откликов на вакансии """ import logging from typing import List, Dict, Optional import time from ..config.settings import settings, AppConstants, UIFormatter from ..config.logging_config import LoggingConfigurator from ..models.vacancy import Vacancy, ApplicationResult from ..services.hh_api_service import HHApiService from ..services.gemini_service import GeminiAIService from ..services.browser_service import BrowserService logger = logging.getLogger(__name__) class AutomationOrchestrator: """Оркестратор процесса автоматизации""" def __init__(self): self.api_service = HHApiService() self.ai_service = GeminiAIService() self.browser_service = BrowserService() def execute_automation_pipeline( self, keywords: Optional[str] = None, use_ai: bool = True ) -> Dict: """Выполнение полного пайплайна автоматизации""" try: vacancies = self._search_and_filter_vacancies(keywords) if not vacancies: return {"error": "Подходящие вакансии не найдены"} if use_ai and self.ai_service.is_available(): vacancies = self._ai_filter_vacancies(vacancies) if not vacancies: return {"error": "После AI фильтрации не осталось подходящих вакансий"} if not self._initialize_browser_and_auth(): return {"error": "Ошибка инициализации браузера или авторизации"} application_results = self._apply_to_vacancies(vacancies) return self._create_stats(application_results) except KeyboardInterrupt: logger.info("Процесс остановлен пользователем") return {"error": "Остановлено пользователем"} except Exception as e: logger.error(f"Критическая ошибка: {e}") return {"error": str(e)} finally: self._cleanup() def _search_and_filter_vacancies(self, keywords: Optional[str] = None) -> List[Vacancy]: """Поиск и базовая фильтрация вакансий""" logger.info("🔍 ЭТАП 1: Поиск вакансий") try: all_vacancies = self.api_service.search_vacancies(keywords) if not all_vacancies: logger.warning("Вакансии не найдены через API") return [] suitable_vacancies = self.api_service.filter_suitable_vacancies(all_vacancies) self._log_search_results(all_vacancies, suitable_vacancies) return suitable_vacancies except Exception as e: logger.error(f"Ошибка поиска вакансий: {e}") return [] def _ai_filter_vacancies(self, vacancies: List[Vacancy]) -> List[Vacancy]: """AI фильтрация вакансий""" logger.info("🤖 ЭТАП 2: AI анализ вакансий") ai_suitable = [] total_count = len(vacancies) for i, vacancy in enumerate(vacancies, 1): truncated_name = UIFormatter.truncate_text(vacancy.name) logger.info(f"Анализ {i}/{total_count}: {truncated_name}...") try: if self.ai_service.should_apply(vacancy): ai_suitable.append(vacancy) logger.info("✅ Добавлено в список для отклика") else: logger.info("❌ Не рекомендуется") if i < total_count: time.sleep(AppConstants.AI_REQUEST_PAUSE) except Exception as e: logger.error(f"Ошибка AI анализа: {e}") ai_suitable.append(vacancy) self._log_ai_results(total_count, ai_suitable) return ai_suitable def _initialize_browser_and_auth(self) -> bool: """Инициализация браузера и авторизация""" logger.info("🌐 ЭТАП 3: Инициализация браузера и авторизация") try: if not self.browser_service.initialize(): logger.error("Не удалось инициализировать браузер") return False if not self.browser_service.authenticate_interactive(): logger.error("Не удалось авторизоваться") return False logger.info("✅ Браузер готов к работе") return True except Exception as e: logger.error(f"Ошибка инициализации: {e}") return False def _apply_to_vacancies(self, vacancies: List[Vacancy]) -> List[ApplicationResult]: """Подача заявок на вакансии""" max_apps = settings.application.max_applications vacancies_to_process = vacancies[:max_apps] logger.info(f"📨 ЭТАП 4: Подача заявок (максимум {max_apps})") logger.info("💡 Между заявками добавляются паузы для безопасности") application_results = [] for i, vacancy in enumerate(vacancies_to_process, 1): truncated_name = UIFormatter.truncate_text(vacancy.name, medium=True) logger.info(f"Обработка {i}/{len(vacancies_to_process)}: {truncated_name}...") try: result = self.browser_service.apply_to_vacancy(vacancy.alternate_url, vacancy.name) application_results.append(result) self._log_application_result(result) if i < len(vacancies_to_process): self.browser_service.add_random_pause() except Exception as e: logger.error(f"Неожиданная ошибка при подаче заявки: {e}") error_result = ApplicationResult( vacancy_id="", vacancy_name=vacancy.name, success=False, error_message=str(e), ) application_results.append(error_result) return application_results def _log_search_results(self, all_vacancies: List[Vacancy], suitable: List[Vacancy]): """Логирование результатов поиска""" logger.info("📊 Результат базовой фильтрации:") logger.info(f" 🔍 Всего: {len(all_vacancies)}") logger.info(f" ✅ Подходящих: {len(suitable)}") if len(all_vacancies) > 0: percentage = UIFormatter.format_percentage(len(suitable), len(all_vacancies)) logger.info(f" 📈 % соответствия: {percentage}") def _log_ai_results(self, total_analyzed: int, ai_suitable: List[Vacancy]): """Логирование результатов AI анализа""" logger.info("🎯 AI фильтрация завершена:") logger.info(f" 🤖 Проанализировано: {total_analyzed}") logger.info(f" ✅ Рекомендовано: {len(ai_suitable)}") if total_analyzed > 0: percentage = UIFormatter.format_percentage(len(ai_suitable), total_analyzed) logger.info(f" 📈 % одобрения: {percentage}") def _log_application_result(self, result: ApplicationResult): """Логирование результата подачи заявки""" if result.success: logger.info(" ✅ Заявка отправлена успешно") elif result.already_applied: logger.info(" ⚠️ Уже откликались ранее") else: logger.warning(f" ❌ Ошибка: {result.error_message}") def _create_stats(self, application_results: List[ApplicationResult]) -> Dict: """Создание итоговой статистики""" total_applications = len(application_results) successful = sum(1 for r in application_results if r.success) already_applied = sum(1 for r in application_results if r.already_applied) failed = total_applications - successful - already_applied return { "total_applications": total_applications, "successful": successful, "failed": failed, "already_applied": already_applied, } def _cleanup(self): """Очистка ресурсов""" logger.info("🔒 Закрытие браузера...") self.browser_service.close() class JobApplicationManager: """Главный менеджер для управления процессом поиска и откликов на вакансии""" def __init__(self): LoggingConfigurator.setup_logging(log_file="logs/hh_bot.log", console_output=False) self.orchestrator = AutomationOrchestrator() self.application_results: List[ApplicationResult] = [] def run_automation(self, keywords: Optional[str] = None, use_ai: bool = True) -> Dict: """Запуск полного цикла автоматизации""" print("🚀 Запуск автоматизации HH.ru") print(UIFormatter.create_separator()) stats = self.orchestrator.execute_automation_pipeline(keywords, use_ai) if "error" not in stats: pass return stats def get_application_results(self) -> List[ApplicationResult]: """Получение результатов подачи заявок""" return self.application_results.copy() def print_detailed_report(self, stats: Dict) -> None: """Детальный отчет о работе""" UIFormatter.print_section_header("📊 ДЕТАЛЬНЫЙ ОТЧЕТ", long=True) if "error" in stats: print(f"❌ Ошибка выполнения: {stats['error']}") return print(f"📝 Всего попыток подачи заявок: {stats['total_applications']}") print(f"✅ Успешно отправлено: {stats['successful']}") print(f"⚠️ Уже откликались ранее: {stats['already_applied']}") print(f"❌ Неудачных попыток: {stats['failed']}") if stats["total_applications"] > 0: success_rate = UIFormatter.format_percentage( stats["successful"], stats["total_applications"] ) print(f"📈 Успешность: {success_rate}") print(UIFormatter.create_separator(long=True)) if stats["successful"] > 0: print(f"🎉 Отлично! Отправлено {stats['successful']} новых заявок!") print("💡 Рекомендуется запускать автоматизацию 2-3 раза в день") elif stats["already_applied"] > 0: print("💡 На большинство подходящих вакансий уже подавали заявки") else: print("😕 Новые заявки не были отправлены") print("💡 Попробуйте изменить ключевые слова поиска или настройки")