File size: 24,908 Bytes
fd485d9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Скрипт для визуализации агрегированных результатов тестирования RAG.

Читает данные из Excel-файла, сгенерированного aggregate_results.py,
и строит различные графики для анализа влияния параметров на метрики.
"""

import argparse
import json
import os

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

# --- Настройки --- 
DEFAULT_RESULTS_FILE = "data/output/aggregated_results.xlsx" # Файл с агрегированными данными
DEFAULT_PLOTS_DIR = "data/output/plots" # Куда сохранять графики

# Настройки графиков
plt.rcParams['font.family'] = 'DejaVu Sans' # Шрифт с поддержкой кириллицы
sns.set_style("whitegrid")
FIGSIZE = (16, 10) # Увеличенный размер для сложных графиков
DPI = 300
PALETTE = "viridis" # Цветовая палитра

# --- Маппинг названий столбцов (копия из aggregate_results.py) ---
COLUMN_NAME_MAPPING = {
    # Параметры запуска из pipeline.py
    'run_id': 'ID Запуска',
    'model_name': 'Модель',
    'chunking_strategy': 'Стратегия Чанкинга',
    'strategy_params': 'Параметры Стратегии',
    'process_tables': 'Обраб. Таблиц',
    'top_n': 'Top N',
    'use_injection': 'Сборка Контекста',
    'use_qe': 'Query Expansion',
    'neighbors_included': 'Вкл. Соседей',
    'similarity_threshold': 'Порог Схожести',

    # Идентификаторы из датасета (для детальных результатов)
    'question_id': 'ID Вопроса',
    'question_text': 'Текст Вопроса',

    # Детальные метрики из pipeline.py
    'chunk_text_precision': 'Точность (Чанк-Текст)',
    'chunk_text_recall': 'Полнота (Чанк-Текст)',
    'chunk_text_f1': 'F1 (Чанк-Текст)',
    'found_puncts': 'Найдено Пунктов',
    'total_puncts': 'Всего Пунктов',
    'relevant_chunks': 'Релевантных Чанков',
    'total_chunks_in_top_n': 'Всего Чанков в Топ-N',
    'assembly_punct_recall': 'Полнота (Сборка-Пункт)',
    'assembled_context_preview': 'Предпросмотр Сборки',
    # 'top_chunk_ids': 'Индексы Топ-Чанков', # Списки, могут плохо отображаться
    # 'top_chunk_similarities': 'Схожести Топ-Чанков', # Списки

    # Агрегированные метрики (добавляются в calculate_aggregated_metrics)
    'weighted_chunk_text_precision': 'Weighted Точность (Чанк-Текст)',
    'weighted_chunk_text_recall': 'Weighted Полнота (Чанк-Текст)',
    'weighted_chunk_text_f1': 'Weighted F1 (Чанк-Текст)',
    'weighted_assembly_punct_recall': 'Weighted Полнота (Сборка-Пункт)',

    'macro_chunk_text_precision': 'Macro Точность (Чанк-Текст)',
    'macro_chunk_text_recall': 'Macro Полнота (Чанк-Текст)',
    'macro_chunk_text_f1': 'Macro F1 (Чанк-Текст)',
    'macro_assembly_punct_recall': 'Macro Полнота (Сборка-Пункт)',

    'micro_text_precision': 'Micro Точность (Текст)',
    'micro_text_recall': 'Micro Полнота (Текст)',
    'micro_text_f1': 'Micro F1 (Текст)',
}
# --- Конец маппинга ---

def parse_args():
    """Парсит аргументы командной строки."""
    parser = argparse.ArgumentParser(description="Визуализация результатов тестирования RAG")

    parser.add_argument("--results-file", type=str, default=DEFAULT_RESULTS_FILE,
                        help=f"Путь к Excel-файлу с агрегированными результатами (по умолчанию: {DEFAULT_RESULTS_FILE})")
    parser.add_argument("--plots-dir", type=str, default=DEFAULT_PLOTS_DIR,
                        help=f"Директория для сохранения графиков (по умолчанию: {DEFAULT_PLOTS_DIR})")
    parser.add_argument("--sheet-name", type=str, default="Агрегированные метрики",
                        help="Название листа в Excel-файле для чтения данных")

    return parser.parse_args()

def setup_plots_directory(plots_dir: str) -> None:
    """Создает директорию для графиков, если она не существует."""
    if not os.path.exists(plots_dir):
        os.makedirs(plots_dir)
        print(f"Создана директория для графиков: {plots_dir}")
    else:
        print(f"Использование существующей директории для графиков: {plots_dir}")

def load_aggregated_data(file_path: str, sheet_name: str) -> pd.DataFrame:
    """Загружает данные из указанного листа Excel-файла."""
    print(f"Загрузка данных из файла: {file_path}, лист: {sheet_name}")
    try:
        df = pd.read_excel(file_path, sheet_name=sheet_name)
        print(f"Загружено {len(df)} строк.")
        print(f"Колонки: {df.columns.tolist()}")
        # Добавим проверку на необходимые колонки (РУССКИЕ НАЗВАНИЯ)
        required_cols_rus = [
            COLUMN_NAME_MAPPING['model_name'], COLUMN_NAME_MAPPING['chunking_strategy'],
            COLUMN_NAME_MAPPING['strategy_params'], COLUMN_NAME_MAPPING['process_tables'],
            COLUMN_NAME_MAPPING['top_n'], COLUMN_NAME_MAPPING['use_injection'],
            COLUMN_NAME_MAPPING['use_qe'], COLUMN_NAME_MAPPING['neighbors_included'],
            COLUMN_NAME_MAPPING['similarity_threshold']
            ]
        # Проверяем только те, что есть в маппинге
        missing_required = [col for col in required_cols_rus if col not in df.columns]
        if missing_required:
            print(f"Предупреждение: Не все ожидаемые колонки параметров найдены в данных: {missing_required}")

        # --- Добавим парсинг strategy_params из JSON строки в словарь ---
        params_col = COLUMN_NAME_MAPPING['strategy_params']
        if params_col in df.columns:
            def safe_json_loads(x):
                try:
                    # Обработка NaN и пустых строк
                    if pd.isna(x) or not isinstance(x, str) or not x.strip():
                        return {}
                    return json.loads(x)
                except (json.JSONDecodeError, TypeError):
                    return {} # Возвращаем пустой словарь при ошибке

            df[params_col] = df[params_col].apply(safe_json_loads)
            # Создаем строковое представление для группировки и лейблов
            df[f"{params_col}_str"] = df[params_col].apply(
                lambda d: json.dumps(d, sort_keys=True, ensure_ascii=False)
            )
            print(f"Колонка '{params_col}' преобразована из JSON строк.")
        # --------------------------------------------------------------

        return df
    except FileNotFoundError:
        print(f"Ошибка: Файл не найден: {file_path}")
        return pd.DataFrame()
    except ValueError as e:
        print(f"Ошибка: Лист '{sheet_name}' не найден в файле {file_path}. Доступные листы: {pd.ExcelFile(file_path).sheet_names}")
        return pd.DataFrame()
    except Exception as e:
        print(f"Ошибка при чтении Excel файла: {e}")
        return pd.DataFrame()

# --- Функции построения графиков --- #

def plot_metric_vs_top_n(
    df: pd.DataFrame,
    metric_name_rus: str, # Ожидаем русское имя метрики
    fixed_strategy: str | None,
    fixed_strategy_params: str | None, # Ожидаем строку JSON или None
    plots_dir: str
) -> None:
    """
    Строит график зависимости метрики от top_n для разных моделей
    (при фиксированных параметрах чанкинга).
    Разделяет линии по значению use_injection.
    Использует русские названия колонок.
    """
    # Используем русские названия колонок из маппинга
    metric_col_rus = metric_name_rus # Передаем уже готовое русское имя
    top_n_col_rus = COLUMN_NAME_MAPPING['top_n']
    model_col_rus = COLUMN_NAME_MAPPING['model_name']
    injection_col_rus = COLUMN_NAME_MAPPING['use_injection']
    strategy_col_rus = COLUMN_NAME_MAPPING['chunking_strategy']
    params_str_col_rus = f"{COLUMN_NAME_MAPPING['strategy_params']}_str" # Используем строковое представление

    if metric_col_rus not in df.columns:
        print(f"График пропущен: Колонка '{metric_col_rus}' не найдена.")
        return

    plot_df = df.copy()

    # Фильтруем по параметрам чанкинга, если задано
    chunk_suffix = "all_strategies_all_params"
    if fixed_strategy and strategy_col_rus in plot_df.columns:
        plot_df = plot_df[plot_df[strategy_col_rus] == fixed_strategy]
        chunk_suffix = f"strategy_{fixed_strategy}"
        # Фильтруем по строковому представлению параметров
        if fixed_strategy_params and params_str_col_rus in plot_df.columns:
            plot_df = plot_df[plot_df[params_str_col_rus] == fixed_strategy_params]
            # Генерируем короткий хэш для параметров в названии файла
            params_hash = hash(fixed_strategy_params) # Хэш от строки
            chunk_suffix += f"_params-{params_hash:x}" # Hex hash

        if plot_df.empty:
            print(f"График Metric vs Top-N пропущен: Нет данных для strategy={fixed_strategy}, params={fixed_strategy_params}")
            return

    plt.figure(figsize=FIGSIZE)
    sns.lineplot(
        data=plot_df,
        x=top_n_col_rus,
        y=metric_col_rus,
        hue=model_col_rus,
        style=injection_col_rus, # Разные стили линий для True/False
        markers=True,
        markersize=8,
        linewidth=2,
        palette=PALETTE
    )

    plt.title(f"Зависимость {metric_col_rus} от top_n ({chunk_suffix})")
    plt.xlabel("Top N")
    plt.ylabel(metric_col_rus.replace("_", " ").title())
    plt.legend(title="Модель / Сборка", bbox_to_anchor=(1.05, 1), loc='upper left')
    plt.grid(True, linestyle='--', alpha=0.7)
    plt.tight_layout(rect=[0, 0, 0.85, 1]) # Оставляем место для легенды

    filename = f"plot_{metric_col_rus.replace(' ', '_').replace('(', '').replace(')', '')}_vs_top_n_{chunk_suffix}.png"
    filepath = os.path.join(plots_dir, filename)
    plt.savefig(filepath, dpi=DPI)
    plt.close()
    print(f"Создан график: {filepath}")

def plot_injection_comparison(
    df: pd.DataFrame,
    metric_name_rus: str, # Ожидаем русское имя метрики
    plots_dir: str
) -> None:
    """
    Сравнивает метрики с использованием и без использования сборки контекста
    в виде парных столбчатых диаграмм для разных моделей и параметров чанкинга.
    Использует русские названия колонок.
    """
    # Русские названия колонок
    metric_col_rus = metric_name_rus
    injection_col_rus = COLUMN_NAME_MAPPING['use_injection']
    model_col_rus = COLUMN_NAME_MAPPING['model_name']
    strategy_col_rus = COLUMN_NAME_MAPPING['chunking_strategy']
    params_str_col_rus = f"{COLUMN_NAME_MAPPING['strategy_params']}_str"
    tables_col_rus = COLUMN_NAME_MAPPING['process_tables']
    qe_col_rus = COLUMN_NAME_MAPPING['use_qe']
    neighbors_col_rus = COLUMN_NAME_MAPPING['neighbors_included']
    top_n_col_rus = COLUMN_NAME_MAPPING['top_n']
    threshold_col_rus = COLUMN_NAME_MAPPING['similarity_threshold']

    if metric_col_rus not in df.columns or injection_col_rus not in df.columns:
        print(f"График сравнения сборки пропущен: Колонки '{metric_col_rus}' или '{injection_col_rus}' не найдены.")
        return

    plot_df = df.copy()
    # Используем русские названия при создании лейбла
    plot_df['config_label'] = plot_df.apply(
        lambda r: (
            f"{r.get(model_col_rus, 'N/A')}\n"
            f"Стратегия: {r.get(strategy_col_rus, 'N/A')}\n"
            # Используем строковое представление параметров
            f"Параметры: {r.get(params_str_col_rus, '{}')[:30]}...\n"
            f"Табл: {r.get(tables_col_rus, 'N/A')}, QE: {r.get(qe_col_rus, 'N/A')}, Соседи: {r.get(neighbors_col_rus, 'N/A')}\n"
            f"TopN: {int(r.get(top_n_col_rus, 0))}, Порог: {r.get(threshold_col_rus, 0):.2f}"
        ),
        axis=1
    )

    # Оставляем только строки, где есть и True, и False для данного флага
    # Группируем по config_label, считаем уникальные значения флага use_injection
    counts = plot_df.groupby('config_label')[injection_col_rus].nunique()
    configs_with_both = counts[counts >= 2].index # Используем >= 2 на случай дубликатов
    plot_df = plot_df[plot_df['config_label'].isin(configs_with_both)]

    if plot_df.empty:
        print(f"График сравнения сборки пропущен: Нет конфигураций с обоими вариантами {injection_col_rus}.")
        return

    # Ограничим количество конфигураций для читаемости (по средней метрике)
    top_configs = plot_df.groupby('config_label')[metric_col_rus].mean().nlargest(10).index # Уменьшил до 10
    plot_df = plot_df[plot_df['config_label'].isin(top_configs)]

    if plot_df.empty:
         print(f"График сравнения сборки пропущен: Не осталось данных после фильтрации топ-конфигураций.")
         return

    plt.figure(figsize=(FIGSIZE[0]*0.9, FIGSIZE[1]*0.7)) # Уменьшил размер
    sns.barplot(
        data=plot_df,
        x='config_label',
        y=metric_col_rus,
        hue=injection_col_rus,
        palette=PALETTE
    )

    plt.title(f"Сравнение {metric_col_rus} с/без {injection_col_rus}")
    plt.xlabel("Конфигурация")
    plt.ylabel(metric_col_rus)
    plt.xticks(rotation=60, ha='right', fontsize=8) # Уменьшил шрифт, увеличил поворот
    plt.legend(title=injection_col_rus)
    plt.grid(True, axis='y', linestyle='--', alpha=0.7)
    plt.tight_layout()

    filename = f"plot_{metric_col_rus.replace(' ', '_').replace('(', '').replace(')', '')}_injection_comparison.png"
    filepath = os.path.join(plots_dir, filename)
    plt.savefig(filepath, dpi=DPI)
    plt.close()
    print(f"Создан график: {filepath}")

# --- Новая функция для сравнения булевых флагов --- 
def plot_boolean_flag_comparison(
    df: pd.DataFrame,
    metric_name_rus: str, # Ожидаем русское имя метрики
    flag_column_eng: str, # Ожидаем английское имя флага для поиска в маппинге
    plots_dir: str
) -> None:
    """
    Сравнивает метрики при True/False значениях указанного булева флага
    в виде парных столбчатых диаграмм для разных конфигураций.
    Использует русские названия колонок.
    """
    # Русские названия колонок
    metric_col_rus = metric_name_rus
    try:
        flag_col_rus = COLUMN_NAME_MAPPING[flag_column_eng]
    except KeyError:
        print(f"Ошибка: Английское имя флага '{flag_column_eng}' не найдено в COLUMN_NAME_MAPPING.")
        return

    model_col_rus = COLUMN_NAME_MAPPING['model_name']
    strategy_col_rus = COLUMN_NAME_MAPPING['chunking_strategy']
    params_str_col_rus = f"{COLUMN_NAME_MAPPING['strategy_params']}_str"
    injection_col_rus = COLUMN_NAME_MAPPING['use_injection']
    top_n_col_rus = COLUMN_NAME_MAPPING['top_n']
    # Другие флаги
    tables_col_rus = COLUMN_NAME_MAPPING['process_tables']
    qe_col_rus = COLUMN_NAME_MAPPING['use_qe']
    neighbors_col_rus = COLUMN_NAME_MAPPING['neighbors_included']


    if metric_col_rus not in df.columns or flag_col_rus not in df.columns:
        print(f"График сравнения флага '{flag_col_rus}' пропущен: Колонки '{metric_col_rus}' или '{flag_col_rus}' не найдены.")
        return

    plot_df = df.copy()
    # Создаем обобщенный лейбл конфигурации, исключая сам флаг
    plot_df['config_label'] = plot_df.apply(
        lambda r: (
            f"{r.get(model_col_rus, 'N/A')}\n"
            f"Стратегия: {r.get(strategy_col_rus, 'N/A')} Параметры: {r.get(params_str_col_rus, '{}')[:20]}...\n"
            f"Сборка: {r.get(injection_col_rus, 'N/A')}, TopN: {int(r.get(top_n_col_rus, 0))}"
            # Динамически добавляем другие флаги, кроме сравниваемого
            + (f", Табл: {r.get(tables_col_rus, 'N/A')}" if flag_col_rus != tables_col_rus else "")
            + (f", QE: {r.get(qe_col_rus, 'N/A')}" if flag_col_rus != qe_col_rus else "")
            + (f", Соседи: {r.get(neighbors_col_rus, 'N/A')}" if flag_col_rus != neighbors_col_rus else "")
        ),
        axis=1
    )

    # Оставляем только строки, где есть и True, и False для данного флага
    counts = plot_df.groupby('config_label')[flag_col_rus].nunique()
    configs_with_both = counts[counts >= 2].index # Используем >= 2
    plot_df = plot_df[plot_df['config_label'].isin(configs_with_both)]

    if plot_df.empty:
        print(f"График сравнения флага '{flag_col_rus}' пропущен: Нет конфигураций с обоими вариантами {flag_col_rus}.")
        return

    # Ограничим количество конфигураций для читаемости (по средней метрике)
    top_configs = plot_df.groupby('config_label')[metric_col_rus].mean().nlargest(10).index # Уменьшил до 10
    plot_df = plot_df[plot_df['config_label'].isin(top_configs)]

    if plot_df.empty:
         print(f"График сравнения флага '{flag_col_rus}' пропущен: Не осталось данных после фильтрации топ-конфигураций.")
         return

    plt.figure(figsize=(FIGSIZE[0]*0.9, FIGSIZE[1]*0.7)) # Уменьшил размер
    sns.barplot(
        data=plot_df,
        x='config_label',
        y=metric_col_rus,
        hue=flag_col_rus,
        palette=PALETTE
    )

    plt.title(f"Сравнение {metric_col_rus} в зависимости от '{flag_col_rus}'")
    plt.xlabel("Конфигурация")
    plt.ylabel(metric_col_rus)
    plt.xticks(rotation=60, ha='right', fontsize=8) # Уменьшил шрифт, увеличил поворот
    plt.legend(title=f"{flag_col_rus}")
    plt.grid(True, axis='y', linestyle='--', alpha=0.7)
    plt.tight_layout()

    filename = f"plot_{metric_col_rus.replace(' ', '_').replace('(', '').replace(')', '')}_{flag_column_eng}_comparison.png"
    filepath = os.path.join(plots_dir, filename)
    plt.savefig(filepath, dpi=DPI)
    plt.close()
    print(f"Создан график: {filepath}")

# --- Основная функция --- 
def main():
    """Основная функция скрипта."""
    args = parse_args()

    setup_plots_directory(args.plots_dir)
    df = load_aggregated_data(args.results_file, args.sheet_name)

    if df.empty:
        print("Нет данных для построения графиков. Завершение.")
        return

    # Определяем метрики для построения графиков (используем английские ключи для поиска русских имен)
    metric_keys = [
        'weighted_chunk_text_recall', 'weighted_chunk_text_f1', 'weighted_assembly_punct_recall',
        'macro_chunk_text_recall', 'macro_chunk_text_f1', 'macro_assembly_punct_recall',
        'micro_text_recall', 'micro_text_f1'
    ]

    # Получаем существующие русские имена метрик в DataFrame
    existing_metrics_rus = [COLUMN_NAME_MAPPING.get(key) for key in metric_keys if COLUMN_NAME_MAPPING.get(key) in df.columns]

    # Определяем фиксированные параметры для некоторых графиков
    strategy_col_rus = COLUMN_NAME_MAPPING.get('chunking_strategy')
    params_str_col_rus = f"{COLUMN_NAME_MAPPING.get('strategy_params')}_str"
    model_col_rus = COLUMN_NAME_MAPPING.get('model_name')

    fixed_strategy_example = df[strategy_col_rus].unique()[0] if strategy_col_rus in df.columns and len(df[strategy_col_rus].unique()) > 0 else None
    fixed_strategy_params_example = None
    if fixed_strategy_example and params_str_col_rus in df.columns:
        params_list = df[df[strategy_col_rus] == fixed_strategy_example][params_str_col_rus].unique()
        if len(params_list) > 0:
            fixed_strategy_params_example = params_list[0]

    fixed_model_example = df[model_col_rus].unique()[0] if model_col_rus in df.columns and len(df[model_col_rus].unique()) > 0 else None
    fixed_top_n_example = 20

    print("--- Построение графиков ---")

    # 1. Графики Metric vs Top-N
    print("\n1. Зависимость метрик от Top-N:")
    for metric_name_rus in existing_metrics_rus:
            # Проверяем, что метрика не micro (у micro нет зависимости от top_n)
            if 'Micro' in metric_name_rus:
                 continue
            plot_metric_vs_top_n(
                df, metric_name_rus,
                fixed_strategy_example, fixed_strategy_params_example,
                args.plots_dir
            )

    # 2. Графики Metric vs Chunking
    print("\n2. Зависимость метрик от параметров чанкинга: [Пропущено - требует переосмысления]")
    # plot_metric_vs_chunking(...) # Закомментировано

    # 3. Графики сравнения Use Injection
    print("\n3. Сравнение метрик с/без сборки контекста:")
    for metric_name_rus in existing_metrics_rus:
             plot_injection_comparison(df, metric_name_rus, args.plots_dir)

    # 4. Графики сравнения других булевых флагов
    boolean_flags_eng = ['process_tables', 'use_qe', 'neighbors_included']
    print("\n4. Сравнение метрик в зависимости от булевых флагов:")
    for flag_eng in boolean_flags_eng:
        flag_rus = COLUMN_NAME_MAPPING.get(flag_eng)
        if not flag_rus or flag_rus not in df.columns:
            print(f"  Пропуск сравнения для флага: '{flag_eng}' (колонка '{flag_rus}' не найдена)")
            continue
        print(f"  Сравнение для флага: '{flag_rus}'")
        for metric_name_rus in existing_metrics_rus:
                 plot_boolean_flag_comparison(df, metric_name_rus, flag_eng, args.plots_dir)

    print("\n--- Построение графиков завершено ---")

if __name__ == "__main__":
    main()