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


Работаем с окном Проверка файлов

1. Выбрать файл - после нажатия на кнопку Обзор пользователь выберает файл для проверки - он отображается в поле

2. В Выбрать группу в выпадающем списке пользователь выберает группу.

3. При нажатии на кнопку Проверить на транзитивность идет проверка матрицы выбранной группы на транзитивность и в процентах отображается числом в поле. Проверка проводится по классическому варианту,без всякого перемножения по правилу: если А>B a B>C то  A>C.

4. Исправление транзитивности. Просчитывается ошибки и исправляются на наиболее подходящий вариант, если исправить не удается из-за очень большого расхождения,то пишется предупреждение пересмотреть оценки.

5. После проверки пользователь нажимает кнопку Нормализовать. Значения в матрице нормализуются и справа создается столбец  Нормализация, куда заносятся нормализованные значения показателей. Принцип - самый важный показатель - самое большое число.

Проверки. Есть ли уже столбец с нормализацией, чтобы не нарушилась матрица 


Файлы для этого окна.












# dukora/modules/experts/check_tab_inter.py
import customtkinter as ctk
from pathlib import Path
from modules.experts.check_tab_work import CheckWorkController


class CheckExpertTab(ctk.CTkFrame):
    def __init__(self, master):
        super().__init__(master)
        self.controller = CheckWorkController()
        
        # Основной контейнер
        main_container = ctk.CTkFrame(self, fg_color="transparent")
        main_container.pack(fill="both", expand=True, padx=10, pady=10)
        
        # --- СЕТКА ИНТЕРФЕЙСА ---
        main_container.grid_columnconfigure(0, weight=0, minsize=160)  
        main_container.grid_columnconfigure(1, weight=1)             
        main_container.grid_columnconfigure(2, weight=0, minsize=140) 
        
        FONT_LABEL = ctk.CTkFont(size=13)
        
        # Строка 0: Блок ВЫБРАТЬ ФАЙЛ
        lbl_file = ctk.CTkLabel(main_container, text="Выбрать файл:", anchor="w", font=FONT_LABEL)
        lbl_file.grid(row=0, column=0, sticky="w", ipady=2, pady=(0, 15)) # Уменьшена высота лейбла
        
        self.file_path_entry = ctk.CTkEntry(main_container, width=400)
        # ПОЛЕ №1: Высота уменьшена до стандартной (как у нижних кнопок)
        self.file_path_entry.grid(row=0, column=1, sticky="ew", padx=5, ipady=2, pady=(0, 15))
        
        browse_btn = ctk.CTkButton(main_container, text="Обзор...", command=self.select_check_file, width=120)
        browse_btn.grid(row=0, column=2, sticky="e", ipady=2, pady=(0, 15)) # Кнопка обзора стала плоской
        
        # Строка 1: Блок ВЫБРАТЬ ГРУППУ
        lbl_group = ctk.CTkLabel(main_container, text="Выбрать группу:", anchor="w", font=FONT_LABEL)
        lbl_group.grid(row=1, column=0, sticky="w", ipady=2, pady=(0, 15))
        
        self.group_cb = ctk.CTkComboBox(main_container, state="readonly", width=400)
        # ПОЛЕ №2: Выпадающий список выровнен по высоте с полем файла
        self.group_cb.grid(row=1, column=1, sticky="ew", padx=5, ipady=2, pady=(0, 15))
        
        # Строка 2: Блок ПРОВЕРИТЬ НА ТРАНЗИТИВНОСТЬ
        check_btn = ctk.CTkButton(main_container, text="Проверить на транзитивность", command=self.run_transitivity_check, width=200)
        # КНОПКА-ЗАГОЛОВОК: Сделана плоской (высота как у "Закрыть")
        check_btn.grid(row=2, column=0, sticky="w", padx=(0, 5), ipady=2, pady=(0, 15)) 
        
        self.trans_var = ctk.StringVar(value="")
        result_entry = ctk.CTkEntry(main_container, textvariable=self.trans_var, justify="center", width=100, height=35)
        # ПОЛЕ РЕЗУЛЬТАТА: Оставлено чуть выше визуально (стандартный Entry без изменения ipady часто выглядит крупнее)
        result_entry.grid(row=2, column=1, sticky="ew", padx=5, pady=(0, 15))
        
        percent_lbl = ctk.CTkLabel(main_container, text="%", anchor="w", font=FONT_LABEL)
        percent_lbl.grid(row=2, column=2, sticky="w", padx=5, ipady=2, pady=(0, 15))
        
        # Строка 3: Нижние кнопки ИСПРАВИТЬ и НОРМАЛИЗОВАТЬ (оставляем как есть или делаем еще компактнее)
        btn_fix_norm_frame = ctk.CTkFrame(main_container, fg_color="transparent")
        btn_fix_norm_frame.grid(row=3, column=0, columnspan=3, sticky="ew", pady=(20, 0))
        
        fix_btn = ctk.CTkButton(btn_fix_norm_frame, text="Исправить транзитивность", fg_color="#28a745", hover_color="#218838", command=self.fix_transitivity)
        # НИЖНИЕ КНОПКИ: Делаем их такими же плоскими, чтобы вся вкладка была монолитной
        fix_btn.pack(side="left", expand=True, fill="x", padx=5, ipady=2) 
        
        norm_btn = ctk.CTkButton(btn_fix_norm_frame, text="Нормализовать", fg_color="#dc3545", hover_color="#c82333", command=self.normalize_data)
        norm_btn.pack(side="left", expand=True, fill="x", padx=5, ipady=2)

        # Настройка растягивания строк
        for r in [0, 1, 2, 3]:
            main_container.grid_rowconfigure(r, weight=0)
        
        # Заполнение комбобокса группами при инициализации
        self.refresh_groups()

    # === МЕТОДЫ УПРАВЛЕНИЯ ===

    def refresh_groups(self):
        self.controller.refresh_groups()
        self.group_cb.configure(values=self.controller.groups)
        if self.controller.groups:
            self.group_cb.set(self.controller.groups[0])
            
    def select_check_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.file_path_entry.delete(0, "end")
            self.file_path_entry.insert(0, file_path)
            self.controller.current_file_path = file_path
            
    def run_transitivity_check(self):
        self.reset_fix_field()
        selected_sheet = self.controller.current_sheet_name
        file_path = self.file_path_entry.get().strip()
        
        if not file_path or not selected_sheet:
            ctk.CTkMessagebox.showwarning("Внимание", "Сначала выберите файл и лист!")
            return
            
        percent_raw = self.controller.check_transitivity(file_path, selected_sheet)
        self.trans_var.set(percent_raw)
        
    def fix_transitivity(self):
        self.reset_check_field()
        selected_sheet = self.controller.current_sheet_name
        file_path = self.file_path_entry.get().strip()
        
        if not file_path or not selected_sheet:
            ctk.CTkMessagebox.showwarning("Внимание", "Сначала выберите файл и лист!")
            return
            
        success = self.controller.apply_transitivity_corrections(file_path, selected_sheet)
        if success:
            ctk.CTkMessagebox.showinfo("Готово", "Нарушения транзитивности исправлены.")
            self.run_transitivity_check() 
        else:
            ctk.CTkMessagebox.showerror("Ошибка", "Не удалось исправить данные.")
            
    def normalize_data(self):
        file_path = self.file_path_entry.get().strip()
        selected_sheet = self.controller.current_sheet_name
        if not file_path or not selected_sheet:
            ctk.CTkMessagebox.showwarning("Внимание", "Сначала выберите файл и лист!")
            return
            
        self.controller.current_file_path = file_path
        self.controller.current_sheet_name = selected_sheet
        success, message = self.controller.normalize_group_data()
        if success:
            ctk.CTkMessagebox.showinfo("Готово", message)
        else:
            msg_type = ctk.CTkMessagebox.showerror
            if "уже проведена" in message or "очистить" in message or "квадратн" in message:
                msg_type = ctk.CTkMessagebox.showwarning
            msg_type("Статус операции", message)
            
    def reset_transitivity_field(self):
        self.trans_var.set("")
        
    def reset_check_field(self):
        self.trans_var.set("")
        
    def reset_fix_field(self):
        self.trans_var.set("")
		
















# dukora/modules/experts/check_tab_work.py
from openpyxl import load_workbook, Workbook
import os
from pathlib import Path
from modules.data.database_module import get_db_connection

def create_excel_template(groups_objects, output_filename):
    wb = Workbook()
    if "Sheet" in wb.sheetnames:
        del wb["Sheet"]
        
    created_any_sheet = False
    
    for group_data in groups_objects:
        indicators_list = group_data.get('indicators', [])
        if not indicators_list:
            continue
            
        sheet_title = f"{group_data['name']}"[:31]
        ws = wb.create_sheet(title=sheet_title)
        created_any_sheet = True
        
        headers = ["№ п/п", "Наименование показателя", "Код"]
        for col_idx, header in enumerate(headers, start=1):
            ws.cell(row=1, column=col_idx, value=header)
            
        for idx, ind_tuple in enumerate(indicators_list, start=1):
            name, code = ind_tuple[0], ind_tuple[1]
            ws.cell(row=idx + 1, column=1, value=str(idx))
            ws.cell(row=idx + 1, column=2, value=name)
            ws.cell(row=idx + 1, column=3, value=code)
            
        matrix_start_col = 4
        matrix_start_row = 1
        
        for col_offset, ind_tuple in enumerate(indicators_list):
            code = ind_tuple[1]
            ws.cell(row=matrix_start_row, column=matrix_start_col + col_offset, value=code)
            
        size = len(indicators_list)
        for r in range(size):
            for c in range(size):
                cell = ws.cell(row=matrix_start_row + r + 1, column=matrix_start_col + c)
                cell.value = 1.0 if r == c else ""
                
    if not created_any_sheet:
        ws = wb.create_sheet(title="Пусто")
        ws['A1'] = 'Нет индикаторов.'
        
    project_root = Path(__file__).resolve().parents[3]
    experts_folder = project_root / "experts"
    os.makedirs(experts_folder, exist_ok=True)
    full_path = experts_folder / output_filename
    wb.save(full_path)

class CheckWorkController:
    def __init__(self):
        self.groups = []
        self._gid_cache = {}
        self.current_file_path = ""
        self.current_sheet_name = ""
        
    def refresh_groups(self):
        conn = get_db_connection()
        if not conn:
            self.groups = ["Ошибка БД"]
            return
            
        cursor = conn.cursor()
        cursor.execute("SELECT id, name FROM groups ORDER BY name")
        rows = cursor.fetchall()
        
        self.groups = [row[1] for row in rows]
        self._gid_cache = {row[1]: row[0] for row in rows}
        
        conn.close()
        
    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"[CHECK TRANS 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)}"