Merge pull request #16 from AlexisLopez-Dev/feature/backend-v3
Añadida funcionalidad de intervalos en cartas blancas
This commit is contained in:
+3
-1
@@ -13,6 +13,7 @@ from api.routers.docmf_validation import router as validation_router
|
||||
from api.routers.auth import router as auth_router
|
||||
from api.routers.history import router as history_router
|
||||
from api.routers.test_mongo import router as test_mongo_router
|
||||
from api.routers.docit2mf_build import router as docit2mf_router
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
@@ -38,4 +39,5 @@ app.include_router(simple_validation_router, prefix="/api/criteria/doc-mf")
|
||||
app.include_router(validation_router, prefix="/api/criteria/doc-mf")
|
||||
app.include_router(test_mongo_router, prefix="/api")
|
||||
app.include_router(auth_router, prefix="/api")
|
||||
app.include_router(history_router, prefix="/api")
|
||||
app.include_router(history_router, prefix="/api")
|
||||
app.include_router(docit2mf_router, prefix="/api")
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# models/docit2mf_models.py
|
||||
|
||||
from pydantic import BaseModel, field_validator
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
|
||||
BlankCardInput = Union[int, Tuple[int, int], List[int]]
|
||||
|
||||
|
||||
class DoCIT2MFRequest(BaseModel):
|
||||
term: str
|
||||
core: Tuple[float, float]
|
||||
support: Tuple[float, float]
|
||||
|
||||
left_nodes_x: List[float]
|
||||
left_blank_cards: List[BlankCardInput]
|
||||
|
||||
right_nodes_x: List[float]
|
||||
right_blank_cards: List[BlankCardInput]
|
||||
|
||||
@field_validator("term")
|
||||
def term_not_empty(cls, v):
|
||||
if not v.strip():
|
||||
raise ValueError("El término no puede estar vacío.")
|
||||
return v
|
||||
|
||||
@field_validator("core")
|
||||
def core_valid(cls, v):
|
||||
a, b = v
|
||||
if a > b:
|
||||
raise ValueError("El núcleo debe cumplir a <= b.")
|
||||
return v
|
||||
|
||||
@field_validator("support")
|
||||
def support_valid(cls, v, info):
|
||||
c, d = v
|
||||
if c >= d:
|
||||
raise ValueError("El soporte debe cumplir c < d.")
|
||||
|
||||
core = info.data.get("core")
|
||||
if core:
|
||||
a, b = core
|
||||
if not (c <= a <= b <= d):
|
||||
raise ValueError("El núcleo debe estar dentro del soporte.")
|
||||
return v
|
||||
|
||||
@field_validator("left_blank_cards", "right_blank_cards")
|
||||
def validate_cards(cls, v):
|
||||
for item in v:
|
||||
# Caso 1: entero
|
||||
if isinstance(item, int):
|
||||
if item < 0:
|
||||
raise ValueError("Las cartas no pueden ser negativas.")
|
||||
# Caso 2: lista o tupla [min,max]
|
||||
elif isinstance(item, (list, tuple)):
|
||||
if len(item) != 2:
|
||||
raise ValueError("Los intervalos deben ser [min, max].")
|
||||
lo, hi = item
|
||||
if lo < 0 or hi < 0:
|
||||
raise ValueError("Las cartas no pueden ser negativas.")
|
||||
if lo > hi:
|
||||
raise ValueError("Debe cumplirse min <= max.")
|
||||
else:
|
||||
raise ValueError("Formato inválido para cartas blancas.")
|
||||
return v
|
||||
|
||||
|
||||
class DoCIT2MFMultiRequest(BaseModel):
|
||||
levels: List[DoCIT2MFRequest]
|
||||
@@ -45,3 +45,4 @@ class DoCMFRequest(BaseModel):
|
||||
|
||||
class DoCMFMultiRequest(BaseModel):
|
||||
levels: List[DoCMFRequest]
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Union
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# MODELOS DE FUNCIONES DIFUSAS
|
||||
# -----------------------------
|
||||
|
||||
class FuzzyTerm(BaseModel):
|
||||
term: str
|
||||
core: List[float]
|
||||
@@ -11,18 +15,32 @@ class FuzzyTerm(BaseModel):
|
||||
right_nodes: List[List[float]]
|
||||
|
||||
|
||||
class IT2FuzzyTerm(BaseModel):
|
||||
term: str
|
||||
lower: FuzzyTerm
|
||||
upper: FuzzyTerm
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# HISTORIAL
|
||||
# -----------------------------
|
||||
|
||||
class HistoryItem(BaseModel):
|
||||
id: Optional[str] = Field(default=None, alias="_id")
|
||||
name: str
|
||||
created_at: datetime
|
||||
results: List[FuzzyTerm]
|
||||
results: List[Union[FuzzyTerm, IT2FuzzyTerm]]
|
||||
|
||||
|
||||
class HistoryCreateRequest(BaseModel):
|
||||
name: str
|
||||
results: List[FuzzyTerm]
|
||||
results: List[Union[FuzzyTerm, IT2FuzzyTerm]]
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# USUARIOS
|
||||
# -----------------------------
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
username: str
|
||||
email: EmailStr
|
||||
@@ -41,4 +59,3 @@ class UserInDB(BaseModel):
|
||||
password_hash: str
|
||||
token: Optional[str] = None
|
||||
history: List[HistoryItem] = []
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# routers/docit2mf_build.py
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from api.models.docit2mf_models import DoCIT2MFMultiRequest
|
||||
from api.services.docit2mf_build_service import build_it2mf_from_level
|
||||
from api.utils.security import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/criteria", tags=["criteria"])
|
||||
|
||||
|
||||
@router.post("/doc-it2mf/build")
|
||||
async def build_doc_it2mf(
|
||||
request: DoCIT2MFMultiRequest,
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
results = []
|
||||
|
||||
try:
|
||||
for level in request.levels:
|
||||
results.append(build_it2mf_from_level(level))
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
return {"levels": results}
|
||||
@@ -3,7 +3,7 @@ from datetime import datetime
|
||||
from bson import ObjectId
|
||||
|
||||
from api.database.mongodb import users_collection
|
||||
from api.models.user_models import FuzzyTerm, HistoryCreateRequest
|
||||
from api.models.user_models import FuzzyTerm, IT2FuzzyTerm, HistoryCreateRequest
|
||||
from api.utils.security import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/history", tags=["history"])
|
||||
@@ -21,7 +21,7 @@ async def add_history_item(
|
||||
"_id": history_item_id,
|
||||
"name": data.name,
|
||||
"created_at": datetime.utcnow(),
|
||||
"results": [r.dict() for r in data.results],
|
||||
"results": [r.dict() for r in data.results], # ahora soporta IT2MF
|
||||
}
|
||||
|
||||
await users_collection.update_one(
|
||||
@@ -35,6 +35,7 @@ async def add_history_item(
|
||||
}
|
||||
|
||||
|
||||
|
||||
@router.delete("/delete/{history_item_id}")
|
||||
async def delete_history_item(
|
||||
history_item_id: str,
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# services/docit2mf_build_service.py
|
||||
|
||||
from typing import List, Union
|
||||
from api.models.docit2mf_models import DoCIT2MFRequest
|
||||
from api.models.docmf_models import DoCMFRequest
|
||||
from api.services.docmf_build_service import build_doc_mf_level
|
||||
|
||||
|
||||
def _extract_bounds(values: List[Union[int, List[int], tuple]], mode: str) -> List[int]:
|
||||
"""
|
||||
Devuelve una lista de enteros:
|
||||
- Si el valor es un entero → se usa tal cual para LMF y UMF
|
||||
- Si es un intervalo [min,max] → se usa min o max según mode
|
||||
"""
|
||||
result = []
|
||||
for item in values:
|
||||
if isinstance(item, int):
|
||||
# valor fijo → mismo para LMF y UMF
|
||||
result.append(item)
|
||||
else:
|
||||
lo, hi = item
|
||||
result.append(lo if mode == "min" else hi)
|
||||
return result
|
||||
|
||||
|
||||
def build_it2mf_from_level(level: DoCIT2MFRequest):
|
||||
# LMF (mínimos)
|
||||
left_min = _extract_bounds(level.left_blank_cards, "min")
|
||||
right_min = _extract_bounds(level.right_blank_cards, "min")
|
||||
|
||||
lower_level = DoCMFRequest(
|
||||
term=level.term,
|
||||
core=level.core,
|
||||
support=level.support,
|
||||
left_nodes_x=level.left_nodes_x,
|
||||
left_blank_cards=left_min,
|
||||
right_nodes_x=level.right_nodes_x,
|
||||
right_blank_cards=right_min,
|
||||
)
|
||||
lower = build_doc_mf_level(lower_level)
|
||||
|
||||
# UMF (máximos)
|
||||
left_max = _extract_bounds(level.left_blank_cards, "max")
|
||||
right_max = _extract_bounds(level.right_blank_cards, "max")
|
||||
|
||||
upper_level = DoCMFRequest(
|
||||
term=level.term,
|
||||
core=level.core,
|
||||
support=level.support,
|
||||
left_nodes_x=level.left_nodes_x,
|
||||
left_blank_cards=left_max,
|
||||
right_nodes_x=level.right_nodes_x,
|
||||
right_blank_cards=right_max,
|
||||
)
|
||||
upper = build_doc_mf_level(upper_level)
|
||||
|
||||
return {
|
||||
"term": level.term,
|
||||
"lower": lower,
|
||||
"upper": upper
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
def build_single_docmf(request):
|
||||
# services/docmf_build_service.py
|
||||
|
||||
from api.models.docmf_models import DoCMFRequest
|
||||
from api.models.user_models import FuzzyTerm
|
||||
|
||||
|
||||
def build_single_docmf(request: DoCMFRequest):
|
||||
a, b = request.core
|
||||
c, d = request.support
|
||||
|
||||
@@ -12,7 +18,7 @@ def build_single_docmf(request):
|
||||
if i == 0:
|
||||
left_nodes.append((x, 0.0))
|
||||
else:
|
||||
acc += request.left_blank_cards[i-1] + 1
|
||||
acc += request.left_blank_cards[i - 1] + 1
|
||||
left_nodes.append((x, round(acc * YL, 4)))
|
||||
|
||||
# RIGHT
|
||||
@@ -25,7 +31,7 @@ def build_single_docmf(request):
|
||||
if i == 0:
|
||||
right_nodes.append((x, 1.0))
|
||||
else:
|
||||
acc += request.right_blank_cards[i-1] + 1
|
||||
acc += request.right_blank_cards[i - 1] + 1
|
||||
right_nodes.append((x, round(1 - acc * YR, 4)))
|
||||
|
||||
return {
|
||||
@@ -43,3 +49,19 @@ def build_docmf_multi(request):
|
||||
result = build_single_docmf(level)
|
||||
results.append(result)
|
||||
return {"results": results}
|
||||
|
||||
|
||||
def build_doc_mf_level(level: DoCMFRequest) -> FuzzyTerm:
|
||||
"""
|
||||
Adaptador para reutilizar build_single_docmf con el modelo DoCMFRequest.
|
||||
Devuelve un FuzzyTerm, que es lo que espera el sistema IT2MF.
|
||||
"""
|
||||
result = build_single_docmf(level)
|
||||
|
||||
return FuzzyTerm(
|
||||
term=result["term"],
|
||||
core=list(result["core"]),
|
||||
support=list(result["support"]),
|
||||
left_nodes=[list(p) for p in result["left_nodes"]],
|
||||
right_nodes=[list(p) for p in result["right_nodes"]],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user