Загрузка данных
Работаем с окном Проверка файлов
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)}"