| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- 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)
|