Загрузка данных


повторяем работу,только теперь в окне Агрегирование файлов.

суть в следующем.
1. В поле создать файл пользователь должен увидеть название вновь созданного пустого файла эксель после нажатия на кнопку Создать. Для этого он должен напечатать имя файла. Если он не напечатал,то должно быть предупреждение,что имя не задано.После его создания полный путь отображается в поле.

2. Указать путь. Здесь имеется ввиду, что файл создан,но с ним не работали еще. Поэтому при нажатии на кнопку Обзор пользователь выбирает/указывает на вновь созданный файл или старый, который будет полностьюперезаписан. путь к этому файлу отображается в поле. Изначально предлагать искать файл по адресу,куда он у нас был записан в предыдущем окне.

3. Выбрать файлы. При нажатии на кнопку Обзор открывыается наша папка с созданными файлами экспертов,либо потом вручнуюпользователь выберет другую. Пользователь выделяет нужные файлы ,нажимает ок - окно закрывается и предупреждается пользователь что файлы выбраны, а сами файлы отображаются в поле.

4. Кнопка Объединить информацию. При нажатии на кнопку проверяется выбран ли файл для сохранения/объединения и выбраны ли файлы для этого объединения,если все правильно,то происходит сведение всех данных по группам и в пустой созданный файл записываются уже сведенные матрицы. Структура у них идентична. Свод происходит по принципу моды. Если моды нет,то выберается нейтральное число - 1.

Ниже данные для этого окна.




# =================================================
# dukora/modules/experts/aggregate_tab_inter.py
# =================================================
import customtkinter as ctk
from pathlib import Path
from modules.experts.aggregate_tab_work import AggregateWorkController


class AggregateExpertTab(ctk.CTkFrame):
    def __init__(self, master):
        super().__init__(master)
        self.controller = AggregateWorkController()
        
        # Основной контейнер с отступами
        main_container = ctk.CTkFrame(self, fg_color="transparent")
        main_container.pack(fill="both", expand=True, padx=10, pady=10)
        
        # --- СЕТКА ИНТЕРФЕЙСА ---
        # Колонки: 0-Заголовок (фикс), 1-Поле ввода (растягивается), 2-Кнопка Обзор (фикс)
        main_container.grid_columnconfigure(0, weight=0, minsize=140)  
        main_container.grid_columnconfigure(1, weight=1)               
        main_container.grid_columnconfigure(2, weight=0, minsize=100)   
        
        # Строка 0: Блок "СОЗДАНИЕ ФАЙЛА"
        lbl_create = ctk.CTkLabel(main_container, text="АГРЕГИРОВАНИЕ ФАЙЛОВ:", font=ctk.CTkFont(size=16, weight="bold"))
        lbl_create.grid(row=0, column=0, columnspan=3, sticky="w", pady=(0, 10))
        
        # Строка 1: Создать файл + Путь
        frame_file = ctk.CTkFrame(main_container, fg_color="transparent")
        frame_file.grid(row=1, column=0, columnspan=3, sticky="ew", pady=5)
        
        # Внутри строки 1 - своя сетка для идеального выравнивания полей
        frame_file.grid_columnconfigure(0, weight=0, minsize=140)
        frame_file.grid_columnconfigure(1, weight=1)
        frame_file.grid_columnconfigure(2, weight=0, minsize=100)
        
        t_lbl_1 = ctk.CTkLabel(frame_file, text="Создать файл:", anchor="w")
        t_lbl_1.grid(row=0, column=0, sticky="w", padx=(0, 5), pady=5)
        
        self.new_file_name_entry = ctk.CTkEntry(frame_file, width=250)
        self.new_file_name_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        
        btn_create = ctk.CTkButton(frame_file, text="Создать", command=self.handle_create_file_request, width=100)
        btn_create.grid(row=0, column=2, sticky="e", padx=5, pady=5)
        
        t_lbl_2 = ctk.CTkLabel(frame_file, text="Указать путь:", anchor="w")
        t_lbl_2.grid(row=1, column=0, sticky="w", padx=(0, 5), pady=5)
        
        self.target_file_entry = ctk.CTkEntry(frame_file, width=250)
        self.target_file_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
        
        browse_target_button = ctk.CTkButton(frame_file, text="Обзор...", command=self.select_target_file, width=100)
        browse_target_button.grid(row=1, column=2, sticky="e", padx=5, pady=5)
        
        # Строка 2: Выбрать файлы
        frame_select = ctk.CTkFrame(main_container, fg_color="transparent")
        frame_select.grid(row=2, column=0, columnspan=3, sticky="ew", pady=5)
        
        # Внутри строки 2 - своя сетка
        frame_select.grid_columnconfigure(0, weight=0, minsize=140)
        frame_select.grid_columnconfigure(1, weight=1)
        frame_select.grid_columnconfigure(2, weight=0, minsize=100)
        
        t_lbl_3 = ctk.CTkLabel(frame_select, text="Выбрать файлы:", anchor="w")
        t_lbl_3.grid(row=0, column=0, sticky="w", padx=(0, 5), pady=5)
        
        self.file_path_entry = ctk.CTkEntry(frame_select, width=250)
        self.file_path_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        
        browse_source_button = ctk.CTkButton(frame_select, text="Обзор...", command=self.select_files_for_aggregation, width=100)
        browse_source_button.grid(row=0, column=2, sticky="e", padx=5, pady=5)
        
        # Строка 3: Большая кнопка Объединить информацию (во всю ширину)
        aggregate_button = ctk.CTkButton(
            main_container,
            text="Объединить информацию",
            command=self.aggregate_data,
            fg_color="#28a745", hover_color="#218838",
            font=ctk.CTkFont(size=13, weight="bold"),
            height=45
        )
        aggregate_button.grid(row=3, column=0, columnspan=3, sticky="ew", pady=(15, 15))
        
        # Настройка растягивания колонок внутри вложенных фреймов
        for frm in [frame_file, frame_select]:
            frm.grid_rowconfigure(0, weight=1)
            frm.grid_rowconfigure(1, weight=1)
            
        main_container.grid_rowconfigure(1, weight=0)
        main_container.grid_rowconfigure(2, weight=0)
        main_container.grid_rowconfigure(3, weight=0)

    # === МЕТОДЫ УПРАВЛЕНИЯ (вызывают controller из excel_aggregate_work.py) ===

    def handle_create_file_request(self):
        filename = self.new_file_name_entry.get().strip()
        result_path = self.controller.create_empty_excel_file(filename)
        if result_path:
             ctk.CTkMessagebox.showinfo("Готово", f"Файл создан по пути:\n{result_path}")
             
    def select_target_file(self):
        initial_dir = str(Path(__file__).resolve().parents[3] / "experts")
        from tkinter import filedialog
        file_path = filedialog.askopenfilename(title="Укажите целевой Excel-файл", initialdir=initial_dir, filetypes=[("Excel Files", "*.xlsx")])
        if file_path:
            self.target_file_entry.delete(0, "end")
            self.target_file_entry.insert(0, file_path)
            
    def select_files_for_aggregation(self):
        print("[GUI SELECT] Нажат Обзор...")
        initial_dir = str(Path(__file__).resolve().parents[3] / "experts")
        from tkinter import filedialog
        try:
            file_paths = filedialog.askopenfilenames(title="Выберите файлы экспертов", initialdir=initial_dir, filetypes=[("Excel Files", "*.xlsx")])
            if not file_paths: return
            self.controller.input_files = list(file_paths)
            display_text = f"... ({len(file_paths)} files)"
            self.file_path_entry.delete(0, "end")
            self.file_path_entry.insert(0, display_text)
        except Exception as e:
            print(f"[GUI FATAL ERROR]: {e}")
            ctk.CTkMessagebox.showerror("Критическая ошибка", f"Не удалось открыть проводник.\n{e}")
            
    def aggregate_data(self):
        output_file_path = self.target_file_entry.get().strip()
        if not output_file_path:
            ctk.CTkMessagebox.showwarning("Предупреждение", "Сначала укажите целевой файл!")
            return
        input_files = getattr(self.controller, 'input_files', [])
        if not input_files or len(input_files) == 0:
            ctk.CTkMessagebox.showwarning("Внимание", "Сначала выберите исходные файлы кнопкой 'Обзор...'.")
            return
        workbook = self.controller.aggregate_data(output_file_path, input_files)
        if workbook is None:
            ctk.CTkMessagebox.showerror("Ошибка данных", "Не удалось собрать данные.\nСмотрите консоль.")
            return
        ctk.CTkMessagebox.showinfo("Готово", f"Сводный отчет успешно сохранен в:\n{output_file_path}")
# =====================================================        









# ================================================
# dukora/modules/experts/aggregate_tab_work.py
# ================================================
from openpyxl import load_workbook, Workbook
import os
import re
from statistics import mean, mode, StatisticsError
from collections import defaultdict
from copy import copy
from pathlib import Path

class AggregateWorkController:
    def __init__(self):
        self.input_files = []
        self._loaded_wbs = {}
        self.current_file_path = ""
        self.current_sheet_name = ""
        
    def get_sheet_names(self, file_path):
        try:
            wb = load_workbook(file_path, read_only=True, data_only=True)
            sheet_names = wb.sheetnames
            wb.close()
            return sheet_names
        except Exception as e:
            print(f"[CTRL ERROR] Не удалось прочитать листы из {file_path}: {e}")
            return []
            
    def create_empty_excel_file(self, filename):
        clean_filename = filename.strip()
        if not clean_filename.endswith('.xlsx'):
            clean_filename += '.xlsx'
        if not clean_filename:
             from tkinter import messagebox
             messagebox.showwarning("Предупреждение", "Имя файла не может быть пустым.")
             return None
             
        invalid_chars_pattern = r'[\\/:*?"<>|]'
        import re
        if re.search(invalid_chars_pattern, clean_filename):
            from tkinter import messagebox
            messagebox.showerror('Ошибка', 'Имя файла содержит недопустимые символы \\ / : * ? " < > |')
            return None
            
        project_root = Path(__file__).resolve().parents[3]
        experts_folder = project_root / "experts"
        try:
            os.makedirs(experts_folder, exist_ok=True)
        except OSError as e:
             from tkinter import messagebox
             messagebox.showerror("Ошибка доступа", f"Не удалось создать папку 'experts':\n{e}")
             return None
        
        full_path = experts_folder / clean_filename
        wb = Workbook()
        if "Sheet" in wb.sheetnames:
            del wb["Sheet"]
        wb.create_sheet(title="Данные")
        wb.save(full_path)
        wb.close()
        from tkinter import messagebox
        messagebox.showinfo("Готово", f"Файл '{clean_filename}' успешно создан.")
        return str(full_path)
        
    def aggregate_data(self, output_file_path, input_files):
        pass # Ваша логика объединения здесь (из доноров)
        
    def check_transitivity(self, file_path, sheet_name):
        n = 0
        try:
            wb = load_workbook(file_path, data_only=True)
            if sheet_name not in wb.sheetnames:
                return "Ошибка"
            ws = wb[sheet_name]
            while True:
                cell_val = ws.cell(row=2, column=4 + n).value
                if cell_val is not None and str(cell_val).strip() != "":
                    n += 1
                else:
                    break
            if n < 3:
                return "100%"
            values = [[None] * n for _ in range(n)]
            for i in range(n):
                for j in range(n):
                    cell = ws.cell(row=2 + i, column=4 + j).value
                    v = None
                    if cell is not None and str(cell).strip() != "":
                        s = str(cell).replace(',', '.').strip().upper()
                        try:
                            num = float(s)
                            v = int(num) if abs(num - int(num)) < 1e-9 else round(num, 3)
                        except ValueError:
                            v = s
                    values[i][j] = v
            total_triads = 0
            consistent_triads = 0
            for i in range(n):
                for j in range(n):
                    if i == j: continue
                    for k in range(n):
                        if k == i or k == j: continue
                        a, b, c = values[i][j], values[j][k], values[i][k]
                        if all(isinstance(x, (int, float)) for x in [a, b, c]):
                            total_triads += 1
                            is_consistent = False
                            if a > b and b > c: is_consistent = (a > c)
                            elif a == b and b > c: is_consistent = (a > c)
                            elif a > b and b == c: is_consistent = (a > c)
                            elif a == b and b == c: is_consistent = (a == c)
                            if is_consistent: consistent_triads += 1
            wb.close()
            if total_triads == 0: return "100%"
            return f"{int((consistent_triads / total_triads) * 100)}%"
        except Exception as e:
            print(f"[GUI TRANS CHECK CRITICAL FAIL]: {e}")
            return "Ошибка"
            
    def apply_transitivity_corrections(self, file_path, sheet_name):
        EPSILON = 0.001
        try:
            wb = load_workbook(file_path)
            if sheet_name not in wb.sheetnames: return False
            ws = wb[sheet_name]
            n = 0
            while True:
                cell_val = ws.cell(row=2, column=4 + n).value
                if cell_val is not None and str(cell_val).strip() != "":
                    n += 1
                else: break
            if n < 3: return True
            MATRIX_START_COL = 4
            DATA_START_ROW = 2
            matrix = [[None] * n for _ in range(n)]
            for i in range(n):
                for j in range(n):
                    cell = ws.cell(row=DATA_START_ROW + i, column=MATRIX_START_COL + j).value
                    val = None
                    if cell is not None and str(cell).strip() != "":
                        s = str(cell).replace(',', '.').strip().upper()
                        try:
                            num = float(s)
                            val = int(num) if abs(num - int(num)) < 1e-9 else round(num, 3)
                        except ValueError:
                            val = s
                    matrix[i][j] = val
            fixed_count = 0
            for i in range(n):
                for j in range(n):
                    if i == j: continue
                    for k in range(n):
                        if k == i or k == j: continue
                        a, b, c = matrix[i][j], matrix[j][k], matrix[i][k]
                        if all(isinstance(x, (int, float)) for x in [a, b, c]):
                            violation_found = False
                            new_c_value = c
                            if a > b and b > c and a <= c: 
                                new_c_value = round(a + EPSILON, 3); violation_found = True
                            elif a == b and b > c and a < c: 
                                new_c_value = round(a + EPSILON, 3); violation_found = True
                            elif a > b and b > c and a == c: 
                                new_c_value = round(a + EPSILON, 3); violation_found = True
                            elif a == b and b == c and a < c: 
                                new_c_value = round(a + EPSILON, 3); violation_found = True
                            if violation_found:
                                ws.cell(row=DATA_START_ROW + i, column=MATRIX_START_COL + k).value = new_c_value
                                matrix[i][k] = new_c_value
                                fixed_count += 1
            if fixed_count > 0: wb.save(file_path)
            wb.close()
            return True
        except Exception as e:
            print(f"[FIX LOAD/SAVE CRITICAL FAIL]: {e}")
            return False
            
    def normalize_group_data(self):
        if not self.current_file_path or not self.current_sheet_name:
            return False, "Файл не выбран"
        try:
            wb = load_workbook(self.current_file_path)
            if self.current_sheet_name not in wb.sheetnames:
                return False, "Лист не найден"
            ws = wb[self.current_sheet_name]
            n = 0
            while True:
                cell_val = ws.cell(row=2, column=4 + n).value
                if cell_val is not None and str(cell_val).strip() != "":
                    n += 1
                else: break
            if n < 2:
                wb.close()
                return False, "Недостаточно данных (менее 2x2)"
            MATRIX_START_COL = 4
            HEADER_ROW = 1      
            DATA_START_ROW = 2  
            target_col = 4 + n 
            occupied = True
            while occupied and target_col < 4 + n + 10:
                header_cell = ws.cell(row=HEADER_ROW, column=target_col).value
                if header_cell is None or (isinstance(header_cell, str) and "нормализ" in header_cell.lower()):
                    occupied = False
                else: target_col += 1
            if occupied:
                wb.close()
                return False, "Нет места для записи нормализованных данных."
            ws.cell(row=HEADER_ROW, column=target_col, value="Нормализованный вес")
            row_sums = []
            for i in range(n):
                row_sum = 0.0
                for j in range(n):
                    cell = ws.cell(row=DATA_START_ROW + i, column=MATRIX_START_COL + j).value
                    val = 0.0
                    if cell is not None and str(cell).strip() != "":
                        s = str(cell).replace(',', '.').strip().upper()
                        try:
                            num = float(s)
                            val = int(num) if abs(num - int(num)) < 1e-9 else round(num, 3)
                        except ValueError:
                            val = 0.0
                    row_sum += val
                row_sums.append(row_sum)
            total_sum = sum(row_sums)
            if total_sum == 0:
                wb.close()
                return False, "Сумма всех оценок равна нулю."
            for i in range(n):
                weight = round((row_sums[i] / total_sum), 4)
                ws.cell(row=DATA_START_ROW + i, column=target_col, value=weight)
            wb.save(self.current_file_path)
            wb.close()
            col_letter = ws.cell(row=1, column=target_col).column_letter
            return True, f"Веса успешно рассчитаны (колонка {col_letter})"
        except Exception as e:
            print(f"[NORM ERROR]: {e}")
            return False, f"Ошибка при записи: {str(e)}"
# ==============================================================