import pandas as pd from datetime import datetime import logging import os from pathlib import Path logger = logging.getLogger(__name__) class JiraCsvImporter: """ Клас для імпорту даних з CSV-файлів Jira """ def __init__(self, file_path): """ Ініціалізація імпортера CSV. Args: file_path (str): Шлях до CSV-файлу """ self.file_path = file_path self.df = None def load_data(self): """ Завантаження даних з CSV-файлу. Returns: pandas.DataFrame: Завантажені дані або None у випадку помилки """ try: logger.info(f"Завантаження CSV-файлу: {self.file_path}") print(f"Завантаження CSV-файлу: {self.file_path}") # Додаткове логування в консоль # Перевірка існування файлу if not os.path.exists(self.file_path): logger.error(f"Файл не знайдено: {self.file_path}") print(f"Файл не знайдено: {self.file_path}") return None # Спробуємо різні кодування try: self.df = pd.read_csv(self.file_path, encoding='utf-8') print(f"Файл успішно прочитано з кодуванням utf-8") except UnicodeDecodeError: try: logger.warning("Помилка декодування UTF-8, спроба з latin1") print("Помилка декодування UTF-8, спроба з latin1") self.df = pd.read_csv(self.file_path, encoding='latin1') print(f"Файл успішно прочитано з кодуванням latin1") except Exception as e: logger.error(f"Помилка при спробі прочитати з latin1: {e}") print(f"Помилка при спробі прочитати з latin1: {e}") # Спробуємо читати без вказання кодування self.df = pd.read_csv(self.file_path) print(f"Файл успішно прочитано зі стандартним кодуванням") # Тимчасово вимкнемо перевірку колонок для діагностики # required_columns = self._check_required_columns() # if not required_columns: # logger.warning("CSV-файл не містить необхідних колонок") # print("CSV-файл не містить необхідних колонок") # return None # Відображення наявних колонок для діагностики print(f"Наявні колонки: {self.df.columns.tolist()}") print(f"Кількість рядків: {len(self.df)}") # Обробка дат self._process_dates() # Очищення та підготовка даних self._clean_data() logger.info(f"Успішно завантажено {len(self.df)} записів") print(f"Успішно завантажено {len(self.df)} записів") return self.df except Exception as e: logger.error(f"Помилка при завантаженні CSV-файлу: {e}") import traceback error_details = traceback.format_exc() print(f"Помилка при завантаженні CSV-файлу: {e}") print(f"Деталі помилки: {error_details}") return None def _check_required_columns(self): """ Перевірка наявності необхідних колонок у CSV-файлі. Returns: bool: True, якщо всі необхідні колонки присутні """ # Основні колонки, які очікуються у файлі Jira basic_columns = ['Summary', 'Issue key', 'Status', 'Issue Type', 'Priority', 'Created', 'Updated'] # Альтернативні назви колонок alternative_columns = { 'Summary': ['Summary', 'Короткий опис'], 'Issue key': ['Issue key', 'Key', 'Ключ'], 'Status': ['Status', 'Статус'], 'Issue Type': ['Issue Type', 'Type', 'Тип'], 'Priority': ['Priority', 'Пріоритет'], 'Created': ['Created', 'Створено'], 'Updated': ['Updated', 'Оновлено'] } # Перевірка наявності колонок missing_columns = [] for col in basic_columns: found = False # Перевірка основної назви if col in self.df.columns: found = True else: # Перевірка альтернативних назв for alt_col in alternative_columns.get(col, []): if alt_col in self.df.columns: # Перейменування колонки до стандартного імені self.df.rename(columns={alt_col: col}, inplace=True) found = True break if not found: missing_columns.append(col) if missing_columns: logger.warning(f"Відсутні колонки: {', '.join(missing_columns)}") print(f"Відсутні колонки: {', '.join(missing_columns)}") return False return True def _process_dates(self): """ Обробка дат у DataFrame. """ try: # Перетворення колонок з датами date_columns = ['Created', 'Updated', 'Resolved', 'Due Date'] for col in date_columns: if col in self.df.columns: try: self.df[col] = pd.to_datetime(self.df[col], errors='coerce') print(f"Колонку {col} успішно конвертовано до datetime") except Exception as e: logger.warning(f"Не вдалося конвертувати колонку {col} до datetime: {e}") print(f"Не вдалося конвертувати колонку {col} до datetime: {e}") except Exception as e: logger.error(f"Помилка при обробці дат: {e}") print(f"Помилка при обробці дат: {e}") def _clean_data(self): """ Очищення та підготовка даних. """ try: # Видалення порожніх рядків if 'Issue key' in self.df.columns: self.df.dropna(subset=['Issue key'], inplace=True) print(f"Видалено порожні рядки за колонкою 'Issue key'") # Додаткова обробка даних if 'Status' in self.df.columns: self.df['Status'] = self.df['Status'].fillna('Unknown') print(f"Заповнено відсутні значення в колонці 'Status'") if 'Priority' in self.df.columns: self.df['Priority'] = self.df['Priority'].fillna('Not set') print(f"Заповнено відсутні значення в колонці 'Priority'") # Створення додаткових колонок для аналізу if 'Created' in self.df.columns and pd.api.types.is_datetime64_dtype(self.df['Created']): self.df['Created_Date'] = self.df['Created'].dt.date self.df['Created_Month'] = self.df['Created'].dt.to_period('M') print(f"Створено додаткові колонки для дат створення") if 'Updated' in self.df.columns and pd.api.types.is_datetime64_dtype(self.df['Updated']): self.df['Updated_Date'] = self.df['Updated'].dt.date self.df['Days_Since_Update'] = (datetime.now() - self.df['Updated']).dt.days print(f"Створено додаткові колонки для дат оновлення") except Exception as e: logger.error(f"Помилка при очищенні даних: {e}") print(f"Помилка при очищенні даних: {e}") def export_to_csv(self, output_path=None): """ Експорт оброблених даних у CSV-файл. Args: output_path (str): Шлях для збереження файлу. Якщо None, створюється автоматично. Returns: str: Шлях до збереженого файлу або None у випадку помилки """ if self.df is None: logger.error("Немає даних для експорту") return None try: if output_path is None: timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') output_dir = Path("exported_data") output_dir.mkdir(exist_ok=True) output_path = output_dir / f"jira_data_{timestamp}.csv" self.df.to_csv(output_path, index=False, encoding='utf-8') logger.info(f"Дані успішно експортовано у {output_path}") return str(output_path) except Exception as e: logger.error(f"Помилка при експорті даних: {e}") return None