feat: доработки функционала бота

1. Подтверждение запуска розыгрыша:
   - Показ диалога с информацией об участниках и призах
   - Кнопки 'Да, провести' и 'Отмена'
   - Индикатор загрузки при проведении

2. Удаление сообщений администратором:
   - Команда /delete для удаления сообщений бота (ответ на сообщение)
   - Callback кнопка delete_message
   - Новый роутер message_admin_router

3. Определение владельцев счетов:
   - Извлечение номера клубной карты (последние 4 цифры)
   - Поиск владельца по club_card_number
   - Отображение владельца в списке обнаруженных счетов
   - Метод UserService.get_user_by_club_card()

4. Тестирование производительности:
   - Скрипт generate_test_accounts.py
   - Генерация файлов с 100, 500, 1000, 2000, 5000 счетов
   - Готовые тестовые файлы для проверки

5. Улучшения парсинга:
   - Обработка текста из кабинета с мусорными данными
   - Построчный парсинг с разбором по пробелам
   - Поддержка формата 'Viposnova  16-11-2025 22:19:36  17-24-66-42-38-31-53  0.00    2918'

6. Исправления багов:
   - AttributeError при отображении победителей без user_id
   - Безопасная обработка winner.user == None
This commit is contained in:
2025-11-17 10:42:41 +09:00
parent 65b550f8c8
commit 79eb66cf51
13 changed files with 8951 additions and 34 deletions

View File

@@ -1 +1 @@
925275 948258

76
generate_test_accounts.py Executable file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""
Генератор тестовых счетов для проверки производительности розыгрыша
"""
import random
def generate_account_number():
"""Генерирует случайный номер счета в формате XX-XX-XX-XX-XX-XX-XX"""
parts = []
for _ in range(7):
part = f"{random.randint(0, 99):02d}"
parts.append(part)
return "-".join(parts)
def generate_accounts(count, card_numbers=None):
"""
Генерирует список уникальных счетов
Args:
count: Количество счетов для генерации
card_numbers: Список номеров карт (опционально)
Returns:
List[str]: Список счетов
"""
accounts = set()
while len(accounts) < count:
account = generate_account_number()
# Добавляем с картой или без
if card_numbers and random.random() > 0.3: # 70% с картой
card = random.choice(card_numbers)
full_account = f"{card} {account}"
else:
full_account = account
accounts.add(full_account)
return list(accounts)
def save_to_file(accounts, filename):
"""Сохраняет счета в файл"""
with open(filename, 'w', encoding='utf-8') as f:
for account in accounts:
f.write(account + '\n')
print(f"✅ Сохранено {len(accounts)} счетов в файл {filename}")
def main():
"""Главная функция"""
print("🎲 Генератор тестовых счетов для розыгрыша\n")
# Параметры
counts = [100, 500, 1000, 2000, 5000]
card_numbers = ['2521', '2522', '2523', '2524', '2525']
for count in counts:
print(f"Генерация {count} счетов...")
accounts = generate_accounts(count, card_numbers)
filename = f"test_accounts_{count}.txt"
save_to_file(accounts, filename)
print("\n✅ Генерация завершена!")
print("\nИспользование:")
print("1. Скопируйте содержимое нужного файла")
print("2. В боте: Управление розыгрышами → Выберите розыгрыш → Участники → Добавить массово")
print("3. Вставьте содержимое файла")
print("4. Проведите розыгрыш и проверьте время выполнения")
if __name__ == "__main__":
main()

View File

@@ -22,6 +22,7 @@ from src.handlers.redraw_handlers import router as redraw_router
from src.handlers.chat_handlers import router as chat_router from src.handlers.chat_handlers import router as chat_router
from src.handlers.admin_chat_handlers import router as admin_chat_router from src.handlers.admin_chat_handlers import router as admin_chat_router
from src.handlers.account_handlers import account_router from src.handlers.account_handlers import account_router
from src.handlers.message_management import message_admin_router
# Настройка логирования # Настройка логирования
logging.basicConfig( logging.basicConfig(
@@ -109,6 +110,7 @@ async def main():
dp.include_router(router) dp.include_router(router)
# 2. Специфичные роутеры # 2. Специфичные роутеры
dp.include_router(message_admin_router) # Управление сообщениями администратором
dp.include_router(admin_router) # Админ панель - самая высокая специфичность dp.include_router(admin_router) # Админ панель - самая высокая специфичность
dp.include_router(registration_router) # Регистрация dp.include_router(registration_router) # Регистрация
dp.include_router(admin_account_router) # Админские команды счетов dp.include_router(admin_account_router) # Админские команды счетов

View File

@@ -148,6 +148,23 @@ class UserService:
if not formatted_number: if not formatted_number:
return None return None
@staticmethod
async def get_user_by_club_card(session: AsyncSession, club_card_number: str) -> Optional[User]:
"""
Получить пользователя по номеру клубной карты
Args:
session: Сессия БД
club_card_number: Номер клубной карты (4 цифры)
Returns:
User или None если не найден
"""
result = await session.execute(
select(User).where(User.club_card_number == club_card_number)
)
return result.scalar_one_or_none()
result = await session.execute( result = await session.execute(
select(User).where(User.account_number == formatted_number) select(User).where(User.account_number == formatted_number)
) )

View File

@@ -42,6 +42,7 @@ async def detect_account_input(message: Message, state: FSMContext):
""" """
Обнаружение ввода счетов в сообщении Обнаружение ввода счетов в сообщении
Активируется только для администраторов Активируется только для администраторов
Извлекает номер клубной карты и определяет владельца
""" """
if not is_admin(message.from_user.id): if not is_admin(message.from_user.id):
return return
@@ -52,16 +53,82 @@ async def detect_account_input(message: Message, state: FSMContext):
if not accounts: if not accounts:
return # Счета не обнаружены, пропускаем return # Счета не обнаружены, пропускаем
# Сохраняем счета в состоянии # Извлекаем номера клубных карт из последних 4 цифр каждого счета
await state.update_data(detected_accounts=accounts) from ..core.services import UserService
from ..core.registration_services import AccountService
# Формируем сообщение async with async_session_maker() as session:
accounts_text = "\n".join([f"{acc}" for acc in accounts]) accounts_with_owners = []
for account in accounts:
# Извлекаем только номер счета (без карты если есть)
parts = account.split()
account_number = parts[-1] if parts else account
# Извлекаем последние 4 цифры (номер клубной карты)
digits_only = account_number.replace('-', '').replace(' ', '')
if len(digits_only) >= 4:
club_card = digits_only[-4:] # Последние 4 цифры
# Ищем пользователя по номеру клубной карты
user = await UserService.get_user_by_club_card(session, club_card)
if user:
owner_info = f"@{user.username}" if user.username else user.first_name
accounts_with_owners.append({
'account': account,
'club_card': club_card,
'owner': owner_info,
'user_id': user.id
})
else:
accounts_with_owners.append({
'account': account,
'club_card': club_card,
'owner': None,
'user_id': None
})
else:
# Счет неверного формата
accounts_with_owners.append({
'account': account,
'club_card': None,
'owner': None,
'user_id': None
})
# Сохраняем счета в состоянии
await state.update_data(
detected_accounts=accounts,
accounts_with_owners=accounts_with_owners
)
# Формируем сообщение с владельцами
accounts_text_parts = []
for item in accounts_with_owners:
account = item['account']
club_card = item['club_card']
owner = item['owner']
if owner:
line = f"{account}{owner} (карта: {club_card})"
elif club_card:
line = f"{account} (карта: {club_card}, владелец не найден)"
else:
line = f"{account} (неверный формат)"
accounts_text_parts.append(line)
accounts_text = "\n".join(accounts_text_parts)
count = len(accounts) count = len(accounts)
# Подсчёт найденных владельцев
owners_found = sum(1 for item in accounts_with_owners if item['owner'])
text = ( text = (
f"🔍 <b>Обнаружен ввод счет{'а' if count == 1 else 'ов'}</b>\n\n" f"🔍 <b>Обнаружен ввод счет{'а' if count == 1 else 'ов'}</b>\n\n"
f"Найдено: <b>{count}</b>\n\n" f"Найдено: <b>{count}</b>\n"
f"Владельцев определено: <b>{owners_found}</b>\n\n"
f"{accounts_text}\n\n" f"{accounts_text}\n\n"
f"Выберите действие:" f"Выберите действие:"
) )

View File

@@ -409,7 +409,14 @@ async def show_lottery_detail(callback: CallbackQuery):
text += f"🏆 Результаты:\n" text += f"🏆 Результаты:\n"
for winner in winners: for winner in winners:
manual_mark = " 👑" if winner.is_manual else "" manual_mark = " 👑" if winner.is_manual else ""
# Безопасная обработка победителя - может быть без user_id
if winner.user:
username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name
else:
# Победитель по номеру счета без связанного пользователя
username = f"Счет: {winner.account_number}"
text += f"{winner.place}. {username}{manual_mark}\n" text += f"{winner.place}. {username}{manual_mark}\n"
buttons = [] buttons = []
@@ -2586,8 +2593,8 @@ async def choose_lottery_for_draw(callback: CallbackQuery):
@admin_router.callback_query(F.data.startswith("admin_conduct_")) @admin_router.callback_query(F.data.startswith("admin_conduct_"))
async def conduct_lottery_draw(callback: CallbackQuery): async def conduct_lottery_draw_confirm(callback: CallbackQuery):
"""Проведение розыгрыша""" """Запрос подтверждения проведения розыгрыша"""
if not is_admin(callback.from_user.id): if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True) await callback.answer("❌ Недостаточно прав", show_alert=True)
return return
@@ -2611,6 +2618,61 @@ async def conduct_lottery_draw(callback: CallbackQuery):
await callback.answer("Нет участников для розыгрыша", show_alert=True) await callback.answer("Нет участников для розыгрыша", show_alert=True)
return return
# Подсчёт призов
prizes_count = len(lottery.prizes) if lottery.prizes else 0
# Формируем сообщение с подтверждением
text = f"⚠️ <b>Подтверждение проведения розыгрыша</b>\n\n"
text += f"🎲 <b>Розыгрыш:</b> {lottery.title}\n"
text += f"👥 <b>Участников:</b> {participants_count}\n"
text += f"🏆 <b>Призов:</b> {prizes_count}\n\n"
if lottery.prizes:
text += "<b>Призы:</b>\n"
for i, prize in enumerate(lottery.prizes, 1):
text += f"{i}. {prize}\n"
text += "\n"
text += "❗️ <b>Внимание:</b> После проведения розыгрыша результаты нельзя будет изменить!\n\n"
text += "Продолжить?"
buttons = [
[InlineKeyboardButton(text="✅ Да, провести розыгрыш", callback_data=f"admin_conduct_confirmed_{lottery_id}")],
[InlineKeyboardButton(text="❌ Отмена", callback_data=f"admin_lottery_{lottery_id}")]
]
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_conduct_confirmed_"))
async def conduct_lottery_draw(callback: CallbackQuery):
"""Проведение розыгрыша после подтверждения"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
if not lottery:
await callback.answer("Розыгрыш не найден", show_alert=True)
return
if lottery.is_completed:
await callback.answer("Розыгрыш уже завершён", show_alert=True)
return
participants_count = await ParticipationService.get_participants_count(session, lottery_id)
if participants_count == 0:
await callback.answer("Нет участников для розыгрыша", show_alert=True)
return
# Показываем индикатор загрузки
await callback.answer("⏳ Проводится розыгрыш...", show_alert=True)
# Проводим розыгрыш через сервис # Проводим розыгрыш через сервис
winners_dict = await LotteryService.conduct_draw(session, lottery_id) winners_dict = await LotteryService.conduct_draw(session, lottery_id)

View File

@@ -0,0 +1,65 @@
"""
Хэндлеры для управления сообщениями администратором
"""
import logging
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from aiogram.filters import Command
from ..core.config import ADMIN_IDS
logger = logging.getLogger(__name__)
message_admin_router = Router(name="message_admin")
def is_admin(user_id: int) -> bool:
"""Проверка, является ли пользователь администратором"""
return user_id in ADMIN_IDS
@message_admin_router.message(Command("delete"))
async def delete_replied_message(message: Message):
"""
Удаление сообщения по команде /delete
Работает только если команда является ответом на сообщение бота
"""
if not is_admin(message.from_user.id):
await message.answer("❌ Недостаточно прав")
return
if not message.reply_to_message:
await message.answer("⚠️ Ответьте на сообщение бота командой /delete чтобы удалить его")
return
if message.reply_to_message.from_user.id != message.bot.id:
await message.answer("⚠️ Можно удалять только сообщения бота")
return
try:
# Удаляем сообщение бота
await message.reply_to_message.delete()
# Удаляем команду
await message.delete()
logger.info(f"Администратор {message.from_user.id} удалил сообщение {message.reply_to_message.message_id}")
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
await message.answer(f"Не удалось удалить сообщение: {str(e)}")
@message_admin_router.callback_query(F.data == "delete_message")
async def delete_message_callback(callback: CallbackQuery):
"""
Удаление сообщения по нажатию кнопки
"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
try:
await callback.message.delete()
await callback.answer("✅ Сообщение удалено")
logger.info(f"Администратор {callback.from_user.id} удалил сообщение {callback.message.message_id} кнопкой")
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
await callback.answer(f"❌ Ошибка: {str(e)}", show_alert=True)

View File

@@ -103,6 +103,9 @@ def parse_accounts_from_message(text: str) -> List[str]:
Поддерживает формат: "КАРТА СЧЕТ" (например "2521 11-22-33-44-55-66-77") Поддерживает формат: "КАРТА СЧЕТ" (например "2521 11-22-33-44-55-66-77")
или просто "СЧЕТ" (например "11-22-33-44-55-66-77") или просто "СЧЕТ" (например "11-22-33-44-55-66-77")
Также обрабатывает текст из кабинета с мусором:
"Viposnova 16-11-2025 22:19:36 17-24-66-42-38-31-53 0.00 2918"
Args: Args:
text: Текст сообщения text: Текст сообщения
@@ -114,6 +117,31 @@ def parse_accounts_from_message(text: str) -> List[str]:
accounts = [] accounts = []
# Обработка построчно - для текста из кабинета с мусорными данными
lines = text.strip().split('\n')
for line in lines:
# Разбиваем строку по пробелам
parts = line.strip().split()
# Ищем в каждой части паттерны счетов
for i, part in enumerate(parts):
# Проверяем, является ли эта часть счетом (7 пар цифр через дефис)
if re.match(r'^\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}$', part):
formatted = format_account_number(part)
if formatted and formatted not in accounts:
# Проверяем предыдущую часть на номер карты (4 цифры)
if i > 0 and re.match(r'^\d{4}$', parts[i-1]):
card = parts[i-1]
full_account = f"{card} {formatted}"
if full_account not in accounts:
accounts.append(full_account)
else:
# Добавляем только счет
accounts.append(formatted)
# Если построчная обработка ничего не нашла, используем старый метод
if not accounts:
# Паттерн 1: номер карты (4 цифры) + пробел + счет (7 пар цифр) # Паттерн 1: номер карты (4 цифры) + пробел + счет (7 пар цифр)
pattern_with_card = r'(\d{4})\s+(\d{2}[-\s]?\d{2}[-\s]?\d{2}[-\s]?\d{2}[-\s]?\d{2}[-\s]?\d{2}[-\s]?\d{2})' pattern_with_card = r'(\d{4})\s+(\d{2}[-\s]?\d{2}[-\s]?\d{2}[-\s]?\d{2}[-\s]?\d{2}[-\s]?\d{2}[-\s]?\d{2})'

100
test_accounts_100.txt Normal file
View File

@@ -0,0 +1,100 @@
2524 13-44-65-38-31-54-67
2523 31-91-70-64-88-67-03
2525 21-87-28-91-13-49-61
2523 35-22-65-25-15-99-32
2525 12-72-37-11-82-58-23
2525 96-39-53-66-81-43-28
2522 31-19-65-97-82-87-06
2521 54-03-08-21-52-27-86
2525 42-85-32-06-39-68-81
2522 94-50-44-81-24-67-25
28-66-94-77-24-23-40
72-64-73-89-62-11-90
2522 12-25-21-03-46-98-22
2524 54-06-23-93-94-44-50
2523 23-61-39-40-29-15-28
2525 13-85-23-66-37-16-95
2525 97-28-72-80-14-30-78
2525 11-69-37-13-79-35-12
89-44-47-63-67-54-12
2525 07-09-98-78-15-23-50
2523 05-03-90-01-62-57-18
65-07-18-74-28-42-66
2525 39-77-17-98-01-23-29
2522 05-50-21-93-79-11-61
2525 61-18-20-81-60-90-05
2521 15-92-74-93-64-78-54
2523 22-21-96-99-90-45-27
2521 30-97-48-67-95-75-79
2524 39-57-99-03-13-46-35
2522 98-54-80-56-33-65-44
20-91-91-30-15-65-25
98-04-80-73-50-11-42
98-34-41-64-88-01-63
2525 29-35-02-04-32-78-51
2523 62-44-20-56-62-78-01
2524 14-36-17-91-34-91-55
2524 17-01-76-83-62-31-93
04-44-22-26-04-55-87
2523 11-43-07-89-40-00-88
2521 84-28-72-28-33-60-44
2525 95-40-78-88-00-43-13
2522 69-21-29-41-81-96-77
2524 37-22-41-64-08-13-92
2524 73-96-94-27-64-09-09
33-27-89-47-46-62-85
2523 75-75-48-01-28-10-88
72-57-79-14-18-91-23
98-32-02-86-87-59-11
97-19-28-45-03-08-64
2523 74-22-18-22-46-58-94
2525 18-13-73-83-02-10-09
2523 41-15-99-26-09-14-97
2525 43-58-60-55-40-73-67
2523 42-97-48-61-70-60-38
80-70-44-15-17-55-49
2522 76-81-33-86-19-53-45
2525 45-94-04-45-89-90-28
2522 20-97-12-37-10-83-76
2524 34-32-51-50-78-80-97
2522 30-97-39-84-02-45-49
83-67-91-16-68-14-66
94-71-04-28-57-75-45
2524 83-82-42-15-67-91-48
2523 97-98-88-10-36-79-53
41-22-09-70-75-40-57
2522 77-94-56-22-88-02-16
2525 43-11-72-35-15-47-04
2525 35-57-25-41-26-07-37
57-06-88-62-15-34-66
2525 98-66-63-02-15-71-13
58-20-77-41-06-52-33
2521 11-98-92-27-38-94-75
2525 09-48-71-70-71-41-26
2525 79-05-30-49-24-22-33
26-70-94-22-64-89-48
2524 34-71-40-14-68-80-57
18-87-93-44-52-37-69
2524 09-39-78-85-80-17-81
2521 32-08-76-43-59-61-14
2523 93-56-87-85-14-53-72
2521 78-51-66-89-56-33-49
2522 20-24-45-32-47-44-53
41-37-43-28-56-43-54
2525 95-88-82-26-44-81-83
95-26-50-93-40-82-27
2521 32-43-09-99-96-51-73
2522 62-54-92-00-89-19-66
2525 28-53-29-95-71-21-66
2523 68-33-54-40-40-99-32
2523 60-51-93-71-70-19-35
2524 01-72-11-22-48-64-15
80-56-98-36-74-46-98
2524 08-02-36-94-18-37-27
2524 33-98-00-04-99-88-91
2523 90-77-79-06-91-29-07
2521 63-16-29-62-15-87-98
2522 61-37-16-90-50-14-83
2521 52-13-01-97-57-81-05
29-11-89-59-59-44-05
96-42-02-79-02-80-82

1000
test_accounts_1000.txt Normal file

File diff suppressed because it is too large Load Diff

2000
test_accounts_2000.txt Normal file

File diff suppressed because it is too large Load Diff

500
test_accounts_500.txt Normal file
View File

@@ -0,0 +1,500 @@
2524 88-62-46-84-72-08-35
2522 10-22-27-22-58-78-17
51-13-02-75-49-33-24
70-89-01-27-80-15-07
34-92-77-76-25-70-93
38-32-72-86-17-33-56
87-60-70-50-25-91-84
2523 21-14-04-05-19-46-25
2524 89-84-04-85-69-48-11
2524 50-35-99-27-26-02-20
2523 28-62-92-35-74-98-25
2522 93-14-72-96-97-42-96
2525 25-30-32-74-67-29-85
2521 36-86-88-64-61-88-89
2522 44-74-59-58-15-14-89
01-30-55-20-38-31-72
71-18-33-96-66-96-26
2524 94-32-58-56-35-13-97
2523 28-87-80-20-45-21-05
2524 72-50-32-62-44-95-03
2522 25-60-16-18-19-11-70
98-79-01-28-64-95-66
96-26-10-27-17-87-71
23-99-31-56-74-73-76
88-78-77-67-55-73-96
95-27-40-11-78-13-64
81-54-62-27-54-62-69
31-94-80-51-25-36-79
2524 44-10-96-63-84-30-07
2522 53-36-32-70-62-28-43
2523 60-82-65-57-94-68-25
2523 90-62-99-58-03-02-57
84-93-24-28-61-92-83
38-97-88-51-57-47-91
2522 97-18-71-19-17-46-11
74-25-19-72-73-69-05
2523 96-41-78-01-63-40-13
2525 93-75-84-73-30-84-68
2523 29-78-54-03-00-21-31
2524 74-45-78-17-55-77-54
2525 42-11-31-48-56-32-88
2525 69-47-22-59-62-43-20
2523 01-22-13-57-05-25-44
2525 59-22-43-08-53-48-82
2525 32-15-12-73-96-25-50
2525 90-04-74-22-33-88-10
2522 30-32-71-15-43-34-55
30-92-54-05-94-53-54
2525 29-58-08-99-46-04-29
46-27-64-43-09-37-58
2525 77-95-40-98-58-08-54
2525 02-66-43-02-60-18-34
16-37-17-50-65-63-51
28-00-31-28-74-01-13
2521 18-65-37-13-86-46-08
2524 88-84-69-86-18-46-49
25-23-65-85-03-80-42
2523 10-64-29-31-20-89-52
2524 65-21-51-30-91-21-68
33-24-81-00-31-10-06
2522 66-21-20-66-66-77-70
64-36-82-81-22-07-90
2524 59-29-33-33-51-95-17
2523 00-93-53-78-54-23-22
2522 73-77-13-34-10-90-73
2521 80-60-56-32-06-52-22
61-17-66-25-81-17-53
2524 60-47-94-82-73-16-91
2524 42-23-08-47-92-68-73
2523 96-42-17-80-54-21-92
43-41-24-82-73-89-70
58-59-94-04-58-25-95
65-09-40-69-61-49-66
2524 50-80-86-64-00-07-03
2525 49-88-90-85-64-35-76
2524 45-24-80-26-42-84-59
2524 95-24-66-37-33-61-07
2523 49-58-55-29-51-10-61
2525 39-03-45-88-41-32-53
2523 73-96-56-70-51-13-71
65-18-22-20-11-92-26
2525 80-30-71-96-23-95-74
2521 68-19-86-32-40-86-59
2522 07-03-45-99-77-61-66
2522 53-26-95-59-95-36-13
2525 41-02-61-74-69-53-72
2521 40-42-28-13-59-79-73
2522 03-31-84-02-95-87-67
04-54-85-07-18-08-63
2522 51-18-39-20-56-42-88
2525 90-88-19-93-08-36-74
2522 23-14-28-13-65-76-55
2523 24-89-75-22-08-30-07
15-26-21-64-07-12-45
2521 79-89-45-51-27-87-84
2525 01-11-24-63-37-93-77
2524 81-41-39-29-85-72-75
2525 64-96-76-67-37-51-52
21-31-25-17-61-80-92
2524 72-32-21-73-93-88-48
84-27-78-23-47-96-13
2523 52-86-55-42-99-36-96
2524 10-33-99-48-82-51-25
95-69-56-50-65-47-42
2525 99-89-69-98-27-91-33
06-20-51-97-71-00-53
2522 22-05-43-81-46-67-40
2521 37-08-49-25-33-08-77
2524 63-03-27-24-77-20-41
65-59-99-21-28-67-74
51-89-42-53-15-48-48
41-60-33-82-91-19-40
2522 47-26-52-13-21-61-61
32-81-00-16-63-90-66
2524 18-12-12-11-89-20-60
2522 29-93-53-71-59-57-17
2522 17-61-02-56-63-48-90
2522 87-56-66-57-13-34-32
27-43-61-72-26-68-94
2525 15-74-04-57-85-46-89
2525 58-35-93-12-58-24-84
41-09-96-02-81-97-85
04-92-76-03-21-36-38
36-82-09-76-50-91-40
2521 31-48-77-83-23-85-58
91-08-41-12-22-67-92
2525 91-01-95-06-20-56-66
2523 92-09-07-53-90-73-56
2523 24-88-11-05-06-18-63
2525 14-89-03-92-45-65-53
2523 73-98-00-08-94-74-60
11-25-05-77-54-25-38
2525 24-14-14-61-13-96-41
28-33-55-89-06-90-31
2523 92-90-32-07-42-96-04
2525 79-80-48-56-75-29-12
2521 77-97-88-83-04-44-09
2523 82-96-37-98-15-52-75
2522 64-34-21-10-96-85-39
2524 31-52-64-02-96-39-16
03-50-03-64-37-62-21
2521 49-63-37-97-53-63-00
2525 94-49-52-77-74-48-81
55-40-74-74-81-86-50
2524 06-70-54-03-82-67-17
75-19-75-29-43-35-82
2521 42-96-95-66-89-84-01
2521 55-33-17-44-67-26-89
2524 56-64-65-06-52-00-85
2522 93-66-95-15-90-23-90
2523 31-25-99-15-61-01-30
2525 54-54-54-47-69-06-33
2525 17-40-02-42-79-86-21
2522 21-12-01-11-51-55-14
2521 46-20-64-13-21-06-15
2523 92-85-71-89-97-70-84
2523 22-84-47-04-78-47-01
62-49-03-81-98-15-91
2524 79-54-71-16-36-91-63
2522 02-11-79-98-69-92-57
2525 32-76-56-57-96-23-90
2523 06-87-57-07-02-01-85
2521 18-35-94-83-28-73-15
2523 97-04-86-66-40-64-86
2521 55-97-94-59-99-20-57
2525 18-46-50-17-69-33-41
2522 09-48-99-58-34-13-61
2523 28-82-53-71-21-05-09
2523 08-12-90-23-74-10-27
2525 32-08-45-22-72-72-76
60-67-63-50-96-10-27
2525 75-03-19-97-62-80-88
2522 97-86-67-50-27-37-08
49-08-22-06-86-17-86
2524 09-80-21-70-82-91-48
96-06-92-25-94-08-57
2525 21-35-94-03-85-72-61
2521 39-93-53-66-86-81-96
2524 06-18-23-18-88-94-09
2521 52-96-14-51-04-51-36
2522 10-62-26-66-78-03-94
2525 58-22-74-01-66-37-97
2524 22-82-49-98-55-97-36
2523 04-16-77-51-80-89-13
70-51-03-12-10-26-56
2521 80-93-55-85-90-06-27
2525 18-63-31-58-45-52-61
17-10-85-46-30-32-82
73-84-60-73-28-53-48
2521 13-98-24-82-40-06-10
2521 58-59-74-00-18-34-85
2524 92-02-64-75-83-14-50
10-26-44-71-18-12-71
2523 25-09-58-53-10-53-54
2521 34-51-86-52-12-41-76
2522 71-42-30-72-71-45-59
2524 00-71-32-40-12-45-68
2524 74-50-48-06-05-52-06
48-88-23-94-23-40-74
2525 91-22-15-04-72-70-70
2521 76-78-90-23-44-92-83
2525 57-39-63-94-24-69-04
14-88-43-54-27-70-11
2522 18-25-25-91-36-53-23
2524 36-15-88-30-21-64-83
2525 66-11-70-60-37-02-63
43-11-84-99-73-28-48
01-03-64-24-84-70-15
2524 48-76-97-28-23-64-71
2524 77-08-08-23-73-96-22
2521 64-02-43-87-85-72-84
2525 85-46-13-04-03-63-60
2524 56-96-76-02-20-13-95
31-54-15-57-42-74-53
89-00-93-32-62-12-11
45-76-98-25-74-09-04
2521 64-30-44-10-39-95-33
44-71-95-86-12-54-08
63-13-57-14-13-48-16
41-87-71-95-17-22-88
2521 55-23-84-04-27-20-38
2523 80-64-38-39-76-43-04
2523 81-83-82-90-45-95-65
2523 57-84-88-16-25-30-98
2525 78-21-73-66-17-08-23
13-96-69-65-56-65-03
2522 76-37-07-36-14-56-29
2525 25-69-00-04-35-06-73
2525 63-19-14-57-67-48-50
2521 35-43-79-88-05-41-04
2525 24-39-13-22-92-33-38
39-87-05-09-65-00-95
2522 18-68-83-63-94-11-52
59-66-84-42-56-03-62
36-35-03-95-91-45-41
16-11-69-63-84-39-80
04-84-19-52-59-91-38
2523 18-18-33-99-33-21-00
2524 23-70-82-88-62-37-02
2524 84-81-71-58-92-39-45
45-37-02-62-10-07-76
82-02-00-62-68-89-90
2524 86-09-14-71-82-07-96
00-46-39-33-52-92-78
2522 52-39-25-89-07-07-57
2524 84-73-35-01-08-20-67
01-20-59-64-93-70-69
2521 54-32-02-66-48-17-66
2522 27-88-88-20-04-95-37
2522 64-20-24-10-80-29-56
97-57-32-45-22-40-46
96-34-25-40-82-57-74
2522 81-31-85-33-45-63-70
2524 66-71-41-81-31-98-25
49-82-16-11-72-89-45
2521 66-43-39-05-15-18-35
2525 33-11-45-38-33-86-68
2522 98-15-12-20-40-53-38
2523 88-42-37-81-18-01-02
2521 11-65-99-21-43-15-22
53-13-41-07-68-00-08
2524 47-73-46-61-53-08-26
2523 08-19-28-22-45-02-64
2521 44-82-74-93-95-67-71
2523 58-08-17-31-34-08-12
2525 14-35-43-99-32-32-85
16-39-50-48-61-01-68
21-01-79-67-64-02-34
2523 29-90-42-53-74-49-24
43-36-98-42-50-74-58
2521 94-81-74-15-33-82-12
2525 58-11-35-62-67-84-14
51-29-63-65-41-59-61
2521 83-82-27-34-21-39-89
2524 02-33-52-60-73-83-02
98-60-39-67-78-63-16
2523 64-01-33-01-30-29-51
11-75-71-71-03-02-16
2522 26-61-47-07-99-43-61
2525 47-52-94-94-22-86-50
38-06-39-62-20-43-40
2525 35-95-33-15-26-71-68
2525 42-85-13-31-42-01-39
2522 49-75-29-96-44-83-78
77-78-32-83-24-38-75
2523 49-04-42-96-56-31-75
2525 97-48-18-70-00-51-18
44-65-44-13-62-33-58
41-59-53-82-42-97-31
2525 25-11-42-32-67-02-45
71-63-18-02-65-19-04
95-17-37-75-09-90-68
2524 03-54-07-90-12-65-23
80-79-45-70-64-72-68
2523 31-58-15-79-76-04-38
20-15-21-46-53-62-33
2521 36-38-82-78-34-89-65
2524 84-20-61-66-19-69-95
2525 48-16-40-86-41-78-35
2524 03-37-64-84-01-78-94
2524 44-67-25-32-81-53-15
2525 48-52-48-87-90-98-18
30-60-22-87-47-25-15
2525 33-84-89-80-86-70-09
73-93-46-17-69-91-97
2522 84-97-55-42-32-60-92
2525 07-07-64-14-63-51-14
2524 55-03-93-60-14-91-74
2523 32-19-25-22-77-78-15
2521 73-53-49-22-54-23-90
2521 78-87-15-24-92-85-90
2522 34-62-94-56-11-17-51
2522 30-07-45-21-59-94-54
2523 55-92-76-54-95-29-71
76-03-18-42-39-37-30
89-26-94-14-17-99-40
50-10-05-18-34-97-32
2521 04-25-61-71-00-32-50
2523 56-82-78-00-94-99-90
2524 34-99-74-17-91-98-84
75-74-30-25-42-81-71
2524 37-69-87-33-41-40-02
50-19-15-78-99-25-22
18-49-62-94-65-95-87
2523 77-16-41-76-81-66-35
2522 59-70-39-69-97-92-96
2525 81-72-07-51-68-40-23
2525 63-60-68-44-43-62-08
2521 73-20-40-52-98-97-29
2523 38-27-54-83-03-00-26
2522 08-39-39-32-25-45-56
2523 40-34-67-04-37-33-29
2524 11-41-84-92-94-16-33
2521 89-55-98-69-20-03-41
2521 27-09-16-26-04-82-81
2521 38-83-20-21-79-29-81
2525 61-09-59-92-28-67-66
47-19-80-43-43-20-93
2521 87-80-59-51-20-32-74
2524 70-14-85-72-40-80-60
2523 77-57-03-64-45-21-38
2521 88-33-82-62-01-49-55
88-11-93-34-85-87-69
06-02-35-69-77-05-11
2525 84-91-87-54-60-51-46
2525 78-99-73-78-24-94-24
29-50-87-38-87-93-90
2521 84-73-41-32-87-95-52
2521 53-62-20-06-17-74-40
2524 13-47-06-47-93-65-29
2522 38-85-34-37-71-05-30
2523 48-39-49-57-23-78-96
2522 81-22-48-06-91-47-42
15-65-95-20-46-73-48
2521 80-46-01-82-74-75-03
2521 11-40-88-15-16-96-49
2524 43-94-42-84-35-12-17
2524 18-12-45-80-30-07-72
2525 57-99-35-42-43-67-68
63-99-70-67-80-84-31
2521 19-80-66-96-16-61-44
90-66-93-65-04-32-71
52-73-25-85-08-22-10
41-42-86-69-91-89-93
2525 69-06-01-51-03-59-91
2522 25-00-80-31-11-83-55
18-77-42-88-77-67-11
2525 83-90-27-60-78-24-26
2523 94-00-59-37-68-05-50
2521 55-74-61-32-63-51-01
2522 61-90-85-23-11-51-03
2523 94-78-26-87-62-57-55
2524 22-42-80-60-85-42-48
2521 47-06-03-02-78-96-05
2524 78-54-40-11-40-54-75
68-20-77-52-00-10-70
2521 04-82-37-21-22-19-17
2524 62-94-76-61-11-56-75
14-04-11-98-47-23-56
2521 54-41-86-59-91-91-61
14-00-07-96-01-62-04
29-18-98-86-00-88-70
62-78-07-66-28-68-93
23-67-08-74-60-57-55
2521 44-26-69-25-31-41-36
2523 65-82-68-93-69-64-68
25-23-22-44-51-33-19
2521 45-37-36-91-84-70-59
2521 99-23-86-83-01-62-70
85-94-26-28-50-89-75
2521 16-30-23-12-48-81-01
36-43-94-12-58-24-73
2522 22-11-15-28-77-93-46
24-00-68-13-80-33-10
2524 79-10-22-21-74-10-56
2525 50-92-57-27-51-67-57
53-28-93-58-39-45-05
2522 49-13-78-56-46-96-33
2523 65-40-89-45-25-45-78
2523 59-35-54-94-01-68-62
2521 21-26-28-37-80-04-15
31-71-93-03-54-89-84
2524 06-16-02-83-98-00-11
2524 79-24-11-13-14-02-37
2522 08-95-10-92-33-49-44
2521 49-65-96-35-05-04-53
2522 41-32-18-41-45-88-81
2521 53-55-62-25-06-39-43
2521 05-14-32-15-50-24-82
2525 60-47-47-27-56-11-89
2521 44-77-64-51-88-05-75
2523 25-51-51-60-61-81-76
2523 92-38-26-84-23-01-06
28-67-09-28-67-04-31
2525 29-39-37-88-09-23-79
33-48-56-81-66-84-89
23-38-63-69-33-39-02
2522 70-04-29-62-18-94-74
2524 31-07-43-44-22-06-24
2524 58-41-39-65-11-94-61
2525 85-80-40-57-39-02-03
2524 45-80-38-47-70-95-24
82-85-24-60-48-90-50
04-03-01-57-35-97-62
2524 82-00-55-91-97-52-37
2523 97-00-38-05-71-74-38
32-09-89-80-29-48-51
84-75-37-85-77-75-29
2523 51-44-85-74-10-90-74
2523 25-63-16-22-75-48-79
80-59-44-91-58-46-30
2522 31-48-06-26-42-59-84
48-50-24-48-30-74-73
31-26-27-54-59-28-34
2522 87-66-84-15-33-31-95
51-85-47-66-51-64-87
2523 55-09-83-65-81-58-51
2522 99-11-54-41-04-24-54
78-44-82-14-91-00-67
31-38-18-34-44-79-59
2521 75-13-20-65-21-16-15
2523 26-44-92-56-41-70-22
95-71-53-73-55-50-94
10-44-09-45-67-13-75
2525 06-21-87-86-54-94-02
2524 31-85-09-42-29-45-57
2525 42-01-75-05-25-11-40
2524 12-14-10-27-19-30-99
79-97-04-48-87-42-00
2521 90-02-73-89-64-29-10
2523 29-17-32-76-08-65-75
2524 70-31-69-39-33-84-38
2525 71-52-62-55-12-16-57
36-69-53-13-49-70-66
85-12-10-39-29-80-35
2524 26-09-42-08-04-99-55
2523 33-23-74-47-43-33-24
2525 06-91-79-15-79-29-41
60-88-10-40-92-23-52
2523 24-05-58-34-80-77-14
2522 74-71-28-79-29-38-72
2521 80-50-12-20-47-99-78
2521 06-83-17-55-45-79-82
2521 13-52-26-76-99-70-20
2524 84-64-14-58-40-09-62
2524 86-97-11-55-57-83-16
2522 79-38-56-35-52-07-41
91-38-01-67-78-65-73
2523 05-11-50-18-20-12-38
2521 03-88-90-27-37-15-37
2525 83-26-08-00-50-20-68
2521 68-65-73-31-70-44-45
2524 54-66-91-09-07-74-26
2525 72-65-73-73-62-24-96
07-41-74-07-86-07-39
2522 64-48-93-29-40-97-14
2525 79-90-61-88-87-15-59
2524 50-47-16-17-09-15-14
2521 46-06-40-88-48-85-88
91-27-05-71-25-84-20
2522 12-22-39-13-04-78-78
2525 58-11-44-63-05-97-71
2521 70-16-43-07-87-51-85
2521 58-92-61-20-12-28-60
57-80-24-58-22-03-15
2524 12-08-29-52-75-46-34
2524 63-17-74-41-08-29-16
81-05-91-02-20-96-92
96-59-37-84-38-68-85
34-09-34-90-82-90-14
45-66-92-96-14-48-83
2522 01-61-02-21-68-28-60
89-01-37-64-20-77-75
14-00-50-43-04-66-06
2521 06-35-29-40-03-24-19
2524 78-34-98-20-72-56-24
54-05-64-46-00-00-54
87-00-71-87-41-99-40
70-50-43-54-84-95-28
2524 87-53-38-76-20-49-78

5000
test_accounts_5000.txt Normal file

File diff suppressed because it is too large Load Diff