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": [ "Сложнее отчетность", "Высокие штрафы за нарушения", "Налог на прибыль 20%", "Сложнее закрыть" ], "taxes": "УСН, ОСНО (20% прибыль + НДС)", "liability": "В пределах уставного капитала (от 10 тыс. руб.)", "registration_cost": "4000-15000 руб.", "best_for": "Бизнес с партнерами, средний бизнес, торговля с юрлицами" }, "АО": { "name": "Акционерное общество", "description": "Форма для крупного бизнеса с возможностью привлечения инвестиций через продажу акций.", "advantages": [ "Возможность привлечения крупных инвестиций", "Высокий статус и доверие", "Ответственность только стоимостью акций", "Свободная продажа акций" ], "disadvantages": [ "Сложная регистрация и отчетность", "Публичное раскрытие информации", "Высокие затраты на содержание", "Строгие требования законодательства" ], "taxes": "ОСНО (20% прибыль + НДС)", "liability": "В пределах стоимости акций", "registration_cost": "от 50000 руб.", "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": "Сельское хозяйство, фермерство, животноводство" } } # Загрузка вопросов 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) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)