This commit is contained in:
itqop 2025-04-07 10:18:31 +03:00
parent 043386268b
commit e3860e2490
11 changed files with 25 additions and 117 deletions

View File

@ -2,27 +2,14 @@ import os
from pydantic_settings import BaseSettings
from dotenv import load_dotenv
# Load .env file from the backend directory (one level up from core)
# BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# load_dotenv(os.path.join(BASE_DIR, '..', '.env')) # Adjust path if needed
# Or simply let pydantic-settings handle it by default if .env is in the root execution dir
load_dotenv()
class Settings(BaseSettings):
DATABASE_URL: str = "sqlite:///./default.db" # Default value if not in .env
# Настройки JWT удалены
# SECRET_KEY: str = "default_secret"
# ALGORITHM: str = "HS256"
# ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
DATABASE_URL: str = "sqlite:///./default.db"
class Config:
env_file = ".env"
env_file_encoding = 'utf-8'
# If your .env file is not in the root directory where you run uvicorn,
# you might need to specify the path explicitly:
# env_file = '../.env' # Example if running from inside 'app' dir
settings = Settings()

View File

@ -2,17 +2,14 @@ from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.core.config import settings
# Create SQLAlchemy engine
engine = create_engine(
settings.DATABASE_URL,
# Required for SQLite to prevent issues with FastAPI's async nature
connect_args={"check_same_thread": False} if "sqlite" in settings.DATABASE_URL else {}
)
# Create a session factory
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Dependency to get DB session (we'll use this in routers)
def get_db():
db = SessionLocal()
try:

View File

@ -2,7 +2,7 @@ import asyncio
import random
from typing import Dict, Any
# Делаем функцию асинхронной
async def solve_cutting_problem(input_params: Dict[str, Any]) -> Dict[str, Any]:
"""
Асинхронная заглушка для имитации сложного расчета раскройки стекла.
@ -10,26 +10,23 @@ async def solve_cutting_problem(input_params: Dict[str, Any]) -> Dict[str, Any]:
"""
print(f"Received input for async fuzzy solver: {input_params}")
# Имитация времени расчета с использованием asyncio.sleep
delay = random.uniform(0.5, 2.0)
print(f"Simulating calculation for {delay:.2f} seconds...")
await asyncio.sleep(delay) # Используем await asyncio.sleep вместо time.sleep
# Пример выходных данных (остается таким же)
await asyncio.sleep(delay)
output_data = {
"layout": [
{"piece_id": 1, "x": 10, "y": 10, "width": 500, "height": 300},
{"piece_id": 2, "x": 520, "y": 10, "width": 400, "height": 300},
# ... другие детали раскроя ...
],
"waste_percentage": round(random.uniform(5.0, 15.0), 2),
"number_of_cuts": random.randint(5, 20),
"processing_time_ms": int(delay * 1000) # Используем задержку как время обработки
"processing_time_ms": int(delay * 1000)
}
# Можно добавить логику на основе input_params, если нужно для тестов
if input_params.get("optimize_for") == "speed":
output_data["number_of_cuts"] = random.randint(15, 25) # Больше резов - быстрее?
output_data["number_of_cuts"] = random.randint(15, 25)
print(f"Async fuzzy solver generated output: {output_data}")
return output_data

View File

@ -1,37 +1,25 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware # Импортируем middleware
from app.routers import calculation # Импортируем роутер
from fastapi.middleware.cors import CORSMiddleware
from app.routers import calculation
app = FastAPI(title="Glass Cutting Optimization API")
# --- Настройка CORS ---
# Список разрешенных источников (origins)
# Для разработки можно разрешить все или указать адрес вашего React-приложения
origins = [
"http://localhost", # Если React запускается на http://localhost:port
"http://localhost:3000", # Стандартный порт для create-react-app
"http://localhost:5173", # Стандартный порт для Vite/React
# "http://127.0.0.1:5173", # Можно добавить и IP-адрес
# "*" # Разрешить все источники (будьте осторожны в production)
"http://localhost",
"http://localhost:3000",
"http://localhost:5173",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins, # Указываем разрешенные источники
allow_credentials=True, # Разрешаем куки (если потребуются в будущем)
allow_methods=["*"], # Разрешаем все методы (GET, POST, PUT, etc.)
allow_headers=["*"], # Разрешаем все заголовки
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# --- Конец настройки CORS ---
# Подключаем роутер расчетов
app.include_router(calculation.router)
@app.get("/")
async def read_root():
return {"message": "Welcome to the Glass Cutting Optimization API"}
# Убедимся, что здесь нет подключения auth роутера
# from .routers import calculation # Позже подключим calculation
# app.include_router(calculation.router)
return {"message": "Welcome to the Glass Cutting Optimization API"}

View File

@ -2,7 +2,5 @@ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import Integer
from typing import Annotated
# Define a base class using SQLAlchemy 2.0 style
class Base(DeclarativeBase):
# Define a primary key type annotation for convenience
pass

View File

@ -8,7 +8,6 @@ from typing import Dict, Any
class Calculation(Base):
__tablename__ = "calculations"
#
id: Mapped[int] = mapped_column(primary_key=True, index=True)
input_params: Mapped[Dict[str, Any]] = mapped_column(JSON, nullable=False)
output_results: Mapped[Dict[str, Any] | None] = mapped_column(JSON, nullable=True)

View File

@ -1,20 +0,0 @@
from sqlalchemy import Column, Integer, String, JSON, Float, DateTime
from sqlalchemy.sql import func
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Calculation(Base):
__tablename__ = "calculations"
id = Column(Integer, primary_key=True, index=True)
input_params = Column(JSON)
output_results = Column(JSON)
timestamp = Column(DateTime(timezone=True), server_default=func.now())
model_name = Column(String, nullable=True)
objective_score = Column(Float, nullable=True)
# Удалите эти строки, если они есть:
# user_id = Column(Integer, ForeignKey("users.id"))
# user = relationship("User", back_populates="calculations")
# Можно вообще удалить класс User, если он больше не нужен

View File

@ -7,8 +7,8 @@ from app.services import calculation_service as service
from app.db.session import get_db
router = APIRouter(
prefix="/calculation", # Общий префикс для эндпоинтов этого роутера
tags=["Calculations"], # Тег для группировки в документации Swagger
prefix="/calculation",
tags=["Calculations"],
)
@router.post("/", response_model=CalculationRead, status_code=status.HTTP_201_CREATED)
@ -45,8 +45,6 @@ async def read_calculation(
)
return db_calc
# Определяем эндпоинт для истории до эндпоинта с ID,
# чтобы FastAPI не принял "history" за ID
@router.get("/history/", response_model=List[CalculationRead])
async def read_calculation_history(
skip: int = 0,

View File

@ -2,34 +2,22 @@ from pydantic import BaseModel, ConfigDict
from datetime import datetime
from typing import Optional, Dict, Any
# --- Базовая схема ---
# Общие поля для расчета
class CalculationBase(BaseModel):
input_params: Dict[str, Any]
model_name: Optional[str] = None # Модель может быть выбрана сервером
model_name: Optional[str] = None
# --- Схема для создания (запуска) расчета ---
# Данные, которые пользователь отправляет для запуска
class CalculationCreate(CalculationBase):
pass # Пока совпадает с Base, но может быть расширена
pass
# --- Схема для чтения ---
# Полная информация о расчете, возвращаемая API
class CalculationRead(CalculationBase):
id: int
output_results: Optional[Dict[str, Any]] = None
timestamp: datetime
objective_score: Optional[float] = None
# Включаем режим ORM
model_config = ConfigDict(from_attributes=True)
# В Pydantic v1 было:
# class Config:
# orm_mode = True
# --- Опционально: Схема для обновления (если результаты добавляются позже) ---
class CalculationUpdate(BaseModel):
output_results: Optional[Dict[str, Any]] = None
objective_score: Optional[float] = None
model_name: Optional[str] = None # Если модель определяется по результату
model_name: Optional[str] = None

View File

@ -6,6 +6,4 @@ class Token(BaseModel):
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
# Можно использовать id пользователя, если удобнее
# user_id: Optional[int] = None
username: Optional[str] = None

View File

@ -3,25 +3,19 @@ from typing import List, Optional, Dict, Any
from app.models.calculation import Calculation
from app.schemas.calculation import CalculationCreate, CalculationUpdate
from app.fuzzy_solver import solve_cutting_problem # Импортируем асинхронную заглушку
from app.fuzzy_solver import solve_cutting_problem
# Делаем функцию создания асинхронной
async def create_calculation(db: Session, calc_in: CalculationCreate) -> Calculation:
"""
Асинхронно создает запись о расчете, вызывает заглушку и сохраняет результат.
"""
# 1. Вызываем асинхронную заглушку расчета
print("Calling async solver...")
output_results = await solve_cutting_problem(calc_in.input_params)
print("Async solver finished.")
# 2. Определяем другие параметры (логика остается)
objective_score = output_results.get("waste_percentage")
model_name = calc_in.model_name or "default_fuzzy_model_v1"
# 3. Создаем объект модели SQLAlchemy
# Операции с БД остаются синхронными в этом примере
# Для полной асинхронности потребовался бы async-драйвер и AsyncSession
print("Creating DB object...")
db_calc = Calculation(
input_params=calc_in.input_params,
@ -30,7 +24,6 @@ async def create_calculation(db: Session, calc_in: CalculationCreate) -> Calcula
model_name=model_name
)
# 4. Сохраняем в базу данных (синхронные операции)
print("Adding to DB session...")
db.add(db_calc)
print("Committing DB session...")
@ -40,8 +33,6 @@ async def create_calculation(db: Session, calc_in: CalculationCreate) -> Calcula
print("Calculation created and saved.")
return db_calc
# Функции чтения можно оставить синхронными, т.к. они быстрые
# и используют синхронную сессию
def get_calculation_by_id(db: Session, calc_id: int) -> Optional[Calculation]:
"""
Получает расчет по его ID.
@ -53,16 +44,3 @@ def get_calculations(db: Session, skip: int = 0, limit: int = 100) -> List[Calcu
Получает список всех расчетов с пагинацией.
"""
return db.query(Calculation).offset(skip).limit(limit).all()
# Опционально: Функция для обновления расчета (если нужно)
# def update_calculation(db: Session, calc_id: int, calc_update: CalculationUpdate) -> Optional[Calculation]:
# db_calc = get_calculation_by_id(db, calc_id)
# if not db_calc:
# return None
# update_data = calc_update.model_dump(exclude_unset=True) # Pydantic v2
# # update_data = calc_update.dict(exclude_unset=True) # Pydantic v1
# for key, value in update_data.items():
# setattr(db_calc, key, value)
# db.commit()
# db.refresh(db_calc)
# return db_calc