Merge branch 'develop' into feature/frontend-v3
This commit is contained in:
@@ -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.auth import router as auth_router
|
||||||
from api.routers.history import router as history_router
|
from api.routers.history import router as history_router
|
||||||
from api.routers.test_mongo import router as test_mongo_router
|
from api.routers.test_mongo import router as test_mongo_router
|
||||||
|
from api.routers.docit2mf_build import router as docit2mf_router
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
@@ -39,3 +40,4 @@ app.include_router(validation_router, prefix="/api/criteria/doc-mf")
|
|||||||
app.include_router(test_mongo_router, prefix="/api")
|
app.include_router(test_mongo_router, prefix="/api")
|
||||||
app.include_router(auth_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):
|
class DoCMFMultiRequest(BaseModel):
|
||||||
levels: List[DoCMFRequest]
|
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 pydantic import BaseModel, EmailStr, Field
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# MODELOS DE FUNCIONES DIFUSAS
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
class FuzzyTerm(BaseModel):
|
class FuzzyTerm(BaseModel):
|
||||||
term: str
|
term: str
|
||||||
core: List[float]
|
core: List[float]
|
||||||
@@ -11,18 +15,32 @@ class FuzzyTerm(BaseModel):
|
|||||||
right_nodes: List[List[float]]
|
right_nodes: List[List[float]]
|
||||||
|
|
||||||
|
|
||||||
|
class IT2FuzzyTerm(BaseModel):
|
||||||
|
term: str
|
||||||
|
lower: FuzzyTerm
|
||||||
|
upper: FuzzyTerm
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# HISTORIAL
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
class HistoryItem(BaseModel):
|
class HistoryItem(BaseModel):
|
||||||
id: Optional[str] = Field(default=None, alias="_id")
|
id: Optional[str] = Field(default=None, alias="_id")
|
||||||
name: str
|
name: str
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
results: List[FuzzyTerm]
|
results: List[Union[FuzzyTerm, IT2FuzzyTerm]]
|
||||||
|
|
||||||
|
|
||||||
class HistoryCreateRequest(BaseModel):
|
class HistoryCreateRequest(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
results: List[FuzzyTerm]
|
results: List[Union[FuzzyTerm, IT2FuzzyTerm]]
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# USUARIOS
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
class UserCreate(BaseModel):
|
class UserCreate(BaseModel):
|
||||||
username: str
|
username: str
|
||||||
email: EmailStr
|
email: EmailStr
|
||||||
@@ -41,4 +59,3 @@ class UserInDB(BaseModel):
|
|||||||
password_hash: str
|
password_hash: str
|
||||||
token: Optional[str] = None
|
token: Optional[str] = None
|
||||||
history: List[HistoryItem] = []
|
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 bson import ObjectId
|
||||||
|
|
||||||
from api.database.mongodb import users_collection
|
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
|
from api.utils.security import get_current_user
|
||||||
|
|
||||||
router = APIRouter(prefix="/history", tags=["history"])
|
router = APIRouter(prefix="/history", tags=["history"])
|
||||||
@@ -21,7 +21,7 @@ async def add_history_item(
|
|||||||
"_id": history_item_id,
|
"_id": history_item_id,
|
||||||
"name": data.name,
|
"name": data.name,
|
||||||
"created_at": datetime.utcnow(),
|
"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(
|
await users_collection.update_one(
|
||||||
@@ -35,6 +35,7 @@ async def add_history_item(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/delete/{history_item_id}")
|
@router.delete("/delete/{history_item_id}")
|
||||||
async def delete_history_item(
|
async def delete_history_item(
|
||||||
history_item_id: str,
|
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
|
a, b = request.core
|
||||||
c, d = request.support
|
c, d = request.support
|
||||||
|
|
||||||
@@ -43,3 +49,19 @@ def build_docmf_multi(request):
|
|||||||
result = build_single_docmf(level)
|
result = build_single_docmf(level)
|
||||||
results.append(result)
|
results.append(result)
|
||||||
return {"results": results}
|
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