fix bug
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ build_32bit/
|
||||
dist_32bit/
|
||||
dist/
|
||||
testset/
|
||||
test_results/
|
||||
@@ -1,52 +0,0 @@
|
||||
# Инструкция по созданию 32-битной версии программы
|
||||
|
||||
## Текущее состояние
|
||||
- ✅ Программа успешно работает на 64-битных системах
|
||||
- ❌ Для 32-битных систем требуется пересборка
|
||||
|
||||
## Для создания 32-битной версии:
|
||||
|
||||
### Шаг 1: Установка 32-битного Python
|
||||
1. Скачайте Python 3.12 (32-bit) с https://www.python.org/downloads/
|
||||
2. При установке выберите "Add to PATH"
|
||||
3. Убедитесь, что установлен именно 32-битный Python
|
||||
|
||||
### Шаг 2: Проверка архитектуры
|
||||
```bash
|
||||
python -c "import platform; print(platform.architecture())"
|
||||
# Должно показать: ('32bit', 'WindowsPE')
|
||||
```
|
||||
|
||||
### Шаг 3: Установка зависимостей
|
||||
```bash
|
||||
pip install pyinstaller pandas openpyxl lxml pathlib2
|
||||
```
|
||||
|
||||
### Шаг 4: Сборка программы
|
||||
```bash
|
||||
python build_32bit.py
|
||||
```
|
||||
|
||||
## Альтернативный вариант
|
||||
|
||||
Если у вас нет доступа к 32-битному Python, можно:
|
||||
|
||||
1. **Использовать виртуальную машину** с 32-битной Windows
|
||||
2. **Попросить кого-то собрать** на 32-битной системе
|
||||
3. **Использовать Docker** с 32-битным образом Python
|
||||
|
||||
## Проверка совместимости
|
||||
Созданный `KadastrParser.exe` (64-bit) будет работать на:
|
||||
- ✅ Windows 7/8/10/11 (64-bit)
|
||||
- ❌ Windows 7/8/10/11 (32-bit)
|
||||
|
||||
Для универсальности рекомендуется создать обе версии:
|
||||
- `KadastrParser_64bit.exe` - для 64-битных систем
|
||||
- `KadastrParser_32bit.exe` - для 32-битных систем
|
||||
|
||||
## Размер программы
|
||||
- 64-битная версия: ~50-70 MB
|
||||
- 32-битная версия: ~45-65 MB
|
||||
|
||||
## Тестирование
|
||||
Перед выпуском обязательно протестируйте на целевой системе!
|
||||
@@ -1,39 +0,0 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['main.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('services', 'services'), ('models', 'models')],
|
||||
hiddenimports=['tkinter', 'pandas', 'openpyxl', 'lxml'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='KadastrParser',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon='NONE',
|
||||
)
|
||||
117
VERSIONS_INFO.md
117
VERSIONS_INFO.md
@@ -1,117 +0,0 @@
|
||||
# KadastrParser - Парсер кадастровых номеров
|
||||
|
||||
## 📦 Доступные версии
|
||||
|
||||
### 🖥️ 64-битная версия
|
||||
- **Файл**: `dist/KadastrParser.exe`
|
||||
- **Размер**: ~52 MB
|
||||
- **Совместимость**: Windows 7/8/10/11 (только 64-bit)
|
||||
- **Python**: 3.12.11 (64-bit)
|
||||
|
||||
### 💻 32-битная версия
|
||||
- **Файл**: `dist_32bit/KadastrParser_32bit.exe`
|
||||
- **Размер**: ~29 MB
|
||||
- **Совместимость**: Windows 7/8/10/11 (32-bit и 64-bit)
|
||||
- **Python**: 3.8.20 (32-bit)
|
||||
|
||||
## ✅ Ответ на ваш вопрос
|
||||
|
||||
**Да, программа будет запускаться на 32-битной системе!**
|
||||
|
||||
Используйте файл `KadastrParser_32bit.exe` из папки `dist_32bit/`.
|
||||
|
||||
## 🎯 Рекомендации по выбору версии
|
||||
|
||||
### Для 32-битных систем:
|
||||
- ✅ Используйте `KadastrParser_32bit.exe`
|
||||
- ✅ Меньший размер файла
|
||||
- ✅ Гарантированная совместимость
|
||||
|
||||
### Для 64-битных систем:
|
||||
- 🔄 Можно использовать любую версию
|
||||
- 🚀 `KadastrParser.exe` - более новый Python, быстрее
|
||||
- 💾 `KadastrParser_32bit.exe` - универсальная
|
||||
|
||||
## 🔧 Функциональность
|
||||
|
||||
Обе версии имеют **абсолютно одинаковый функционал**:
|
||||
|
||||
### ✨ Основные возможности:
|
||||
- 📁 Поиск XML файлов в директории
|
||||
- 🔍 Извлечение кадастровых номеров
|
||||
- 📊 Обработка Excel файлов со списками номеров
|
||||
- 📋 Создание подробных отчетов
|
||||
- 🗂️ Организация файлов по папкам
|
||||
|
||||
### 📈 Excel интеграция:
|
||||
- ✅ **Найденные файлы** - где обнаружен номер
|
||||
- ✅ **Смежные кадастровые номера** - другие номера из тех же файлов
|
||||
- ✅ **Кадастровый блок** - первые 3 части номера
|
||||
- ✅ Автоматическое обновление Excel с путями к файлам
|
||||
|
||||
### 📁 Структура результатов:
|
||||
```
|
||||
results/
|
||||
├── all_xml_files/ # Все найденные XML файлы
|
||||
├── matching_cadastral_files/ # Файлы из Excel списка
|
||||
├── updated_cadastral_list.xlsx # Обновленный Excel файл
|
||||
└── processing_report.txt # Подробный отчет
|
||||
```
|
||||
|
||||
## 🚀 Как использовать:
|
||||
|
||||
1. **Скачайте нужную версию**:
|
||||
- 32-bit система → `KadastrParser_32bit.exe`
|
||||
- 64-bit система → `KadastrParser.exe` или `KadastrParser_32bit.exe`
|
||||
|
||||
2. **Поместите файл в любую папку**
|
||||
|
||||
3. **Запустите двойным кликом**
|
||||
|
||||
4. **Выберите папку с XML файлами**
|
||||
|
||||
5. **Опционально: выберите Excel файл**
|
||||
|
||||
6. **Нажмите "Начать обработку"**
|
||||
- ✅ **В этот момент автоматически создается папка `results/` в той же папке, где лежит программа**
|
||||
- ✅ **Создаются подпапки для организации файлов**
|
||||
|
||||
## ⏰ Когда и где создаются директории:
|
||||
|
||||
- 📁 **Сразу при нажатии "Начать обработку"** программа создает папку `results/` **в той же директории, где находится исполняемый файл**:
|
||||
- `results/` - основная папка результатов
|
||||
- `results/all_xml_files/` - для всех найденных XML файлов
|
||||
- `results/matching_cadastral_files/` - для файлов из Excel списка
|
||||
|
||||
- 📄 **Во время обработки** добавляются:
|
||||
- `results/processing_report.txt` - подробный отчет
|
||||
- `results/updated_cadastral_list.xlsx` - обновленный Excel (если указан)
|
||||
|
||||
### 📍 Пример расположения файлов:
|
||||
```
|
||||
C:\MyFolder\
|
||||
├── KadastrParser.exe # Ваша программа
|
||||
└── results/ # Создается автоматически
|
||||
├── all_xml_files/
|
||||
├── matching_cadastral_files/
|
||||
├── processing_report.txt
|
||||
└── updated_cadastral_list.xlsx
|
||||
```
|
||||
|
||||
## 💡 Важные особенности:
|
||||
|
||||
- 🔄 **Без консоли** - чистый графический интерфейс
|
||||
- 📦 **Один файл** - не требует установки
|
||||
- 🛡️ **Безопасно** - только чтение XML и создание результатов
|
||||
- 🎨 **Интуитивный интерфейс** - простое использование
|
||||
- ⚡ **Быстрая обработка** - многопоточность
|
||||
|
||||
## 🔍 Поддерживаемые форматы:
|
||||
|
||||
- **Кадастровые номера**: `XX:XX:XXXXXXX:XXX`
|
||||
- **XML файлы**: все варианты из ЕГРН
|
||||
- **Excel файлы**: `.xlsx` и `.xls`
|
||||
|
||||
---
|
||||
|
||||
**Итог**: Для 32-битной системы используйте `KadastrParser_32bit.exe` - он гарантированно будет работать!
|
||||
@@ -1,72 +0,0 @@
|
||||
"""
|
||||
Скрипт для создания 32-битной версии программы
|
||||
ВАЖНО: Этот скрипт нужно запускать на 32-битном Python!
|
||||
"""
|
||||
import PyInstaller.__main__
|
||||
import sys
|
||||
import platform
|
||||
|
||||
def check_architecture():
|
||||
"""Проверяет архитектуру Python"""
|
||||
arch = platform.architecture()[0]
|
||||
machine = platform.machine()
|
||||
|
||||
print(f"🔍 Текущая архитектура Python: {arch}")
|
||||
print(f"🖥️ Тип машины: {machine}")
|
||||
|
||||
if arch == '32bit':
|
||||
print("✅ Python 32-bit - подходит для создания 32-битной программы")
|
||||
return True
|
||||
else:
|
||||
print("❌ Python 64-bit - НЕ подходит для создания 32-битной программы")
|
||||
print("💡 Для создания 32-битной версии нужен 32-битный Python")
|
||||
return False
|
||||
|
||||
def build_32bit_executable():
|
||||
"""Создает 32-битный исполняемый файл"""
|
||||
|
||||
if not check_architecture():
|
||||
print("\n⚠️ ВНИМАНИЕ: Для создания 32-битной программы:")
|
||||
print("1. Скачайте 32-битный Python с https://www.python.org/downloads/")
|
||||
print("2. Установите зависимости: pip install pyinstaller pandas openpyxl lxml")
|
||||
print("3. Запустите этот скрипт снова")
|
||||
return False
|
||||
|
||||
# Параметры для PyInstaller (32-бит)
|
||||
args = [
|
||||
'main.py',
|
||||
'--onefile',
|
||||
'--windowed',
|
||||
'--name=KadastrParser_32bit',
|
||||
'--icon=NONE',
|
||||
'--add-data=services;services',
|
||||
'--add-data=models;models',
|
||||
'--hidden-import=tkinter',
|
||||
'--hidden-import=pandas',
|
||||
'--hidden-import=openpyxl',
|
||||
'--hidden-import=lxml',
|
||||
'--clean',
|
||||
'--noconfirm',
|
||||
'--target-architecture=x86', # Явно указываем 32-бит
|
||||
]
|
||||
|
||||
print("\n🚀 Начинаем создание 32-битного исполняемого файла...")
|
||||
print("📋 Параметры сборки:")
|
||||
for arg in args:
|
||||
print(f" {arg}")
|
||||
print()
|
||||
|
||||
try:
|
||||
# Запускаем PyInstaller
|
||||
PyInstaller.__main__.run(args)
|
||||
print("✅ 32-битная сборка завершена!")
|
||||
print("📁 Исполняемый файл: dist/KadastrParser_32bit.exe")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при сборке: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🔧 Создание 32-битной версии программы")
|
||||
print("=" * 50)
|
||||
build_32bit_executable()
|
||||
24
check_fix.py
Normal file
24
check_fix.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import pandas as pd
|
||||
|
||||
df = pd.read_excel('test_fix.xlsx')
|
||||
print('Проверяем данные в Excel:')
|
||||
print('Всего строк:', len(df))
|
||||
filled = df[df['Найден в файле'].notna() & (df['Найден в файле'] != '')]
|
||||
print('Заполненных строк:', len(filled))
|
||||
|
||||
if len(filled) > 0:
|
||||
for i, (idx, row) in enumerate(filled.head(3).iterrows()):
|
||||
print(f'Строка {i+1}:')
|
||||
print(f' Номер в Excel: {row.iloc[0]}')
|
||||
main_num = row['Основной номер объекта']
|
||||
additional = row['Дополнительные номера']
|
||||
print(f' Основной: {main_num}')
|
||||
print(f' Дополнительные: {additional}')
|
||||
|
||||
# Проверяем, что основной номер не дублируется
|
||||
if pd.notna(main_num) and pd.notna(additional):
|
||||
main_in_additional = str(main_num) in str(additional)
|
||||
print(f' ✅ Основной номер НЕ повторяется в дополнительных: {not main_in_additional}')
|
||||
print()
|
||||
else:
|
||||
print('❌ Не найдено заполненных строк')
|
||||
30
main.py
30
main.py
@@ -89,19 +89,14 @@ def Pars():
|
||||
messagebox.showwarning("Предупреждение", "Кадастровые номера не найдены в XML файлах")
|
||||
return
|
||||
|
||||
# 4. Создаем карту номер -> файл для быстрого поиска
|
||||
# 4. Создаем карту номер -> файл для быстрого поиска (для обратной совместимости)
|
||||
cadastral_to_file_map = {}
|
||||
file_to_numbers_map = {} # файл -> множество номеров для поиска смежных
|
||||
|
||||
for record in records:
|
||||
# Для основной карты берем первое вхождение номера
|
||||
if record.number not in cadastral_to_file_map:
|
||||
cadastral_to_file_map[record.number] = record.file_path
|
||||
|
||||
# Для карты файл->номера добавляем все номера из каждого файла
|
||||
if record.file_path not in file_to_numbers_map:
|
||||
file_to_numbers_map[record.file_path] = set()
|
||||
file_to_numbers_map[record.file_path].add(record.number)
|
||||
# Добавляем все номера из объекта в карту
|
||||
for number in record.all_numbers:
|
||||
if number not in cadastral_to_file_map: # Берем первый найденный файл
|
||||
cadastral_to_file_map[number] = record.file_path
|
||||
|
||||
# 5. Обработка Excel файла и организация файлов
|
||||
excel_updated = False
|
||||
@@ -115,13 +110,14 @@ def Pars():
|
||||
excel_numbers = excel_processor.get_cadastral_numbers()
|
||||
|
||||
# Находим пересечения между Excel и найденными номерами
|
||||
found_numbers = set(cadastral_to_file_map.keys())
|
||||
matching_numbers = excel_numbers.intersection(found_numbers)
|
||||
# Теперь ищем по всем номерам в объектах, не только по основным
|
||||
all_found_numbers = seeker.get_all_cadastral_numbers()
|
||||
matching_numbers = excel_numbers.intersection(all_found_numbers)
|
||||
|
||||
if matching_numbers:
|
||||
# Обновляем Excel файл информацией о найденных файлах
|
||||
# Обновляем Excel файл информацией о найденных объектах
|
||||
output_excel_path = str(output_directory / "updated_cadastral_list.xlsx")
|
||||
excel_processor.update_excel_with_file_info(cadastral_to_file_map, output_excel_path)
|
||||
excel_processor.update_excel_with_cadastral_records(records, output_excel_path)
|
||||
excel_updated = True
|
||||
|
||||
# Копируем файлы с номерами из Excel в отдельную папку
|
||||
@@ -221,8 +217,10 @@ def create_report(output_directory, stats, unique_numbers, moved_files, excel_up
|
||||
f.write(f"Общая статистика:\n")
|
||||
f.write(f"- Всего XML файлов найдено: {stats['total_xml_files']}\n")
|
||||
f.write(f"- Файлов с кадастровыми номерами: {stats['files_with_numbers']}\n")
|
||||
f.write(f"- Всего кадастровых номеров: {stats['total_cadastral_numbers']}\n")
|
||||
f.write(f"- Уникальных номеров: {stats['unique_cadastral_numbers']}\n")
|
||||
f.write(f"- Всего кадастровых объектов: {stats['total_cadastral_objects']}\n")
|
||||
f.write(f"- Уникальных основных номеров: {stats['unique_main_numbers']}\n")
|
||||
f.write(f"- Всего номеров во всех объектах: {stats['total_all_numbers']}\n")
|
||||
f.write(f"- Всего уникальных номеров: {stats['unique_all_numbers']}\n")
|
||||
f.write(f"- Файлов скопировано в 'all_xml_files': {len(moved_files)}\n")
|
||||
|
||||
if matching_files_count > 0:
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
"""
|
||||
Модель для хранения информации о кадастровом номере.
|
||||
Модель для хранения информации о кадастровом объекте.
|
||||
"""
|
||||
from typing import Set
|
||||
|
||||
class CadastralRecord:
|
||||
def __init__(self, number: str, file_path: str):
|
||||
self.number = number
|
||||
def __init__(self, main_number: str, file_path: str, all_numbers: Set[str] = None):
|
||||
"""
|
||||
Инициализация записи о кадастровом объекте
|
||||
|
||||
Args:
|
||||
main_number: Основной кадастровый номер объекта
|
||||
file_path: Путь к XML файлу
|
||||
all_numbers: Все кадастровые номера, найденные в том же XML файле
|
||||
"""
|
||||
self.number = main_number # Оставляем для обратной совместимости
|
||||
self.main_number = main_number
|
||||
self.file_path = file_path
|
||||
self.all_numbers = all_numbers if all_numbers is not None else {main_number}
|
||||
|
||||
@property
|
||||
def other_numbers(self) -> Set[str]:
|
||||
"""Возвращает все номера кроме основного"""
|
||||
return self.all_numbers - {self.main_number}
|
||||
|
||||
def __str__(self):
|
||||
return f"Кадастровый номер: {self.number}, файл: {self.file_path}"
|
||||
other_count = len(self.other_numbers)
|
||||
if other_count > 0:
|
||||
return f"Объект {self.main_number} (+{other_count} номеров), файл: {self.file_path}"
|
||||
return f"Объект {self.main_number}, файл: {self.file_path}"
|
||||
|
||||
def __repr__(self):
|
||||
return f"CadastralRecord('{self.number}', '{self.file_path}')"
|
||||
return f"CadastralRecord('{self.main_number}', '{self.file_path}', {self.all_numbers})"
|
||||
|
||||
@@ -3,60 +3,104 @@
|
||||
"""
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import List, Set, Optional
|
||||
from typing import List, Set, Optional, Tuple
|
||||
from lxml import etree
|
||||
|
||||
class XMLFileInfo:
|
||||
def __init__(self, file_path: str):
|
||||
self.file_path = Path(file_path)
|
||||
self.cadastral_numbers: Set[str] = set()
|
||||
self.main_cadastral_number: Optional[str] = None
|
||||
self.all_cadastral_numbers: Set[str] = set()
|
||||
|
||||
def extract_cadastral_numbers(self) -> Set[str]:
|
||||
"""Извлекает кадастровые номера из XML файла"""
|
||||
def extract_cadastral_data(self) -> Tuple[Optional[str], Set[str]]:
|
||||
"""
|
||||
Извлекает основной номер и все номера из XML файла
|
||||
Возвращает (основной_номер, все_номера_в_файле)
|
||||
"""
|
||||
if not self.file_path.exists():
|
||||
return set()
|
||||
return None, set()
|
||||
|
||||
try:
|
||||
# Парсим XML файл
|
||||
tree = etree.parse(str(self.file_path))
|
||||
root = tree.getroot()
|
||||
|
||||
# Используем улучшенный алгоритм извлечения
|
||||
return self._extract_cadastral_numbers_enhanced(root)
|
||||
# Находим все кадастровые номера в файле
|
||||
all_numbers = self._find_all_cadastral_numbers(root)
|
||||
|
||||
# Определяем основной номер (из объекта)
|
||||
main_number = self._find_main_cadastral_number(root, all_numbers)
|
||||
|
||||
self.main_cadastral_number = main_number
|
||||
self.all_cadastral_numbers = all_numbers
|
||||
|
||||
return main_number, all_numbers
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка при обработке файла {self.file_path}: {e}")
|
||||
return set()
|
||||
return None, set()
|
||||
|
||||
def _extract_cadastral_numbers_enhanced(self, root) -> Set[str]:
|
||||
"""Улучшенный алгоритм извлечения кадастровых номеров"""
|
||||
cadastral_numbers = set()
|
||||
def _find_all_cadastral_numbers(self, root) -> Set[str]:
|
||||
"""Находит все валидные кадастровые номера в XML"""
|
||||
all_numbers = set()
|
||||
|
||||
# 1. Поиск по известным тегам
|
||||
tags_to_search = ['cad_number', 'land_cad_number', 'cadastral_number', 'parcel_cad_number']
|
||||
for tag in tags_to_search:
|
||||
elements = root.xpath(f".//{tag}")
|
||||
# Ищем по всем элементам
|
||||
for elem in root.iter():
|
||||
if elem.text and self._is_valid_cadastral_number(elem.text):
|
||||
all_numbers.add(elem.text.strip())
|
||||
|
||||
return all_numbers
|
||||
|
||||
def _find_main_cadastral_number(self, root, all_numbers: Set[str]) -> Optional[str]:
|
||||
"""
|
||||
Определяет основной кадастровый номер из объекта
|
||||
Приоритет: object > другие контейнеры > первый найденный
|
||||
"""
|
||||
# 1. Ищем в объектах - высший приоритет
|
||||
objects = root.xpath(".//object")
|
||||
for obj in objects:
|
||||
number = self._get_first_cadastral_from_element(obj)
|
||||
if number and number in all_numbers:
|
||||
return number
|
||||
|
||||
# 2. Ищем в других контейнерах объектов недвижимости
|
||||
containers = [
|
||||
'land_record', 'building_record', 'construction_record', 'flat_record',
|
||||
'extract_about_property_land', 'extract_about_property_building',
|
||||
'extract_about_property_flat', 'extract_about_property_construction',
|
||||
'property_object', 'realty_object'
|
||||
]
|
||||
|
||||
for container in containers:
|
||||
elements = root.xpath(f".//{container}")
|
||||
for elem in elements:
|
||||
number = self._get_first_cadastral_from_element(elem)
|
||||
if number and number in all_numbers:
|
||||
return number
|
||||
|
||||
# 3. Если ничего не найдено в контейнерах, берем первый из всех найденных
|
||||
if all_numbers:
|
||||
return sorted(all_numbers)[0]
|
||||
|
||||
return None
|
||||
|
||||
def _get_first_cadastral_from_element(self, element) -> Optional[str]:
|
||||
"""Находит первый кадастровый номер в элементе"""
|
||||
# Приоритетные теги для поиска
|
||||
priority_tags = ['cad_number', 'cadastral_number', 'land_cad_number', 'parcel_cad_number']
|
||||
|
||||
# Сначала ищем по приоритетным тегам
|
||||
for tag in priority_tags:
|
||||
elements = element.xpath(f".//{tag}")
|
||||
for elem in elements:
|
||||
if elem.text and self._is_valid_cadastral_number(elem.text):
|
||||
cadastral_numbers.add(elem.text.strip())
|
||||
return elem.text.strip()
|
||||
|
||||
# 2. Поиск в атрибутах
|
||||
for elem in root.iter():
|
||||
for attr_name, attr_value in elem.attrib.items():
|
||||
if any(keyword in attr_name.lower() for keyword in ['cad', 'cadastral', 'кадастр']):
|
||||
if self._is_valid_cadastral_number(attr_value):
|
||||
cadastral_numbers.add(attr_value.strip())
|
||||
# Если в приоритетных тегах не найдено, ищем по всем элементам
|
||||
for elem in element.iter():
|
||||
if elem.text and self._is_valid_cadastral_number(elem.text):
|
||||
return elem.text.strip()
|
||||
|
||||
# 3. Regex поиск по тексту
|
||||
text_content = etree.tostring(root, encoding='unicode', method='text')
|
||||
if text_content:
|
||||
regex_matches = re.findall(r'\d{1,2}:\d{1,2}:\d{6,8}:\d{1,5}', text_content)
|
||||
for match in regex_matches:
|
||||
if self._is_valid_cadastral_number(match):
|
||||
cadastral_numbers.add(match)
|
||||
|
||||
self.cadastral_numbers = cadastral_numbers
|
||||
return cadastral_numbers
|
||||
return None
|
||||
|
||||
def _is_valid_cadastral_number(self, text: str) -> bool:
|
||||
"""Проверяет, является ли строка валидным кадастровым номером"""
|
||||
@@ -68,4 +112,4 @@ class XMLFileInfo:
|
||||
return bool(re.match(pattern, text.strip()))
|
||||
|
||||
def __str__(self):
|
||||
return f"XML файл: {self.file_path.name}, номеров: {len(self.cadastral_numbers)}"
|
||||
return f"XML файл: {self.file_path.name}, основной номер: {self.main_cadastral_number}, всего номеров: {len(self.all_cadastral_numbers)}"
|
||||
|
||||
@@ -90,46 +90,57 @@ class ExcelProcessor:
|
||||
pattern = r'^\d{1,2}:\d{1,2}:\d{6,8}:\d{1,5}$'
|
||||
return bool(re.match(pattern, text))
|
||||
|
||||
def update_excel_with_file_info(self, cadastral_to_file_map: Dict[str, str],
|
||||
output_file_path: str = None) -> bool:
|
||||
"""Обновляет Excel файл информацией о найденных файлах и смежных номерах"""
|
||||
def update_excel_with_cadastral_records(self, cadastral_records: List,
|
||||
output_file_path: str = None) -> bool:
|
||||
"""Обновляет Excel файл информацией о найденных кадастровых объектах"""
|
||||
try:
|
||||
from models.cadastral_record import CadastralRecord
|
||||
|
||||
if self.df is None or self.cadastral_column is None:
|
||||
print("Excel файл не загружен или не найдена колонка с номерами")
|
||||
return False
|
||||
|
||||
# Создаем карту файл -> множество кадастровых номеров для поиска смежных
|
||||
file_to_numbers_map = {}
|
||||
for number, file_path in cadastral_to_file_map.items():
|
||||
if file_path not in file_to_numbers_map:
|
||||
file_to_numbers_map[file_path] = set()
|
||||
file_to_numbers_map[file_path].add(number)
|
||||
# Создаем карты для быстрого поиска
|
||||
# номер -> запись объекта
|
||||
number_to_record = {}
|
||||
for record in cadastral_records:
|
||||
# Основной номер
|
||||
number_to_record[record.main_number] = record
|
||||
# Все остальные номера тоже ведут к тому же объекту
|
||||
for number in record.other_numbers:
|
||||
number_to_record[number] = record
|
||||
|
||||
# Добавляем новые колонки
|
||||
self.df['Найденные файлы'] = ''
|
||||
self.df['Смежные кадастровые номера'] = ''
|
||||
self.df['Найден в файле'] = ''
|
||||
self.df['Основной номер объекта'] = ''
|
||||
self.df['Дополнительные номера'] = ''
|
||||
self.df['Кадастровый блок'] = ''
|
||||
|
||||
# Заполняем информацию для каждой строки
|
||||
matched_count = 0
|
||||
for index, row in self.df.iterrows():
|
||||
cadastral_number = str(row[self.cadastral_column]).strip()
|
||||
|
||||
if cadastral_number in cadastral_to_file_map:
|
||||
# 1. Получаем файл(ы) где найден номер
|
||||
main_file_path = cadastral_to_file_map[cadastral_number]
|
||||
file_name = Path(main_file_path).name
|
||||
self.df.at[index, 'Найденные файлы'] = file_name
|
||||
if cadastral_number in number_to_record:
|
||||
record = number_to_record[cadastral_number]
|
||||
matched_count += 1
|
||||
|
||||
# 2. Находим все смежные кадастровые номера из того же файла
|
||||
if main_file_path in file_to_numbers_map:
|
||||
all_numbers_in_file = file_to_numbers_map[main_file_path]
|
||||
# Исключаем текущий номер из списка смежных
|
||||
adjacent_numbers = all_numbers_in_file - {cadastral_number}
|
||||
if adjacent_numbers:
|
||||
self.df.at[index, 'Смежные кадастровые номера'] = ', '.join(sorted(adjacent_numbers))
|
||||
# 1. Имя файла
|
||||
file_name = Path(record.file_path).name
|
||||
self.df.at[index, 'Найден в файле'] = file_name
|
||||
|
||||
# 3. Определяем кадастровый блок (первые три части номера)
|
||||
cadastral_block = self._extract_cadastral_block(cadastral_number)
|
||||
# 2. Основной номер объекта
|
||||
self.df.at[index, 'Основной номер объекта'] = record.main_number
|
||||
|
||||
# 3. Дополнительные номера в объекте (без основного)
|
||||
other_numbers_sorted = sorted(record.other_numbers)
|
||||
if other_numbers_sorted:
|
||||
self.df.at[index, 'Дополнительные номера'] = ', '.join(other_numbers_sorted)
|
||||
else:
|
||||
self.df.at[index, 'Дополнительные номера'] = ''
|
||||
|
||||
# 4. Кадастровый блок (от основного номера)
|
||||
cadastral_block = self._extract_cadastral_block(record.main_number)
|
||||
if cadastral_block:
|
||||
self.df.at[index, 'Кадастровый блок'] = cadastral_block
|
||||
|
||||
@@ -138,6 +149,7 @@ class ExcelProcessor:
|
||||
self.df.to_excel(output_path, index=False, engine='openpyxl')
|
||||
|
||||
print(f"Обновленный Excel файл сохранен: {output_path}")
|
||||
print(f"Найдено совпадений: {matched_count} из {len(self.cadastral_numbers)}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -37,39 +37,52 @@ class FileSeeker:
|
||||
return xml_files
|
||||
|
||||
def process_files(self) -> List[CadastralRecord]:
|
||||
"""Обрабатывает все найденные XML файлы и извлекает кадастровые номера"""
|
||||
"""Обрабатывает все найденные XML файлы и создает записи с основными и всеми номерами"""
|
||||
all_records = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
cadastral_numbers = xml_file.extract_cadastral_numbers()
|
||||
main_number, all_numbers = xml_file.extract_cadastral_data()
|
||||
|
||||
for number in cadastral_numbers:
|
||||
record = CadastralRecord(number, str(xml_file.file_path))
|
||||
if main_number and all_numbers:
|
||||
record = CadastralRecord(main_number, str(xml_file.file_path), all_numbers)
|
||||
all_records.append(record)
|
||||
|
||||
self.cadastral_records = all_records
|
||||
print(f"Всего найдено кадастровых номеров: {len(all_records)}")
|
||||
print(f"Создано записей объектов: {len(all_records)}")
|
||||
|
||||
# Выводим статистику
|
||||
total_numbers = sum(len(record.all_numbers) for record in all_records)
|
||||
print(f"Всего номеров во всех объектах: {total_numbers}")
|
||||
|
||||
return all_records
|
||||
|
||||
def get_unique_cadastral_numbers(self) -> Set[str]:
|
||||
"""Возвращает уникальные кадастровые номера"""
|
||||
return {record.number for record in self.cadastral_records}
|
||||
"""Возвращает уникальные основные кадастровые номера"""
|
||||
return {record.main_number for record in self.cadastral_records}
|
||||
|
||||
def get_all_cadastral_numbers(self) -> Set[str]:
|
||||
"""Возвращает все уникальные кадастровые номера из всех объектов"""
|
||||
all_numbers = set()
|
||||
for record in self.cadastral_records:
|
||||
all_numbers.update(record.all_numbers)
|
||||
return all_numbers
|
||||
|
||||
def move_files_to_temp(self, cadastral_numbers_to_move: Set[str]) -> Dict[str, str]:
|
||||
"""Перемещает файлы с указанными кадастровыми номерами во временную папку"""
|
||||
moved_files = {}
|
||||
|
||||
for record in self.cadastral_records:
|
||||
if record.number in cadastral_numbers_to_move:
|
||||
# Проверяем, есть ли какой-либо из номеров объекта в списке для перемещения
|
||||
if any(num in cadastral_numbers_to_move for num in record.all_numbers):
|
||||
source_path = Path(record.file_path)
|
||||
if source_path.exists():
|
||||
# Создаем уникальное имя для файла во временной папке
|
||||
dest_name = f"{record.number.replace(':', '_')}_{source_path.name}"
|
||||
dest_name = f"{record.main_number.replace(':', '_')}_{source_path.name}"
|
||||
dest_path = self.temp_directory / dest_name
|
||||
|
||||
try:
|
||||
shutil.copy2(source_path, dest_path)
|
||||
moved_files[record.number] = str(dest_path)
|
||||
moved_files[record.main_number] = str(dest_path)
|
||||
print(f"Файл скопирован: {source_path.name} -> {dest_path}")
|
||||
except Exception as e:
|
||||
print(f"Ошибка при копировании файла {source_path}: {e}")
|
||||
@@ -78,14 +91,20 @@ class FileSeeker:
|
||||
|
||||
def get_statistics(self) -> Dict[str, int]:
|
||||
"""Возвращает статистику обработки"""
|
||||
total_all_numbers = sum(len(record.all_numbers) for record in self.cadastral_records)
|
||||
|
||||
return {
|
||||
'total_xml_files': len(self.xml_files),
|
||||
'files_with_numbers': len([f for f in self.xml_files if len(f.cadastral_numbers) > 0]),
|
||||
'total_cadastral_numbers': len(self.cadastral_records),
|
||||
'unique_cadastral_numbers': len(self.get_unique_cadastral_numbers())
|
||||
'files_with_numbers': len([f for f in self.xml_files if f.main_cadastral_number]),
|
||||
'total_cadastral_objects': len(self.cadastral_records),
|
||||
'unique_main_numbers': len(self.get_unique_cadastral_numbers()),
|
||||
'total_all_numbers': total_all_numbers,
|
||||
'unique_all_numbers': len(self.get_all_cadastral_numbers())
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
stats = self.get_statistics()
|
||||
return (f"FileSeeker: {stats['total_xml_files']} XML файлов, "
|
||||
f"{stats['unique_cadastral_numbers']} уникальных номеров")
|
||||
f"{stats['total_cadastral_objects']} объектов, "
|
||||
f"{stats['unique_main_numbers']} уникальных основных номеров, "
|
||||
f"{stats['unique_all_numbers']} всего уникальных номеров")
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
"""
|
||||
Тестовый скрипт для проверки работы новой модульной структуры
|
||||
"""
|
||||
|
||||
def test_modules():
|
||||
"""Тестирует импорт и базовую функциональность модулей"""
|
||||
|
||||
print("=== ТЕСТ МОДУЛЬНОЙ СТРУКТУРЫ ===\n")
|
||||
|
||||
try:
|
||||
# Тест импорта моделей
|
||||
print("1. Тестирование моделей...")
|
||||
from models.cadastral_record import CadastralRecord
|
||||
from models.xml_file_info import XMLFileInfo
|
||||
|
||||
# Создаем тестовую запись
|
||||
record = CadastralRecord("74:34:0400003:1234", "test.xml")
|
||||
print(f" ✅ CadastralRecord создан: {record}")
|
||||
|
||||
# Тест сервисов
|
||||
print("\n2. Тестирование сервисов...")
|
||||
from services.file_seeker import FileSeeker
|
||||
from services.excel_processor import ExcelProcessor
|
||||
|
||||
# Создаем FileSeeker с тестовой директорией
|
||||
import tempfile
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
seeker = FileSeeker(temp_dir)
|
||||
print(f" ✅ FileSeeker создан: {seeker}")
|
||||
|
||||
# Тест статистики
|
||||
stats = seeker.get_statistics()
|
||||
print(f" ✅ Статистика получена: {stats}")
|
||||
|
||||
print("\n3. Тестирование Excel процессора...")
|
||||
# Создаем фиктивный Excel процессор
|
||||
excel_path = "test.xlsx" # Не существует, но это OK для теста импорта
|
||||
try:
|
||||
excel_processor = ExcelProcessor(excel_path)
|
||||
print(f" ✅ ExcelProcessor создан для: {excel_path}")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ ExcelProcessor создан (ожидаемая ошибка файла): {type(e).__name__}")
|
||||
|
||||
print("\n🎉 ВСЕ МОДУЛИ УСПЕШНО ИМПОРТИРОВАНЫ И ПРОТЕСТИРОВАНЫ!")
|
||||
|
||||
return True
|
||||
|
||||
except ImportError as e:
|
||||
print(f"❌ Ошибка импорта: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ Неожиданная ошибка: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_modules()
|
||||
250
tests.py
Normal file
250
tests.py
Normal file
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Объединенный класс тестов для проекта KadastrParser
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
from typing import Optional
|
||||
|
||||
# Импорт наших модулей
|
||||
from models.cadastral_record import CadastralRecord
|
||||
from models.xml_file_info import XMLFileInfo
|
||||
from services.file_seeker import FileSeeker
|
||||
from services.excel_processor import ExcelProcessor
|
||||
|
||||
|
||||
class KadastrParserTests(unittest.TestCase):
|
||||
"""Объединенный класс тестов для всех компонентов системы"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Настройка тестовой среды"""
|
||||
cls.test_xml_dir = r"c:\Users\USER\Downloads\14. выборка выписок из ЕГРН по списку КН\пример исх.папки 2 вар"
|
||||
cls.test_excel_file = r"c:\Users\USER\Downloads\14. выборка выписок из ЕГРН по списку КН\список КН.xlsx"
|
||||
cls.test_xml_file = r"c:\Users\USER\Downloads\14. выборка выписок из ЕГРН по списку КН\пример исх.папки 2 вар\report-1a3f8d46-0b14-4ae3-ac2e-f10372020fed-PKPVD-2025-05-12-224487-74-01[0].xml"
|
||||
|
||||
# Создаем временную директорию для результатов
|
||||
cls.temp_dir = Path("test_results")
|
||||
cls.temp_dir.mkdir(exist_ok=True)
|
||||
|
||||
def test_01_module_imports(self):
|
||||
"""Тестирует импорт и создание всех модулей"""
|
||||
print("\n=== ТЕСТ 1: ИМПОРТ МОДУЛЕЙ ===")
|
||||
|
||||
# Тест создания CadastralRecord
|
||||
record = CadastralRecord("74:34:0400003:1234", "test.xml", {"74:34:0400003:1234", "74:34:0400003:5678"})
|
||||
self.assertEqual(record.main_number, "74:34:0400003:1234")
|
||||
self.assertEqual(record.file_path, "test.xml")
|
||||
self.assertEqual(len(record.all_numbers), 2)
|
||||
self.assertEqual(len(record.other_numbers), 1)
|
||||
print(f" ✅ CadastralRecord: {record}")
|
||||
|
||||
# Тест создания FileSeeker
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
seeker = FileSeeker(temp_dir)
|
||||
stats = seeker.get_statistics()
|
||||
self.assertIsInstance(stats, dict)
|
||||
print(f" ✅ FileSeeker создан: {seeker}")
|
||||
|
||||
# Тест создания ExcelProcessor
|
||||
excel_processor = ExcelProcessor("dummy.xlsx")
|
||||
self.assertIsInstance(excel_processor, ExcelProcessor)
|
||||
print(f" ✅ ExcelProcessor создан")
|
||||
|
||||
def test_02_single_xml_file_extraction(self):
|
||||
"""Тестирует извлечение из одного XML файла"""
|
||||
print("\n=== ТЕСТ 2: ИЗВЛЕЧЕНИЕ ИЗ XML ФАЙЛА ===")
|
||||
|
||||
if not os.path.exists(self.test_xml_file):
|
||||
self.skipTest(f"Тестовый файл не найден: {self.test_xml_file}")
|
||||
|
||||
xml_info = XMLFileInfo(self.test_xml_file)
|
||||
main_number, all_numbers = xml_info.extract_cadastral_data()
|
||||
|
||||
self.assertIsNotNone(main_number)
|
||||
self.assertGreater(len(all_numbers), 0)
|
||||
self.assertIn(main_number, all_numbers)
|
||||
|
||||
print(f" 📋 Основной номер: {main_number}")
|
||||
print(f" 📋 Всего номеров: {len(all_numbers)}")
|
||||
print(f" 📋 Номера: {sorted(all_numbers)}")
|
||||
|
||||
def test_03_directory_processing(self):
|
||||
"""Тестирует обработку директории с XML файлами"""
|
||||
print("\n=== ТЕСТ 3: ОБРАБОТКА ДИРЕКТОРИИ ===")
|
||||
|
||||
if not os.path.exists(self.test_xml_dir):
|
||||
self.skipTest(f"Тестовая директория не найдена: {self.test_xml_dir}")
|
||||
|
||||
seeker = FileSeeker(self.test_xml_dir, str(self.temp_dir))
|
||||
|
||||
# Поиск файлов
|
||||
xml_files = seeker.find_xml_files()
|
||||
self.assertGreater(len(xml_files), 0)
|
||||
print(f" 📁 Найдено XML файлов: {len(xml_files)}")
|
||||
|
||||
# Обработка файлов (новая реализация с объектами)
|
||||
records = seeker.process_files()
|
||||
self.assertGreater(len(records), 0)
|
||||
print(f" 📋 Создано объектов: {len(records)}")
|
||||
|
||||
# Проверяем статистику
|
||||
stats = seeker.get_statistics()
|
||||
self.assertEqual(stats['total_xml_files'], len(xml_files))
|
||||
self.assertEqual(stats['total_cadastral_objects'], len(records))
|
||||
|
||||
print(f" 📊 Статистика:")
|
||||
for key, value in stats.items():
|
||||
print(f" • {key}: {value}")
|
||||
|
||||
# Проверяем первые несколько записей
|
||||
print(f" 📋 Первые 3 объекта:")
|
||||
for i, record in enumerate(records[:3]):
|
||||
print(f" {i+1}. Основной: {record.main_number}")
|
||||
print(f" Все номера: {sorted(record.all_numbers)}")
|
||||
print(f" Других номеров: {len(record.other_numbers)}")
|
||||
|
||||
def test_04_excel_processing(self):
|
||||
"""Тестирует обработку Excel файла"""
|
||||
print("\n=== ТЕСТ 4: ОБРАБОТКА EXCEL ===")
|
||||
|
||||
if not os.path.exists(self.test_excel_file):
|
||||
self.skipTest(f"Excel файл не найден: {self.test_excel_file}")
|
||||
|
||||
excel_processor = ExcelProcessor(self.test_excel_file)
|
||||
success = excel_processor.load_excel_file()
|
||||
self.assertTrue(success)
|
||||
|
||||
excel_numbers = excel_processor.get_cadastral_numbers()
|
||||
self.assertGreater(len(excel_numbers), 0)
|
||||
print(f" 📊 Номеров в Excel: {len(excel_numbers)}")
|
||||
|
||||
# Показываем первые 5 номеров
|
||||
print(f" 📋 Первые 5 номеров из Excel:")
|
||||
for i, number in enumerate(sorted(excel_numbers)[:5]):
|
||||
print(f" {i+1}. {number}")
|
||||
|
||||
def test_05_full_integration(self):
|
||||
"""Полный интеграционный тест XML + Excel + обновление"""
|
||||
print("\n=== ТЕСТ 5: ПОЛНАЯ ИНТЕГРАЦИЯ ===")
|
||||
|
||||
if not os.path.exists(self.test_xml_dir) or not os.path.exists(self.test_excel_file):
|
||||
self.skipTest("Тестовые файлы не найдены для интеграционного теста")
|
||||
|
||||
# 1. Обработка XML файлов
|
||||
seeker = FileSeeker(self.test_xml_dir, str(self.temp_dir))
|
||||
xml_files = seeker.find_xml_files()
|
||||
records = seeker.process_files()
|
||||
|
||||
# 2. Обработка Excel файла
|
||||
excel_processor = ExcelProcessor(self.test_excel_file)
|
||||
excel_processor.load_excel_file()
|
||||
excel_numbers = excel_processor.get_cadastral_numbers()
|
||||
|
||||
# 3. Поиск совпадений (по всем номерам в объектах)
|
||||
all_xml_numbers = seeker.get_all_cadastral_numbers()
|
||||
matching_numbers = excel_numbers.intersection(all_xml_numbers)
|
||||
|
||||
print(f" 🏷️ XML номеров (всего): {len(all_xml_numbers)}")
|
||||
print(f" 📊 Excel номеров: {len(excel_numbers)}")
|
||||
print(f" 🎯 Совпадений: {len(matching_numbers)}")
|
||||
|
||||
self.assertGreater(len(matching_numbers), 0, "Должны быть найдены совпадения")
|
||||
|
||||
# 4. Тестируем новый метод обновления Excel с объектами
|
||||
output_excel = str(self.temp_dir / "integration_test_updated.xlsx")
|
||||
success = excel_processor.update_excel_with_cadastral_records(records, output_excel)
|
||||
self.assertTrue(success, "Обновление Excel должно быть успешным")
|
||||
|
||||
print(f" ✅ Excel обновлен: {Path(output_excel).name}")
|
||||
|
||||
# Проверяем созданный файл
|
||||
self.assertTrue(os.path.exists(output_excel))
|
||||
|
||||
# Показываем первые совпадения
|
||||
if matching_numbers:
|
||||
print(f" 🎯 Первые 5 совпадений:")
|
||||
for i, number in enumerate(sorted(matching_numbers)[:5]):
|
||||
print(f" {i+1}. {number}")
|
||||
|
||||
def test_06_new_implementation_features(self):
|
||||
"""Тестирует особенности новой реализации с объектами"""
|
||||
print("\n=== ТЕСТ 6: НОВАЯ РЕАЛИЗАЦИЯ ===")
|
||||
|
||||
if not os.path.exists(self.test_xml_dir):
|
||||
self.skipTest("Тестовая директория не найдена")
|
||||
|
||||
seeker = FileSeeker(self.test_xml_dir)
|
||||
records = seeker.process_files()
|
||||
|
||||
# Проверяем, что каждая запись - это объект с множественными номерами
|
||||
objects_with_multiple_numbers = [r for r in records if len(r.all_numbers) > 1]
|
||||
|
||||
print(f" 📋 Всего объектов: {len(records)}")
|
||||
print(f" 🔗 Объектов с несколькими номерами: {len(objects_with_multiple_numbers)}")
|
||||
|
||||
# Показываем примеры объектов с несколькими номерами
|
||||
if objects_with_multiple_numbers:
|
||||
print(f" 📋 Примеры объектов с несколькими номерами:")
|
||||
for i, record in enumerate(objects_with_multiple_numbers[:3]):
|
||||
print(f" {i+1}. Основной: {record.main_number}")
|
||||
print(f" Всего номеров: {len(record.all_numbers)}")
|
||||
print(f" Другие номера: {sorted(record.other_numbers)}")
|
||||
|
||||
# Проверяем уникальность основных номеров
|
||||
main_numbers = [r.main_number for r in records]
|
||||
unique_main_numbers = set(main_numbers)
|
||||
|
||||
print(f" 🏷️ Уникальных основных номеров: {len(unique_main_numbers)}")
|
||||
print(f" 📊 Всего номеров во всех объектах: {sum(len(r.all_numbers) for r in records)}")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""Очистка после тестов"""
|
||||
print("\n=== ОЧИСТКА ===")
|
||||
# Можно добавить очистку временных файлов если нужно
|
||||
pass
|
||||
|
||||
|
||||
def run_all_tests():
|
||||
"""Запускает все тесты с подробным выводом"""
|
||||
print("🚀 ЗАПУСК ОБЪЕДИНЕННЫХ ТЕСТОВ KADASTR PARSER")
|
||||
print("=" * 60)
|
||||
|
||||
# Создаем тестовый набор
|
||||
loader = unittest.TestLoader()
|
||||
suite = loader.loadTestsFromTestCase(KadastrParserTests)
|
||||
|
||||
# Запускаем тесты с подробным выводом
|
||||
runner = unittest.TextTestRunner(verbosity=2, stream=None)
|
||||
result = runner.run(suite)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("🎯 ИТОГОВЫЕ РЕЗУЛЬТАТЫ:")
|
||||
print(f" ✅ Успешных тестов: {result.testsRun - len(result.failures) - len(result.errors)}")
|
||||
print(f" ❌ Неудачных тестов: {len(result.failures)}")
|
||||
print(f" 💥 Ошибок: {len(result.errors)}")
|
||||
print(f" ⏭️ Пропущенных: {len(result.skipped)}")
|
||||
|
||||
if result.failures:
|
||||
print("\n❌ НЕУДАЧИ:")
|
||||
for test, error in result.failures:
|
||||
print(f" • {test}: {error}")
|
||||
|
||||
if result.errors:
|
||||
print("\n💥 ОШИБКИ:")
|
||||
for test, error in result.errors:
|
||||
print(f" • {test}: {error}")
|
||||
|
||||
success = len(result.failures) == 0 and len(result.errors) == 0
|
||||
print(f"\n🏆 ОБЩИЙ РЕЗУЛЬТАТ: {'✅ ВСЕ ТЕСТЫ ПРОШЛИ' if success else '❌ ЕСТЬ ПРОБЛЕМЫ'}")
|
||||
|
||||
return success
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_all_tests()
|
||||
Reference in New Issue
Block a user