add algam

This commit is contained in:
2025-09-22 11:54:42 +05:00
parent 3c418a97b4
commit 5b9c55a4dc
34 changed files with 4700 additions and 426 deletions

337
bot_old.py Normal file
View File

@@ -0,0 +1,337 @@
import os
from io import BytesIO
from aiogram.types import KeyboardButton, ReplyKeyboardMarkup
import asyncio
from datetime import datetime
import psycopg2
import psycopg2.extras
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
from aiogram.filters.state import StateFilter
from dotenv import load_dotenv
import logging
# Загрузка переменных окружения
load_dotenv()
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Конфигурация из переменных окружения
TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
if not TOKEN:
raise ValueError("TELEGRAM_BOT_TOKEN не найден в переменных окружения")
# ===== PostgreSQL =====
try:
conn = psycopg2.connect(
dbname=os.getenv("DB_NAME", "botdb"),
user=os.getenv("DB_USER", "botuser"),
password=os.getenv("DB_PASSWORD"),
host=os.getenv("DB_HOST", "localhost"),
port=os.getenv("DB_PORT", "5432")
)
cur = conn.cursor()
logger.info("Подключение к базе данных установлено")
except Exception as e:
logger.error(f"Ошибка подключения к базе данных: {e}")
raise
bot = Bot(token=TOKEN)
dp = Dispatcher(storage=MemoryStorage())
# ===== FSM =====
class Form(StatesGroup):
consent = State()
fio = State()
dob = State()
phone = State()
city = State()
klimov = State()
# ===== /start =====
@dp.message(Command("start"))
async def start(message: types.Message, state: FSMContext):
kb = [
[KeyboardButton(text="✅ Да, согласен"), KeyboardButton(text="❌ Нет")]
]
keyboard = ReplyKeyboardMarkup(keyboard=kb, resize_keyboard=True)
await message.answer(
"Привет! Я помогу пройти тест Климова.\n\n"
"Сначала — согласие на обработку персональных данных.\n"
"Ты согласен?",
reply_markup=keyboard
)
await state.set_state(Form.consent)
# ===== Согласие =====
@dp.message(Form.consent)
async def consent(message: types.Message, state: FSMContext):
if message.text.startswith(""):
await state.set_state(Form.fio)
await message.answer("Введите своё ФИО:", reply_markup=ReplyKeyboardRemove())
else:
await message.answer("Без согласия мы не можем продолжить 🙏", reply_markup=ReplyKeyboardRemove())
await state.clear()
# ===== ФИО =====
@dp.message(Form.fio)
async def fio(message: types.Message, state: FSMContext):
await state.update_data(fio=message.text)
await state.set_state(Form.dob)
await message.answer("Введите дату рождения (ДД.MM.ГГГГ):")
# ===== Дата рождения =====
@dp.message(Form.dob)
async def dob(message: types.Message, state: FSMContext):
await state.update_data(dob=message.text)
await state.set_state(Form.phone)
await message.answer("Введите номер телефона:")
# ===== Телефон =====
@dp.message(Form.phone)
async def phone(message: types.Message, state: FSMContext):
await state.update_data(phone=message.text)
await state.set_state(Form.city)
await message.answer("Из какого ты населённого пункта?")
# ===== Город + меню тестов =====
@dp.message(Form.city)
async def city(message: types.Message, state: FSMContext):
await state.update_data(city=message.text)
data = await state.get_data()
# Преобразуем дату в формат PostgreSQL
try:
dob_parsed = datetime.strptime(data["dob"], "%d.%m.%Y").date()
except ValueError:
await message.answer("Неверный формат даты. Введите дату в формате ДД.MM.ГГГГ")
await state.set_state(Form.dob)
return
try:
cur.execute(
"""
INSERT INTO users (telegram_id, fio, dob, phone, city)
VALUES (%s, %s, %s, %s, %s)
ON CONFLICT (telegram_id) DO UPDATE
SET fio=EXCLUDED.fio, dob=EXCLUDED.dob, phone=EXCLUDED.phone, city=EXCLUDED.city
""",
(message.from_user.id, data["fio"], dob_parsed, data["phone"], data["city"])
)
conn.commit()
await message.answer("Анкета сохранена ✅")
logger.info(f"Пользователь {message.from_user.id} сохранил анкету")
except Exception as e:
conn.rollback()
logger.error(f"Ошибка при сохранении анкеты пользователя {message.from_user.id}: {e}")
await message.answer("Произошла ошибка при сохранении анкеты. Попробуйте позже.")
return
kb = [
[KeyboardButton(text="📘 Тест Климова")],
[KeyboardButton(text="📗 Тест №2"), KeyboardButton(text="📙 Тест №3")]
]
keyboard = ReplyKeyboardMarkup(keyboard=kb, resize_keyboard=True)
await state.clear()
await message.answer("Теперь выбери тест:", reply_markup=keyboard)
# ===== Выбор теста после анкеты =====
# Выбор теста — только когда FSM пустой (после анкеты)
@dp.message(StateFilter(None))
async def select_test(message: types.Message, state: FSMContext):
if message.text == "📘 Тест Климова":
try:
cur.execute("SELECT id, text1, category1, text2, category2, image FROM klimov_questions ORDER BY id")
questions = cur.fetchall()
if not questions:
await message.answer("Вопросы теста не найдены. Обратитесь к администратору.")
logger.warning("Вопросы теста Климова не найдены в базе данных")
return
await state.update_data(klimov_questions=questions, klimov_index=0, klimov_result={})
await state.set_state(Form.klimov)
await send_klimov_question(message.from_user.id, state)
logger.info(f"Пользователь {message.from_user.id} начал тест Климова")
except Exception as e:
logger.error(f"Ошибка при загрузке теста Климова: {e}")
await message.answer("Произошла ошибка при загрузке теста. Попробуйте позже.")
else:
await message.answer("Пока этот тест не реализован.")
import os
from aiogram.types import KeyboardButton, ReplyKeyboardMarkup, FSInputFile
from aiogram.fsm.context import FSMContext
async def send_klimov_question(user_id, state: FSMContext):
data = await state.get_data()
index = data["klimov_index"]
questions = data["klimov_questions"]
if index >= len(questions):
await send_klimov_result(user_id, state)
await state.clear()
return
q = questions[index] # (id, text1, category1, text2, category2, image)
# Клавиатура с вариантами
kb = [
[KeyboardButton(text=q[1])],
[KeyboardButton(text=q[3])]
]
keyboard = ReplyKeyboardMarkup(keyboard=kb, resize_keyboard=True)
# Отправка картинки, если есть
image_filename = os.path.basename(q[5].strip()) if q[5] else None
if image_filename:
image_path = os.path.join(os.getcwd(), "images", image_filename)
logger.debug(f"Путь к изображению: {image_path}, существует: {os.path.isfile(image_path)}")
if os.path.isfile(image_path):
try:
photo = FSInputFile(image_path)
await bot.send_photo(
chat_id=user_id,
photo=photo,
caption=f"Вопрос {index+1} из {len(questions)}:\nВыберите вариант",
reply_markup=keyboard
)
return
except Exception as e:
logger.error(f"Ошибка отправки изображения {image_path}: {e}")
# Продолжаем с текстовым сообщением
# Если картинки нет, просто отправляем текст
await bot.send_message(
chat_id=user_id,
text=f"Вопрос {index+1} из {len(questions)}:\nВыберите вариант",
reply_markup=keyboard
)
# ===== Ответ на вопросы =====
@dp.message(StateFilter(Form.klimov))
async def klimov_answer(message: types.Message, state: FSMContext):
data = await state.get_data()
index = data["klimov_index"]
questions = data["klimov_questions"]
result = data["klimov_result"]
q = questions[index]
# Проверяем какой вариант выбрал пользователь
if message.text == q[1]:
result[q[2]] = result.get(q[2], 0) + 1
elif message.text == q[3]:
result[q[4]] = result.get(q[4], 0) + 1
index += 1
await state.update_data(klimov_index=index, klimov_result=result)
# Переходим к следующему вопросу
await send_klimov_question(message.from_user.id, state)
# ===== Функция показа вопроса =====
# Функция показа вопроса
# ===== Функция для сохранения результата в БД =====
async def save_klimov_result(user_id, state: FSMContext):
data = await state.get_data()
result = data.get("klimov_result", {})
try:
conn = psycopg2.connect(
dbname=os.getenv("DB_NAME", "botdb"),
user=os.getenv("DB_USER", "botuser"),
password=os.getenv("DB_PASSWORD"),
host=os.getenv("DB_HOST", "localhost"),
port=os.getenv("DB_PORT", "5432")
)
cur = conn.cursor()
# Получаем user_id из таблицы users
cur.execute("SELECT id FROM users WHERE telegram_id = %s", (user_id,))
user_row = cur.fetchone()
if user_row:
db_user_id = user_row[0]
cur.execute(
"INSERT INTO results (user_id, test_name, result) VALUES (%s, %s, %s)",
(db_user_id, "Тест Климова", psycopg2.extras.Json(result))
)
conn.commit()
logger.info(f"Результат теста сохранен для пользователя {user_id}")
cur.close()
conn.close()
except Exception as e:
logger.error(f"Ошибка сохранения результата теста: {e}")
# Не прерываем работу бота из-за ошибки сохранения
# Словарь с описаниями профессий по категориям
professions_desc = {
"Человек-природа": "К типу 'человек-природа' можно отнести профессии, связанные с изучением живой и неживой природы (микробиолог, агрохимик, геолог), с уходом за растениями и животными (лесовод, овощевод, зоотехник).",
"Человек-техника": "Тип 'человек-техника' включает в себя профессии, связанные с созданием, монтажом, сборкой и наладкой технических устройств (каменщик, монтажник, сварщик, инженер-конструктор), эксплуатацией технических средств (водитель, крановщик, токарь, швея), ремонтом техники.",
"Человек-человек": "К профессиям типа 'человек-человек' относятся профессии, связанные с медицинским обслуживанием (врач, медсестра, санитарка), обучением и воспитанием (воспитатель, няня, учитель, преподаватель, тренер), бытовым обслуживанием (продавец, проводник, официант).",
"Человек-знаковая система": "Тип 'человек - знаковая система' объединяет профессии, связанные с текстами (корректор, оператор ПК, переводчик, библиотекарь), с цифрами, формулами и таблицами (программист, экономист, бухгалтер, кассир), с чертежами, картами, схемами (штурман, чертежник).",
"Человек-художественный образ": "К типу 'человек - художественный образ' можно отнести профессии, связанные с созданием, проектированием, моделированием художественных произведений (художник, журналист, модельер, композитор), с воспроизведением, изготовлением различных изделий по эскизу, образцу."
}
# ===== Функция для форматированного вывода результатов =====
# ===== Форматированный вывод результатов с процентами по максимуму =====
async def send_klimov_result(user_id, state: FSMContext):
data = await state.get_data()
result = data.get("klimov_result", {})
questions = data.get("klimov_questions", [])
if not questions:
await bot.send_message(user_id, "Вы не ответили ни на один вопрос.")
return
# Подсчёт максимального количества очков для каждой категории
max_points = {}
for q in questions:
# q[2] и q[4] — категории для вариантов
max_points[q[2]] = max_points.get(q[2], 0) + 1
max_points[q[4]] = max_points.get(q[4], 0) + 1
output = "Ваши результаты:\n"
for category, max_pt in max_points.items():
pts = result.get(category, 0)
percent = (pts / max_pt) * 100
output += f"- {category} - {percent:.1f}%\n"
# Определяем категорию с наибольшим результатом
if result:
top_category = max(result, key=result.get)
output += f"\nТип профессии: {top_category}"
await bot.send_message(user_id, output, reply_markup=types.ReplyKeyboardRemove())
# Затем по очереди отправляем описания
for category in ["Человек-природа", "Человек-техника", "Человек-человек",
"Человек-знаковая система", "Человек-художественный образ"]:
await bot.send_message(user_id, f"🟢 {category}\n\n{professions_desc[category]}")
# Последнее сообщение с контактами / ссылками
await bot.send_message(
user_id,
" Задать вопрос или записаться на индивидуальную консультацию по построению образовательного маршрута можно здесь: @chrtakova11\n\n"
"Узнать подробнее о профессиях: https://postupi.online/professii/so/"
)
# ===== Запуск бота =====
async def main():
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())