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