from fastapi import FastAPI, HTTPException, Depends, Request from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware from typing import List, Dict, Any import json import httpx from models import * from auth import * app = FastAPI(title="Recommendation Test API") # CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # In-memory storage users_db = {} user_sessions = {} user_answers_db = {} # LLM configuration LLM_API_URL = "https://api.openai.com/v1/chat/completions" LLM_API_KEY = "your-llm-api-key" # Словарь форм предпринимательства с описаниями BUSINESS_FORMS = { "ИП": { "name": "Индивидуальный предприниматель (ИП)", "description": "Самая популярная форма для малого бизнеса. Простая регистрация, минимальная отчетность, но полная ответственность по обязательствам.", "advantages": [ "Простота регистрации и закрытия", "Минимальная отчетность", "Свободное распоряжение доходами", "Низкие налоги при УСН", "Можно работать без расчетного счета" ], "disadvantages": [ "Ответственность всем личным имуществом", "Ограничения на некоторые виды деятельности", "Не подходит для привлечения инвесторов", "Нельзя продать бизнес" ], "taxes": "УСН (6% с доходов или 15% с прибыли), Патент, НПД", "liability": "Полная всем имуществом", "registration_cost": "800-5000 руб.", "best_for": "Небольшой бизнес, фриланс, розничная торговля, услуги" }, "ООО": { "name": "Общество с ограниченной ответственностью", "description": "Юридическое лицо, идеально подходящее для бизнеса с партнерами. Ответственность ограничена уставным капиталом.", "advantages": [ "Ответственность в пределах уставного капитала", "Возможность привлечения партнеров", "Больше доверия у контрагентов", "Нет ограничений по видам деятельности", "Можно продать долю в бизнесе" ], "disadvantages": [ "Сложнее отчетность", "Высокие штрафы за нарушения", "Обязательный расчетный счет", "Сложнее закрыть" ], "taxes": "УСН, ОСНО (20% прибыль + НДС)", "liability": "В пределах уставного капитала (от 10 тыс. руб.)", "registration_cost": "4000-15000 руб.", "best_for": "Бизнес с партнерами, средний бизнес, торговля с юрлицами" }, "ПАО": { "name": "Публичное акционерное общество", "description": "Форма для крупного бизнеса с возможностью привлечения инвестиций через открытую подписку на акции.", "advantages": [ "Свободное обращение акций на бирже", "Возможность привлечения крупных инвестиций", "Высокий статус и доверие", "Ответственность только стоимостью акций" ], "disadvantages": [ "Сложная регистрация и отчетность", "Публичное раскрытие информации", "Высокие затраты на содержание", "Строгие требования законодательства" ], "taxes": "ОСНО (20% прибыль + НДС)", "liability": "В пределах стоимости акций", "registration_cost": "от 100000 руб.", "best_for": "Крупный бизнес, выход на IPO, привлечение массовых инвестиций" }, "НАО": { "name": "Непубличное акционерное общество", "description": "Акционерное общество без публичного размещения акций, подходит для бизнеса с ограниченным кругом акционеров.", "advantages": [ "Ограниченный круг акционеров", "Более гибкое управление", "Меньше требований к раскрытию информации", "Ответственность стоимостью акций" ], "disadvantages": [ "Сложнее чем ООО", "Ограничения на обращение акций", "Высокие требования к уставному капиталу" ], "taxes": "ОСНО (20% прибыль + НДС)", "liability": "В пределах стоимости акций", "registration_cost": "от 50000 руб.", "best_for": "Средний и крупный бизнес, семейные компании, стратегические партнерства" }, "ПТ": { "name": "Полное товарищество", "description": "Объединение предпринимателей для совместной деятельности с полной солидарной ответственностью.", "advantages": [ "Простота создания", "Доверительные отношения между участниками", "Объединение компетенций и ресурсов" ], "disadvantages": [ "Неограниченная ответственность всех участников", "Риск конфликтов между участниками", "Сложности при выходе участников" ], "taxes": "Участники платят налоги как ИП", "liability": "Полная солидарная ответственность", "registration_cost": "5000-20000 руб.", "best_for": "Профессиональные услуги, семейный бизнес, доверительные партнерства" }, "ТНВ": { "name": "Товарищество на вере (коммандитное)", "description": "Товарищество, в котором есть полные товарищи и вкладчики с ограниченной ответственностью.", "advantages": [ "Возможность привлечения инвесторов-вкладчиков", "Разделение рисков", "Гибкая структура управления" ], "disadvantages": [ "Сложная организационная структура", "Конфликт интересов полных товарищей и вкладчиков" ], "taxes": "Участники платят налоги самостоятельно", "liability": "Полные товарищи - полная, вкладчики - в пределах вклада", "registration_cost": "10000-30000 руб.", "best_for": "Инвестиционные проекты, венчурные предприятия" }, "ПК": { "name": "Производственный кооператив", "description": "Добровольное объединение граждан для совместной производственной или хозяйственной деятельности.", "advantages": [ "Равные права участников", "Распределение прибыли по труду", "Государственная поддержка", "Социальная направленность" ], "disadvantages": [ "Сложность управления", "Необходимость личного трудового участия", "Ограниченный привлечение капитала" ], "taxes": "УСН, ОСНО", "liability": "Субсидиарная ответственность", "registration_cost": "5000-15000 руб.", "best_for": "Сельхозпроизводство, ремесла, народные промыслы, социальные предприятия" }, "УПХВ": { "name": "Унитарное предприятие на праве хозяйственного ведения", "description": "Коммерческая организация, созданная государством или муниципалитетом для управления госимуществом.", "advantages": [ "Использование государственного имущества", "Государственная поддержка", "Стабильность" ], "disadvantages": [ "Ограниченная самостоятельность", "Жесткий государственный контроль", "Бюрократия" ], "taxes": "ОСНО", "liability": "В пределах имущества предприятия", "registration_cost": "Регистрируется государством", "best_for": "Управление государственным имуществом, стратегические предприятия" }, "УПОУ": { "name": "Унитарное предприятие на праве оперативного управления", "description": "Государственное или муниципальное предприятие для выполнения специфических государственных задач.", "advantages": [ "Полная государственная поддержка", "Выполнение важных государственных задач", "Стабильное финансирование" ], "disadvantages": [ "Минимальная самостоятельность", "Полный государственный контроль", "Ограничения по деятельности" ], "taxes": "ОСНО", "liability": "Субсидиарная ответственность учредителя", "registration_cost": "Регистрируется государством", "best_for": "Казенные предприятия, оборонные заводы, стратегические объекты" }, "Самозанятость": { "name": "Самозанятость (НПД)", "description": "Упрощенная система для небольших доходов без найма сотрудников. Максимально простая отчетность через приложение.", "advantages": [ "Нет отчетности (кроме чеков)", "Налог всего 4-6%", "Регистрация через приложение", "Никаких взносов", "Можно работать без расчетного счета" ], "disadvantages": [ "Лимит дохода 2.4 млн руб. в год", "Нельзя нанимать сотрудников", "Ограничения по видам деятельности", "Не подходит для бизнеса с юрлицами" ], "taxes": "НПД 4% (с физлиц) или 6% (с юрлиц)", "liability": "Минимальная", "registration_cost": "Бесплатно", "best_for": "Фриланс, репетиторство, небольшие услуги, консультации" }, "КФХ": { "name": "Крестьянское (фермерское) хозяйство", "description": "Специальная форма для сельскохозяйственной деятельности, может создаваться без образования юрлица.", "advantages": [ "Льготные налоги (ЕСХН 6%)", "Господдержка и субсидии", "Проще отчетность чем у ООО", "Можно без образования юрлица", "Льготные кредиты" ], "disadvantages": [ "Только для сельского хозяйства", "Сезонные риски", "Зависимость от погодных условий", "Специфические требования" ], "taxes": "ЕСХН (6% с прибыли)", "liability": "Зависит от формы регистрации", "registration_cost": "1000-5000 руб.", "best_for": "Сельское хозяйство, фермерство, животноводство, растениеводство" }, "СПК": { "name": "Сельскохозяйственный производственный кооператив", "description": "Кооператив, созданный сельскохозяйственными товаропроизводителями для совместной деятельности.", "advantages": [ "Специальные льготы для сельского хозяйства", "Объединение ресурсов фермеров", "Государственная поддержка", "Льготное налогообложение" ], "disadvantages": [ "Сложность управления", "Необходимость личного участия", "Зависимость от урожая" ], "taxes": "ЕСХН, УСН", "liability": "Субсидиарная ответственность", "registration_cost": "5000-20000 руб.", "best_for": "Сельхозкооперативы, объединения фермеров, переработка сельхозпродукции" } } # Загрузка вопросов try: with open('questions.json', 'r', encoding='utf-8') as f: questions = json.load(f) except FileNotFoundError: questions = [ { "id": 1, "text": "Какой вид отдыха вы предпочитаете?", "ans_1": "Активный туризм", "ans_2": "Пляжный отдых", "ans_3": "Экскурсии по городам", "ans_4": "Горнолыжный курорт", "vals": [3, 1, 2, 4] } ] # Корневой эндпоинт @app.get("/") async def root(): return {"message": "Recommendation Test API", "status": "running"} # Эндпоинт для favicon @app.get("/favicon.ico") async def favicon(): return JSONResponse(content={}, status_code=200) # 1. Auth endpoint @app.post("/auth/register") async def register(user: UserCreate): if user.username in users_db: raise HTTPException(status_code=400, detail="Username already exists") hashed_password = get_password_hash(user.password) users_db[user.username] = { "username": user.username, "hashed_password": hashed_password } # Инициализируем пустые ответы для пользователя user_answers_db[user.username] = [] access_token = create_access_token(data={"sub": user.username}) return {"access_token": access_token, "token_type": "bearer"} @app.post("/auth/login") async def login(user: UserLogin): user_data = users_db.get(user.username) if not user_data or not verify_password(user.password, user_data["hashed_password"]): raise HTTPException(status_code=401, detail="Invalid credentials") # Убедимся, что у пользователя есть запись для ответов if user.username not in user_answers_db: user_answers_db[user.username] = [] access_token = create_access_token(data={"sub": user.username}) return {"access_token": access_token, "token_type": "bearer"} # 2. Получить все вопросы @app.get("/questions") async def get_all_questions(current_user: dict = Depends(get_current_user)): # Возвращаем вопросы без значений vals formatted_questions = [] for q in questions: formatted_questions.append({ "id": q["id"], "text": q["text"], "answers": [ q["ans_1"], q["ans_2"], q["ans_3"], q["ans_4"] ] }) return formatted_questions # 3. Получить конкретный вопрос @app.get("/questions/{question_id}") async def get_question(question_id: int, current_user: dict = Depends(get_current_user)): question = next((q for q in questions if q["id"] == question_id), None) if not question: raise HTTPException(status_code=404, detail="Question not found") return { "id": question["id"], "text": question["text"], "answers": [ question["ans_1"], question["ans_2"], question["ans_3"], question["ans_4"] ] } # 4. Сохранить ответ @app.post("/answers") async def save_answers(answers: UserAnswers, current_user: dict = Depends(get_current_user)): username = current_user["sub"] if username not in user_answers_db: user_answers_db[username] = [] user_answers_db[username].extend(answers.answers) return {"message": "Answers saved successfully", "answers_count": len(user_answers_db[username])} # 5. Сброс прогресса @app.post("/reset") async def reset_progress(current_user: dict = Depends(get_current_user)): username = current_user["sub"] user_answers_db[username] = [] return {"message": "Progress reset successfully"} # 6. Получить результаты @app.get("/results") async def get_results(current_user: dict = Depends(get_current_user)): username = current_user["sub"] user_answers = user_answers_db.get(username, []) if not user_answers: raise HTTPException(status_code=400, detail="No answers found") # Подсчет баллов для каждой формы предпринимательства scores = {form: 0 for form in BUSINESS_FORMS.keys()} for answer in user_answers: question = next((q for q in questions if q["id"] == answer.question_id), None) if question and answer.answer_index < len(question["business_forms"]): selected_form = question["business_forms"][answer.answer_index] if selected_form in scores: scores[selected_form] += 1 # Сортируем по количеству баллов и берем топ-3 top_results = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:3] # Форматируем результаты с полной информацией formatted_results = [] for form_id, score in top_results: if form_id in BUSINESS_FORMS: form_info = BUSINESS_FORMS[form_id].copy() form_info["id"] = form_id form_info["match_score"] = score form_info["match_percentage"] = int((score / len(user_answers)) * 100) formatted_results.append(form_info) return { "results": formatted_results, "total_questions": len(questions), "answered_questions": len(user_answers) } # 7. Получить детали результата @app.post("/details") async def get_result_details(request: ResultRequest, current_user: dict = Depends(get_current_user)): form_id = request.result_id username = current_user["sub"] user_answers = user_answers_db.get(username, []) if form_id not in BUSINESS_FORMS: raise HTTPException(status_code=404, detail="Business form not found") if not user_answers: raise HTTPException(status_code=400, detail="No answers found") form_info = BUSINESS_FORMS[form_id] # Собираем ответы пользователя для контекста answer_summary = [] for answer in user_answers: question = next((q for q in questions if q["id"] == answer.question_id), None) if question: answer_text = [ question["ans_1"], question["ans_2"], question["ans_3"], question["ans_4"] ][answer.answer_index] answer_summary.append(f"{question['text']}: {answer_text}") # Формируем детальное описание detailed_description = f""" {form_info['name']} {form_info['description']} 📊 Основные характеристики: • Налогообложение: {form_info['taxes']} • Ответственность: {form_info['liability']} • Стоимость регистрации: {form_info['registration_cost']} ✅ Преимущества: {chr(10).join(['• ' + adv for adv in form_info['advantages']])} ⚠️ Недостатки: {chr(10).join(['• ' + dis for dis in form_info['disadvantages']])} 🎯 Лучше всего подходит для: {form_info['best_for']} На основе ваших ответов, эта форма предпринимательства соответствует вашим требованиям. """ return { "result_id": form_id, "name": form_info["name"], "description": detailed_description, "advantages": form_info["advantages"], "disadvantages": form_info["disadvantages"], "taxes": form_info["taxes"], "liability": form_info["liability"] } # 11. Эндпоинт для деталей через LLM @app.post("/details-llm") async def get_result_details_llm(request: ResultRequest, current_user: dict = Depends(get_current_user)): username = current_user["sub"] user_answers = user_answers_db.get(username, []) if request.result_id not in BUSINESS_FORMS: raise HTTPException(status_code=404, detail="Business form not found") if not user_answers: raise HTTPException(status_code=400, detail="No answers found") form_info = BUSINESS_FORMS[request.result_id] # Формируем системный промпт для LLM system_prompt = """ Ты - опытный консультант по бизнесу и предпринимательству в РФ. Твоя задача - дать развернутую, персонализированную консультацию о форме предпринимательства на основе ответов пользователя. Будь конкретен, давай практические советы и учитывай особенности российского законодательства. """ # Формируем пользовательский промпт с контекстом user_prompt = f""" Пользователь рассматривает форму предпринимательства: {form_info['name']} Основная информация о форме: - {form_info['description']} - Налогообложение: {form_info['taxes']} - Ответственность: {form_info['liability']} - Стоимость регистрации: {form_info['registration_cost']} - Преимущества: {', '.join(form_info['advantages'])} - Недостатки: {', '.join(form_info['disadvantages'])} Ответы пользователя в тесте: """ # Добавляем ответы пользователя for answer in user_answers: question = next((q for q in questions if q["id"] == answer.question_id), None) if question: answer_text = [ question["ans_1"], question["ans_2"], question["ans_3"], question["ans_4"] ][answer.answer_index] user_prompt += f"\n- {question['text']}: {answer_text}" user_prompt += f""" Проанализируй ответы пользователя и дай развернутую консультацию: 1. Почему эта форма предпринимательства подходит/не подходит пользователю 2. Какие конкретные шаги нужно предпринять для регистрации 3. На какие подводные камни обратить внимание 4. Какие налоговые льготы или программы поддержки можно использовать 5. Практические рекомендации по ведению бизнеса в этой форме Будь максимально конкретным и полезным! """ # Отправляем запрос к LLM (пример для OpenAI) try: async with httpx.AsyncClient() as client: response = await client.post( LLM_API_URL, headers={ "Authorization": f"Bearer {LLM_API_KEY}", "Content-Type": "application/json" }, json={ "model": "gpt-3.5-turbo", "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], "max_tokens": 1500, "temperature": 0.7 }, timeout=30.0 ) if response.status_code == 200: llm_response = response.json() llm_description = llm_response["choices"][0]["message"]["content"] return { "result_id": request.result_id, "name": form_info["name"], "description": llm_description, "llm_generated": True, "model": "gpt-3.5-turbo" } else: # Fallback если LLM не доступен return await get_result_details(request, current_user) except Exception as e: # В случае ошибки возвращаем стандартное описание print(f"LLM API error: {e}") return await get_result_details(request, current_user) # Эндпоинт для отладки - получить текущие ответы пользователя @app.get("/debug/answers") async def get_debug_answers(current_user: dict = Depends(get_current_user)): username = current_user["sub"] return { "username": username, "answers": user_answers_db.get(username, []), "total_answers": len(user_answers_db.get(username, [])) } if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)