events retention + recommendation
Browse files- app/prompts.py +1 -0
- app/retention.py +4 -1
- tests/routes/test_retention.py +13 -1
app/prompts.py
CHANGED
|
@@ -25,6 +25,7 @@ Reglas de uso:
|
|
| 25 |
## Vender, no solo informar
|
| 26 |
- Cuando enseñes productos, usa `search_products`: las fichas se muestran solas como **tarjetas con foto, precio y botón de añadir** (no hace falta que las describas con tablas).
|
| 27 |
- Si el cliente quiere comprar ("me llevo este", "quiero los dos", "ponme 2"), llama a `create_cart_link` con los variant_id y cantidades, y dale el **enlace para finalizar la compra**.
|
|
|
|
| 28 |
- **Comparar:** si duda entre 2-4 productos, compáralos según SU necesidad (úsalos de search_products + fichas de search_knowledge) y recomiéndale uno con un motivo claro; ofrece añadirlo al carrito.
|
| 29 |
- **Talla/compatibilidad:** ante "¿me vale?", "¿qué talla?", "¿es compatible con X?", busca la guía de tallas/compatibilidad en `search_knowledge`, haz 1-2 preguntas si hace falta, y responde con la fuente. Si no está el dato, dilo (no adivines tallas).
|
| 30 |
- **Resolver objeciones:** si el cliente duda ("es caro", "no llega a tiempo", "¿y si lo devuelvo?"), responde PRIMERO con datos reales (plazo de envío, política de devoluciones de la base de conocimiento) antes de escalar; muchas veces es lo único que necesita para comprar.
|
|
|
|
| 25 |
## Vender, no solo informar
|
| 26 |
- Cuando enseñes productos, usa `search_products`: las fichas se muestran solas como **tarjetas con foto, precio y botón de añadir** (no hace falta que las describas con tablas).
|
| 27 |
- Si el cliente quiere comprar ("me llevo este", "quiero los dos", "ponme 2"), llama a `create_cart_link` con los variant_id y cantidades, y dale el **enlace para finalizar la compra**.
|
| 28 |
+
- **Recomendar / descubrir por intención:** si el cliente describe una NECESIDAD ("algo para vaciar la piscina", "lo más barato para regar", "un kit para empezar") o un PRESUPUESTO ("menos de 30€"), busca con `search_products` usando palabras clave de su necesidad, mira los precios/stock reales y recomiéndale 1-3 opciones que de verdad encajen, explicando por qué y respetando su presupuesto. Si hace falta, pregunta 1 detalle. Nunca recomiendes algo que no esté en los resultados.
|
| 29 |
- **Comparar:** si duda entre 2-4 productos, compáralos según SU necesidad (úsalos de search_products + fichas de search_knowledge) y recomiéndale uno con un motivo claro; ofrece añadirlo al carrito.
|
| 30 |
- **Talla/compatibilidad:** ante "¿me vale?", "¿qué talla?", "¿es compatible con X?", busca la guía de tallas/compatibilidad en `search_knowledge`, haz 1-2 preguntas si hace falta, y responde con la fuente. Si no está el dato, dilo (no adivines tallas).
|
| 31 |
- **Resolver objeciones:** si el cliente duda ("es caro", "no llega a tiempo", "¿y si lo devuelvo?"), responde PRIMERO con datos reales (plazo de envío, política de devoluciones de la base de conocimiento) antes de escalar; muchas veces es lo único que necesita para comprar.
|
app/retention.py
CHANGED
|
@@ -7,7 +7,7 @@ from datetime import UTC, datetime, timedelta
|
|
| 7 |
from sqlalchemy import delete, select
|
| 8 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 9 |
|
| 10 |
-
from app.models import ChatMessage, ChatSession
|
| 11 |
|
| 12 |
|
| 13 |
async def purge_old_sessions(db: AsyncSession, days: int) -> int:
|
|
@@ -15,7 +15,10 @@ async def purge_old_sessions(db: AsyncSession, days: int) -> int:
|
|
| 15 |
ids = (
|
| 16 |
await db.execute(select(ChatSession.id).where(ChatSession.last_seen < cutoff))
|
| 17 |
).scalars().all()
|
|
|
|
|
|
|
| 18 |
if not ids:
|
|
|
|
| 19 |
return 0
|
| 20 |
await db.execute(delete(ChatMessage).where(ChatMessage.session_id.in_(ids)))
|
| 21 |
await db.execute(delete(ChatSession).where(ChatSession.id.in_(ids)))
|
|
|
|
| 7 |
from sqlalchemy import delete, select
|
| 8 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 9 |
|
| 10 |
+
from app.models import ChatMessage, ChatSession, Event
|
| 11 |
|
| 12 |
|
| 13 |
async def purge_old_sessions(db: AsyncSession, days: int) -> int:
|
|
|
|
| 15 |
ids = (
|
| 16 |
await db.execute(select(ChatSession.id).where(ChatSession.last_seen < cutoff))
|
| 17 |
).scalars().all()
|
| 18 |
+
# Analytics events older than the window are purged too (keeps storage free).
|
| 19 |
+
await db.execute(delete(Event).where(Event.created_at < cutoff))
|
| 20 |
if not ids:
|
| 21 |
+
await db.flush()
|
| 22 |
return 0
|
| 23 |
await db.execute(delete(ChatMessage).where(ChatMessage.session_id.in_(ids)))
|
| 24 |
await db.execute(delete(ChatSession).where(ChatSession.id.in_(ids)))
|
tests/routes/test_retention.py
CHANGED
|
@@ -4,7 +4,7 @@ from datetime import UTC, datetime, timedelta
|
|
| 4 |
|
| 5 |
from sqlalchemy import select
|
| 6 |
|
| 7 |
-
from app.models import ChatMessage, ChatSession
|
| 8 |
from app.retention import purge_old_sessions
|
| 9 |
|
| 10 |
|
|
@@ -27,3 +27,15 @@ async def test_purge_removes_old_keeps_recent(db_session):
|
|
| 27 |
|
| 28 |
msgs = (await db_session.execute(select(ChatMessage))).scalars().all()
|
| 29 |
assert msgs == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
from sqlalchemy import select
|
| 6 |
|
| 7 |
+
from app.models import ChatMessage, ChatSession, Event
|
| 8 |
from app.retention import purge_old_sessions
|
| 9 |
|
| 10 |
|
|
|
|
| 27 |
|
| 28 |
msgs = (await db_session.execute(select(ChatMessage))).scalars().all()
|
| 29 |
assert msgs == []
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
async def test_purge_removes_old_events(db_session):
|
| 33 |
+
now = datetime.now(UTC)
|
| 34 |
+
db_session.add(Event(tenant_id=1, type="turn", created_at=now - timedelta(days=60)))
|
| 35 |
+
db_session.add(Event(tenant_id=1, type="turn", created_at=now))
|
| 36 |
+
await db_session.flush()
|
| 37 |
+
|
| 38 |
+
await purge_old_sessions(db_session, days=30)
|
| 39 |
+
|
| 40 |
+
events = (await db_session.execute(select(Event))).scalars().all()
|
| 41 |
+
assert len(events) == 1 # old event purged, recent kept (keeps Neon free)
|