import requests #для запроса к API
import xml.etree.ElementTree #для обработки xml-ответа API
import matplotlib.pyplot as plt #для построения графиков
import pandas as pd #для создания датафрейма и разденеия всех свечей на два типа: close и open
import datetime #для дат по оси иксов
import pickle #для хранения переменных в файле
import json #для работы с датабазой

#нет проблем с инструментами теханализа и пустыми днями (без торгов), тк дни без торгов в датафрейме не нули, а просто не существуют. Поэтому они не портят значения индикаторов
#класс тикер, методы график и тек. цена
class ticker():
    """Тикер акции и всё с ним связанное, через MoexApi \n
    Требуются библеотеки: \n
    requests \n
    xml.etree.ElementTree \n
    matplotlib.pyplot as plt \n
    pandas as pd \n
    datetime \n
    pickle \n
    json \n
    """
    def __init__(self, name: str):
        """self.name - имя тикера
        self.tech_dict - словарь теханализа"""
        self.name = name
        """Имя тикера, то есть сам по себе тикер"""
        #в принципе тут можно менять общие для всех юзеров настройки по умолчанию. Потенциально надо через это реализовать кастомные инструменты теханализа
        self.tech_dict = {"value" : {"use" : False, "has_periods" : False, "need_graph_space" : True},
                          "sma" : {"use" : False, "has_periods" : True, "periods" : [], "need_graph_space" : False},
                          "ema" : {"use" : False, "has_periods" : True, "periods" : [],"need_graph_space" : False}
                          }
        """Словарь реализованных опций теханализа. Имеет вид \n
        {"sma": {"use": True, "periods": [20, 50], "need_graph_space": False}, "rsi": {"use": True, "periods": [10], "need_graph_space": True}} \n
        Где use отвечает за использование, period - список периодов, по которым будут считаться значения, need_graph_space за то, требует ли осциллятор доп места на графике \n
        Изначально все use имеют значение False, а списки периодов пусты \n \n
        При реализации нового инструмента теханализа достаточно дописать его в self.tech_dict \n
        При этом функцию, соответствующую этому инструменту важно назвать также, как и сам инструмент в словаре. А её аргументы - self и ax (редактируемый/заполняемый график) \n
        Доп графики инструментов теханализа, которые их требуют, будут отображаться в таком же порядке, в котором инструменты располагаются в словаре. Также в этом порядке будут высвечиваться кнопки в боте и уже выбранные инструменты теханализа"""
    def correct_name(self):
        """Проверка имени тикера на наличие в множестве тикеров. Множество обновляется не чаще раза в день"""
        info_opened_file = open(r"D:\MoexAPI_bot_aiogram3\data_files\Info.json", "r", encoding="utf-8") #открываем файл инфы, encoding чтобы не было
        info = json.load(info_opened_file)
        info_opened_file.close()
        if datetime.datetime.now() - datetime.timedelta(days=1) > datetime.datetime.strptime(info["last_day_check"]["ticker"], "%Y-%m-%d %H:%M:%S.%f"): #проверяем условие что дата перезаписи списка тикеров это хотя бы 1 день назад
            #если отличается более чем на 1 день, то переписываем список (множество) тикеров:
            set_tickers = set() #создаём пустое множество, в него будем заливать тикеры
            s = "https://iss.moex.com/iss/engines/stock/markets/shares/boards/TQBR/securities.xml?iss.meta=off"
            r = requests.get(s)
            root = xml.etree.ElementTree.fromstring(r.content) #запрос всё равно выдаёт данные сайта как строку, так что без fromstring никак
            for data in root.findall("data"):
                if data.get("id") == "securities":
                    rows = data.find("rows")
                    for row in rows.findall("row"):
                        set_tickers.add(row.get("SECID")) #заливаем тикеры в наше множество
            set_tickers_file_opened = open(r"D:\MoexAPI_bot_aiogram3\data_files\set_tickers.bin", "wb") #открываем файл для бинарной записи множества тикеров в него
            pickle.dump(set_tickers, set_tickers_file_opened) #закидываем созданное множество в файл. Если что, каждый раз будет перезаписываться (проверено)
            set_tickers_file_opened.close() #закрываем файл
            #поменяем время последнего обновления
            info["last_day_check"]["ticker"] = str(datetime.datetime.now())
            info_opened_file = open(r"D:\MoexAPI_bot_aiogram3\data_files\Info.json", "w", encoding="utf-8")
            json.dump(info, info_opened_file, indent = 3, ensure_ascii = False) #запишем новый файл
            info_opened_file.close()
        #теперь просто проверим есть ли тикер в списке тикеров
        set_tickers_file_opened = open(r"D:\MoexAPI_bot_aiogram3\data_files\set_tickers.bin", "rb") #открываем файл с множеством тикеров чтобы его оттуда получить
        set_tickers = pickle.load(set_tickers_file_opened) #из открытого файла выгружаем значение множества тикеров в переменную. Если вдруг запишется несколько множеств (такого быть не должно), то откроется только первое из них
        if self.name in set_tickers: #просто проверяем есть ли тикер в множестве тикеров
            return True
        else:
            return False
    def CurrentPrice(self):
        """Текущая цена по этому тикеру"""
        s = "https://iss.moex.com/iss/engines/stock/markets/shares/boards/TQBR/securities/" + self.name + ".xml?iss.meta=off"
        r = requests.get(s) #получаем r в формате xml, r.status_code - код ответа, r.content - содержимое ответа строкой, r.text - содержимое в виде текста
        root = xml.etree.ElementTree.fromstring(r.content) #берём именно контент из r и суём в переменную. Помимо контента r несёт как минимум инфу о состоянии запроса (ошибка 404, всё ок 400 и тд)
        for data in root.findall("data"): #внутри root находим все контейнеры data и проходимся по ним
            if data.get("id") == "marketdata": #внутри data по которому проходимся смотрим атрибут id и хотим чтобы он был marketdata
                rows = data.find("rows") #внутри rows находим первый контейнер с тэгом row
                row = rows.find("row") #внутри rows несколько row, ищем именно тот, который с tqbr
                return(row.get("LAST")) #return оборвёт циклы, поэтому тут проверки найдена ли инфа не нужны
    def candles(self, candles_name: str, timeframe: str, start: str, end: str): #добавить временной диапозон
        """Лист свечей для этого тикера \n
        candles_name - необходимая составляющая свечей \n
        candles_name: open, close, high, low, value, volume, begin, end \n
        timeframe - таймфрейм: 1 - 1 мин, 10 - 10 мин, 60 - 1ч, 24 - 1д, 7 - 1н, 31 - 1мес, 4 - 4мес \n
        start, end - начало и конец периода, формат ГГГГ-ММ-ДД ЧЧ:ММ:СС
        """
        s = "https://iss.moex.com/iss/engines/stock/markets/shares/boards/TQBR/securities/" + self.name + f"/candles.xml?iss.meta=off&interval={timeframe}&till={end}&from={start}"
        r = requests.get(s)
        root = xml.etree.ElementTree.fromstring(r.content)
        candles = root.find("data")
        rows = candles.find("rows")
        listcandles = []
        if candles_name == "begin" or candles_name == "end": #для этих будем брать значения из iss в формате datetime подключенного модуля (дата и время)
            for row in rows.findall("row"):
                datetime_str = row.get(candles_name) #datetime_name сейчас строка в формате api
                #можно было бы datetime.datetime.strptime(), но там с годами не круто, не универсально. Поэтому так
                datetime_datetime = datetime.datetime(int(datetime_str[0:4]), int(datetime_str[5:7]), int(datetime_str[8:10]), int(datetime_str[11:13]), int(datetime_str[14:16]), int(datetime_str[17:])) #нарезаем строку с датой и временем на части даты и части времени,необходимые модулю datetime (год, месяц, день, час, минута, секунда). При этом не забывает всё сделать int
                listcandles.append(datetime_datetime)
        else:
            for row in rows.findall("row"):
                listcandles.append(float(row.get(candles_name)))#ВАЖЕН FLOAT, тк иначе импортируется строка,
                #а график строит строки тупо подряд, без адекватного выстроения значений по их значениям
        return(listcandles)
    def setattr_candles_dataframe(self, timeframe = str("24"), start = str(""), end = str("")):
        #создание датафрейма свечей как атрибута как минимум позволяет не передавать его каждый раз аргументом функции, накладывающей инструмент теханализа (тк она передаётся в self)
        """Создаёт датафрйм свечей с соответствующим timeframe, start и end и помещает в self.candles_dataframe \n
        Не при инициации, так как если тикер инициируется для получения текущей цены, нет причин делать лишние операции"""
                #создаём датафрейм всей инфы по свечам и заливаем её с помощью ранее написанного метода получения инфы по свечам
        candles_dataframe = pd.DataFrame({"open" :  self.candles("open", timeframe, start, end),
                                          "close" : self.candles("close", timeframe, start, end),
                                          "high" : self.candles("high", timeframe, start, end),
                                          "low" : self.candles("low", timeframe, start, end),
                                          "value" : self.candles("value", timeframe, start, end),
                                          "begin" : self.candles("begin", timeframe, start, end)
                                          #"end" вроде не нужно, бегина хватает
                                        })
        setattr(self, "candles_dataframe", candles_dataframe)
    def graphic(self, timeframe = str("24"), start = str(""), end = str("")):
        """возвращает открытый свечной график цены от времени \n
        timeframe - таймфрейм: 1 - 1 мин, 10 - 10 мин, 60 - 1ч, 24 - 1д, 7 - 1н, 31 - 1мес, 4 - 4мес | None = 24 \n
        start, end - начало и конец периода, формат ГГГГ-ММ-ДД ЧЧ:ММ:СС  | None = "" \n
        sma - нужная ли sma, sma_periods - массив периодов sma  | None = False, [] \n
        ema - нужная ли ema, ema_periods - массив периодов ema  | None = False, []\n
        """
        #создадим нужный датафрейм
        self.setattr_candles_dataframe(timeframe, start, end)
        #делаем up и down - новые датафреймы, части старого, но удовлетворяющие определённым условиям
        up = self.candles_dataframe[self.candles_dataframe.close >= self.candles_dataframe.open]
        down = self.candles_dataframe[self.candles_dataframe.close < self.candles_dataframe.open]
        #запишем это как атрибуты, так как некоторым инструментам теханализа важно, какие свечи растут, а какие падают
        setattr(self, "up", up)
        setattr(self, "down", down)
        #создадим width_big и width_small - ширины свечей, зависящие от таймфрейма
        #судя по всему 1 день по оси x соответствует 1 единице толщины столбика на диаграмме (питон вероятно умный)
        #хотя на 4мес уже не работает, хотя странно, потому что для всех остальных работает
        #но во всяком случае от увеличения или уменьшения диапазона свечи не начинают наезжать/иметь большие промежутки. Значит ширина связана именно с датами
        if timeframe == "1": #минута
            width_big = 1/24/60
        elif timeframe == "10": #10 минут
            width_big = 1/24/6
        elif timeframe == "60": #час
            width_big = 1/24
        elif timeframe == "24": #день
            width_big = 1
        elif timeframe == "7": #неделя
            width_big = 7
        elif timeframe == "31": #месяц
            width_big = 30
        elif timeframe == "4": #4 месяца
            width_big = 90
        else:
            width_big = 0 #такое по идее не может произойти
        width_small = width_big/10
        setattr(self, "width_big", width_big) #засунем width_big в self, чтобы потом использовать в инструментах теханализа, изображающихся как bar graph
        #разберёмся с теханализом. Для начала поймём сколько доп графиков для них нужно
        number_of_additional_graphics = int(0)
        for tech in self.tech_dict:
            if self.tech_dict[tech]["use"] and self.tech_dict[tech]["need_graph_space"]: #если инструмент теханализа используется И если этому инструменту теханала нужно место под доп график, посчитаем его
                number_of_additional_graphics += 1
        #если 1 и более инструментов теханала хотят доп график
        if number_of_additional_graphics != 0:
            height_rations_list = [10 - number_of_additional_graphics] + [1] * number_of_additional_graphics #массив отношений высот графиков, зависящий от числа графиков. Потом передадим его в subplots. Имеет вид [8, 1, 1]
            fig, axs = plt.subplots(nrows = 1 + number_of_additional_graphics, ncols = 1, sharex = True, height_ratios = height_rations_list) #создаём subplots. fig - контейнер графиков, axs[i] - iй график
            plt.suptitle(self.name, fontsize = 15) #заголовок - имя тикера
            axs[0].grid(True) #сетка для упрощения восприятия графика
            #заполняем его свечами up
            #это столбчатая диаграмма; plt.bar(x = ось x, height = высота столбика, width = ширина столбика, bottom = нижняя координата столбика, хз дальше странная * и потом ещё что-то непонятное)
            #ещё есть аргумент color, но в официальной документации я не нашёл. Возможно это входит в странную *
            axs[0].bar(x = up.begin, height = up.close - up.open,  width = width_big, bottom = up.open, color = "green") #для уточнения какой именно аргумент функции пишем можно писать имя_аргумента = значение_которое_даём
            axs[0].bar(x = up.begin, height = up.high - up.close,  width = width_small, bottom = up.close, color = "green")
            axs[0].bar(x = up.begin, height = up.open - up.low,  width = width_small, bottom = up.low, color = "green")
            #заполняем свечами down
            axs[0].bar(x = down.begin, height = down.open - down.close,  width = width_big, bottom = down.close, color = "red")
            axs[0].bar(x = down.begin, height = down.high - down.open,  width = width_small, bottom = down.open, color = "red")
            axs[0].bar(x = down.begin, height = down.close - down.low,  width = width_small, bottom = down.low, color = "red")
            #добавляем на график инструменты теханализа
            for tech in self.tech_dict:
                if self.tech_dict[tech]["use"]: #если инструмент теханализа используется
                    if self.tech_dict[tech]["use"] and not self.tech_dict[tech]["need_graph_space"]: #если не требует доп графика, вызовем соответствующую функцию
                        tech_func = getattr(self, tech) #теперь tech_func - это фукнция того теханализа, имя которого сейчас несёт в себе tech
                        tech_func(axs[0])
                    else : #если требует доп график, то
                        for i in range(number_of_additional_graphics):
                            tech_func = getattr(self, tech) #теперь уже tech - название функции, которая требует доп график
                            axs[i + 1].grid(True) #включим сетку также на каждом доп графике
                            tech_func(axs[i + 1]) #для каждого нового инструмента используем новый график
        #если 0 инструментов теханала просят доп график
        else:
            fig = plt.figure() #создаём контейнер графиков
            plt.title(self.name, fontsize = 15) #заголовок - имя тикера
            ax = fig.add_subplot() #ax - это сам график
            ax.grid(True) #сетка для упрощения восприятия графика
            #заполняем его свечами up
            #это столбчатая диаграмма; plt.bar(x = ось x, height = высота столбика, width = ширина столбика, bottom = нижняя координата столбика, хз дальше странная * и потом ещё что-то непонятное)
            #ещё есть аргумент color, но в официальной документации я не нашёл. Возможно это входит в странную *
            ax.bar(x = up.begin, height = up.close - up.open,  width = width_big, bottom = up.open, color = "green") #для уточнения какой именно аргумент функции пишем можно писать имя_аргумента = значение_которое_даём
            ax.bar(x = up.begin, height = up.high - up.close,  width = width_small, bottom = up.close, color = "green")
            ax.bar(x = up.begin, height = up.open - up.low,  width = width_small, bottom = up.low, color = "green")
            #заполняем свечами down
            ax.bar(x = down.begin, height = down.open - down.close,  width = width_big, bottom = down.close, color = "red")
            ax.bar(x = down.begin, height = down.high - down.open,  width = width_small, bottom = down.open, color = "red")
            ax.bar(x = down.begin, height = down.close - down.low,  width = width_small, bottom = down.low, color = "red")
            #добавляем на график инструменты теханализа, не требующие доп графика (в данном разделе это все используемые инструменты, так как раньше было условие о том, что нет инструментов с доп графиком)
            for tech in self.tech_dict:
                if self.tech_dict[tech]["use"]: #если используется и не требует доп графика, вызовем соответствующую функцию
                    tech_func = getattr(self, tech) #теперь tech_func - это фукнция того теханализа, имя которого сейчас несёт в себе tech, при этом подвязанная к self. Иначе говоря её применение аналогично применению self.sma(...) при tech = sma
                    tech_func(ax)

        #сохраняем график как картинку и ретёрним её открытую для отправки
        fig.savefig(r"D:\Python files\!MoexApiBot\graphic.png")
        opened_graphic = open(r"D:\Python files\!MoexApiBot\graphic.png", "rb")
        return opened_graphic
    def sma(self, ax):
        for period in self.tech_dict["sma"]["periods"]: #для каждого нужного периода sma создадим список значений sma и докинем его в график
            if period <=  len(self.candles_dataframe.begin): #так как иначе при построении графика список оси x пуст, а оси y не пуст (потому что там есть база рекурренты)
                sma_list = [] #список значений sma (соответсует датам из датафрейма)
                sma_list.append(sum(self.candles_dataframe.close[0: period])/period) #делаем рекуррентой, чтобы не считать каждый раз большую сумму
                for i in range(period, len(self.candles_dataframe.begin)): #начало сдвинуто, тк sma считается не раньше чем из period свечей
                    sma_list.append(sma_list[i - period] + (self.candles_dataframe.close[i] - self.candles_dataframe.close[i - period])/period) #добавим новую свечу к прошлому значению sma и уберём самую старую
                ax.plot(self.candles_dataframe.begin[period - 1:], sma_list) #тут нужен срез по оси x, чтобы осциллятор начинался с даты, с которой мы его считаем
    def ema(self, ax):
        for period in self.tech_dict["ema"]["periods"]:
            if period <=  len(self.candles_dataframe.begin): #так как иначе при построении графика список оси x пуст, а оси y не пуст (потому что там есть база рекурренты)
                ema_list = []
                ema_list.append(sum(self.candles_dataframe.close[0: period])/period) #первое значение ema - это sma по тому же периоду
                for i in range(period, len(self.candles_dataframe.begin)):
                    ema_list.append(((period - 1)*ema_list[i - period] + 2 * self.candles_dataframe.close[i])/(period + 1))
                ax.plot(self.candles_dataframe.begin[period - 1:], ema_list)
    def value(self, ax):
        ax.bar(x = self.up.begin, height = self.up.value, width = self.width_big, color = "green")
        ax.bar(x = self.down.begin, height = self.down.value, width = self.width_big, color = "red")
        ax.set_title("Value", fontsize = 7)

"""
Тесты
"""

"""
beb = ticker("SBER")
beb.setattr_candles_dataframe("24", "2024-01-01", "2024-01-07")
print(beb.candles_dataframe)
"""

"""
beb.tech_dict["value"]["use"] = True
beb.graphic("24", "2024-01-01", "2024-10-01")
plt.show
"""

"""
beb = ticker("SBER")
beb.tech_dict["sma"]["use"] = True
beb.tech_dict["sma"]["periods"] = [20, 10]
beb.tech_dict["ema"]["use"] = True
beb.tech_dict["ema"]["periods"] = [150, 250]
beb.tech_dict["value"]["use"] = True
beb.graphic("24", "2024-01-01", "2024-05-01")
"""

Instruction: перепиши асинхронно

import pytest
import inspect
import os
import sys
from unittest.mock import patch, MagicMock, AsyncMock
import xml.etree.ElementTree as ET
from io import BytesIO, StringIO
import json
import pickle
import datetime
import tempfile
import re
import asyncio
import aiohttp

class AsyncContextManagerMock(AsyncMock):
    async def __aenter__(self):
        return self.aenter_return

    async def __aexit__(self, *args):
        pass

@pytest.fixture
def mock_files():
    """Create temporary files for testing"""
    with tempfile.TemporaryDirectory() as temp_dir:
        info_path = os.path.join(temp_dir, "Info.json")
        tickers_path = os.path.join(temp_dir, "set_tickers.bin")
        graphic_path = os.path.join(temp_dir, "graphic.png")

        # Create info.json
        info = {"last_day_check": {"ticker": (datetime.datetime.now() - datetime.timedelta(days=2)).strftime("%Y-%m-%d %H:%M:%S.%f")}}
        with open(info_path, "w", encoding="utf-8") as f:
            json.dump(info, f)

        # Create tickers bin
        tickers = {"SBER", "LKOH", "GAZP"}
        with open(tickers_path, "wb") as f:
            pickle.dump(tickers, f)

        # Return paths
        return {
            "info_path": info_path,
            "tickers_path": tickers_path,
            "graphic_path": graphic_path,
            "dir_path": temp_dir
        }

def get_ticker_class(implementation):
    """Helper function to safely get the ticker class from an implementation"""
    impl_name, module = implementation

    # Check if the module contains a ticker class
    for name, obj in inspect.getmembers(module):
        if inspect.isclass(obj) and name.lower() == "ticker":
            return obj

    # If no class is found with name 'ticker', look for any class definition
    for name, obj in inspect.getmembers(module):
        if inspect.isclass(obj) and obj.__module__ == module.__name__:
            return obj

    # If no class is found at all, raise a helpful exception
    raise ValueError(f"Could not find ticker class in implementation {impl_name}")

def test_has_required_imports(implementation):
    """Test whether the implementation has the required imports for async code"""
    test_impl_name, module = implementation

    # Get the source code
    try:
        source_code = inspect.getsource(module)
    except (TypeError, OSError):
        pytest.skip(f"Could not get source code for {test_impl_name}")

    # Make this test more lenient - check if any async library is imported or uses async syntax
    async_libraries = [
        "aiohttp", "aiofiles", "asyncio", "trio", "httpx",
        "AsyncClient", "ClientSession", "async with", "async def"
    ]

    # Check if any async library is imported or async syntax is used
    has_async_features = any(lib in source_code for lib in async_libraries)
    assert has_async_features, "No async libraries or syntax found. Expected at least one of: aiohttp, aiofiles, asyncio, or async syntax."

def test_has_async_correct_name_method(implementation):
    """Test whether the implementation has an asynchronous method for correct_name"""
    test_impl_name, module = implementation

    try:
        ticker_class = get_ticker_class(implementation)
    except ValueError:
        pytest.skip(f"Could not find ticker class in {test_impl_name}")

    # Skip if implementation doesn't have correct_name
    if not hasattr(ticker_class, "correct_name"):
        pytest.skip(f"Implementation {test_impl_name} doesn't have correct_name method")

    # Check if it's using async syntax or context manager
    try:
        source_code = inspect.getsource(ticker_class.correct_name)
        is_async_method = (
            "async def" in source_code or
            inspect.iscoroutinefunction(ticker_class.correct_name) or
            "async with" in source_code
        )

        assert is_async_method, "correct_name method should use async syntax or async context managers"
    except (TypeError, OSError):
        pytest.skip(f"Could not get source code for correct_name in {test_impl_name}")

def test_currentprice_method_is_not_async(implementation):
    """Test whether CurrentPrice is not async (no need for it to be async since it's used synchronously)"""
    test_impl_name, module = implementation

    try:
        ticker_class = get_ticker_class(implementation)
    except ValueError:
        pytest.skip(f"Could not find ticker class in {test_impl_name}")

    # Check if CurrentPrice is defined
    if not hasattr(ticker_class, "CurrentPrice"):
        pytest.skip(f"Implementation {test_impl_name} doesn't have CurrentPrice method")

    # Check if it's not an async method
    assert not inspect.iscoroutinefunction(ticker_class.CurrentPrice), "CurrentPrice method should not be async"

def test_implementation_functionality_preserved(implementation):
    """Test if the core functionality of the ticker class is preserved"""
    test_impl_name, module = implementation

    try:
        ticker_class = get_ticker_class(implementation)
    except ValueError:
        pytest.skip(f"Could not find ticker class in {test_impl_name}")

    # Patch requests functionality to avoid actual API calls
    with patch("requests.get") as mock_get:
        # Mock the response
        mock_response = MagicMock()
        mock_response.content = b'<data id="marketdata"><rows><row LAST="123.45"/></rows></data>'
        mock_get.return_value = mock_response

        # Create instance
        ticker_instance = ticker_class("SBER")

        # Test tech_dict structure
        assert hasattr(ticker_instance, "tech_dict"), "Missing tech_dict attribute"

        # Check tech_dict keys
        tech_dict = ticker_instance.tech_dict
        assert isinstance(tech_dict, dict), "tech_dict is not a dictionary"

        # Check at least some expected keys exist
        expected_keys = ["sma", "ema", "value"]
        found_keys = [key for key in expected_keys if key in tech_dict]
        assert found_keys, f"No expected tech_dict keys found. Expected at least one of: {expected_keys}"

        # Test methods exist
        assert hasattr(ticker_instance, "CurrentPrice"), "Missing CurrentPrice method"

        # Check if candles-related methods exist
        assert hasattr(ticker_instance, "candles"), "Missing candles method"
        assert hasattr(ticker_instance, "setattr_candles_dataframe"), "Missing setattr_candles_dataframe method"

def test_source_code_has_async_syntax(implementation):
    """Test if the implementation uses async/await syntax"""
    test_impl_name, module = implementation

    try:
        source_code = inspect.getsource(module)
    except (TypeError, OSError):
        pytest.skip(f"Could not get source code for {test_impl_name}")

    # Check for async/await syntax with more flexibility
    async_patterns = ["async def", "async with", "await ", "AsyncContextManager"]
    has_async_syntax = any(pattern in source_code for pattern in async_patterns)

    assert has_async_syntax, "No async syntax found in implementation. Expected 'async def', 'async with', or 'await'."

def test_async_file_operations(implementation):
    """Test if the implementation uses async file operations"""
    test_impl_name, module = implementation

    try:
        source_code = inspect.getsource(module)
    except (TypeError, OSError):
        pytest.skip(f"Could not get source code for {test_impl_name}")

    # Check for async file operations with more flexibility
    async_file_patterns = [
        "aiofiles",
        "async with.*open",
        "await.*file",
        "await.*read",
        "await.*write",
        "async.*file"
    ]

    # Allow more flexible pattern matching with regex
    file_operation_found = any(re.search(pattern, source_code, re.DOTALL) for pattern in async_file_patterns)

    assert file_operation_found, "No async file operations found in implementation"

@pytest.mark.asyncio
async def test_async_correct_name_implementation(implementation, mock_files):
    """Test if correct_name is properly implemented as an async function and works."""
    test_impl_name, module = implementation

    try:
        ticker_class = get_ticker_class(implementation)
    except ValueError:
        pytest.skip(f"Could not find ticker class in {test_impl_name}")

    if not hasattr(ticker_class, "correct_name") or not inspect.iscoroutinefunction(ticker_class.correct_name):
        pytest.skip(f"Implementation {test_impl_name} doesn't have an async correct_name method")

    # Set up mocks
    info_path = mock_files["info_path"]
    tickers_path = mock_files["tickers_path"]

    ticker_instance = ticker_class("SBER")

    # Create aiofiles mock for Info.json read
    aiofiles_open_mock = AsyncMock()
    file_mock = AsyncMock()
    file_mock.read.return_value = json.dumps({
        "last_day_check": {
            "ticker": (datetime.datetime.now() - datetime.timedelta(days=2)).strftime("%Y-%m-%d %H:%M:%S.%f")
        }
    })
    aiofiles_open_mock.return_value.__aenter__.return_value = file_mock

    # aiohttp mock
    session_mock = AsyncMock()
    response_mock = AsyncMock()
    response_mock.text.return_value = (
        '<data id="securities"><rows><row SECID="SBER"/><row SECID="LKOH"/></rows></data>'
    )
    session_mock.__aenter__.return_value.get.return_value.__aenter__.return_value = response_mock

    # Patch pickle
    pickle_dumps_mock = MagicMock()
    pickle_load_mock = MagicMock(return_value={"SBER", "GAZP", "LKOH"})

    with (
        patch('aiofiles.open', aiofiles_open_mock),
        patch('aiohttp.ClientSession', return_value=session_mock),
        patch('pickle.dump', pickle_dumps_mock),
        patch('pickle.load', pickle_load_mock),
        patch('json.loads', side_effect=json.loads)  # Correctly patch loads
    ):
        result = await ticker_instance.correct_name()

        # Assertions
        assert isinstance(result, bool), "correct_name should return a boolean"
        assert result is True, "correct_name should return True for SBER in set"

aiohttp
aiofiles
matplotlib
pandas
pytest
pytest-mock
pytest-asyncio

import pytest
import os
import sys
import json
from typing import Dict, List, Optional, Any

# Import from local test_utils.py in the same directory
from test_utils import TestUtils, TestResultsManager

# Load all implementations in the current sandbox
implementations = TestUtils.load_all_implementations()
test_results = TestResultsManager()

@pytest.fixture(scope="session")
def sandbox_dir():
    """Fixture to provide the sandbox directory path."""
    return os.path.dirname(os.path.abspath(__file__))

@pytest.fixture(scope="session")
def sandbox_name():
    """Fixture to provide the sandbox name."""
    return os.path.basename(os.path.dirname(os.path.abspath(__file__)))

@pytest.fixture(scope="session")
def all_implementations():
    """Fixture to provide all implementations as a dictionary."""
    return implementations

@pytest.fixture(params=list(implementations.items()))
def implementation(request):
    """Fixture to provide each implementation to tests one at a time."""
    return request.param

@pytest.fixture(scope="session")
def results_manager():
    """Fixture to provide access to the test results manager."""
    return test_results

# Hook for collecting test results
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """Pytest hook to collect test results."""
    # Execute all other hooks to obtain the report object
    outcome = yield
    rep = outcome.get_result()

    # We're only interested in the call outcome
    if rep.when == "call":
        if hasattr(item, "callspec") and "implementation" in item.callspec.params:
            # Get implementation name and module
            impl_name, _ = item.callspec.params["implementation"]

            # Get test name
            test_name = item.nodeid.split("::")[-1]

            # Record result
            if rep.passed:
                test_results.record_result(impl_name, test_name, True)
            elif rep.failed:
                error_msg = str(rep.longrepr) if rep.longrepr else "Test failed"
                test_results.record_result(impl_name, test_name, False, error_msg)
            elif rep.skipped:
                skip_reason = rep.longrepr[2] if rep.longrepr else "Test skipped"
                test_results.record_skip(impl_name, test_name, skip_reason)

# Hook to save results at the end of testing
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
    """Save test results at the end of the test session."""
    test_results.save_results()

import os
import sys
import glob
import re
import importlib.util
import traceback
import types
from typing import Dict, List, Optional, Any, Tuple

class TestUtils:
    @staticmethod
    def discover_implementation_files(directory: str = None) -> List[str]:
        """Find all implementation files in the current sandbox directory."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        patterns = [
            r'modified_code\d+\.py',
            r'new_code\d+\.py',
            r'original_code\.py',
            r'implementation\d*\.py'
        ]

        pattern = re.compile('|'.join(f'({p})' for p in patterns))
        implementations = []

        for file_path in glob.glob(os.path.join(directory, '*.py')):
            if pattern.search(os.path.basename(file_path)):
                implementations.append(file_path)

        # Sort files numerically
        def sort_key(path):
            filename = os.path.basename(path)
            match = re.search(r'(\d+)', filename)
            return int(match.group(1)) if match else 0

        return sorted(implementations, key=sort_key)

    @staticmethod
    def create_mock_module(file_path: str, module_name: str, error_info: str) -> types.ModuleType:
        """Create a mock module that contains error information but can still be tested."""
        # Create a new module object
        mock_module = types.ModuleType(module_name)

        # Add basic attributes
        mock_module.__file__ = file_path
        mock_module.__name__ = module_name
        mock_module.__display_name__ = module_name
        mock_module.__error__ = error_info

        # Add a dummy function that can be detected by test functions
        def dummy_function(*args, **kwargs):
            return f"Error in module: {error_info}"

        setattr(mock_module, "implementation_error", dummy_function)

        return mock_module

    @staticmethod
    def load_module(file_path: str, module_name: Optional[str] = None) -> Any:
        """
        Safely load a module from a file path with proper error handling.
        If the module has errors, return a mock module that can still be tested.
        """
        if module_name is None:
            module_name = os.path.basename(file_path).replace('.py', '')

        # Create a unique module name to avoid conflicts
        sandbox_id = os.path.basename(os.path.dirname(file_path))
        unique_module_name = f"{sandbox_id}_{module_name}"

        try:
            # First, try to read the file to check for syntax errors
            with open(file_path, 'r') as f:
                source_code = f.read()

            # Check for syntax errors by compiling the code
            try:
                compiled = compile(source_code, file_path, 'exec')
            except SyntaxError as e:
                error_msg = f"Syntax error: {str(e)}"
                print(f"Syntax error in {file_path}: {e}")
                print(f"  Line {e.lineno}, column {e.offset}: {e.text}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module spec
            spec = importlib.util.spec_from_file_location(unique_module_name, file_path)
            if spec is None or spec.loader is None:
                error_msg = f"Could not create spec for {file_path}"
                print(f"Error: {error_msg}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module object
            module = importlib.util.module_from_spec(spec)
            sys.modules[unique_module_name] = module

            # Special handling for execution errors
            try:
                # Execute the module code in a safe way
                spec.loader.exec_module(module)
                # Store the original name for reference
                module.__display_name__ = module_name
                return module
            except Exception as e:
                error_msg = f"Runtime error: {str(e)}"
                traceback_str = traceback.format_exc()
                print(f"Error executing module {file_path}: {e}")
                print(traceback_str)

                # Create a partial module that contains what we loaded before the error
                mock_module = TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

                # Copy any attributes that might have been defined before the error
                for attr_name in dir(module):
                    if not attr_name.startswith('__'):
                        try:
                            setattr(mock_module, attr_name, getattr(module, attr_name))
                        except Exception:
                            pass  # Skip attributes that can't be copied

                return mock_module

        except FileNotFoundError as e:
            error_msg = f"File not found: {str(e)}"
            print(f"Error: {error_msg}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)
        except Exception as e:
            error_msg = f"Unexpected error: {str(e)}"
            print(f"Error loading module {file_path}: {e}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

    @classmethod
    def load_all_implementations(cls, directory: str = None) -> Dict[str, Any]:
        """Load all implementation files in the directory, including those with errors."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        implementations = {}

        implementation_files = cls.discover_implementation_files(directory)
        if not implementation_files:
            print("WARNING: No implementation files found. Check your file naming patterns.")

        for file_path in implementation_files:
            module_name = os.path.basename(file_path).replace('.py', '')
            module = cls.load_module(file_path, module_name)

            # Always add the module, even if it has errors
            implementations[module_name] = module

            if hasattr(module, '__error__'):
                print(f"Loaded with errors: {module_name} - {module.__error__}")
            else:
                print(f"Successfully loaded: {module_name}")

        return implementations

class TestResultsManager:
    def __init__(self):
        self.results = {}
        self.sandbox_name = os.path.basename(os.path.dirname(os.path.abspath(__file__)))

    def record_result(self, impl_name: str, test_name: str, passed: bool,
                     error_msg: Optional[str] = None) -> None:
        """Record a test result for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        if passed:
            self.results[impl_name]["passed"] += 1
        else:
            self.results[impl_name]["failed"] += 1
            if error_msg:
                self.results[impl_name]["errors"].append({
                    "test": test_name,
                    "error": error_msg
                })

    def record_skip(self, impl_name: str, test_name: str,
                   reason: Optional[str] = None) -> None:
        """Record a skipped test for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        self.results[impl_name]["skipped"] += 1
        if reason:
            self.results[impl_name]["errors"].append({
                "test": test_name,
                "error": f"SKIPPED: {reason}"
            })

    def get_winner(self) -> Tuple[Optional[int], Dict]:
        """Determine the winner based on test results."""
        winner = None
        max_passed = -1

        for impl_name, results in self.results.items():
            if impl_name == "original_code":
                continue  # Skip original code when determining winner

            if results["passed"] > max_passed:
                max_passed = results["passed"]
                winner = impl_name
            # Break ties by looking at failure count
            elif results["passed"] == max_passed and winner is not None:
                if results["failed"] < self.results[winner]["failed"]:
                    winner = impl_name

        # Convert winner to numeric index if possible
        winner_index = -1
        if winner and re.match(r'modified_code\d+', winner):
            try:
                winner_index = int(re.search(r'(\d+)', winner).group(1))
            except (AttributeError, ValueError):
                pass

        return winner_index, self.results

    def save_results(self, filename: str = "test_results.json") -> None:
        """Save test results to a JSON file."""
        import json

        winner_index, results = self.get_winner()

        # Check if all tests were skipped
        all_skipped = all(
            stats["skipped"] == stats["passed"] + stats["failed"] + stats["skipped"]
            for impl_name, stats in results.items()
            if impl_name != "original_code"
        )

        output = {
            "winner": winner_index,
            "all_skipped": all_skipped,
            "results": {
                name: {
                    "passed": stats["passed"],
                    "failed": stats["failed"],
                    "skipped": stats["skipped"],
                    "total": stats["passed"] + stats["failed"] + stats["skipped"]
                }
                for name, stats in results.items()
                if not name.startswith("_")  # Skip internal items
            }
        }

        with open(filename, "w") as f:
            json.dump(output, f, indent=2)

        print(f"Test results saved to {filename}")

        return output


from langchain_ollama import ChatOllama
from langchain_core.prompts.chat import ChatPromptTemplate
import json

# Initialize the ChatOllama model
chat_model = ChatOllama(model="llama3.2", base_url="http://localhost:11434")

# Load SHAP values from JSON file
with open("Vuori_Final_Approval_2024_09_24.json", "r") as file:
    shap_values_json = json.load(file).get("shap_values")

# Load system prompt from file
with open("system.prompt", "r") as file:
    sys_prompt = file.read().strip()

# Prepare the messages
template = ChatPromptTemplate([
    ("system", sys_prompt),
    ("human", "{user_input}"),
])

# Generate the response
prompt_value = template.invoke(json.dumps(shap_values_json))

chain = prompt_value | chat_model

# Print the response
chain.invoke()

Instruction: fix this code

import pytest
import json
import inspect
from unittest.mock import patch, mock_open, MagicMock

# Mock file data for tests
MOCK_SHAP_FILE_DATA = {
    "shap_values": {"feature1": 0.5, "feature2": -0.3}
}
MOCK_SYSTEM_PROMPT = "You are an AI assistant analyzing SHAP values."

def test_handles_file_errors(implementation):
    """Test if implementation handles file errors gracefully"""
    impl_name, module = implementation

    # Extract module code as string
    module_code = inspect.getsource(module)

    # Check if implementation has error handling for file operations
    has_file_error_handling = (
        "try:" in module_code and
        any([
            "except FileNotFoundError" in module_code,
            "except json.JSONDecodeError" in module_code,
            "except Exception" in module_code,
            "except (FileNotFoundError" in module_code,
            "except:" in module_code,
        ])
    ) or "with open" in module_code  # Consider context managers as a form of handling

    # Only enforce error handling checks for new implementations

    assert has_file_error_handling, f"{impl_name} should handle file errors with try/except blocks or context managers"

def test_user_input_formatting(implementation):
    """Test if implementation correctly formats user input"""
    impl_name, module = implementation

    # Get module code
    module_code = inspect.getsource(module)

    has_proper_input_formatting = any([
        # Check if user_input is properly injected
        ("user_input" in module_code and "{user_input}" in module_code),
        # Or if invoke directly uses a dictionary with user_input
        ("invoke({" in module_code and "\"user_input\"" in module_code),
        # Or if template.invoke with json.dumps
        ("template.invoke" in module_code and "json.dumps(" in module_code),
        # More flexible check for input formatting
        ("prompt_value = template.invoke" in module_code)
    ])
    assert has_proper_input_formatting, f"{impl_name} should properly format user input"

def test_response_handling(implementation):
    """Test if implementation properly handles and displays responses"""
    impl_name, module = implementation

    # Get module code
    module_code = inspect.getsource(module)

    has_response_handling = any([
        # Check if response is captured and printed
        ("response = " in module_code and "print(response" in module_code),
        # Or if response content is printed
        "print(response.content)" in module_code,
        # Or any form of printing after chain invocation
        ("chain.invoke" in module_code and "print(" in module_code)
    ])

    # Check specifically for the key issue of just calling invoke without capturing result
    has_invoke_without_capture = "chain.invoke()" in module_code and not any([
        "result = chain.invoke()" in module_code,
        "response = chain.invoke()" in module_code,
        "output = chain.invoke()" in module_code,
        "print(chain.invoke()" in module_code
    ])

    if has_invoke_without_capture:
        pytest.fail(f"{impl_name} is calling chain.invoke() without capturing or printing the result")

    assert has_response_handling, f"{impl_name} should properly capture and display response"

def test_improves_original_code(implementation):
    """Test if implementation improves upon the original code"""
    impl_name, module = implementation

    module_code = inspect.getsource(module)

    # Check for specific improvements
    improvements = [
        # Check for any form of error handling
        (("try:" in module_code and "except" in module_code) or
            "with open" in module_code),  # Context managers provide some error handling

        # More flexible JSON parsing check
        any([
            ".get(\"shap_values\"" in module_code,
            "shap_data.get(\"shap_values\"" in module_code,
            "['shap_values']" in module_code,
            ".get('shap_values'" in module_code
        ]),

        # More flexible response handling check
        any([
            ("response = " in module_code and "print(response" in module_code),
            "print(response.content)" in module_code,
            "chain.invoke()" in module_code,
            ("chain.invoke" in module_code and "print(" in module_code)
        ]),

        # More flexible template usage check
        any([
            "ChatPromptTemplate.from_messages" in module_code,
            "ChatPromptTemplate(" in module_code
        ]),

        # More flexible chain creation check
        any([
            ("chain = " in module_code or "chain=" in module_code),
            "| chat_model" in module_code,
            "__or__" in module_code,
            "prompt_value | chat_model" in module_code
        ])
    ]

    # An implementation should have at least 3 improvements
    assert sum(1 for imp in improvements if imp) >= 3, f"{impl_name} should have at least 3 improvements over the original code"

pytest
pytest-mock
langchain-ollama
langchain-core

import pytest
import os
import sys
import json
from typing import Dict, List, Optional, Any

# Import from local test_utils.py in the same directory
from test_utils import TestUtils, TestResultsManager

# Load all implementations in the current sandbox
implementations = TestUtils.load_all_implementations()
test_results = TestResultsManager()

@pytest.fixture(scope="session")
def sandbox_dir():
    """Fixture to provide the sandbox directory path."""
    return os.path.dirname(os.path.abspath(__file__))

@pytest.fixture(scope="session")
def sandbox_name():
    """Fixture to provide the sandbox name."""
    return os.path.basename(os.path.dirname(os.path.abspath(__file__)))

@pytest.fixture(scope="session")
def all_implementations():
    """Fixture to provide all implementations as a dictionary."""
    return implementations

@pytest.fixture(params=list(implementations.items()))
def implementation(request):
    """Fixture to provide each implementation to tests one at a time."""
    return request.param

@pytest.fixture(scope="session")
def results_manager():
    """Fixture to provide access to the test results manager."""
    return test_results

# Hook for collecting test results
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """Pytest hook to collect test results."""
    # Execute all other hooks to obtain the report object
    outcome = yield
    rep = outcome.get_result()

    # We're only interested in the call outcome
    if rep.when == "call":
        if hasattr(item, "callspec") and "implementation" in item.callspec.params:
            # Get implementation name and module
            impl_name, _ = item.callspec.params["implementation"]

            # Get test name
            test_name = item.nodeid.split("::")[-1]

            # Record result
            if rep.passed:
                test_results.record_result(impl_name, test_name, True)
            elif rep.failed:
                error_msg = str(rep.longrepr) if rep.longrepr else "Test failed"
                test_results.record_result(impl_name, test_name, False, error_msg)
            elif rep.skipped:
                skip_reason = rep.longrepr[2] if rep.longrepr else "Test skipped"
                test_results.record_skip(impl_name, test_name, skip_reason)

# Hook to save results at the end of testing
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
    """Save test results at the end of the test session."""
    test_results.save_results()

import os
import sys
import glob
import re
import importlib.util
import traceback
import types
from typing import Dict, List, Optional, Any, Tuple

class TestUtils:
    @staticmethod
    def discover_implementation_files(directory: str = None) -> List[str]:
        """Find all implementation files in the current sandbox directory."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        patterns = [
            r'modified_code\d+\.py',
            r'new_code\d+\.py',
            r'original_code\.py',
            r'implementation\d*\.py'
        ]

        pattern = re.compile('|'.join(f'({p})' for p in patterns))
        implementations = []

        for file_path in glob.glob(os.path.join(directory, '*.py')):
            if pattern.search(os.path.basename(file_path)):
                implementations.append(file_path)

        # Sort files numerically
        def sort_key(path):
            filename = os.path.basename(path)
            match = re.search(r'(\d+)', filename)
            return int(match.group(1)) if match else 0

        return sorted(implementations, key=sort_key)

    @staticmethod
    def create_mock_module(file_path: str, module_name: str, error_info: str) -> types.ModuleType:
        """Create a mock module that contains error information but can still be tested."""
        # Create a new module object
        mock_module = types.ModuleType(module_name)

        # Add basic attributes
        mock_module.__file__ = file_path
        mock_module.__name__ = module_name
        mock_module.__display_name__ = module_name
        mock_module.__error__ = error_info

        # Add a dummy function that can be detected by test functions
        def dummy_function(*args, **kwargs):
            return f"Error in module: {error_info}"

        setattr(mock_module, "implementation_error", dummy_function)

        return mock_module

    @staticmethod
    def load_module(file_path: str, module_name: Optional[str] = None) -> Any:
        """
        Safely load a module from a file path with proper error handling.
        If the module has errors, return a mock module that can still be tested.
        """
        if module_name is None:
            module_name = os.path.basename(file_path).replace('.py', '')

        # Create a unique module name to avoid conflicts
        sandbox_id = os.path.basename(os.path.dirname(file_path))
        unique_module_name = f"{sandbox_id}_{module_name}"

        try:
            # First, try to read the file to check for syntax errors
            with open(file_path, 'r') as f:
                source_code = f.read()

            # Check for syntax errors by compiling the code
            try:
                compiled = compile(source_code, file_path, 'exec')
            except SyntaxError as e:
                error_msg = f"Syntax error: {str(e)}"
                print(f"Syntax error in {file_path}: {e}")
                print(f"  Line {e.lineno}, column {e.offset}: {e.text}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module spec
            spec = importlib.util.spec_from_file_location(unique_module_name, file_path)
            if spec is None or spec.loader is None:
                error_msg = f"Could not create spec for {file_path}"
                print(f"Error: {error_msg}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module object
            module = importlib.util.module_from_spec(spec)
            sys.modules[unique_module_name] = module

            # Special handling for execution errors
            try:
                # Execute the module code in a safe way
                spec.loader.exec_module(module)
                # Store the original name for reference
                module.__display_name__ = module_name
                return module
            except Exception as e:
                error_msg = f"Runtime error: {str(e)}"
                traceback_str = traceback.format_exc()
                print(f"Error executing module {file_path}: {e}")
                print(traceback_str)

                # Create a partial module that contains what we loaded before the error
                mock_module = TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

                # Copy any attributes that might have been defined before the error
                for attr_name in dir(module):
                    if not attr_name.startswith('__'):
                        try:
                            setattr(mock_module, attr_name, getattr(module, attr_name))
                        except Exception:
                            pass  # Skip attributes that can't be copied

                return mock_module

        except FileNotFoundError as e:
            error_msg = f"File not found: {str(e)}"
            print(f"Error: {error_msg}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)
        except Exception as e:
            error_msg = f"Unexpected error: {str(e)}"
            print(f"Error loading module {file_path}: {e}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

    @classmethod
    def load_all_implementations(cls, directory: str = None) -> Dict[str, Any]:
        """Load all implementation files in the directory, including those with errors."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        implementations = {}

        implementation_files = cls.discover_implementation_files(directory)
        if not implementation_files:
            print("WARNING: No implementation files found. Check your file naming patterns.")

        for file_path in implementation_files:
            module_name = os.path.basename(file_path).replace('.py', '')
            module = cls.load_module(file_path, module_name)

            # Always add the module, even if it has errors
            implementations[module_name] = module

            if hasattr(module, '__error__'):
                print(f"Loaded with errors: {module_name} - {module.__error__}")
            else:
                print(f"Successfully loaded: {module_name}")

        return implementations

class TestResultsManager:
    def __init__(self):
        self.results = {}
        self.sandbox_name = os.path.basename(os.path.dirname(os.path.abspath(__file__)))

    def record_result(self, impl_name: str, test_name: str, passed: bool,
                     error_msg: Optional[str] = None) -> None:
        """Record a test result for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        if passed:
            self.results[impl_name]["passed"] += 1
        else:
            self.results[impl_name]["failed"] += 1
            if error_msg:
                self.results[impl_name]["errors"].append({
                    "test": test_name,
                    "error": error_msg
                })

    def record_skip(self, impl_name: str, test_name: str,
                   reason: Optional[str] = None) -> None:
        """Record a skipped test for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        self.results[impl_name]["skipped"] += 1
        if reason:
            self.results[impl_name]["errors"].append({
                "test": test_name,
                "error": f"SKIPPED: {reason}"
            })

    def get_winner(self) -> Tuple[Optional[int], Dict]:
        """Determine the winner based on test results."""
        winner = None
        max_passed = -1

        for impl_name, results in self.results.items():
            if impl_name == "original_code":
                continue  # Skip original code when determining winner

            if results["passed"] > max_passed:
                max_passed = results["passed"]
                winner = impl_name
            # Break ties by looking at failure count
            elif results["passed"] == max_passed and winner is not None:
                if results["failed"] < self.results[winner]["failed"]:
                    winner = impl_name

        # Convert winner to numeric index if possible
        winner_index = -1
        if winner and re.match(r'modified_code\d+', winner):
            try:
                winner_index = int(re.search(r'(\d+)', winner).group(1))
            except (AttributeError, ValueError):
                pass

        return winner_index, self.results

    def save_results(self, filename: str = "test_results.json") -> None:
        """Save test results to a JSON file."""
        import json

        winner_index, results = self.get_winner()

        # Check if all tests were skipped
        all_skipped = all(
            stats["skipped"] == stats["passed"] + stats["failed"] + stats["skipped"]
            for impl_name, stats in results.items()
            if impl_name != "original_code"
        )

        output = {
            "winner": winner_index,
            "all_skipped": all_skipped,
            "results": {
                name: {
                    "passed": stats["passed"],
                    "failed": stats["failed"],
                    "skipped": stats["skipped"],
                    "total": stats["passed"] + stats["failed"] + stats["skipped"]
                }
                for name, stats in results.items()
                if not name.startswith("_")  # Skip internal items
            }
        }

        with open(filename, "w") as f:
            json.dump(output, f, indent=2)

        print(f"Test results saved to {filename}")

        return output