так короче поправка чтобы не result назывался файл а в формате  Край_город_кладбище using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using CsvHelper;
using CsvHelper.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
using Zenject;
using ClosedXML.Excel;

public class ResultParser
{
    [Inject] private StateManager stateManager;

    public List<ResultData> ParseResults(byte[] resultContent)
    {
        var results = new List<ResultData>();
        string contentString = System.Text.Encoding.UTF8.GetString(resultContent);
        string[] lines = contentString.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);

        foreach (string line in lines)
        {
            try
            {
                var result = JsonConvert.DeserializeObject<Dictionary<string, object>>(line);
                var response = result["response"] as JObject;
                var responseBody = response?["body"] as JObject;
                var choices = responseBody?["choices"] as JArray;
                var usage = responseBody?["usage"] as JObject;

                // Логируем полученный результат для отладки
                Debug.Log($"Получен результат для строки: {JsonConvert.SerializeObject(result)}");

                string content = "";
                if (choices != null && choices.Count > 0)
                {
                    var message = choices[0]["message"] as JObject;
                    content = message?["content"]?.ToString() ?? message?["refusal"]?.ToString() ?? "";
                }

                var resultData = new ResultData
                {
                    CustomId = result["custom_id"]?.ToString() ?? "",
                    Content = content,
                    PromptTokens = usage?["prompt_tokens"]?.Value<int>() ?? 0,
                    CompletionTokens = usage?["completion_tokens"]?.Value<int>() ?? 0,
                    CachedTokens = usage?["prompt_tokens_details"]?["cached_tokens"]?.Value<int>() ?? 0,
                    TotalTokens = usage?["total_tokens"]?.Value<int>() ?? 0
                };
                results.Add(resultData);
            }
            catch (Exception e)
            {
                Debug.LogError($"Ошибка парсинга результата: {e.Message}");
                results.Add(new ResultData { IsError = true });
            }
        }

        return results;
    }

    public void WriteResultsToCsv(
        List<ResultData> results,
        string csvFileName,
        List<string> imageFiles,
        int startingIndex,
        BatchTotals totals,
        BatchInfo batch,
        Dictionary<string, ImageLinkPair> imageLinks
    )
    {
        string csvPath = Path.Combine(Application.streamingAssetsPath, csvFileName);
        bool fileExists = File.Exists(csvPath);

        var records = GenerateRecords(results, imageFiles, startingIndex, totals, batch, imageLinks);

        var config = new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            HasHeaderRecord = !fileExists,
            Encoding = System.Text.Encoding.UTF8
        };

        using (var writer = new StreamWriter(csvPath, true, System.Text.Encoding.UTF8))
        using (var csv = new CsvWriter(writer, config))
        {
            if (!fileExists)
            {
                csv.WriteHeader<TableRecord>();
                csv.NextRecord();
            }

            csv.WriteRecords(records);
        }
    }

    public void WriteResultsToExcel(
        List<ResultData> results,
        string excelFileName,
        List<string> imageFiles,
        int startingIndex,
        BatchTotals totals,
        BatchInfo batch,
        Dictionary<string, ImageLinkPair> imageLinks
    )
    {
        string excelPath = Path.Combine(Application.streamingAssetsPath, excelFileName);
        var records = GenerateRecords(results, imageFiles, startingIndex, totals, batch, imageLinks);

        var workbook = File.Exists(excelPath) ? new XLWorkbook(excelPath) : new XLWorkbook();
        var worksheet = workbook.Worksheets.Contains("Results")
            ? workbook.Worksheet("Results")
            : workbook.Worksheets.Add("Results");

        int currentRow = worksheet.LastRowUsed()?.RowNumber() + 1 ?? 1;

        if (currentRow == 1)
        {
            // Записываем заголовки
            worksheet.Cell(currentRow, 1).InsertTable(new List<TableRecord>());
            currentRow++;
        }

        foreach (var record in records)
        {
            worksheet.Cell(currentRow++, 1).InsertData(new[] { record });
        }

        workbook.SaveAs(excelPath);
        stateManager.SaveTotals();
    }

    private List<TableRecord> GenerateRecords(
        List<ResultData> results,
        List<string> imageFiles,
        int startingIndex,
        BatchTotals totals,
        BatchInfo batch,
        Dictionary<string, ImageLinkPair> imageLinks // Обновлено
    )
    {
        var records = new List<TableRecord>();
        var resultsDict = results.ToDictionary(res => res.CustomId, res => res);
        var metadataDict = batch.ImageMetadatas.ToDictionary(meta => meta.Filename, meta => meta);

        for (int index = 0; index < imageFiles.Count; index++)
        {
            string filename = Path.GetFileName(imageFiles[index]);
            string taskId =
                Path.GetFileNameWithoutExtension(filename)
                    .Replace("-origin", ""); // Убираем суффикс '-origin' если присутствует
            string graveID = taskId;

            var record = new TableRecord
            {
                Index = startingIndex + index,
                Region = batch.Region,
                City = batch.City,
                Cemetery = batch.Cemetery,
                BatchCreationDate = batch.CreationDate.ToString("yyyy-MM-dd HH:mm:ss"),
                BurialID = graveID,
                Filename = filename
            };

            if (imageLinks.TryGetValue(filename, out var linkPair))
            {
                record.PreparedImageURL = linkPair.PreparedImageURL ?? "-";
                record.OriginalImageURL = linkPair.OriginalImageURL ?? "-";
            }
            else
            {
                record.PreparedImageURL = "-";
                record.OriginalImageURL = "-";
            }

            if (metadataDict.TryGetValue(filename, out var metadata))
            {
                record.Latitude = metadata.Latitude?.ToString() ?? "-";
                record.Longitude = metadata.Longitude?.ToString() ?? "-";
            }
            else
            {
                record.Latitude = "-";
                record.Longitude = "-";
            }

            if (resultsDict.TryGetValue(taskId, out var res) && !res.IsError && !string.IsNullOrEmpty(res.Content))
            {
                try
                {
                    string content = res.Content.Trim('`', 'j', 's', 'o', 'n');

                    var dataList = content.StartsWith("[")
                        ? JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(content)
                        : new List<Dictionary<string, string>>
                            { JsonConvert.DeserializeObject<Dictionary<string, string>>(content) };

                    foreach (var data in dataList)
                    {
                        record.LastName = FormatName(data.GetValueOrDefault("фамилия", "-"));
                        record.FirstName = FormatName(data.GetValueOrDefault("имя", "-"));
                        record.MiddleName = FormatName(data.GetValueOrDefault("отчество", "-"));
                        record.BirthDate = data.GetValueOrDefault("дата рождения", "-");
                        record.DeathDate = data.GetValueOrDefault("дата смерти", "-");
                        record.Status = data.GetValueOrDefault("статус", "Обработка");
                    }

                    record.PromptTokens = res.PromptTokens;
                    record.CompletionTokens = res.CompletionTokens;
                    record.CachedTokens = res.CachedTokens;
                    record.TotalTokens = res.TotalTokens;
                    record.Cost = CalculateCost(res.PromptTokens, res.CompletionTokens);

                    totals.PromptTokens += res.PromptTokens;
                    totals.CompletionTokens += res.CompletionTokens;
                    totals.CachedTokens += res.CachedTokens;
                    totals.TotalTokens += res.TotalTokens;
                    totals.Cost += record.Cost;
                }
                catch (Exception e)
                {
                    Debug.LogError($"Ошибка парсинга контента для {filename}: {e.Message}");
                    record.Status = "Ошибка";
                }
            }
            else
            {
                Debug.LogError($"Пустая строка парсинга контента для {filename}");
                record.Status = "Обработка";
            }

            records.Add(record);
        }

        return records;
    }

    private string FormatName(string name)
    {
        return string.IsNullOrEmpty(name) || name == "-" ? "-" : char.ToUpper(name[0]) + name.Substring(1).ToLower();
    }

    private float CalculateCost(int promptTokens, int completionTokens)
    {
        const float PROMPT_COST_PER_1000 = 0.00125f;
        const float COMPLETION_COST_PER_1000 = 0.005f;
        return ((promptTokens / 1000f) * PROMPT_COST_PER_1000) +
               ((completionTokens / 1000f) * COMPLETION_COST_PER_1000);
    }
}

public class ResultData
{
    public string CustomId;
    public string Content;
    public int PromptTokens;
    public int CompletionTokens;
    public int CachedTokens;
    public int TotalTokens;
    public bool IsError = false;
}

public class TableRecord
{
    public int Index { get; set; }
    public string Region { get; set; }
    public string City { get; set; }
    public string Cemetery { get; set; }
    public string BatchCreationDate { get; set; }
    public string BurialID { get; set; }
    public string Filename { get; set; }
    public string PreparedImageURL { get; set; }
    public string OriginalImageURL { get; set; }
    public string Latitude { get; set; } = "-";
    public string Longitude { get; set; } = "-";
    public string LastName { get; set; } = "-";
    public string FirstName { get; set; } = "-";
    public string MiddleName { get; set; } = "-";
    public string BirthDate { get; set; } = "-";
    public string DeathDate { get; set; } = "-";
    public string Status { get; set; } = "Обработка";
    public int PromptTokens { get; set; } = 0;
    public int CompletionTokens { get; set; } = 0;
    public int CachedTokens { get; set; } = 0;
    public int TotalTokens { get; set; } = 0;
    public float Cost { get; set; } = 0.0f;
} а вот откуда вызывается код 
    private IEnumerator HandleBatchCompletion(BatchInfo batch)
    {
        int batchNum = batch.BatchNum;

        bool statusSuccess = false;
        Dictionary<string, object> statusInfo = null;

        // Получаем информацию о статусе Batch Job
        yield return openAIClient.CheckBatchJobStatus(batch.JobId, info =>
        {
            statusInfo = info;
            statusSuccess = true;
        }, error => { Debug.LogError($"Ошибка получения информации о Batch Job {batchNum}: {error}"); });

        if (!statusSuccess)
            yield break;

        // Проверяем наличие 'output_file_id' или 'error_file_id'
        string resultFileId = null;
        bool isErrorFile = false;

        if (statusInfo.TryGetValue("output_file_id", out object outputFileIdObj) && outputFileIdObj != null)
        {
            resultFileId = outputFileIdObj.ToString();
            Debug.Log($"Batch Job {batchNum}: найден 'output_file_id' = {resultFileId}");
        }
        else if (statusInfo.TryGetValue("error_file_id", out object errorFileIdObj) && errorFileIdObj != null)
        {
            resultFileId = errorFileIdObj.ToString();
            isErrorFile = true;
            Debug.LogWarning($"Batch Job {batchNum}: найден 'error_file_id' = {resultFileId}");
        }
        else
        {
            Debug.LogError(
                $"Batch Job {batchNum}: ни 'output_file_id', ни 'error_file_id' не найдены в информации о статусе задачи.");
            yield break;
        }

        // Скачиваем файл результатов или ошибок
        byte[] fileContent = null;
        bool downloadSuccess = false;
        yield return openAIClient.DownloadFile(resultFileId, data =>
        {
            fileContent = data;
            downloadSuccess = true;
        }, error => { Debug.LogError($"Ошибка загрузки файла для Batch Job {batchNum}: {error}"); });

        if (!downloadSuccess)
            yield break;

        if (isErrorFile)
        {
            // Обрабатываем файл с ошибками
            string errorText = System.Text.Encoding.UTF8.GetString(fileContent);
            Debug.LogError($"Содержимое файла с ошибкой для Batch Job {batchNum}:\n{errorText}");

            // Сохраняем файл с ошибкой (опционально)
            string errorFilePath = Path.Combine(Application.persistentDataPath, $"batch_{batchNum}_error.log");
            File.WriteAllText(errorFilePath, errorText);
            Debug.Log($"Файл с ошибкой сохранён по пути: {errorFilePath}");
        }
        else
        {
            // Обработка результатов
            var results = resultParser.ParseResults(fileContent);

            // **Загрузка изображений на Selectel и получение ссылок**
            // Получаем экземпляр SelectelStorageManager
            SelectelStorageManager storageManager = FindObjectOfType<SelectelStorageManager>();
            if (storageManager == null)
            {
                Debug.LogError("SelectelStorageManager не найден.");
                yield break;
            }

            // Создаём словарь для хранения ссылок на изображения
            // Используем класс ImageLinkPair для хранения двух ссылок
            Dictionary<string, ImageLinkPair> imageLinks = new Dictionary<string, ImageLinkPair>();

            for (int i = 0; i < batch.FilePaths.Count; i++)
            {
                string preparedFilePath = batch.FilePaths[i];
                string preparedFileName = Path.GetFileName(preparedFilePath);

                // Создаём экземпляр ImageLinkPair для текущего файла
                ImageLinkPair linkPair = new ImageLinkPair();

                // Загружаем подготовленное изображение и получаем ссылку
                yield return StartCoroutine(storageManager.UploadFileAndGetUrl(preparedFilePath, "prepared", url =>
                {
                    linkPair.PreparedImageURL = url;
                    Debug.Log(
                        $"Файл '{preparedFileName}' загружен на Selectel (подготовленное). URL: {linkPair.PreparedImageURL}");
                }, error =>
                {
                    Debug.LogError($"Ошибка загрузки файла '{preparedFileName}' на Selectel (подготовленное): {error}");
                    linkPair.PreparedImageURL = "Ошибка загрузки";
                }));

                // Определяем путь к оригинальному файлу (предполагается, что он находится в originalDir и имеет суффикс '-origin')
                string originalFileName = Path.GetFileNameWithoutExtension(preparedFileName) + "-origin" +
                                          Path.GetExtension(preparedFileName);
                string originalFilePath = Path.Combine(originalDir, originalFileName);

                // Проверяем наличие оригинального файла
                if (File.Exists(originalFilePath))
                {
                    // Загружаем оригинальное изображение и получаем ссылку
                    yield return StartCoroutine(storageManager.UploadFileAndGetUrl(originalFilePath, "original", url =>
                    {
                        linkPair.OriginalImageURL = url;
                        Debug.Log(
                            $"Оригинальный файл '{originalFileName}' загружен на Selectel. URL: {linkPair.OriginalImageURL}");
                    }, error =>
                    {
                        Debug.LogError(
                            $"Ошибка загрузки оригинального файла '{originalFileName}' на Selectel: {error}");
                        linkPair.OriginalImageURL = "Ошибка загрузки";
                    }));
                }
                else
                {
                    Debug.LogWarning($"Оригинальный файл не найден: {originalFilePath}");
                    linkPair.OriginalImageURL = "Файл не найден";
                }

                // Добавляем пару ссылок в словарь
                imageLinks[preparedFileName] = linkPair;
            }

            // Записываем результаты в CSV и Excel, передавая ссылки на изображения
            List<string> batchFilenames = new List<string>();
            foreach (string path in batch.FilePaths)
            {
                batchFilenames.Add(Path.GetFileName(path));
            }

            resultParser.WriteResultsToCsv(
                results,
                "results.csv",
                batchFilenames,
                batch.StartingIndex,
                stateManager.Totals,
                batch,
                imageLinks
            );

            resultParser.WriteResultsToExcel(
                results,
                "results.xlsx",
                batchFilenames,
                batch.StartingIndex,
                stateManager.Totals,
                batch,
                imageLinks
            );

            // Обновляем UI
            mainUI.UpdateBatchInfoUI(batch);

            Debug.Log("Данные успешно загружены и результаты обновлены.");
        }
    }