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


import java.util.stream.Collectors; // ИЗМЕНЕНО: добавлен импорт Collectors

@Slf4j
@Service
public class CsvGeneratorService {

    private static final int DEFAULT_SLA = 3;
    private static final String STATIC_URL = "/ic/ufs/corp-retail/{{version}}/js/app/corp-retail-mf.js";
    private static final String STATIC_OPERATION_ID = "UC00_StaticJS_1_WEB";
    private static final String GET_CONTACTS_OPERATION_ID = "UC29_GET_contacts_WEB";
    private static final int STATIC_INTENSITY_DEFAULT = 300000;
    private static final int STATIC_SLA_DEFAULT = 1;

    @Autowired
    private UriNormalizer uriNormalizer;

    private String urlPrefix;
    private String projectType;

    private Set<String> lastExtraInScript = new HashSet<>();

    public Set<String> getLastExtraInScript() {
        return lastExtraInScript;
    }

    public List<CsvRow> generateProfile(Map<String, List<String>> urlToOpIds,
                                        List<ProfileEntry> profileEntries,
                                        Map<String, OldProfileData> lastProfileData,
                                        String outputFolder,
                                        String outputFilename,
                                        String requirement,
                                        String urlPrefix,
                                        String projectType) {

        this.urlPrefix = urlPrefix;
        this.projectType = projectType;

        // Маппинг: "method|normalizedUri" -> operationId из прошлого профиля
        Map<String, String> oldUriToOpId = new HashMap<>();
        if (lastProfileData != null) {
            for (Map.Entry<String, OldProfileData> oldEntry : lastProfileData.entrySet()) {
                String oldOpId = oldEntry.getKey();
                OldProfileData oldData = oldEntry.getValue();
                if (oldData.getFullService() != null && oldData.getFullService().contains(" ")) {
                    String oldMethod = oldData.getFullService().substring(0, oldData.getFullService().indexOf(' '));
                    String oldUri = oldData.getFullService().substring(oldData.getFullService().indexOf(' ') + 1);
                    String normalizedOldUri = uriNormalizer.normalizeUri(oldUri, projectType);
                    oldUriToOpId.put(oldMethod.toUpperCase() + "|" + normalizedOldUri, oldOpId);
                }
            }
        }

        List<CsvRow> rowsForCsv = new ArrayList<>();      // для CSV (intensity > 0)
        List<CsvRow> allRowsForExcel = new ArrayList<>(); // для Excel (все операции)

        // ========== 1. Статический запрос (ТОЛЬКО ДЛЯ WEB) ==========
        if ("WEB".equals(projectType)) {
            int staticIntensity = STATIC_INTENSITY_DEFAULT;
            int staticSla = STATIC_SLA_DEFAULT;

            if (lastProfileData.containsKey(STATIC_OPERATION_ID)) {
                OldProfileData data = lastProfileData.get(STATIC_OPERATION_ID);
                staticIntensity = data.getIntensity();
                staticSla = data.getSla();
                log.info("   Найдена в прошлом профиле {}: интенсивность = {}, SLA = {} сек",
                        STATIC_OPERATION_ID, staticIntensity, staticSla);
            } else {
                log.info("  ⚠️ Не найдена в прошлом профиле {}, используем значения по умолчанию: инт = {}, SLA = {} сек",
                        STATIC_OPERATION_ID, staticIntensity, staticSla);
            }

            CsvRow staticRow = new CsvRow(
                    STATIC_OPERATION_ID,
                    STATIC_URL,
                    "GET",
                    staticIntensity,
                    staticSla,
                    STATIC_OPERATION_ID,
                    requirement,
                    ""
            );

            rowsForCsv.add(staticRow);
            allRowsForExcel.add(staticRow);

            log.info("   Добавлен статический запрос: {} (инт: {}, SLA: {})",
                    STATIC_OPERATION_ID, staticIntensity, staticSla);
        } else if ("MOBILE".equals(projectType)) {
            log.debug("   Статический запрос не добавляется для проекта: {}", projectType);
        } else if ("PPRB".equals(projectType)) {
            log.debug("   Статический запрос не добавляется для проекта: {}", projectType);
        }

        // ========== 2. Собираем URI из профиля аналитика ==========
        Map<String, ProfileEntry> profileEntryMap = new HashMap<>();
        for (ProfileEntry entry : profileEntries) {
            if (entry.getIntensity() > 0) {
                String key;
                if ("PPRB".equals(projectType)) {
                    String normalizedUri = uriNormalizer.normalizeUri(entry.getUri(), projectType);
                    key = entry.getMethod() + "|" + normalizedUri;
                } else {
                    key = entry.getMethod() + "|" + entry.getUri();
                }
                profileEntryMap.put(key, entry);
            }
        }

        // ========== 3. Исключаем статический URL из проверок (только для Web) ==========
        int totalScriptOperations = 0;
        for (List<String> opIds : urlToOpIds.values()) {
            totalScriptOperations += opIds.size();
        }

        Map<String, List<String>> filteredUrlToOpIds = new HashMap<>();
        boolean removedStatic = false;

        for (Map.Entry<String, List<String>> entry : urlToOpIds.entrySet()) {
            if ("WEB".equals(projectType) && entry.getKey().equals(STATIC_URL)) {
                removedStatic = true;
                continue;
            }
            filteredUrlToOpIds.put(entry.getKey(), entry.getValue());
        }

        int scriptOperationsAfterRemoval = 0;
        for (List<String> opIds : filteredUrlToOpIds.values()) {
            scriptOperationsAfterRemoval += opIds.size();
        }

        log.info(" Всего операций в скрипте: {}", totalScriptOperations);
        if (removedStatic) {
            log.info("   Удален статический запрос: {}", STATIC_URL);
            log.info(" Скрипт после исключения статики: {} операций", scriptOperationsAfterRemoval);
        } else {
            log.info(" Скрипт: {} операций", scriptOperationsAfterRemoval);
        }

        // ========== 4. Находим операции, которые есть в скрипте, но НЕТ в профиле ==========
        Set<String> scriptKeys = new HashSet<>();
        for (Map.Entry<String, List<String>> scriptEntry : filteredUrlToOpIds.entrySet()) {
            String scriptUri = scriptEntry.getKey();
            for (String opId : scriptEntry.getValue()) {
                String methodFromOpId = extractMethodFromOperationId(opId);
                String key;
                if ("PPRB".equals(projectType)) {
                    String normalizedUri = uriNormalizer.normalizeUri(scriptUri, projectType);
                    key = methodFromOpId + "|" + normalizedUri;
                } else {
                    key = methodFromOpId + "|" + scriptUri;
                }
                scriptKeys.add(key);
            }
        }

        Set<String> profileKeys = new HashSet<>();
        for (ProfileEntry entry : profileEntries) {
            if (entry.getIntensity() > 0) {
                String key;
                if ("PPRB".equals(projectType)) {
                    String normalizedUri = uriNormalizer.normalizeUri(entry.getUri(), projectType);
                    key = entry.getMethod() + "|" + normalizedUri;
                } else {
                    key = entry.getMethod() + "|" + entry.getUri();
                }
                profileKeys.add(key);
            }
        }

        Set<String> extraInScript = new HashSet<>(scriptKeys);
        extraInScript.removeAll(profileKeys);

        this.lastExtraInScript = extraInScript;

        if (!extraInScript.isEmpty()) {
            log.info("\n⚠️ ВНИМАНИЕ! Операции, которые есть в скрипте, но ОТСУТСТВУЮТ в профиле аналитика:");
            log.info("   (нужно удалить из скрипта)");
            log.info("========================================");
            for (String key : extraInScript) {
                String[] parts = key.split("\\|", 2);
                String method = parts[0];
                String uri = parts[1];
                List<String> opIds = filteredUrlToOpIds.get(uri);
                log.info("   {} {} → {}", method, uri, opIds);
            }
            log.info("========================================");
            log.info("  Всего лишних операций в скрипте: {}\n", extraInScript.size());
        } else {
            log.info("\n Все операции из скрипта присутствуют в профиле аналитика\n");
        }

        // ========== 5. Находим операции, которые есть в профиле, но НЕТ в скрипте ==========
        Set<String> extraInProfile = new HashSet<>(profileKeys);
        extraInProfile.removeAll(scriptKeys);

        if (!extraInProfile.isEmpty()) {
            log.info("\n СПИСОК: Операции, которые есть в профиле аналитика, но ОТСУТСТВУЮТ в скрипте:");
            log.info("   (будут созданы как новые запросы)");
            log.info("========================================");
            for (String key : extraInProfile) {
                String[] parts = key.split("\\|", 2);
                String method = parts[0];
                String uri = parts[1];
                log.info("   {} {} (интенсивность: {})", method, uri,
                        profileEntryMap.get(key).getIntensity());
            }
            log.info("========================================");
            log.info("  Всего новых операций (нужно создать): {}\n", extraInProfile.size());
        } else {
            log.info("\n Все операции из профиля аналитика присутствуют в скрипте\n");
        }

        log.debug("\n=== Сравнение с нормализацией ===");
        for (ProfileEntry entry : profileEntries) {
            if (entry.getIntensity() == 0) continue;

            String normalizedProfileUri = uriNormalizer.normalizeUri(entry.getUri(), projectType);
            boolean found = false;
            String matchedScriptUri = null;

            for (String scriptUri : filteredUrlToOpIds.keySet()) {
                String normalizedScriptUri = uriNormalizer.normalizeUri(scriptUri, projectType);
                if (normalizedScriptUri.equals(normalizedProfileUri)) {
                    found = true;
                    matchedScriptUri = scriptUri;
                    break;
                }
            }

            if (!found) {
                log.warn("   НЕ СОВПАДАЕТ: Профиль='{}' -> нормализовано='{}'",
                        entry.getUri(), normalizedProfileUri);
            }
            else {
                log.debug("   СОВПАДАЕТ: Профиль='{}' -> Скрипт='{}'", entry.getUri(), matchedScriptUri);
            }
        }
        log.info("========================================\n");

        // ========== 6. Обрабатываем операции из профиля аналитика ==========
        Set<String> usedOpIds = new HashSet<>();
        for (List<String> opIds : filteredUrlToOpIds.values()) {
            usedOpIds.addAll(opIds);
        }
        if ("WEB".equals(projectType)) {
            usedOpIds.add(STATIC_OPERATION_ID);
        }

        Set<String> matchedOpIds = new HashSet<>();

        int nextUcNum = getNextUcNumber(usedOpIds);

        int matchedCount = 0;
        int newCount = 0;
        int validProfileEntriesCount = 0;

        log.debug("--- Обработка операций из профиля аналитика ---");

        for (ProfileEntry entry : profileEntries) {
            log.debug("  Обработка entry: метод={}, uri={}, интенсивность={}", entry.getMethod(), entry.getUri(), entry.getIntensity());

            if (entry.getIntensity() > 0) {
                validProfileEntriesCount++;
            } else {
                log.info("   Операция с нулевой интенсивностью: {} {} - будет покрашена красным в Excel",
                        entry.getMethod(), entry.getUri());
            }

            String operationId = null;
            String profileMethod = entry.getMethod();

            if (filteredUrlToOpIds.containsKey(entry.getUri())) {
                List<String> opIds = filteredUrlToOpIds.get(entry.getUri());

                for (String opId : opIds) {
                    if (!matchedOpIds.contains(opId)) {
                        String opMethod = extractMethodFromOperationId(opId);
                        if (opMethod.equals(profileMethod)) {
                            operationId = opId;
                            matchedOpIds.add(opId);
                            break;
                        }
                    }
                }

                if (operationId == null) {
                    for (String opId : opIds) {
                        if (!matchedOpIds.contains(opId)) {
                            operationId = opId;
                            matchedOpIds.add(opId);
                            if (entry.getIntensity() > 0) {
                                log.warn("  ⚠️ Метод не совпадает! Профиль: {}, operationId: {} (URI: {})",
                                        profileMethod, opId, entry.getUri());
                            }
                            break;
                        }
                    }
                }

                if (operationId != null) {
                    if (entry.getIntensity() > 0) {
                        if (opIds.size() > 1) {
                            log.debug("   Найден в скрипте (один из {}): {} {} → {} (инт: {})",
                                    opIds.size(), profileMethod, entry.getUri(), operationId, entry.getIntensity());
                        } else {
                            log.debug("   Найден в скрипте: {} {} → {} (инт: {})",
                                    profileMethod, entry.getUri(), operationId, entry.getIntensity());
                        }
                        matchedCount++;
                    }
                } else {
                    // Все operation_id для URI уже использованы — ищем в прошлом профиле
                    String normalizedEntryUri = uriNormalizer.normalizeUri(entry.getUri(), projectType);
                    String oldKey = entry.getMethod().toUpperCase() + "|" + normalizedEntryUri;
                    String existingOpId = oldUriToOpId.get(oldKey);

                    if (existingOpId != null && !usedOpIds.contains(existingOpId)) {
                        operationId = existingOpId;
                        usedOpIds.add(operationId);
                        if (entry.getIntensity() > 0) {
                            log.info("   Восстановлен из прошлого профиля (URI в скрипте): {} {} → {} (инт: {})",
                                    profileMethod, entry.getUri(), operationId, entry.getIntensity());
                            matchedCount++;
                        }
                    } else {
                        if (entry.getIntensity() > 0) {
                            log.warn("  ⚠️ Все operation_id для URI {} уже использованы. Создаём новый.", entry.getUri());
                        }
                        operationId = generateNewOperationId(entry, usedOpIds, nextUcNum);
                        nextUcNum = extractUcNumber(operationId) + 1;
                        usedOpIds.add(operationId);
                        if (entry.getIntensity() > 0) {
                            newCount++;
                        }
                    }
                }
            } else {
                // URI не найден в скрипте — ищем в прошлом профиле
                String normalizedEntryUri = uriNormalizer.normalizeUri(entry.getUri(), projectType);
                String oldKey = entry.getMethod().toUpperCase() + "|" + normalizedEntryUri;
                String existingOpId = oldUriToOpId.get(oldKey);

                if (existingOpId != null && !usedOpIds.contains(existingOpId)) {
                    operationId = existingOpId;
                    usedOpIds.add(operationId);
                    if (entry.getIntensity() > 0) {
                        log.info("   Восстановлен из прошлого профиля: {} {} → {} (инт: {})",
                                profileMethod, entry.getUri(), operationId, entry.getIntensity());
                        matchedCount++;
                    } else {
                        log.info("   Восстановлен ID из прошлого профиля для нулевой операции: {} {} → {} (инт: 0)",
                                profileMethod, entry.getUri(), operationId);
                    }
                } else {
                    operationId = generateNewOperationId(entry, usedOpIds, nextUcNum);
                    nextUcNum = extractUcNumber(operationId) + 1;
                    usedOpIds.add(operationId);
                    if (entry.getIntensity() > 0) {
                        log.info("   Создан новый: {} {} → {} (инт: {})",
                                profileMethod, entry.getUri(), operationId, entry.getIntensity());
                        newCount++;
                    } else {
                        log.info("   Создан ID для нулевой операции: {} {} → {} (инт: 0)",
                                profileMethod, entry.getUri(), operationId);
                    }
                }
            }

            int sla = DEFAULT_SLA;
            if (lastProfileData.containsKey(operationId)) {
                sla = lastProfileData.get(operationId).getSla();
            }

            String fullUri;
            String uriFromProfile = entry.getUri();

            if ("PPRB".equals(projectType)) {
                // Для PPRB не добавляем префикс и не меняем URI
                fullUri = uriFromProfile;
            } else if (uriFromProfile.startsWith("/ic/ufs/corp-retail/") ||
                    uriFromProfile.startsWith("/ufs/mobile/")) {
                fullUri = uriFromProfile;
            } else if (uriFromProfile.startsWith("/")) {
                fullUri = urlPrefix + uriFromProfile;
            } else {
                fullUri = urlPrefix + "/" + uriFromProfile;
            }

            CsvRow row = new CsvRow(
                    operationId,
                    fullUri,
                    entry.getMethod(),
                    entry.getIntensity(),  // может быть 0
                    sla,
                    operationId,
                    requirement,
                    ""
            );

            // ИЗМЕНЕНО: добавляем ВСЕ операции в список для Excel
            allRowsForExcel.add(row);

            // ИЗМЕНЕНО: в CSV добавляем ТОЛЬКО операции с интенсивностью > 0
            if (entry.getIntensity() > 0) {
                rowsForCsv.add(row);
                log.info("   Добавлена в CSV и Excel: {} -> интенсивность={}", operationId, entry.getIntensity());
            } else {
                log.info("   Добавлена ТОЛЬКО в Excel (не в CSV): {} -> интенсивность=0", operationId);
            }
        }

        // ========== СИНХРОНИЗАЦИЯ СТАТИКИ С UC29 ==========
        if ("WEB".equals(projectType)) {
            Optional<CsvRow> contactsRow = allRowsForExcel.stream()
                    .filter(row -> GET_CONTACTS_OPERATION_ID.equals(row.getName()))
                    .findFirst();

            Optional<CsvRow> staticRow = allRowsForExcel.stream()
                    .filter(row -> STATIC_OPERATION_ID.equals(row.getName()))
                    .findFirst();

            if (contactsRow.isPresent() && staticRow.isPresent()) {
                int contactsIntensity = contactsRow.get().getIntensity();
                CsvRow oldStaticRow = staticRow.get();

                if (contactsIntensity != oldStaticRow.getIntensity()) {
                    CsvRow updatedStaticRow = new CsvRow(
                            oldStaticRow.getName(),
                            oldStaticRow.getUri(),
                            oldStaticRow.getMethod(),
                            contactsIntensity,
                            oldStaticRow.getSla(),
                            oldStaticRow.getTransactionName(),
                            oldStaticRow.getRequirement(),
                            oldStaticRow.getComment()
                    );

                    int index = allRowsForExcel.indexOf(oldStaticRow);
                    allRowsForExcel.set(index, updatedStaticRow);

                    // ИЗМЕНЕНО: также обновляем в rowsForCsv, если там есть
                    int csvIndex = rowsForCsv.indexOf(oldStaticRow);
                    if (csvIndex != -1) {
                        rowsForCsv.set(csvIndex, updatedStaticRow);
                    }

                    log.info(" Синхронизирована интенсивность: {} -> {} (как у {})",
                            STATIC_OPERATION_ID, contactsIntensity, GET_CONTACTS_OPERATION_ID);
                }
            }
        }

        // ========== 7. Сортировка ==========

// Сортировка для Excel
        List<CsvRow> rowsToSort;
        boolean hasStatic = "WEB".equals(projectType) && allRowsForExcel.size() > 0 &&
                allRowsForExcel.get(0).getName().equals(STATIC_OPERATION_ID);

        if (hasStatic) {
            rowsToSort = allRowsForExcel.subList(1, allRowsForExcel.size());
            log.debug("Сортируем {} строк для Excel (без статики)", rowsToSort.size());
        } else {
            rowsToSort = allRowsForExcel;
            log.debug("Сортируем все {} строк для Excel", rowsToSort.size());
        }

        rowsToSort.sort((row1, row2) -> {
            int num1 = extractUcNumber(row1.getName());
            int num2 = extractUcNumber(row2.getName());
            return Integer.compare(num1, num2);
        });

        List<CsvRow> finalRowsForExcel;
        if (hasStatic) {
            finalRowsForExcel = new ArrayList<>();
            finalRowsForExcel.add(allRowsForExcel.get(0));
            finalRowsForExcel.addAll(rowsToSort);
        } else {
            finalRowsForExcel = rowsToSort;
        }

// ========== 8. Сортировка для CSV ==========
        List<CsvRow> csvRowsToSort;
        boolean hasStaticCsv = "WEB".equals(projectType) && rowsForCsv.size() > 0 &&
                rowsForCsv.get(0).getName().equals(STATIC_OPERATION_ID);

        if (hasStaticCsv) {
            csvRowsToSort = rowsForCsv.subList(1, rowsForCsv.size());
            log.debug("Сортируем {} строк для CSV (без статики)", csvRowsToSort.size());
        } else {
            csvRowsToSort = rowsForCsv;
            log.debug("Сортируем все {} строк для CSV", csvRowsToSort.size());
        }

        csvRowsToSort.sort((row1, row2) -> {
            int num1 = extractUcNumber(row1.getName());
            int num2 = extractUcNumber(row2.getName());
            return Integer.compare(num1, num2);
        });

        List<CsvRow> finalRowsForCsv;
        if (hasStaticCsv) {
            finalRowsForCsv = new ArrayList<>();
            finalRowsForCsv.add(rowsForCsv.get(0));
            finalRowsForCsv.addAll(csvRowsToSort);
        } else {
            finalRowsForCsv = csvRowsToSort;
        }

// ========== 9. Сохранение CSV и возврат результата ==========
        log.info("\n--- Статистика ---");
        log.info("   Скрипт (операций): {} операций", scriptOperationsAfterRemoval);
        log.info("   Профиль аналитика (инт > 0): {} операций", validProfileEntriesCount);
        log.info("   Нужно удалить из скрипта: {} операций", extraInScript.size());
        log.info("   Нужно добавить в скрипт: {} операций", extraInProfile.size());
        log.info("   Найдено совпадений: {}", matchedCount);
        log.info("   Создано новых ID: {}", newCount);
        log.info("   Итого в CSV (intensity > 0): {} строк", finalRowsForCsv.size());
        log.info("   Итого в Excel (все операции): {} строк", finalRowsForExcel.size());

        saveCsv(finalRowsForCsv, outputFolder, outputFilename);
        return finalRowsForExcel; }

    private String generateNewOperationId(ProfileEntry entry, Set<String> usedOpIds, int nextUcNum) {
        String method = entry.getMethod();
        String uri = entry.getUri();
        String suffix;

        if ("MOBILE".equals(projectType)) {
            String path = uri;
            path = path.replaceFirst("^/ic/ufs/mobile/corp-retail/rest/", "");
            path = path.replaceFirst("^/ufs/mobile/corp-retail/rest/", "");
            path = path.replaceFirst("^/ufs/", "");
            path = path.replaceFirst("^/business-analytics/", "");
            path = path.replaceFirst("^/dictionaries/", "");
            path = path.replaceFirst("^/", "");

            suffix = path.replaceAll("/", "_");
            suffix = suffix.replaceAll("-", "_");
            suffix = suffix.replaceAll("[^a-zA-Z0-9_]", "_");
            suffix = suffix.replaceAll("_+", "_");
            suffix = suffix.replaceAll("^_|_$", "");

            String methodPrefix = method.toUpperCase();
            suffix = methodPrefix + "_" + suffix;

            if (suffix.length() > 50) {
                suffix = suffix.substring(0, 50);
            }

            while (true) {
                String candidate = String.format("UC%02d_%s_WEB", nextUcNum, suffix);
                if (!usedOpIds.contains(candidate)) {
                    return candidate;
                }
                nextUcNum++;
            }
        } else {
            String pathAfterPrefix = uri;
            if (uri.startsWith(urlPrefix)) {
                pathAfterPrefix = uri.substring(urlPrefix.length());
            }
            if (pathAfterPrefix.startsWith("/")) {
                pathAfterPrefix = pathAfterPrefix.substring(1);
            }
            if (pathAfterPrefix.endsWith("/")) {
                pathAfterPrefix = pathAfterPrefix.substring(0, pathAfterPrefix.length() - 1);
            }
            suffix = pathAfterPrefix.replaceAll("/", "_");
            suffix = suffix.replaceAll("-", "_");
            suffix = suffix.replaceAll("[^a-zA-Z0-9_]", "_");
            suffix = suffix.replaceAll("_+", "_");
            suffix = suffix.replaceAll("^_|_$", "");

            while (true) {
                String candidate = String.format("UC%02d_%s_%s_WEB", nextUcNum, method, suffix);
                if (!usedOpIds.contains(candidate)) {
                    return candidate;
                }
                nextUcNum++;
            }
        }
    }

    private int getNextUcNumber(Set<String> usedOpIds) {
        if (usedOpIds == null || usedOpIds.isEmpty()) {
            return 1;
        }

        int maxNum = 0;
        for (String opId : usedOpIds) {
            int num = extractUcNumber(opId);
            if (num > maxNum && num < 1000) {
                maxNum = num;
            }
        }
        return maxNum + 1;
    }

    private int extractUcNumber(String operationId) {
        if (operationId == null || !operationId.startsWith("UC")) {
            return 0;
        }
        try {
            int underscoreIndex = operationId.indexOf('_', 2);
            if (underscoreIndex == -1) {
                return 0;
            }
            return Integer.parseInt(operationId.substring(2, underscoreIndex));
        } catch (Exception e) {
            return 0;
        }
    }

    private void saveCsv(List<CsvRow> rows, String outputFolder, String outputFilename) {
        File folder = new File(outputFolder);
        if (!folder.exists()) {
            folder.mkdirs();
        }

        File outputFile = new File(folder, outputFilename);

        try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile, StandardCharsets.UTF_8))) {
            writer.println("Название;Интенсивность выполнения;Требование к времени отклика;Название транзакции;Требование;Комментарий");

            for (CsvRow row : rows) {
                writer.printf("%s;%d;%d;%s;%s;%s%n",
                        row.getName(),
                        row.getIntensity(),
                        row.getSla(),
                        row.getTransactionName(),
                        row.getRequirement(),
                        row.getComment()
                );
            }

            log.info("\n Профиль сохранён: {}", outputFile.getAbsolutePath());
            log.info(" Всего строк в CSV: {}", rows.size());

        } catch (IOException e) {
            log.error("Ошибка при сохранении CSV: {}", e.getMessage());
        }
    }

    private String extractMethodFromOperationId(String operationId) {
        if (operationId == null) return "GET";

        if (operationId.contains("_POST_") || operationId.contains("POST_")) {
            return "POST";
        }
        if (operationId.contains("_GET_") || operationId.contains("GET_")) {
            return "GET";
        }

        String[] parts = operationId.split("_");
        for (String part : parts) {
            if (part.equalsIgnoreCase("POST")) {
                return "POST";
            }
            if (part.equalsIgnoreCase("GET")) {
                return "GET";
            }
        }

        return "GET";
    }
}