Загрузка данных
повторяем работу,только теперь в окне Агрегирование файлов.
суть в следующем.
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)}"
# ==============================================================