fix(mcp): status active + search_products guard + item.product + cleanup auto-name

This commit is contained in:
2026-05-28 06:41:04 +02:00
parent 87efbcb03d
commit 5d7dbec67c
2 changed files with 27 additions and 18 deletions
+19 -18
View File
@@ -361,19 +361,19 @@ async def get_shopping_lists() -> str:
@mcp.tool() @mcp.tool()
async def get_active_shopping_list() -> str: async def get_active_shopping_list() -> str:
"""Retourne la première liste en statut 'draft' avec tous ses articles triés.""" """Retourne la première liste en statut 'draft' ou 'active' avec tous ses articles triés."""
async with AsyncSessionLocal() as session: async with AsyncSessionLocal() as session:
stmt = ( stmt = (
select(ShoppingList) select(ShoppingList)
.where(ShoppingList.status == "draft") .where(ShoppingList.status.in_(["draft", "active"]))
.options(selectinload(ShoppingList.items).selectinload(ListItem.product)) .options(selectinload(ShoppingList.items).selectinload(ListItem.product))
.order_by(ShoppingList.created_at.desc()) .order_by(ShoppingList.status.asc(), ShoppingList.created_at.desc())
.limit(1) .limit(1)
) )
result = await session.execute(stmt) result = await session.execute(stmt)
lst = result.scalar_one_or_none() lst = result.scalar_one_or_none()
if not lst: if not lst:
return _dumps({"error": "Aucune liste active (statut draft)"}) return _dumps({"error": "Aucune liste active (statut draft ou active)"})
sorted_items = sorted(lst.items, key=lambda i: (i.sort_order or 999, str(i.id))) sorted_items = sorted(lst.items, key=lambda i: (i.sort_order or 999, str(i.id)))
return _dumps({ return _dumps({
"id": lst.id, "id": lst.id,
@@ -394,6 +394,8 @@ async def get_active_shopping_list() -> str:
@mcp.tool() @mcp.tool()
async def search_products(q: str) -> str: async def search_products(q: str) -> str:
"""Recherche dans le catalogue produits par nom, description ou catégorie.""" """Recherche dans le catalogue produits par nom, description ou catégorie."""
if not q or not q.strip():
return _dumps({"error": "Paramètre q requis"})
async with AsyncSessionLocal() as session: async with AsyncSessionLocal() as session:
stmt = ( stmt = (
select(Product) select(Product)
@@ -490,19 +492,18 @@ async def check_shopping_item(list_id: str, item_id: str) -> str:
return _dumps({"error": f"Article introuvable : {item_id} dans liste {list_id}"}) return _dumps({"error": f"Article introuvable : {item_id} dans liste {list_id}"})
was_checked = item.is_checked was_checked = item.is_checked
item.is_checked = True item.is_checked = True
if not was_checked and item.product_id: if not was_checked and item.product:
product = await session.get(Product, item.product_id) product = item.product
if product: today = date_type.today()
today = date_type.today() if product.last_purchased_at and product.last_purchased_at < today:
if product.last_purchased_at and product.last_purchased_at < today: days = (today - product.last_purchased_at).days
days = (today - product.last_purchased_at).days if product.avg_interval_days is None:
if product.avg_interval_days is None: product.avg_interval_days = Decimal(str(days))
product.avg_interval_days = Decimal(str(days)) else:
else: product.avg_interval_days = Decimal(str(
product.avg_interval_days = Decimal(str( round(float(product.avg_interval_days) * 0.7 + days * 0.3, 1)
round(float(product.avg_interval_days) * 0.7 + days * 0.3, 1) ))
)) product.last_purchased_at = today
product.last_purchased_at = today product.frequency_score += 1
product.frequency_score += 1
await session.commit() await session.commit()
return _dumps({"id": item.id, "is_checked": item.is_checked}) return _dumps({"id": item.id, "is_checked": item.is_checked})
+8
View File
@@ -1,4 +1,5 @@
import json import json
import uuid
import pytest import pytest
from sqlalchemy import delete from sqlalchemy import delete
import app.api.mcp_server as mcp_server_module import app.api.mcp_server as mcp_server_module
@@ -155,7 +156,14 @@ async def test_create_shopping_list_outil():
async def test_create_shopping_list_nom_auto(): async def test_create_shopping_list_nom_auto():
result = await create_shopping_list() result = await create_shopping_list()
data = json.loads(result) data = json.loads(result)
# Le nom auto est au format "S{semaine} {année}" (ex: "S22 2026")
assert data["name"].startswith("S") assert data["name"].startswith("S")
# Cleanup manuel — la liste auto n'a pas le préfixe TEST_MCP_
async with mcp_server_module.AsyncSessionLocal() as session:
await session.execute(
delete(ShoppingList).where(ShoppingList.id == uuid.UUID(data["id"]))
)
await session.commit()
async def test_add_shopping_item_outil(): async def test_add_shopping_item_outil():