""" Модуль для работы с Excel файлами. Обрабатывает список кадастровых номеров и заполняет информацию о них. """ import pandas as pd from pathlib import Path from typing import List, Dict, Optional from collections import defaultdict class ExcelProcessor: """Класс для работы с Excel файлами списков кадастровых номеров.""" def __init__(self, excel_file_path: str): self.excel_file_path = Path(excel_file_path) self.df = None self.cadastral_column = None def load_excel(self, sheet_name: Optional[str] = None, cadastral_column: Optional[str] = None): """Загружает Excel файл и определяет колонку с кадастровыми номерами.""" try: # Загружаем Excel файл if sheet_name: self.df = pd.read_excel(self.excel_file_path, sheet_name=sheet_name) else: # Берем первый лист self.df = pd.read_excel(self.excel_file_path) print(f"✅ Загружен Excel файл: {self.excel_file_path}") print(f"📊 Размер таблицы: {len(self.df)} строк, {len(self.df.columns)} колонок") print(f"📋 Колонки: {list(self.df.columns)}") # Определяем колонку с кадастровыми номерами if cadastral_column: self.cadastral_column = cadastral_column else: self.cadastral_column = self._find_cadastral_column() if self.cadastral_column: print(f"🎯 Найдена колонка с кадастровыми номерами: '{self.cadastral_column}'") cadastral_count = self.df[self.cadastral_column].notna().sum() print(f"📄 Кадастровых номеров в файле: {cadastral_count}") else: raise ValueError("Не удалось найти колонку с кадастровыми номерами") except Exception as e: print(f"❌ Ошибка при загрузке Excel файла: {e}") raise def _find_cadastral_column(self) -> Optional[str]: """Автоматически находит колонку с кадастровыми номерами.""" # Возможные названия колонок possible_names = [ 'кадастровый номер', 'кадастровый_номер', 'кн', 'cadastral_number', 'номер', 'cadastral', 'кадастр', 'kad_number' ] # Ищем по названию колонки for col in self.df.columns: col_lower = str(col).lower().strip() for name in possible_names: if name in col_lower: return col # Ищем по содержимому (ищем колонку где есть номера вида XX:XX:XXXXXX:XXXX) import re pattern = r'\d{1,2}:\d{1,2}:\d{6,7}:\d{1,4}' for col in self.df.columns: # Проверяем первые несколько непустых значений sample_values = self.df[col].dropna().head(10) match_count = 0 for value in sample_values: if isinstance(value, str) and re.match(pattern, value.strip()): match_count += 1 # Если больше половины значений похожи на кадастровые номера if match_count > len(sample_values) * 0.5 and match_count > 0: return col return None def get_cadastral_numbers_from_excel(self) -> List[str]: """Возвращает список всех кадастровых номеров из Excel файла.""" if not self.cadastral_column: return [] # Получаем все непустые кадастровые номера cadastral_numbers = [] for value in self.df[self.cadastral_column].dropna(): if isinstance(value, str): clean_value = value.strip() if clean_value: cadastral_numbers.append(clean_value) elif pd.notna(value): # Если число, преобразуем в строку cadastral_numbers.append(str(value).strip()) return cadastral_numbers def create_cadastral_mapping(self, cadastral_records: List) -> Dict[str, Dict]: """Создает маппинг кадастровых номеров к их данным.""" mapping = defaultdict(lambda: { 'source_files': set(), 'related_numbers': set(), 'quarter_numbers': set() }) for record in cadastral_records: cadastral_num = record.cadastral_number # Добавляем источник файла source_file = record.source_xml_info.get_display_name() mapping[cadastral_num]['source_files'].add(source_file) # Добавляем связанные номера for related in record.related_numbers: mapping[cadastral_num]['related_numbers'].add(related) # Добавляем квартал if record.quarter_number: mapping[cadastral_num]['quarter_numbers'].add(record.quarter_number) # Преобразуем sets в списки для удобства for cadastral_num in mapping: mapping[cadastral_num]['source_files'] = list(mapping[cadastral_num]['source_files']) mapping[cadastral_num]['related_numbers'] = list(mapping[cadastral_num]['related_numbers']) mapping[cadastral_num]['quarter_numbers'] = list(mapping[cadastral_num]['quarter_numbers']) return dict(mapping) def fill_cadastral_info(self, cadastral_records: List, output_file: Optional[str] = None): """Заполняет информацию о кадастровых номерах в Excel файле.""" if self.df is None or not self.cadastral_column: raise ValueError("Excel файл не загружен или не найдена колонка с кадастровыми номерами") print(f"\n{'='*80}") print(f"ЗАПОЛНЕНИЕ ИНФОРМАЦИИ В EXCEL ФАЙЛЕ") print(f"{'='*80}") # Создаем маппинг cadastral_mapping = self.create_cadastral_mapping(cadastral_records) # Добавляем новые колонки если их нет if 'Источники файлов' not in self.df.columns: self.df['Источники файлов'] = '' if 'Смежные номера' not in self.df.columns: self.df['Смежные номера'] = '' if 'Кадастровый блок' not in self.df.columns: self.df['Кадастровый блок'] = '' # Статистика found_count = 0 not_found_count = 0 # Заполняем информацию для каждого кадастрового номера for index, row in self.df.iterrows(): cadastral_num = str(row[self.cadastral_column]).strip() if cadastral_num in cadastral_mapping: data = cadastral_mapping[cadastral_num] # Заполняем источники файлов sources = ', '.join(data['source_files']) self.df.at[index, 'Источники файлов'] = sources # Заполняем смежные номера related = ', '.join(data['related_numbers']) self.df.at[index, 'Смежные номера'] = related # Заполняем кадастровый блок (квартал) quarters = ', '.join(data['quarter_numbers']) self.df.at[index, 'Кадастровый блок'] = quarters found_count += 1 print(f"✅ {cadastral_num}: найден в {len(data['source_files'])} файлах") else: not_found_count += 1 print(f"❌ {cadastral_num}: не найден в XML файлах") print(f"\n📊 Статистика заполнения:") print(f" Найдено: {found_count}") print(f" Не найдено: {not_found_count}") print(f" Всего: {found_count + not_found_count}") # Сохраняем результат if output_file: output_path = Path(output_file) else: # Создаем имя файла с результатами output_path = self.excel_file_path.parent / f"{self.excel_file_path.stem}_результат.xlsx" try: self.df.to_excel(output_path, index=False) print(f"\n💾 Результат сохранен в: {output_path}") return output_path except Exception as e: print(f"❌ Ошибка при сохранении: {e}") raise def print_excel_preview(self, rows: int = 10): """Выводит предварительный просмотр Excel файла.""" if self.df is None: print("Excel файл не загружен") return print(f"\n📋 Предварительный просмотр Excel файла (первые {rows} строк):") print("-" * 80) # Показываем первые строки preview_df = self.df.head(rows) # Ограничиваем ширину колонок для читаемости pd.set_option('display.max_columns', None) pd.set_option('display.width', None) pd.set_option('display.max_colwidth', 50) print(preview_df.to_string(index=False)) if len(self.df) > rows: print(f"\n... и еще {len(self.df) - rows} строк") def get_statistics(self): """Возвращает статистику по Excel файлу.""" if self.df is None: return {} stats = { 'total_rows': len(self.df), 'total_columns': len(self.df.columns), 'columns': list(self.df.columns), 'cadastral_column': self.cadastral_column } if self.cadastral_column: cadastral_numbers = self.get_cadastral_numbers_from_excel() stats['cadastral_numbers_count'] = len(cadastral_numbers) stats['empty_cadastral_count'] = len(self.df) - len(cadastral_numbers) return stats