import pandas as pd
import tkinter as tk
from tkinter import filedialog
import shutil
import re
from openpyxl import load_workbook
from openpyxl.styles import PatternFill
from concurrent.futures import ThreadPoolExecutor

def process_excel_file():
    # Открытие диалогового окна для выбора исходного файла
    root = tk.Tk()
    root.withdraw()
    input_file_path = filedialog.askopenfilename(title="Выберите исходный Excel файл", filetypes=[("Excel files", "*.xlsx;*.xls")])
    
    if not input_file_path:
        print("Файл не выбран.")
        return
    
    # Создание копии выбранного файла
    output_file_path = filedialog.asksaveasfilename(defaultextension=".xlsx", title="Сохраните обработанный файл", filetypes=[("Excel files", "*.xlsx;*.xls")])
    if not output_file_path:
        print("Местоположение для сохранения файла не выбрано.")
        return
    
    shutil.copy(input_file_path, output_file_path)
    
    # Чтение данных из Excel файла
    df = pd.read_excel(output_file_path)
    
    # Сохранение неизменённых данных (индексы 0-11)
    unchanged_data = df.iloc[:, :11].copy()
    
    # Обработка данных с индекса 11 и далее
    data = df.iloc[:, 11:]
    
    # Определение границ для разделения данных
    num_columns = len(data.columns)
    letter_index = next((i for i, col in enumerate(data.columns) if any(char.isalpha() for char in str(col))), num_columns)
    
    # Разделение данных на три части
    third = letter_index // 3
    abc_answers = data.iloc[:, :third].copy()
    correct_abc_answers = data.iloc[:, third:2*third].copy()
    binary_answers = data.iloc[:, 2*third:letter_index].copy()
    
    # Функция для назначения меток
    def assign_labels(indices, prefix):
        nonlocal question_counter
        for index in indices:
            question_labels[index] = f"{prefix}-{question_counter}"
            question_counter += 1
    
    # Извлечение и распознавание данных из оставшихся заголовков
    extra_headers = df.columns[letter_index:]
    parsed_headers = []
    binary_start_index = 11 + 2 * third

    question_labels = {}
    question_counter = 1
    current_index = binary_start_index + 1

    subject_question_info = {}
    category_scores = {}
    
    for header in extra_headers:
        lines = header.split('\n')
        if len(lines) == 4:
            subject = lines[0].strip()
            category = lines[1].strip()
            
            question_count_str = re.search(r'\d+(?:,\d{3})*(?:[.,]\d+)?', lines[2]).group() if re.search(r'\d+(?:,\d{3})*(?:[.,]\d+)?', lines[2]) else '0'
            score_per_question_str = re.search(r'\d+(?:,\d{3})*(?:[.,]\d+)?', lines[3]).group() if re.search(r'\d+(?:,\d{3})*(?:[.,]\d+)?', lines[3]) else '0'
            
            question_count = float(question_count_str.replace(',', '').replace('.', '').replace(',', '.')) if question_count_str.count(',') > 1 else float(question_count_str.replace(',', ''))
            score_per_question = float(score_per_question_str.replace(',', '').replace('.', '').replace(',', '.')) if score_per_question_str.count(',') > 1 else float(score_per_question_str.replace(',', ''))
            
            prefix = category[0].upper()
            indices = list(range(current_index, current_index + int(question_count)))
            assign_labels(indices, prefix)
            parsed_headers.append((subject, category, question_count, score_per_question, indices[0], indices[-1]))
            if subject not in subject_question_info:
                subject_question_info[subject] = {}
            subject_question_info[subject][category] = indices
            category_scores[(subject, category)] = score_per_question
            current_index += int(question_count)
    
    # Присвоение меток бинарным ответам
    binary_answers.columns = [question_labels.get(i, col) for i, col in enumerate(binary_answers.columns, start=binary_start_index + 1)]
    
    # Создаем копию бинарных ответов для дальнейших вычислений
    numeric_df = binary_answers.copy()

    for subject, categories in subject_question_info.items():
        for category, indices in categories.items():
            original_col = f"{subject} {category} (Оригинал)"
            multiplied_col = f"{subject} {category} (Умноженный)"
            
            # Создание столбцов для оригинала и умноженного значений
            numeric_df[original_col] = numeric_df.loc[:, [question_labels[i] for i in indices]].sum(axis=1)
            numeric_df[multiplied_col] = numeric_df[original_col] * category_scores[(subject, category)]

        # Создание итоговых значений
    for subject, categories in subject_question_info.items():
        # Сумма оригиналов
        original_columns = [f"{subject} {category} (Оригинал)" for category in categories.keys()]
        numeric_df[f"{subject} Сумма (Оригинал)"] = numeric_df[original_columns].sum(axis=1)
        
        # Сумма умноженных значений
        multiplied_columns = [f"{subject} {category} (Умноженный)" for category in categories.keys()]
        numeric_df[f"{subject} Сумма (Умноженный)"] = numeric_df[multiplied_columns].sum(axis=1)
        
        # Процент
        numeric_df[f"{subject} Процент"] = (numeric_df[f"{subject} Сумма (Умноженный)"] / 40) * 100

    # Подсчет общего количества правильных ответов и общего балла
    numeric_df["Количество правильных ответов"] = numeric_df.filter(like="Сумма (Оригинал)").sum(axis=1)
    numeric_df["Общий балл"] = numeric_df.filter(like="Сумма (Умноженный)").sum(axis=1)




    # Рассчет общего процента
    num_subjects = len(subject_question_info)
    total_questions = num_subjects * 40
    numeric_df["Процент"] = (numeric_df["Общий балл"] / total_questions) * 100

    # Подсчет четных и нечетных ответов
    even_indices = [col for col in binary_answers.columns if int(re.search(r'\d+', col).group()) % 2 == 0]
    odd_indices = [col for col in binary_answers.columns if int(re.search(r'\d+', col).group()) % 2 != 0]

    numeric_df["Четные ответы"] = numeric_df[even_indices].sum(axis=1)
    numeric_df["Нечетные ответы"] = numeric_df[odd_indices].sum(axis=1)



    # Объединение обработанных данных
    processed_data = pd.concat([abc_answers, correct_abc_answers, numeric_df], axis=1)
    final_df = pd.concat([unchanged_data, processed_data], axis=1)

    # Сохранение результата в Excel
    final_df.to_excel(output_file_path, index=False)

    # Окрашивание столбцов
    wb = load_workbook(output_file_path)
    ws = wb.active

    colors = {
        "B-": "#1773c8",
        "Q-": "#ff7e25",
        "M-": "#ffc100",
        "Bilish": "#1773c8",
        "Qo‘llash": "#ff7e25",
        "Mulohaza": "#ffc100"
    }
    
    def color_columns(headers, color_dict):
        for col in ws.columns:
            col_letter = col[0].column_letter
            header = col[0].value
            if header:
                header = str(header).strip()
                for key, color in color_dict.items():
                    if key in header:
                        fill = PatternFill(start_color=color.replace("#", ""), end_color=color.replace("#", ""), fill_type="solid")
                        for cell in col:
                            cell.fill = fill
                        break
    
    # Запуск многопоточности для окрашивания столбцов
    with ThreadPoolExecutor() as executor:
        executor.submit(color_columns, binary_answers.columns, colors)
        executor.submit(color_columns, final_df.columns[letter_index:], colors)
    
    wb.save(output_file_path)
    print(f"Обработка завершена. Файл сохранён по пути: {output_file_path}")

process_excel_file()

надо будет добавить новую операцию в этот код, сохранив все что есть

Логика новой операции:
Исходные данные:

У вас есть два массива: abc_answers и binary_answers, которые содержат данные с предметным разделением.
Массивы автоматически разделяются по предметам, и код уже знает, как правильно определить эти диапазоны.
Копирование на новый лист:

Для каждого предмета необходимо создать два отдельных листа в Excel:
Лист для abc_answers с названием: Дистракторы_Класс_Предмет, например: Дистракторы_9_История.
Лист для binary_answers с названием: БМ_Класс_Предмет, например: БМ_9_История.
Каждый лист будет содержать данные, соответствующие своему массиву, где ответы разделены по предметам.
Фильтрация по классам:

В столбце J (столбец с индексом 9) находится информация о классах учеников.
После копирования данных на новый лист, необходимо выполнить фильтрацию по классам.
Например, если класс — это "9", данные фильтруются так, чтобы на лист попали только ответы учеников 9-го класса.
Алгоритм работы кода:

Определить диапазон данных для каждого предмета.
Создать новый лист для каждого предмета и класса с указанными именами (например, Дистракторы_9_История и БМ_9_История).
Скопировать данные из соответствующих массивов (abc_answers и binary_answers) на новый лист.
Выполнить фильтрацию данных на основе значений столбца J (фильтрация по классам).
Названия листов:

Листы для abc_answers будут называться в формате Дистракторы_Класс_Предмет.
Листы для binary_answers будут называться в формате БМ_Класс_Предмет.
Результат:

В итоге для каждого предмета и класса будет создано два листа, один с дистракторами (ответами abc_answers), а другой с бинарными ответами (binary_answers), с именами, отражающими класс и предмет.
Пример:
Предположим, что у вас есть следующие данные:

Класс: 9
Предмет: История
Код создаст два листа:

Лист для abc_answers с именем: Дистракторы_9_История.
Лист для binary_answers с именем: БМ_9_История.
На каждом листе будет выполнена фильтрация данных, чтобы там оказались только ответы учеников 9-го класса.

Основные шаги для программиста:
Определить диапазоны данных для каждого предмета в массивах abc_answers и binary_answers.
Создать новые листы с правильными именами.
Скопировать данные из массивов на новые листы.
Фильтровать данные по столбцу J (по классам).
Повторить процесс для каждого предмета и класса.
Эта логика позволит автоматизировать процесс создания листов для различных предметов и классов, а также удобное разделение данных по дистракторам и бинарным ответам.