короче я понял ошибку он каждый новый батч начинает считать custom_id с 0 тоесть если 3 батча то каждый батч будет с 0 1 2 3 а потом вот тут стираются данные с одиннаковыми номерами 
# Функция для записи результатов в CSV с форматированием имен
def write_results_to_csv(results, csv_path, image_files):
    # Создаем словарь для быстрого доступа по custom_id
    results_dict = {res['custom_id']: res for res in results}

    # Открываем CSV файл
    with open(csv_path, 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.writer(csvfile)
        # Записываем заголовок
        writer.writerow(['Filename', 'Фамилия', 'Имя', 'Отчество', 'Дата рождения', 'Дата смерти', 'Статус'])

        for index, filename in enumerate(image_files):
            task_id = f"task-{index}"
            res = results_dict.get(task_id, None)

            if res and 'response' in res:
                try:
                    content = res['response']['body']['choices'][0]['message']['content']
                    # Удаляем тройные обратные кавычки, если они есть
                    content = content.strip('```json').strip('```').strip()
                    parsed = json.loads(content)

                    # Проверяем, является ли parsed списком и содержит ли он элементы
                    if isinstance(parsed, list) and len(parsed) > 0:
                        parsed_data = parsed[0]  # Берём первый объект из списка
                    elif isinstance(parsed, dict):
                        parsed_data = parsed  # Если это словарь, используем его напрямую
                    else:
                        parsed_data = {}  # В противном случае, пустой словарь

                    # Извлекаем данные из parsed_data с форматированием имен
                    last_name = format_name(parsed_data.get('фамилия', '-'))
                    first_name = format_name(parsed_data.get('имя', '-'))
                    middle_name = format_name(parsed_data.get('отчество', '-'))
                    birth_date = parsed_data.get('дата рождения', '-')
                    death_date = parsed_data.get('дата смерти', '-')
                    status = parsed_data.get('статус', 'Обработка')

                except Exception as e:
                    logging.error(f"Ошибка парсинга результата для {filename}: {e}")
                    last_name = first_name = middle_name = birth_date = death_date = '-'
                    status = 'Обработка'
            else:
                # Если результат отсутствует или возникла ошибка
                last_name = first_name = middle_name = birth_date = death_date = '-'
                status = 'Обработка'

            writer.writerow([filename, last_name, first_name, middle_name, birth_date, death_date, status])
 полная херня, давай чтобы custom_id продолжался а не начинался с 0 каждый батч обработанный import openai
import base64
import requests
import os
import csv
import json
import re
from configparser import ConfigParser
import time
import logging
from PIL import Image, ImageOps
import shutil


# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s %(levelname)s:%(message)s',
    handlers=[
        logging.FileHandler("batch_processing.log"),
        logging.StreamHandler()
    ]
)

# Настройка OpenAI API
def load_api_key(config_path='config.ini'):
    config = ConfigParser()
    config.read(config_path)
    return config.get('openai', 'api_key')

# Функция для кодирования изображения в base64
def encode_image(image_path):
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')

# Функция для создания задач для Batch API
def create_batch_tasks(image_files, directory_path, prompt_template):
    tasks = []
    for index, filename in enumerate(image_files):
        image_path = os.path.join(directory_path, filename)
        base64_image = encode_image(image_path)
        
        task = {
            "custom_id": f"task-{index}",
            "method": "POST",
            "url": "/v1/chat/completions",
            "body": {
                "model": "gpt-4o",
                "messages": [
                    {
                        "role": "user",
                        "content": [
                            {"type": "text", "text": prompt_template},
                            {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}", "detail": "high"}}
                        ]
                    }
                ],
                "max_tokens": 300
            }
        }
        tasks.append(task)
    return tasks

# Функция для создания JSONL файла
def write_jsonl_file(tasks, file_path):
    with open(file_path, 'w', encoding='utf-8') as f:
        for task in tasks:
            f.write(json.dumps(task, ensure_ascii=False) + '\n')

# Функция для загрузки файла на OpenAI
def upload_file(file_path, api_key):
    headers = {
        "Authorization": f"Bearer {api_key}"
    }
    with open(file_path, "rb") as f:
        response = requests.post(
            "https://api.openai.com/v1/files",
            headers=headers,
            files={
                'file': (os.path.basename(file_path), f, 'application/jsonl')
            },
            data={
                'purpose': 'batch'
            }
        )
    response.raise_for_status()
    return response.json()['id']

# Функция для создания Batch Job
def create_batch_job(file_id, api_key):
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    payload = {
        "input_file_id": file_id,
        "endpoint": "/v1/chat/completions",
        "completion_window": "24h"  # Максимальное время выполнения
    }
    response = requests.post(
        "https://api.openai.com/v1/batches",
        headers=headers,
        json=payload
    )
    response.raise_for_status()
    return response.json()['id']

# Функция для проверки статуса Batch Job
def check_batch_job_status(job_id, api_key):
    headers = {
        "Authorization": f"Bearer {api_key}"
    }
    response = requests.get(
        f"https://api.openai.com/v1/batches/{job_id}",
        headers=headers
    )
    response.raise_for_status()
    return response.json()

# Функция для загрузки результатов Batch Job
def download_batch_results(result_file_id, api_key):
    headers = {
        "Authorization": f"Bearer {api_key}"
    }
    response = requests.get(
        f"https://api.openai.com/v1/files/{result_file_id}/content",
        headers=headers
    )
    response.raise_for_status()
    return response.content

# Функция для парсинга результатов
def parse_results(result_content):
    results = []
    for line in result_content.decode('utf-8').splitlines():
        results.append(json.loads(line))
    return results

# Функция для записи результатов в CSV с форматированием имен
def write_results_to_csv(results, csv_path, image_files):
    # Создаем словарь для быстрого доступа по custom_id
    results_dict = {res['custom_id']: res for res in results}

    # Открываем CSV файл
    with open(csv_path, 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.writer(csvfile)
        # Записываем заголовок
        writer.writerow(['Filename', 'Фамилия', 'Имя', 'Отчество', 'Дата рождения', 'Дата смерти', 'Статус'])

        for index, filename in enumerate(image_files):
            task_id = f"task-{index}"
            res = results_dict.get(task_id, None)

            if res and 'response' in res:
                try:
                    content = res['response']['body']['choices'][0]['message']['content']
                    # Удаляем тройные обратные кавычки, если они есть
                    content = content.strip('```json').strip('```').strip()
                    parsed = json.loads(content)

                    # Проверяем, является ли parsed списком и содержит ли он элементы
                    if isinstance(parsed, list) and len(parsed) > 0:
                        parsed_data = parsed[0]  # Берём первый объект из списка
                    elif isinstance(parsed, dict):
                        parsed_data = parsed  # Если это словарь, используем его напрямую
                    else:
                        parsed_data = {}  # В противном случае, пустой словарь

                    # Извлекаем данные из parsed_data с форматированием имен
                    last_name = format_name(parsed_data.get('фамилия', '-'))
                    first_name = format_name(parsed_data.get('имя', '-'))
                    middle_name = format_name(parsed_data.get('отчество', '-'))
                    birth_date = parsed_data.get('дата рождения', '-')
                    death_date = parsed_data.get('дата смерти', '-')
                    status = parsed_data.get('статус', 'Обработка')

                except Exception as e:
                    logging.error(f"Ошибка парсинга результата для {filename}: {e}")
                    last_name = first_name = middle_name = birth_date = death_date = '-'
                    status = 'Обработка'
            else:
                # Если результат отсутствует или возникла ошибка
                last_name = first_name = middle_name = birth_date = death_date = '-'
                status = 'Обработка'

            writer.writerow([filename, last_name, first_name, middle_name, birth_date, death_date, status])

# Функция для форматирования имен: первая буква заглавная, остальные строчные
def format_name(name):
    if name == '-' or not name:
        return '-'
    return name.capitalize()

# Функция для сжатия изображений и удаления метаданных
def compress_and_remove_metadata(input_path, output_path, quality=85):
    try:
        with Image.open(input_path) as img:
            # Корректировка ориентации изображения согласно EXIF
            img = ImageOps.exif_transpose(img)

            # Сжатие изображения и удаление всех метаданных
            img.save(output_path, "JPEG", quality=quality, optimize=True)
    except Exception as e:
        logging.error(f"Ошибка при обработке изображения {input_path}: {e}")

# Функция для загрузки промпта из файла
def load_prompt_from_file(prompt_file_path):
    try:
        with open(prompt_file_path, 'r', encoding='utf-8') as file:
            return file.read()
    except Exception as e:
        logging.error(f"Ошибка загрузки промпта из файла: {e}")
        return None

# Функция для разделения файлов на батчи с ограничением по размеру
def get_batches(file_paths, max_size_mb=100):
    batches = []
    current_batch = []
    current_size = 0

    for file in file_paths:
        size_mb = os.path.getsize(file) / (1024 * 1024)
        if current_size + size_mb > max_size_mb:
            if current_batch:
                batches.append(current_batch)
            current_batch = [file]
            current_size = size_mb
        else:
            current_batch.append(file)
            current_size += size_mb

    if current_batch:
        batches.append(current_batch)

    return batches

# Основная функция
def main():
    # Загрузка API ключа
    try:
        api_key = load_api_key()
    except Exception as e:
        logging.error(f"Ошибка загрузки API ключа: {e}")
        return

    # Путь к папке с оригинальными изображениями
    original_dir = os.path.join(os.getcwd(), 'photos')

    # Путь к папке для подготовленных изображений
    prepared_dir = os.path.join(os.getcwd(), 'photos_prepared')
    os.makedirs(prepared_dir, exist_ok=True)

    # Проверка существования папки с изображениями
    if not os.path.exists(original_dir):
        logging.error(f"Папка с изображениями '{original_dir}' не существует.")
        return

    # Список файлов оригинальных изображений
    image_files = [f for f in os.listdir(original_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg'))]

    if not image_files:
        logging.info("Нет файлов для обработки в папке 'photos2'.")
        return

    # Загрузка и сжатие изображений, удаление метаданных
    logging.info("Сжатие изображений и удаление метаданных...")
    prepared_files = []
    for filename in image_files:
        original_path = os.path.join(original_dir, filename)
        prepared_path = os.path.join(prepared_dir, f"prepared_{filename}")
        compress_and_remove_metadata(original_path, prepared_path)
        prepared_files.append(prepared_path)

    # Загрузка промпта из файла
    prompt_file_path = 'prompt.txt'
    prompt_template = load_prompt_from_file(prompt_file_path)
    if not prompt_template:
        logging.error("Промпт не был загружен. Завершение работы.")
        return

    # Разделение подготовленных файлов на батчи
    logging.info("Разделение файлов на батчи...")
    batches = get_batches(prepared_files, max_size_mb=1)
    logging.info(f"Всего батчей: {len(batches)}")

    # Переменная для хранения результатов всех батчей
    all_results = []

    # Обработка каждого батча по очереди
    for batch_num, batch_files in enumerate(batches, start=1):
        logging.info(f"Обработка батча {batch_num}/{len(batches)} с {len(batch_files)} файлами...")

        # Создание задач для текущего батча
        tasks = create_batch_tasks([os.path.basename(f) for f in batch_files], prepared_dir, prompt_template)

        # Запись задач в JSONL файл
        tasks_file_path = f"data/batch_tasks_{batch_num}.jsonl"
        os.makedirs(os.path.dirname(tasks_file_path), exist_ok=True)
        logging.info(f"Запись задач в файл {tasks_file_path}...")
        write_jsonl_file(tasks, tasks_file_path)

        # Загрузка файла на OpenAI
        logging.info("Загрузка задач на OpenAI...")
        try:
            batch_file_id = upload_file(tasks_file_path, api_key)
            logging.info(f"Файл батча {batch_num} загружен. ID файла: {batch_file_id}")
        except Exception as e:
            logging.error(f"Ошибка загрузки файла батча {batch_num} на OpenAI: {e}")
            continue  # Переходим к следующему батчу

        # Создание Batch Job
        logging.info("Создание Batch Job...")
        try:
            batch_job_id = create_batch_job(batch_file_id, api_key)
            logging.info(f"Batch Job для батча {batch_num} создан. ID задания: {batch_job_id}")
        except Exception as e:
            logging.error(f"Ошибка создания Batch Job для батча {batch_num}: {e}")
            continue  # Переходим к следующему батчу

        # Отслеживание состояния задания
        logging.info("Отслеживание состояния Batch Job...")
        while True:
            try:
                status_info = check_batch_job_status(batch_job_id, api_key)
                status = status_info.get('status')
                logging.info(f"Текущий статус батча {batch_num}: {status}")
                if status in ['completed', 'failed']:
                    break
                time.sleep(5)  # Ждем 60 секунд перед следующей проверкой
            except Exception as e:
                logging.error(f"Ошибка проверки статуса Batch Job для батча {batch_num}: {e}")
                break  # Переходим к следующему батчу

        if status != 'completed':
            logging.error(f"Batch Job батча {batch_num} завершилось с ошибкой.")
            logging.error(status_info)
            continue  # Переходим к следующему батчу

        logging.info(f"Batch Job батча {batch_num} успешно завершено.")
        logging.info(status_info)

        # Получение результатов
        result_file_id = status_info.get('output_file_id')
        if not result_file_id:
            logging.error(f"Не найден 'output_file_id' в статусе Batch Job для батча {batch_num}.")
            continue  # Переходим к следующему батчу

        logging.info(f"Загрузка результатов из файла ID: {result_file_id} для батча {batch_num}...")
        try:
            result_content = download_batch_results(result_file_id, api_key)
        except Exception as e:
            logging.error(f"Ошибка загрузки результатов Batch Job для батча {batch_num}: {e}")
            continue  # Переходим к следующему батчу

        logging.info(result_content)

        # Парсинг результатов
        logging.info("Парсинг результатов...")
        try:
            results = parse_results(result_content)
            all_results.extend(results)
        except Exception as e:
            logging.error(f"Ошибка парсинга результатов батча {batch_num}: {e}")
            continue  # Переходим к следующему батчу

    if not all_results:
        logging.error("Нет результатов для записи в CSV.")
        return

    # Запись всех результатов в единый CSV файл
    csv_output_path = "results.csv"
    logging.info(f"Запись всех результатов в CSV файл: {csv_output_path}...")
    write_results_to_csv(all_results, csv_output_path, prepared_files)

    logging.info("Обработка завершена успешно.")

if __name__ == "__main__":
    main()