import asyncio
import aioschedule
import logging
import sys
import nltk
import string
from datetime import datetime
from gotquestions import gq_connector
from aiogram import Bot, Dispatcher, html
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.filters import CommandStart
from aiogram.types import Message
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.types import CallbackQuery
from aiogram import Router, F
from aiogram.filters.callback_data import CallbackData

TOKEN = 'MASK_1' # test bot
#TOKEN = 'MASK_2'   # real bot

# Bot token can be obtained via https://t.me/BotFather
#TOKEN = getenv("BOT_TOKEN")

# All handlers should be attached to the Router (or Dispatcher)
bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
dp = Dispatcher()
start_router = Router()

class MyCallback(CallbackData, prefix="my"):
    command: str
    chat_id: int

class question:
    def __init__(self, number, text, answer, razdatkaText=None, razdatkaPic=None,  answerPic=None, zachet=None, nezachet=None, comment=None, note=None,
                 commentPic=None, source=None, authors=None, editors=None, controversials=None, appeals=None, teams=None, correctAnswers=None
                 ):
        self.number = number
        self.text   = text
        self.answer = answer
        self.zachet = zachet
        self.nezachet = nezachet
        self.comment = comment
        self.note    = note
        self.razdatkaText = razdatkaText
        self.razdatkaPic = razdatkaPic
        self.answerPic = answerPic
        self.zachet = zachet
        self.nezachet = nezachet
        self.comment = comment
        self.note    = note
        self.commentPic = commentPic
        self.source = source
        self.authors = authors
        self.editors = editors
        self.controversials = controversials
        self.appeals = appeals
        self.teams = teams
        self.correctAnswers = correctAnswers

class chat_info:
    cur_pack        = {}
    cur_question    = -1
    cur_timer_on    = True
    cur_timer       = 60
    cur_question_dt = datetime.now()
    questions       = []
    running         = False
    list_message    = None
    list_page       = 0
    num_pages       = 15
    packs_list      = []

all_chats = {}

async def set_timer(chat_id, timer):
    await set_chat_info( chat_id = chat_id, timer = timer )
    await bot.send_message( chat_id, f"Таймер установлен в {timer} минут")

async def set_chat_info(chat_id, pack=None, question_num=None, timer_on=None, timer=None, question_dt=None, list_page=0, list_message=None, packs_list=None, num_pages=None):
    if chat_id not in all_chats:
        all_chats[chat_id] = chat_info()

    all_chats[chat_id].cur_pack         = pack if pack is not None else all_chats[chat_id].cur_pack

    if pack is not None:

        all_chats[chat_id].questions = []
        all_chats[chat_id].cur_question = -1

        num_tours = len(pack["tours"])
        for cur_tour in range(num_tours):
            num_questions = len(pack["tours"][cur_tour]["questions"])
            for cur_question in range(num_questions):

                q = pack["tours"][cur_tour]["questions"][cur_question]

                editors_str = ""
                for editor in q["editors"]:
                    editors_str += editor["name"]

                authors_str = ""
                for author in q["editors"]:
                    authors_str += author["name"]

                r = question (  number = q["number"], text =   q["text"], answer = q["answer"], razdatkaText=q["razdatkaText"], razdatkaPic=q["razdatkaPic"],  answerPic=q["answerPic"], zachet=q["zachet"], nezachet=q["nezachet"], comment=q["comment"], note=q["note"],
                 commentPic=q["commentPic"], source=q["source"], authors=authors_str, editors=editors_str, controversials=q["controversials"], appeals=q["appeals"], teams=q["teams"], correctAnswers=q["correctAnswers"])

                all_chats[chat_id].questions.append(r)

    all_chats[chat_id].cur_question     = question_num if question_num is not None else all_chats[chat_id].cur_question
    all_chats[chat_id].cur_timer_on     = timer_on if timer_on is not None else all_chats[chat_id].cur_timer_on
    all_chats[chat_id].cur_timer        = timer if timer is not None else all_chats[chat_id].cur_timer
    all_chats[chat_id].cur_question_dt  = question_dt if question_dt is not None else all_chats[chat_id].cur_question_dt
    all_chats[chat_id].list_page        = list_page if list_page is not None else all_chats[chat_id].list_page
    all_chats[chat_id].num_pages        = num_pages if num_pages is not None else all_chats[chat_id].num_pages
    all_chats[chat_id].list_message     = list_message if list_message is not None else all_chats[chat_id].list_message
    all_chats[chat_id].packs_list       = packs_list  if packs_list is not None else all_chats[chat_id].packs_list

def answer_message(q: question, print_answer=True):

    answer = ""

    if print_answer:
        answer += f"Ответ:\n"
        answer += f"{q.answer}\n\n"

    if ( q.zachet != ""):
        answer += f"Зачет:\n"
        answer += f"{q.zachet}\n\n"

    if ( q.answerPic != ""):
        answer += f"Картинка:\n"
        answer += f"{q.answerPic}\n\n"

    answer += f"Комментарий:\n"
    answer += f"{q.comment}\n\n"

    if ( q.source != ""):
        answer += f"Источник:\n"
        answer += f"{q.source}\n\n"

    if ( q.editors != ""):
        answer += f"Редактор(ы): {q.editors}\n\n"

    if ( q.authors != ""):
        answer += f"Автор(ы): {q.authors}\n\n"

    if ( q.teams is not None and q.teams != 0):
        answer += f"Взятий: {q.correctAnswers}/{q.teams}({round(100*q.correctAnswers/q.teams)}%)\n"

    return answer

@start_router.callback_query(MyCallback.filter(F.command == 'send_hint'))
async def send_hint(query: CallbackQuery, callback_data: MyCallback):
    # чтобы кнопка не мигала
    await query.answer()

    cur_chat_id = callback_data.chat_id
    q = all_chats[cur_chat_id].questions[all_chats[cur_chat_id].cur_question]

    masked_answer = "".join([ '_' if c in string.punctuation else '*' if c.isalpha() else '0' if c.isdigit() else ' ' for c in q.answer ])

    # remove last dot
    if masked_answer[-1:] == '.':
        masked_answer = masked_answer[:-1]

    await bot.send_message( cur_chat_id, masked_answer )

@start_router.callback_query(MyCallback.filter(F.command == 'send_next'))
async def send_next_question(query: CallbackQuery, callback_data: MyCallback):
    # чтобы кнопка не мигала
    await query.answer()

    cur_chat_id = callback_data.chat_id
    await ask_next_question(cur_chat_id)

@start_router.callback_query(MyCallback.filter(F.command == 'list_none'))
async def list_none(query: CallbackQuery, callback_data: MyCallback):
    await query.answer()

@start_router.callback_query(MyCallback.filter(F.command == 'list_backward'))
async def list_backward(query: CallbackQuery, callback_data: MyCallback):
    await query.answer()

    chat_id = callback_data.chat_id
    num_pages = all_chats[chat_id].num_pages
    await set_chat_info(chat_id = chat_id, list_page = all_chats[chat_id].list_page + 1)
    print ("Backward:" + str(all_chats[chat_id].list_page))

    await show_packs_page(chat_id, first_time = False, num_pages = num_pages)

@start_router.callback_query(MyCallback.filter(F.command == 'list_forward'))
async def list_forward(query: CallbackQuery, callback_data: MyCallback):
    await query.answer()

    chat_id = callback_data.chat_id
    num_pages = all_chats[chat_id].num_pages
    await set_chat_info(chat_id = chat_id, list_page = all_chats[chat_id].list_page - 1)
    print ("Backward:" + str(all_chats[chat_id].list_page))

    await show_packs_page(chat_id, first_time = False, num_pages = num_pages)

@start_router.callback_query(MyCallback.filter(F.command == 'send_answer'))
async def send_answer(query: CallbackQuery, callback_data: MyCallback):
    # чтобы кнопка не мигала
    await query.answer()
    await direct_send_answer( callback_data.chat_id)

async def direct_send_answer(cur_chat_id):

    q = all_chats[cur_chat_id].questions[all_chats[cur_chat_id].cur_question]

    if ( q.answerPic != ""):
        await bot.send_photo( cur_chat_id, "http://gotquestions.online" + q.answerPic)

    if ( q.commentPic != ""):
        await bot.send_photo( cur_chat_id, "http://gotquestions.online" + q.commentPic)

    answer = answer_message( q, True)

    inline_kb_list = [
        [
        InlineKeyboardButton(text="Дальше", callback_data = MyCallback(command = 'send_next', chat_id = cur_chat_id).pack())
        ]
    ]

    keyboard = InlineKeyboardMarkup(inline_keyboard=inline_kb_list )

    await bot.send_message( cur_chat_id, answer, reply_markup= keyboard )
    all_chats[cur_chat_id].running = False

async def ask_next_question(chat_id):

    all_chats[chat_id].cur_question += 1
    all_chats[chat_id].cur_question_dt = datetime.now()
    all_chats[chat_id].running = True

    q = all_chats[chat_id].questions[all_chats[chat_id].cur_question]
    if ( q.razdatkaPic != ""):
        await bot.send_photo( chat_id, "http://gotquestions.online" + q.razdatkaPic)

    if ( q.razdatkaText != ""):
        await bot.send_message( chat_id, q.razdatkaText)

    text = f"Вопрос {q.number}.\n\n"
    text += f"{q.text}"

    inline_kb_list = [
        [
        InlineKeyboardButton(text="Подсказка",  callback_data = MyCallback(command = 'send_hint'  , chat_id = chat_id).pack()),
        InlineKeyboardButton(text="Ответ",  callback_data = MyCallback(command = 'send_answer'  , chat_id = chat_id).pack()),
        InlineKeyboardButton(text="Дальше", callback_data = MyCallback(command = 'send_next', chat_id = chat_id).pack())
        ]
    ]

    keyboard = InlineKeyboardMarkup(inline_keyboard=inline_kb_list )

    Message = await bot.send_message( chat_id, text, reply_markup= keyboard )

@dp.message(CommandStart())
async def command_start_handler(message: Message) -> None:
    """
    This handler receives messages with `/start` command
    """
    # Most event objects have aliases for API methods that can be called in events' context
    # For example if you want to answer to incoming message you can use `message.answer(...)` alias
    # and the target chat will be passed to :ref:`aiogram.methods.send_message.SendMessage`
    # method automatically or call API method directly via
    # Bot instance: `bot.send_message(chat_id=message.chat.id, ...)`
    await message.answer(f"Hello, {html.bold(message.from_user.full_name)}!")

async def load_pack(chat_id, num_pack):
    Message = await bot.send_message( chat_id, 'Загружаем пакет номер ' + str(num_pack))

    connector = gq_connector()
    json = connector.get_pack(num_pack)

    title = json["title"]
    played = json["endDate"]

    pack_info =  f"{title}\n\n"
    pack_info += f"{played[0:10]}\n\n"

    pack_info += f"Редакторы пакета: "
    for editor in json["editors"]:
        pack_info += f"{editor["name"]},"

    if json["info"] != "":
        pack_info += f"\n\n{json["info"]}"

    Message = await bot.send_message( chat_id, pack_info)
    await set_chat_info(chat_id = chat_id, pack = json)
    await ask_next_question(chat_id)

async def check_answer(chat_id, text_command, from_user):

    q = all_chats[chat_id].questions[all_chats[chat_id].cur_question]

    # first remove all symbols except alpha-numeric
    processed_command = ''.join(ch for ch in text_command if ch.isalnum()).lower()
    processed_answer = ''.join(ch for ch in q.answer if ch.isalnum()).lower()

    zachets = q.zachet.split(",")
    processed_zachets = []

    for z in zachets:
        processed_zachets.append(''.join(ch for ch in z if ch.isalnum()).lower())

    correct_answer = False
    approximate_answer = False

    if processed_command == processed_answer:
        correct_answer = True

    if not correct_answer:
        for z in processed_zachets:
            if processed_command == z:
                correct_answer = True
                break

    if not correct_answer:
        dist1 = nltk.edit_distance(processed_command, processed_answer)
        print ( dist1 )

        dist2 = 99999

        for z in processed_zachets:
            dist2 = min(  dist2, nltk.edit_distance(processed_command, z))
            print ( dist2 )

        dist = min(dist1, dist2)
        print ( dist )
        print ( processed_command )
        print ( processed_answer )

        if dist * 4 <=  min( len(processed_command), len(processed_answer)):
            approximate_answer = True
        else:
            approximate_answer = False

    if correct_answer:
        ans =  f"Блестяще, {from_user}!\n"
        ans += f"{text_command} абсолютно верный ответ.\n\n"
    elif approximate_answer:
        ans =  f"Отлично, {from_user}!\n"
        ans += f"{text_command} не совсем верный ответ, но я его зачту. Верный ответ: {q.answer}\n\n"

    if correct_answer or approximate_answer:

        if ( q.answerPic != ""):
            await bot.send_photo( chat_id, "http://gotquestions.online" + q.answerPic)

        if ( q.commentPic != ""):
            await bot.send_photo( chat_id, "http://gotquestions.online" + q.commentPic)

        ans += answer_message( q, False)

        inline_kb_list = [
            [
                InlineKeyboardButton(text="Дальше", callback_data = MyCallback(command = 'send_next', chat_id = chat_id).pack())
            ]
        ]

        keyboard = InlineKeyboardMarkup(inline_keyboard=inline_kb_list )
        await bot.send_message(chat_id, ans, reply_markup = keyboard)
        all_chats[chat_id].running = False

    else:
        print ( processed_command)
        print ( q.answer.lower() )
        print ( dist )
        await bot.send_message(chat_id, f"{text_command} это неверный ответ. Попробуйте еще раз.")

async def packs_list_message(chat_id):
    packs_list = all_chats[chat_id].packs_list
    list_page = all_chats[chat_id].list_page
    print ( "Packs:" + str(list_page) )
    packs_per_page = 6

    final_message = ""

    for pack in packs_list[ packs_per_page * list_page : packs_per_page * (list_page + 1 ) ]:
            trueDl_str = ""
            if len(pack.trueDl) >= 1:
                trueDl_str = f"{pack.trueDl[0]}: "

            final_message += f"{trueDl_str}{pack.title}({pack.editors})\n"
            final_message += f"Сыграно {0} из {pack.questions}   Дата: {pack.endDate[0:10]}\n"
            final_message += f"Выбрать:    /load_{pack.id}\n\n"

    return final_message

async def show_packs_page(chat_id, first_time, num_pages):

    final_message = await packs_list_message(chat_id)
    list_page = all_chats[chat_id].list_page
    print( "list_page = " + str(num_pages))
    print( "pages = " + str(num_pages))

    if ( list_page > 0 and list_page < num_pages - 1):
        inline_kb_list = [[
        InlineKeyboardButton(text="Более новые ",  callback_data = MyCallback(command = 'list_forward'  , chat_id = chat_id).pack()),
        InlineKeyboardButton(text="Более старые",  callback_data = MyCallback(command = 'list_backward' , chat_id = chat_id).pack()),
        ]]
    elif list_page == 0:
        inline_kb_list = [[
        InlineKeyboardButton(text="            ",  callback_data = MyCallback(command = 'list_none'      , chat_id = chat_id).pack()),
        InlineKeyboardButton(text="Более старые",  callback_data = MyCallback(command = 'list_backward'  , chat_id = chat_id).pack()),
        ]]
    else:
        inline_kb_list = [[
        InlineKeyboardButton(text="Более новые ",  callback_data = MyCallback(command = 'list_forward' , chat_id = chat_id).pack()),
        InlineKeyboardButton(text="            ",  callback_data = MyCallback(command = 'list_none'   , chat_id = chat_id).pack()),
        ]]

    keyboard = InlineKeyboardMarkup(inline_keyboard=inline_kb_list )

    # Сохраняем сообщение чтобы в будущем его править при нажатии кнопок вперед-назад
    if first_time:
        list_message = await bot.send_message( chat_id, final_message, reply_markup= keyboard)
        print ( "Сохранили: " + str(list_message.message_id))
        await set_chat_info(chat_id = chat_id, list_message = list_message.message_id)
        print ( "Точно сохранили: " + str(all_chats[chat_id].list_message))
    else:
        print ( "Теперь читаем: " + str(all_chats[chat_id].list_message))
        await bot.edit_message_text( chat_id = chat_id, message_id = all_chats[chat_id].list_message, text = final_message, reply_markup= keyboard)

async def show_packs(chat_id, num_pages):

    connector = gq_connector()

    # Почему деленное на 3? Потому что у gq в странице 18 пакетов, а у нас - 6
    packs_list = connector.get_packs_list(int((num_pages+5)/3))

    await set_chat_info(chat_id = chat_id, list_page = 0, packs_list = packs_list, num_pages = num_pages)

    await show_packs_page(chat_id, first_time = True, num_pages = num_pages)

async def process_command(chat_id, text_command, from_user):

    if text_command.startswith('/timer'):
        if text_command[7:].isdigit():
            timer = int(text_command[7:])
            await set_timer(chat_id, timer)
            return

    if text_command.startswith('/list'):
        if text_command[6:].isdigit():
            num_pages = int(text_command[6:])
        else:
            num_pages = 15

        await show_packs(chat_id, num_pages)
        return

    if text_command.startswith('/load'):
# find digits in text command after /load but before character @
        if text_command[6:].isdigit():
            num_pack = int(text_command[6:])
            await load_pack(chat_id, num_pack)
            return

    if text_command.startswith('/'):
        if ( all_chats[chat_id].cur_question != -1):
            await check_answer(chat_id, text_command[1:], from_user)
            return

    Message = await bot.send_message( chat_id, text_command[::-1])

@dp.message()
async def echo_handler(message: Message) -> None:
    """
    Handler will forward receive a message back to the sender

    By default, message handler will handle all message types (like a text, photo, sticker etc.)
    """
    #try:
        # Send a copy of the received message
    await process_command(message.chat.id, message.text, message.from_user.full_name)
        #await message.answer(message)
        #await message.answer(f"Hello, {html.bold(message.from_user.full_name)}!")
        #Message = await bot.send_message(chat_id=message.chat.id, text= message.text[2:4])

    #TODO: catch exceptions later
    #except TypeError:
        # But not all the types is supported to be copied so need to handle it
    #    await message.answer("Something happened: wrong type!")

async def scheduler(delay: int):

    while True:
        for chat_id in all_chats:
            if all_chats[chat_id].cur_timer_on:
                if all_chats[chat_id].running:
                    cur_dt = datetime.now()
                    delta = cur_dt - all_chats[chat_id].cur_question_dt
                    if delta.total_seconds() >  all_chats[chat_id].cur_timer * 60 - 60 and delta.total_seconds() <= all_chats[chat_id].cur_timer  * 60 - 50 and  all_chats[chat_id].cur_timer > 0:
                        await bot.send_message( chat_id, "Поторопитесь! Осталось меньше минуты до истечения таймера")

                    if delta.total_seconds() > all_chats[chat_id].cur_timer * 60:
                        await direct_send_answer(chat_id)
                        all_chats[chat_id].running = False

        await asyncio.sleep(delay=delay)

async def main() -> None:
    # Initialize Bot instance with default bot properties which will be passed to all API calls
    #bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))

    # And the run events dispatching
    task = asyncio.create_task(coro=scheduler(delay=10))
    dp.include_router(start_router)
    await dp.start_polling(bot)

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, stream=sys.stdout)
    asyncio.run(main())

Instruction: # find digits in text command after /load but before character @

import pytest
import re
import inspect
import asyncio
from unittest.mock import patch, MagicMock, AsyncMock
import importlib
from types import ModuleType
from typing import Tuple, List, Any, Callable, Dict, Optional, Union
from contextlib import ExitStack

def test_command_handling_exists(implementation):
    """Test that the implementation has functionality to handle commands."""
    impl_name, module = implementation

    # Skip if module couldn't be loaded or has syntax errors
    if not module or isinstance(module, str):
        pytest.skip(f"Module {impl_name} could not be loaded")

    try:
        # Get module source code
        module_source = inspect.getsource(module)
    except Exception as e:
        pytest.skip(f"Module {impl_name} has syntax errors: {str(e)}")

    # Check for command processing functions with more flexible naming patterns
    has_process_command = hasattr(module, 'process_command')
    has_message_handler = any([
        hasattr(module, 'echo_handler'),
        hasattr(module, 'message_handler'),
        hasattr(module, 'handle_message'),
        'async def echo_handler' in module_source,
        '@dp.message()' in module_source,
        '@start_router.callback_query' in module_source
    ])

    # Look for load command processing in the source code
    handles_load_commands = '/load' in module_source

    assert has_process_command or has_message_handler, \
        f"{impl_name} is missing command processing functionality"

    assert handles_load_commands, \
        f"{impl_name} doesn't handle /load commands"

@pytest.fixture
def mock_bot():
    """Create a mock bot for testing."""
    mock = AsyncMock()
    mock.send_message = AsyncMock()
    return mock

@pytest.fixture
def mock_load_pack():
    """Create a mock load_pack function for testing."""
    return AsyncMock()

@pytest.fixture
def mock_message():
    """Create a mock message for testing."""
    mock = MagicMock()
    mock.chat = MagicMock()
    mock.chat.id = 12345
    mock.from_user = MagicMock()
    mock.from_user.full_name = "Test User"
    mock.text = ""  # Initialize with empty text
    return mock

@pytest.fixture
def mock_connector():
    """Create a mock connector for testing."""
    mock = MagicMock()
    mock.get_pack = MagicMock(return_value={
        "title": "Test Pack",
        "endDate": "2023-05-15",
        "editors": [{"name": "Test Editor"}],
        "info": "Test info",
        "tours": [
            {
                "questions": [
                    {
                        "number": 1,
                        "text": "Test question",
                        "answer": "Test answer",
                        "razdatkaText": "",
                        "razdatkaPic": "",
                        "answerPic": "",
                        "zachet": "",
                        "nezachet": "",
                        "comment": "Test comment",
                        "note": "",
                        "commentPic": "",
                        "source": "",
                        "editors": [],
                        "controversials": [],
                        "appeals": [],
                        "teams": 0,
                        "correctAnswers": 0
                    }
                ]
            }
        ]
    })
    return mock

async def execute_command(module, command, mock_bot, mock_load_pack, mock_message, mock_connector=None):
    """Execute a command using the appropriate function in the module."""
    # Skip if module couldn't be loaded or has syntax errors
    if not module or isinstance(module, str):
        return

    # Update mock message with the command
    mock_message.text = command

    # Prepare patches
    patches = []

    # Add bot patch if bot exists in the module
    if hasattr(module, 'bot'):
        patches.append(patch.object(module, 'bot', mock_bot))

    # Add load_pack patch if it exists in the module
    if hasattr(module, 'load_pack'):
        patches.append(patch.object(module, 'load_pack', mock_load_pack))

    try:
        # Apply all the patches
        with ExitStack() as stack:
            # Apply all patches in the list
            for p in patches:
                stack.enter_context(p)

            # Mock the gq_connector import
            if mock_connector:
                stack.enter_context(patch.dict('sys.modules', {'gotquestions': MagicMock()}))
                stack.enter_context(patch(f"{module.__name__}.gq_connector", return_value=mock_connector))

            # Call the command processing function
            if hasattr(module, 'process_command'):
                await module.process_command(mock_message.chat.id, command, mock_message.from_user.full_name)
            elif hasattr(module, 'echo_handler'):
                await module.echo_handler(mock_message)
            else:
                # If direct function isn't found, simulate message handler call
                handlers = [obj for name, obj in inspect.getmembers(module)
                          if inspect.iscoroutinefunction(obj) and ('handler' in name or 'process' in name)]

                if handlers:
                    await handlers[0](mock_message)
                else:
                    # Last resort: look for any async function that might handle messages
                    for name, obj in inspect.getmembers(module):
                        if inspect.iscoroutinefunction(obj) and not name.startswith('_'):
                            try:
                                await obj(mock_message)
                                break
                            except Exception:
                                continue
    except Exception as e:
        pytest.skip(f"Error executing command on module: {str(e)}")

def extract_load_command_handler(implementation):
    """Extract the load command handler function from the implementation."""
    impl_name, module = implementation

    # Skip if module couldn't be loaded or has syntax errors
    if not module or isinstance(module, str):
        return None

    # Find functions that might handle load commands
    load_command_handlers = []

    if hasattr(module, 'process_command'):
        load_command_handlers.append(module.process_command)

    # Look for other functions that handle /load commands
    for name, obj in inspect.getmembers(module):
        if inspect.isfunction(obj) or inspect.iscoroutinefunction(obj):
            try:
                source = inspect.getsource(obj)
                if '/load' in source and ('text_command' in source or 'message' in source):
                    load_command_handlers.append(obj)
            except (TypeError, OSError):
                pass

    return load_command_handlers[0] if load_command_handlers else None

@pytest.mark.asyncio
@pytest.mark.parametrize("command,expected_id", [
    # Basic test cases
    ("/load123", 123),
    ("/load456@botname", 456),
    ("/load 789", 789),
    ("/load 321@something", 321),
    # Edge cases
    ("/load42@", 42),
    ("/load 00042@botname", 42),  # Leading zeros
])
async def test_load_command_extraction(implementation, command, expected_id, mock_bot, mock_load_pack, mock_message, mock_connector):
    """Test that the implementation correctly extracts numeric IDs from load commands."""
    impl_name, module = implementation

    # Skip if module couldn't be loaded or has syntax errors
    if not module or isinstance(module, str):
        pytest.skip(f"Module {impl_name} could not be loaded")

    try:
        inspect.getsource(module)
    except Exception as e:
        pytest.skip(f"Module {impl_name} has syntax errors: {str(e)}")

    # Check if the module has the load_pack function
    has_load_pack = hasattr(module, 'load_pack')
    if not has_load_pack:
        pytest.skip(f"Module {impl_name} doesn't have a load_pack function")

    # Execute the command
    try:
        # Mock the import first
        with patch.dict('sys.modules', {'gotquestions': MagicMock()}):
            # Apply patches and execute command
            with patch.object(module, 'bot', mock_bot):
                with patch.object(module, 'load_pack', mock_load_pack):
                    with patch(f"{module.__name__}.gq_connector", return_value=mock_connector):
                        # For each implementation, determine if we need to directly test a function
                        load_handler = extract_load_command_handler(implementation)

                        if load_handler:
                            # If we have a direct handler function, test it
                            if 'text_command' in inspect.signature(load_handler).parameters:
                                # If handler takes a text_command parameter
                                await load_handler(mock_message.chat.id, command, mock_message.from_user.full_name)
                            else:
                                # Try with modified message object
                                mock_message.text = command
                                await load_handler(mock_message)
                        else:
                            # Otherwise use our general execution function
                            await execute_command(module, command, mock_bot, mock_load_pack, mock_message, mock_connector)

        # Check if load_pack was called with the extracted ID
        assert mock_load_pack.called, f"{impl_name}: load_pack wasn't called for command '{command}'"

        # Get arguments passed to load_pack
        call_args = mock_load_pack.call_args[0]

        # First argument should be chat_id, second should be the extracted pack ID
        assert call_args[0] == mock_message.chat.id, f"{impl_name}: Wrong chat_id passed to load_pack"
        assert call_args[1] == expected_id, f"{impl_name}: Failed to extract correct ID from '{command}'"

    except Exception as e:
        pytest.skip(f"Error testing {impl_name} with command '{command}': {str(e)}")

@pytest.mark.asyncio
@pytest.mark.parametrize("command", [
    "/load",  # No ID provided
    "/loadabc",  # Non-numeric ID
    "/load abc@botname",  # Non-numeric ID with @
])
async def test_load_command_handles_invalid_input(implementation, command, mock_bot, mock_load_pack, mock_message, mock_connector):
    """Test that the implementation gracefully handles invalid load commands."""
    impl_name, module = implementation

    # Skip if module couldn't be loaded or has syntax errors
    if not module or isinstance(module, str):
        pytest.skip(f"Module {impl_name} could not be loaded")

    try:
        inspect.getsource(module)
    except Exception as e:
        pytest.skip(f"Module {impl_name} has syntax errors: {str(e)}")

    # Execute with error handling to ensure test doesn't fail on implementation error
    try:
        # Mock the imports first
        with patch.dict('sys.modules', {'gotquestions': MagicMock()}):
            # Use a simple patch for gq_connector
            with patch(f"{module.__name__}.gq_connector", return_value=mock_connector):
                await execute_command(module, command, mock_bot, mock_load_pack, mock_message, mock_connector)

        # If we reach here, no exception was raised - implementation handled it gracefully
        assert True
    except Exception as e:
        pytest.skip(f"{impl_name}: Implementation has errors that prevent testing: {str(e)}")

def test_command_implementation_quality(implementation):
    """
    Test that the implementation follows good patterns for command extraction.
    """
    impl_name, module = implementation

    # Skip if module couldn't be loaded or has syntax errors
    if not module or isinstance(module, str):
        pytest.skip(f"Module {impl_name} could not be loaded")

    try:
        source = inspect.getsource(module)
    except Exception as e:
        pytest.skip(f"Module {impl_name} has syntax errors: {str(e)}")

    # Look for quality patterns in the code
    quality_patterns = [
        # Using string methods effectively
        re.search(r'text_command\.find\([\'"]@[\'"]\)', source) is not None,
        re.search(r'text_command\.split\([\'"]@[\'"]\)', source) is not None,

        # Using regular expressions for robust parsing
        re.search(r'import re', source) is not None and re.search(r're\.(search|match|findall)', source) is not None,

        # Using proper conditional handling for @ character
        re.search(r'if\s+[\'"]@[\'"]\s+in\s+text_command', source) is not None or
        re.search(r'text_command\.find\([\'"]@[\'"]\)', source) is not None,

        # Using string slicing with proper index calculation
        re.search(r'num_start\s*=.*\/load.*\+\s*len', source) is not None or
        re.search(r'text_command\[.*\/load.*\.find\(', source) is not None,

        # Handling bot name after @ properly
        re.search(r'num_end\s*=\s*text_command\.find\([\'"]@[\'"]\)', source) is not None or
        re.search(r'text_command\.split\([\'"]@[\'"]\)', source) is not None,

        # Additional patterns for good command handling
        re.search(r'text_command\[.*:.*\]\.strip\(\)', source) is not None or
        re.search(r'\.strip\(\)', source) is not None
    ]

    # Implementation should use at least one of these quality patterns
    assert any(quality_patterns), \
        f"{impl_name}: Implementation doesn't show evidence of quality command parsing"

    # Check that the implementation handles spaces in commands properly
    space_handling = (
        re.search(r'\.strip\(\)', source) is not None or
        re.search(r'text_command\[.*:.*\]\.strip\(\)', source) is not None
    )

    assert space_handling, \
        f"{impl_name}: Implementation doesn't properly handle spaces in commands"

pytest
pytest-mock
pytest-asyncio
aiogram
aioschedule
nltk

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 main13 import knn, mlp
import pandas as pd

for pclass in [1, 2, 3]:
    for fare in range(10, 200, 10):
        for embarked in ["S", "Q", "C"]:
            my_df = pd.DataFrame({"Pclass": pclass,
                                "Name": 24,
                                "Sex": 0,
                                "Age": 19,
                                "SibSp": 0,
                                "Parch": 0,
                                "Fare": fare,
                                "Embarked": embarked
                                })
            my_df = pd.get_dummies(my_df, columns=["Embarked"], prefix="Embarked") #делаем one-hot
            if "Embarked_S" in my_df.columns:
                my_df["Embarked_S"] = my_df["Embarked_S"].map({True: 1, False: 0})
            if "Embarked_C" in my_df.columns:
                my_df["Embarked_C"] = my_df["Embarked_C"].map({True: 1, False: 0})
            if "Embarked_Q" in my_df.columns:
                my_df["Embarked_Q"] = my_df["Embarked_Q"].map({True: 1, False: 0})

            print(f"""-------------------------------------------------------
                  Параметры: класс {pclass}, плата {fare}, embarked {embarked}
                  По knn: {knn.predict(my_df)}
                  По mlp: {mlp.predict(my_df)}""")

Instruction: переделай чтобы работало

import pandas as pd
import pytest
from unittest.mock import patch, MagicMock, call
import sys
import inspect
import logging

# Setup logging for debugging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Create a mocked version of main13
class MockKNN:
    def predict(self, df):
        # Check if df is properly formatted for KNN model
        if not isinstance(df, pd.DataFrame):
            raise TypeError("Input must be a DataFrame")
        if len(df) == 0:
            raise ValueError("DataFrame is empty")

        # Return a simple prediction based on the existence of data
        return ["Survived"] if len(df) > 0 else ["Not survived"]

class MockMLP:
    def predict(self, df):
        # Check if df is properly formatted for MLP model
        if not isinstance(df, pd.DataFrame):
            raise TypeError("Input must be a DataFrame")
        if len(df) == 0:
            raise ValueError("DataFrame is empty")

        # Return a simple prediction based on the existence of data
        return ["Survived"] if len(df) > 0 else ["Not survived"]

# Mock main13 module with our models
@pytest.fixture(autouse=True)
def mock_main13():
    sys.modules['main13'] = MagicMock()
    sys.modules['main13'].knn = MockKNN()
    sys.modules['main13'].mlp = MockMLP()
    yield
    # Clean up
    if 'main13' in sys.modules:
        del sys.modules['main13']

def test_implementation_creates_correct_dataframe(implementation):
    """
    Test that the implementation creates a correctly formatted DataFrame.
    The original issue was that the DataFrame was incorrectly initialized.
    """
    impl_name, module = implementation

    # Extract code to determine implementation pattern
    main_code = inspect.getsource(module)

    # Setup mocks
    with patch('main13.knn.predict', return_value=["Survived"]) as mock_knn_predict, \
         patch('main13.mlp.predict', return_value=["Survived"]) as mock_mlp_predict:

        # Execute a controlled version of the implementation's first iteration
        # Instead of executing the whole module, run just enough to create one DataFrame
        pclass, fare, embarked = 1, 10, "S"

        # Extract the DataFrame creation pattern from the implementation
        if "my_df = pd.DataFrame([{" in main_code:
            # Format 1: Using list of dicts
            df = pd.DataFrame([{
                "Pclass": pclass,
                "Name": 24,
                "Sex": 0,
                "Age": 19,
                "SibSp": 0,
                "Parch": 0,
                "Fare": fare,
                "Embarked": embarked
            }])
        else:
            # Format 2: Using lists for each column
            df = pd.DataFrame({
                "Pclass": [pclass],
                "Name": [24],
                "Sex": [0],
                "Age": [19],
                "SibSp": [0],
                "Parch": [0],
                "Fare": [fare],
                "Embarked": [embarked]
            })

        # One-hot encode the Embarked column
        df = pd.get_dummies(df, columns=["Embarked"], prefix="Embarked")

        # Convert boolean values to integers if necessary
        for col in [c for c in df.columns if c.startswith("Embarked_")]:
            if df[col].dtype == bool:
                df[col] = df[col].astype(int)

        # Call the predict methods using our test DataFrame
        module_globals = {'__name__': '__main__', 'pd': pd, 'knn': sys.modules['main13'].knn, 'mlp': sys.modules['main13'].mlp}

        # Call the models with our DataFrame
        knn_prediction = sys.modules['main13'].knn.predict(df)
        mlp_prediction = sys.modules['main13'].mlp.predict(df)

        # Ensure we have expected structure
        assert isinstance(df, pd.DataFrame), "DataFrame not properly created"
        assert len(df) == 1, "DataFrame should have exactly one row"
        assert any(col.startswith("Embarked_") for col in df.columns), "One-hot encoding not applied"

        # Verify one-hot encoding structure
        for port in ["S", "C", "Q"]:
            col = f"Embarked_{port}"
            if col in df.columns:
                assert df[col].iloc[0] in [0, 1], f"One-hot column {col} should be 0 or 1"
                if embarked == port:
                    assert df[col].iloc[0] == 1, f"One-hot column for selected port should be 1"

def test_implementation_creates_proper_row_structure(implementation):
    """
    Test that the implementation correctly creates rows in the DataFrame.
    Original issue was scalar values instead of lists for each row.
    """
    impl_name, module = implementation

    # Extract the code pattern
    main_code = inspect.getsource(module)

    # Define test parameters
    pclass, fare, embarked = 2, 20, "C"

    # Set up mocks
    with patch('main13.knn.predict', return_value=["Survived"]) as mock_knn_predict, \
         patch('main13.mlp.predict', return_value=["Survived"]) as mock_mlp_predict:

        # Create the DataFrame in the same way as the implementation
        if "my_df = pd.DataFrame([{" in main_code:
            # Format 1: Using list of dicts
            df = pd.DataFrame([{
                "Pclass": pclass,
                "Name": 24,
                "Sex": 0,
                "Age": 19,
                "SibSp": 0,
                "Parch": 0,
                "Fare": fare,
                "Embarked": embarked
            }])
        else:
            # Format 2: Using lists for each column
            df = pd.DataFrame({
                "Pclass": [pclass],
                "Name": [24],
                "Sex": [0],
                "Age": [19],
                "SibSp": [0],
                "Parch": [0],
                "Fare": [fare],
                "Embarked": [embarked]
            })

        # Apply one-hot encoding
        df = pd.get_dummies(df, columns=["Embarked"], prefix="Embarked")

        # Convert boolean values to integers if necessary
        for col in [c for c in df.columns if c.startswith("Embarked_")]:
            if df[col].dtype == bool:
                df[col] = df[col].astype(int)

        # Check DataFrame structure
        assert isinstance(df, pd.DataFrame), "Not a DataFrame"
        assert len(df) == 1, "DataFrame should have exactly one row"

        # Test accessing values to validate structure
        try:
            # Try to access scalar values using iloc
            df["Pclass"].iloc[0]
            df["Name"].iloc[0]
            df["Sex"].iloc[0]
            df["Age"].iloc[0]
            df["SibSp"].iloc[0]
            df["Parch"].iloc[0]
            df["Fare"].iloc[0]

            # Check for Embarked columns
            assert any(col.startswith("Embarked_") for col in df.columns), "No one-hot encoded columns"

        except Exception as e:
            pytest.fail(f"DataFrame has incorrect structure: {str(e)}")

def test_implementation_handles_one_hot_encoding(implementation):
    """
    Test that one-hot encoding is applied correctly for the Embarked column.
    """
    impl_name, module = implementation

    # Extract the code pattern
    main_code = inspect.getsource(module)

    # Test each port to ensure one-hot encoding works correctly
    for port in ["S", "C", "Q"]:
        pclass, fare, embarked = 1, 10, port

        # Create a test DataFrame based on implementation pattern
        if "my_df = pd.DataFrame([{" in main_code:
            # Format 1: Using list of dicts
            df = pd.DataFrame([{
                "Pclass": pclass,
                "Name": 24,
                "Sex": 0,
                "Age": 19,
                "SibSp": 0,
                "Parch": 0,
                "Fare": fare,
                "Embarked": embarked
            }])
        else:
            # Format 2: Using lists for each column
            df = pd.DataFrame({
                "Pclass": [pclass],
                "Name": [24],
                "Sex": [0],
                "Age": [19],
                "SibSp": [0],
                "Parch": [0],
                "Fare": [fare],
                "Embarked": [embarked]
            })

        # Apply one-hot encoding
        df = pd.get_dummies(df, columns=["Embarked"], prefix="Embarked")

        # Convert boolean values to integers if necessary
        for col in [c for c in df.columns if c.startswith("Embarked_")]:
            if df[col].dtype == bool:
                df[col] = df[col].astype(int)

        # Verify one-hot encoding for the current port
        expected_column = f"Embarked_{port}"
        assert expected_column in df.columns, f"One-hot column for {port} not created"
        assert df[expected_column].iloc[0] == 1, f"One-hot encoding value for {port} should be 1"

        # Other ports should be 0 or not present
        for other_port in ["S", "C", "Q"]:
            if other_port != port:
                other_col = f"Embarked_{other_port}"
                if other_col in df.columns:
                    assert df[other_col].iloc[0] == 0, f"One-hot value for non-selected port should be 0"

def test_implementation_makes_predictions(implementation):
    """
    Test that the implementation successfully calls the prediction models.
    """
    impl_name, module = implementation

    # Instead of executing the whole module, simulate one iteration
    with patch('main13.knn.predict', return_value=["Survived"]) as mock_knn_predict, \
         patch('main13.mlp.predict', return_value=["Not survived"]) as mock_mlp_predict:

        # Run just one iteration of the implementation logic
        pclass, fare, embarked = 1, 10, "S"

        main_code = inspect.getsource(module)

        # Create DataFrame based on implementation pattern
        if "my_df = pd.DataFrame([{" in main_code:
            df = pd.DataFrame([{
                "Pclass": pclass,
                "Name": 24,
                "Sex": 0,
                "Age": 19,
                "SibSp": 0,
                "Parch": 0,
                "Fare": fare,
                "Embarked": embarked
            }])
        else:
            df = pd.DataFrame({
                "Pclass": [pclass],
                "Name": [24],
                "Sex": [0],
                "Age": [19],
                "SibSp": [0],
                "Parch": [0],
                "Fare": [fare],
                "Embarked": [embarked]
            })

        # Apply one-hot encoding
        df = pd.get_dummies(df, columns=["Embarked"], prefix="Embarked")

        # Convert boolean values to integers if necessary
        for col in [c for c in df.columns if c.startswith("Embarked_")]:
            if df[col].dtype == bool:
                df[col] = df[col].astype(int)

        # Make predictions
        knn_result = sys.modules['main13'].knn.predict(df)
        mlp_result = sys.modules['main13'].mlp.predict(df)

        # Check that predictions work
        assert mock_knn_predict.called, "knn.predict not called"
        assert mock_mlp_predict.called, "mlp.predict not called"

        # Verify both models were called with the same DataFrame
        knn_df = mock_knn_predict.call_args[0][0]
        mlp_df = mock_mlp_predict.call_args[0][0]
        pd.testing.assert_frame_equal(knn_df, mlp_df, "Different DataFrames passed to models")

def test_implementation_iterates_all_combinations(implementation):
    """
    Test that the implementation iterates through all combinations of parameters.
    """
    impl_name, module = implementation

    # Expected parameter values
    expected_pclass_values = [1, 2, 3]
    expected_fare_values = list(range(10, 200, 10))
    expected_embarked_values = ["S", "Q", "C"]
    expected_iterations = len(expected_pclass_values) * len(expected_fare_values) * len(expected_embarked_values)

    # Setup mocks to track calls
    with patch('main13.knn.predict', return_value=["Survived"]) as mock_knn_predict, \
         patch('main13.mlp.predict', return_value=["Survived"]) as mock_mlp_predict, \
         patch('builtins.print') as mock_print:

        # Execute only the necessary nested loops structure
        seen_combinations = set()

        # Extract loop structure from code
        main_code = inspect.getsource(module)
        has_list_dict_format = "my_df = pd.DataFrame([{" in main_code

        # Simulate the nested loops without executing the whole module
        for pclass in expected_pclass_values:
            for fare in expected_fare_values:
                for embarked in expected_embarked_values:
                    # Create DataFrame based on implementation pattern
                    if has_list_dict_format:
                        df = pd.DataFrame([{
                            "Pclass": pclass,
                            "Name": 24,
                            "Sex": 0,
                            "Age": 19,
                            "SibSp": 0,
                            "Parch": 0,
                            "Fare": fare,
                            "Embarked": embarked
                        }])
                    else:
                        df = pd.DataFrame({
                            "Pclass": [pclass],
                            "Name": [24],
                            "Sex": [0],
                            "Age": [19],
                            "SibSp": [0],
                            "Parch": [0],
                            "Fare": [fare],
                            "Embarked": [embarked]
                        })

                    # Apply one-hot encoding
                    df = pd.get_dummies(df, columns=["Embarked"], prefix="Embarked")

                    # Convert boolean values to integers if necessary
                    for col in [c for c in df.columns if c.startswith("Embarked_")]:
                        if df[col].dtype == bool:
                            df[col] = df[col].astype(int)

                    # Make predictions
                    sys.modules['main13'].knn.predict(df)
                    sys.modules['main13'].mlp.predict(df)
                    seen_combinations.add((pclass, fare, embarked))

        # Verify all combinations were used

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