import os import logging from pathlib import Path from datetime import datetime import traceback import docker_patch # Налаштування логування logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ # logging.FileHandler("jira_assistant.log"), logging.FileHandler("/tmp/jira_assistant.log"), logging.StreamHandler() ] ) logger = logging.getLogger("jira_assistant") # Створення необхідних директорій for directory in ["data", "reports", "temp", "logs"]: Path(directory).mkdir(exist_ok=True, parents=True) # Імпорт необхідних модулів from modules.data_import.csv_importer import JiraCsvImporter from modules.data_analysis.statistics import JiraDataAnalyzer from modules.data_analysis.visualizations import JiraVisualizer from modules.reporting.report_generator import ReportGenerator from modules.core.app_manager import AppManager class JiraAssistantApp: """ Головний клас додатку, який координує роботу всіх компонентів """ def __init__(self): self.app_manager = AppManager() self.current_data = None self.current_analysis = None self.visualizations = None def analyze_csv_file(self, file_path, inactive_days=14, include_ai=False, api_key=None, model_type="openai"): """ Аналіз CSV-файлу Jira Args: file_path (str): Шлях до CSV-файлу inactive_days (int): Кількість днів для визначення неактивних тікетів include_ai (bool): Чи використовувати AI-аналіз api_key (str): API ключ для LLM (якщо include_ai=True) model_type (str): Тип моделі LLM ("openai" або "gemini") Returns: dict: Результати аналізу """ try: logger.info(f"Аналіз файлу: {file_path}") # Завантаження даних csv_importer = JiraCsvImporter(file_path) self.current_data = csv_importer.load_data() if self.current_data is None: return {"error": "Не вдалося завантажити дані з CSV-файлу"} # Аналіз даних analyzer = JiraDataAnalyzer(self.current_data) # Базова статистика stats = analyzer.generate_basic_statistics() # Аналіз неактивних тікетів inactive_issues = analyzer.analyze_inactive_issues(days=inactive_days) # Створення візуалізацій visualizer = JiraVisualizer(self.current_data) self.visualizations = { "status": visualizer.plot_status_counts(), "priority": visualizer.plot_priority_counts(), "type": visualizer.plot_type_counts(), "created_timeline": visualizer.plot_created_timeline(), "inactive": visualizer.plot_inactive_issues(days=inactive_days) } # AI аналіз, якщо потрібен ai_analysis = None if include_ai and api_key: from modules.ai_analysis.llm_connector import LLMConnector llm = LLMConnector(api_key=api_key, model_type=model_type) ai_analysis = llm.analyze_jira_data(stats, inactive_issues) # Генерація звіту report_generator = ReportGenerator(self.current_data, stats, inactive_issues, ai_analysis) report = report_generator.create_markdown_report(inactive_days=inactive_days) # Зберігаємо поточний аналіз self.current_analysis = { "stats": stats, "inactive_issues": inactive_issues, "report": report, "ai_analysis": ai_analysis } return { "report": report, "visualizations": self.visualizations, "ai_analysis": ai_analysis, "error": None } except Exception as e: error_msg = f"Помилка аналізу: {str(e)}\n\n{traceback.format_exc()}" logger.error(error_msg) return {"error": error_msg} def save_report(self, format_type="markdown", include_visualizations=True, filepath=None): """ Збереження звіту у файл Args: format_type (str): Формат звіту ("markdown", "html", "pdf") include_visualizations (bool): Чи включати візуалізації у звіт filepath (str): Шлях для збереження файлу Returns: str: Шлях до збереженого файлу або повідомлення про помилку """ try: if not self.current_analysis or "report" not in self.current_analysis: return "Помилка: спочатку виконайте аналіз даних" # Створення імені файлу, якщо не вказано if not filepath: timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') report_filename = f"jira_report_{timestamp}" reports_dir = Path("reports") if format_type == "markdown": filepath = reports_dir / f"{report_filename}.md" elif format_type == "html": filepath = reports_dir / f"{report_filename}.html" elif format_type == "pdf": filepath = reports_dir / f"{report_filename}.pdf" # Створення генератора звітів report_generator = ReportGenerator( self.current_data, self.current_analysis.get("stats"), self.current_analysis.get("inactive_issues"), self.current_analysis.get("ai_analysis") ) # Збереження звіту saved_path = report_generator.save_report( filepath=filepath, format=format_type, include_visualizations=include_visualizations, visualization_data=self.visualizations if include_visualizations else None ) if saved_path: return f"Звіт успішно збережено: {saved_path}" else: return "Не вдалося зберегти звіт" except Exception as e: error_msg = f"Помилка при збереженні звіту: {str(e)}\n\n{traceback.format_exc()}" logger.error(error_msg) return error_msg def test_jira_connection(self, jira_url, username, api_token): """ Тестування підключення до Jira Args: jira_url (str): URL сервера Jira username (str): Ім'я користувача api_token (str): API токен Returns: bool: True якщо підключення успішне, False інакше """ from modules.data_import.jira_api import JiraConnector return JiraConnector.test_connection(jira_url, username, api_token) def generate_visualization(self, viz_type, limit=10, groupby="day"): """ Генерація конкретної візуалізації Args: viz_type (str): Тип візуалізації limit (int): Ліміт для топ-N елементів groupby (str): Групування для часових діаграм ('day', 'week', 'month') Returns: matplotlib.figure.Figure: Об'єкт figure """ if self.current_data is None: logger.error("Немає даних для візуалізації") return None # Створюємо візуалізатор visualizer = JiraVisualizer(self.current_data) # Вибір типу візуалізації if viz_type == "Статуси": return visualizer.plot_status_counts() elif viz_type == "Пріоритети": return visualizer.plot_priority_counts() elif viz_type == "Типи тікетів": return visualizer.plot_type_counts() elif viz_type == "Призначені користувачі": return visualizer.plot_assignee_counts(limit=limit) elif viz_type == "Активність створення": return visualizer.plot_timeline(date_column='Created', groupby=groupby, cumulative=False) elif viz_type == "Активність оновлення": return visualizer.plot_timeline(date_column='Updated', groupby=groupby, cumulative=False) elif viz_type == "Кумулятивне створення": return visualizer.plot_timeline(date_column='Created', groupby=groupby, cumulative=True) elif viz_type == "Неактивні тікети": return visualizer.plot_inactive_issues() elif viz_type == "Теплова карта: Типи/Статуси": return visualizer.plot_heatmap(row_col='Issue Type', column_col='Status') elif viz_type == "Часова шкала проекту": timeline_plots = visualizer.plot_project_timeline() return timeline_plots[0] if timeline_plots[0] is not None else None elif viz_type == "Склад статусів з часом": timeline_plots = visualizer.plot_project_timeline() return timeline_plots[1] if timeline_plots[1] is not None else None else: logger.error(f"Невідомий тип візуалізації: {viz_type}") return None def generate_infographic(self): """ Генерація комплексної інфографіки з ключовими показниками Returns: matplotlib.figure.Figure: Об'єкт figure з інфографікою """ try: if self.current_data is None: logger.error("Немає даних для інфографіки") return None # Створюємо візуалізатор visualizer = JiraVisualizer(self.current_data) # Генеруємо інфографіку return visualizer.generate_infographic() except Exception as e: logger.error(f"Помилка при генерації інфографіки: {e}") return None def save_visualization(self, viz_type, filepath, limit=10, groupby="day"): """ Збереження конкретної візуалізації у файл Args: viz_type (str): Тип візуалізації filepath (str): Шлях для збереження файлу limit (int): Ліміт для топ-N елементів groupby (str): Групування для часових діаграм Returns: str: Шлях до збереженого файлу або повідомлення про помилку """ try: fig = self.generate_visualization(viz_type, limit, groupby) if fig is None: return f"Помилка: не вдалося створити візуалізацію типу '{viz_type}'" # Перевірка наявності розширення if not any(filepath.lower().endswith(ext) for ext in ['.png', '.jpg', '.svg', '.pdf']): filepath += '.png' # Створення директорії, якщо не існує os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True) # Збереження візуалізації fig.savefig(filepath, dpi=300, bbox_inches='tight') return f"Візуалізацію успішно збережено: {filepath}" except Exception as e: error_msg = f"Помилка при збереженні візуалізації: {str(e)}\n\n{traceback.format_exc()}" logger.error(error_msg) return error_msg def save_infographic(self, filepath): """ Збереження інфографіки у файл Args: filepath (str): Шлях для збереження файлу Returns: str: Шлях до збереженого файлу або повідомлення про помилку """ try: infographic = self.generate_infographic() if infographic is None: return "Помилка: не вдалося створити інфографіку" # Перевірка наявності розширення if not any(filepath.lower().endswith(ext) for ext in ['.png', '.jpg', '.svg', '.pdf']): filepath += '.png' # Створення директорії, якщо не існує os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True) # Збереження інфографіки infographic.savefig(filepath, dpi=300, bbox_inches='tight') return f"Інфографіку успішно збережено: {filepath}" except Exception as e: error_msg = f"Помилка при збереженні інфографіки: {str(e)}\n\n{traceback.format_exc()}" logger.error(error_msg) return error_msg # Створення екземпляру додатку app = JiraAssistantApp() # Точка входу для запуску з командного рядка if __name__ == "__main__": from interface import launch_interface interface = launch_interface(app) interface.launch()