""" ⚙️ Конфигурация для HH.ru автоматизации """ import os from dataclasses import dataclass from pathlib import Path class AppConstants: """Константы приложения""" HH_BASE_URL = "https://api.hh.ru" HH_SITE_URL = "https://hh.ru" GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta" GEMINI_MODEL = "gemini-2.0-flash" DEFAULT_TIMEOUT = 30 API_PAUSE_SECONDS = 0.5 AI_REQUEST_PAUSE = 1 MAX_VACANCIES_PER_PAGE = 50 MAX_SEARCH_PAGES = 5 DEFAULT_MAX_APPLICATIONS = 40 DEFAULT_EXPERIENCE_FILE = "data/experience.txt" DEFAULT_ABOUT_FILE = "data/about_me.txt" DEFAULT_SKILLS_FILE = "data/skills.txt" DEFAULT_AI_THRESHOLD = 0.7 MIN_AI_SCORE = 0.0 MAX_AI_SCORE = 1.0 SHORT_SEPARATOR_LENGTH = 50 LONG_SEPARATOR_LENGTH = 60 SHORT_TEXT_LIMIT = 50 MEDIUM_TEXT_LIMIT = 60 GEMINI_TEMPERATURE = 0.3 GEMINI_MAX_OUTPUT_TOKENS = 1000 LOG_FILE_MAX_SIZE_MB = 10 PERCENT_MULTIPLIER = 100 @dataclass class HHSearchConfig: """Настройки поиска вакансий""" keywords: str = "python junior" area: str = "1" experience: str = "noExperience" per_page: int = AppConstants.MAX_VACANCIES_PER_PAGE max_pages: int = 3 order_by: str = "publication_time" @dataclass class BrowserConfig: """Настройки браузера""" headless: bool = False wait_timeout: int = 15 page_load_timeout: int = 30 implicit_wait: int = 10 @dataclass class ApplicationConfig: """Настройки подачи заявок""" max_applications: int = AppConstants.DEFAULT_MAX_APPLICATIONS pause_min: float = 3.0 pause_max: float = 6.0 manual_login: bool = True @dataclass class GeminiConfig: """Настройки Gemini AI""" api_key: str = "" model: str = AppConstants.GEMINI_MODEL base_url: str = AppConstants.GEMINI_BASE_URL match_threshold: float = AppConstants.DEFAULT_AI_THRESHOLD @dataclass class ResumeConfig: """Настройки резюме""" experience_file: str = AppConstants.DEFAULT_EXPERIENCE_FILE about_me_file: str = AppConstants.DEFAULT_ABOUT_FILE skills_file: str = AppConstants.DEFAULT_SKILLS_FILE class ResumeFileManager: """Менеджер для работы с файлами резюме""" @staticmethod def create_sample_files() -> None: """Создание примеров файлов резюме""" data_dir = Path("data") data_dir.mkdir(exist_ok=True) experience_file = data_dir / "experience.txt" if not experience_file.exists(): experience_file.write_text( """ Опыт работы: - Изучаю Python уже 6 месяцев - Прошел курсы по основам программирования - Делал учебные проекты: калькулятор, игра в крестики-нолики - Изучаю Django и Flask для веб-разработки - Базовые знания SQL и работы с базами данных - Знаком с Git для контроля версий """.strip(), encoding="utf-8", ) print(f"✅ Создан файл: {experience_file}") about_file = data_dir / "about_me.txt" if not about_file.exists(): about_file.write_text( """ О себе: Начинающий Python разработчик с большим желанием учиться и развиваться. Интересуюсь веб-разработкой и анализом данных. Быстро обучаюсь, ответственно подхожу к работе. Готов к стажировке или junior позиции для получения практического опыта. Хочу работать в команде опытных разработчиков и вносить вклад в интересные проекты. """.strip(), encoding="utf-8", ) print(f"✅ Создан файл: {about_file}") skills_file = data_dir / "skills.txt" if not skills_file.exists(): skills_file.write_text( """ Технические навыки: - Python (основы, ООП, модули) - SQL (SELECT, JOIN, базовые запросы) - Git (commit, push, pull, merge) - HTML/CSS (базовые знания) - Django (учебные проекты) - Flask (микрофреймворк) - PostgreSQL, SQLite - Linux (базовые команды) - VS Code, PyCharm """.strip(), encoding="utf-8", ) print(f"✅ Создан файл: {skills_file}") class UIFormatter: """Утилиты для форматирования пользовательского интерфейса""" @staticmethod def create_separator(long: bool = False) -> str: """Создание разделительной линии""" length = AppConstants.LONG_SEPARATOR_LENGTH if long else AppConstants.SHORT_SEPARATOR_LENGTH return "=" * length @staticmethod def truncate_text(text: str, medium: bool = False) -> str: """Обрезание текста до заданного лимита""" limit = AppConstants.MEDIUM_TEXT_LIMIT if medium else AppConstants.SHORT_TEXT_LIMIT return text[:limit] @staticmethod def format_percentage(value: float, total: float) -> str: """Форматирование процентного соотношения""" if total <= 0: return "0.0%" percentage = (value / total) * AppConstants.PERCENT_MULTIPLIER return f"{percentage:.1f}%" @staticmethod def print_section_header(title: str, long: bool = False) -> None: """Печать заголовка секции с разделителями""" separator = UIFormatter.create_separator(long) print(f"\n{separator}") print(title) print(separator) class Settings: """Главный класс настроек""" def __init__(self): self._load_env() self.hh_search = HHSearchConfig() self.browser = BrowserConfig() self.application = ApplicationConfig() self.gemini = GeminiConfig(api_key=os.getenv("GEMINI_API_KEY", "")) self.resume = ResumeConfig() self._validate_config() def _load_env(self) -> None: """Загрузка переменных окружения""" try: from dotenv import load_dotenv load_dotenv() except ImportError: print("💡 Установите python-dotenv для работы с .env файлами") def _validate_config(self) -> None: """Валидация настроек""" if not self.gemini.api_key: print("⚠️ GEMINI_API_KEY не установлен в переменных окружения") data_dir = Path("data") data_dir.mkdir(exist_ok=True) logs_dir = Path("logs") logs_dir.mkdir(exist_ok=True) def update_search_keywords(self, keywords: str) -> None: """Обновление ключевых слов поиска""" self.hh_search.keywords = keywords print(f"🔄 Обновлены ключевые слова: {keywords}") def enable_ai_matching(self) -> bool: """Проверяем можно ли использовать AI сравнение""" return bool(self.gemini.api_key) settings = Settings()