Spaces:
Running
Running
Comentarios en parte BD
Browse files- backend/db.py +31 -16
- backend/repositories/__init__.py +2 -0
- backend/repositories/history_repository.py +223 -70
- backend/services/__init__.py +2 -0
backend/db.py
CHANGED
|
@@ -1,21 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import sqlite3
|
| 2 |
|
| 3 |
from config import HISTORY_DB_PATH
|
| 4 |
|
| 5 |
|
| 6 |
-
def
|
|
|
|
|
|
|
|
|
|
| 7 |
# Cada conexion usa sqlite3.Row para acceder por nombre de columna.
|
| 8 |
-
conn = sqlite3.connect(HISTORY_DB_PATH)
|
| 9 |
-
conn.row_factory = sqlite3.Row
|
| 10 |
return conn
|
| 11 |
|
| 12 |
|
| 13 |
-
def
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
| 15 |
# Historial de visionado con rating opcional por usuario.
|
| 16 |
conn.execute(
|
| 17 |
"""
|
| 18 |
-
CREATE TABLE IF NOT EXISTS
|
| 19 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 20 |
user_id TEXT NOT NULL,
|
| 21 |
movie_id TEXT NOT NULL,
|
|
@@ -28,21 +40,23 @@ def init_history_db() -> None:
|
|
| 28 |
"""
|
| 29 |
)
|
| 30 |
# Compatibilidad con BDs existentes creadas sin columna de valoracion.
|
| 31 |
-
columns = conn.execute("PRAGMA table_info(
|
| 32 |
column_names = {row[1] for row in columns}
|
| 33 |
if "user_rating" not in column_names:
|
| 34 |
-
conn.execute("ALTER TABLE
|
| 35 |
|
| 36 |
# Indices para lecturas frecuentes por usuario + fecha.
|
| 37 |
conn.execute(
|
| 38 |
"""
|
| 39 |
-
CREATE INDEX IF NOT EXISTS
|
| 40 |
-
ON
|
| 41 |
"""
|
| 42 |
)
|
|
|
|
|
|
|
| 43 |
conn.execute(
|
| 44 |
"""
|
| 45 |
-
CREATE TABLE IF NOT EXISTS
|
| 46 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 47 |
user_id TEXT NOT NULL,
|
| 48 |
text TEXT,
|
|
@@ -53,13 +67,14 @@ def init_history_db() -> None:
|
|
| 53 |
)
|
| 54 |
conn.execute(
|
| 55 |
"""
|
| 56 |
-
CREATE INDEX IF NOT EXISTS
|
| 57 |
-
ON
|
| 58 |
"""
|
| 59 |
)
|
|
|
|
| 60 |
conn.execute(
|
| 61 |
"""
|
| 62 |
-
CREATE TABLE IF NOT EXISTS
|
| 63 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 64 |
user_id TEXT NOT NULL,
|
| 65 |
pre_text TEXT,
|
|
@@ -78,8 +93,8 @@ def init_history_db() -> None:
|
|
| 78 |
)
|
| 79 |
conn.execute(
|
| 80 |
"""
|
| 81 |
-
CREATE INDEX IF NOT EXISTS
|
| 82 |
-
ON
|
| 83 |
"""
|
| 84 |
)
|
| 85 |
conn.commit()
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Este archivo se encarga de gestionar la conexion con la BD
|
| 3 |
+
así como de crear las tablas iniciales necesarias para almacenar
|
| 4 |
+
el historial de visionado
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
import sqlite3
|
| 8 |
|
| 9 |
from config import HISTORY_DB_PATH
|
| 10 |
|
| 11 |
|
| 12 |
+
def obtener_conexion_bd() -> sqlite3.Connection:
|
| 13 |
+
"""
|
| 14 |
+
Crea la conexión con la base de datos SQLite
|
| 15 |
+
"""
|
| 16 |
# Cada conexion usa sqlite3.Row para acceder por nombre de columna.
|
| 17 |
+
conn = sqlite3.connect(HISTORY_DB_PATH) # Se conecta a la BD en la ruta configurada
|
| 18 |
+
conn.row_factory = sqlite3.Row # Permite acceder a las filas como diccionarios por nombre de columna
|
| 19 |
return conn
|
| 20 |
|
| 21 |
|
| 22 |
+
def iniciar_historial_usuario() -> None:
|
| 23 |
+
"""
|
| 24 |
+
Inicia la BD creando todas las tablas necesarias
|
| 25 |
+
"""
|
| 26 |
+
with obtener_conexion_bd() as conn:
|
| 27 |
# Historial de visionado con rating opcional por usuario.
|
| 28 |
conn.execute(
|
| 29 |
"""
|
| 30 |
+
CREATE TABLE IF NOT EXISTS historial_peliculas (
|
| 31 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 32 |
user_id TEXT NOT NULL,
|
| 33 |
movie_id TEXT NOT NULL,
|
|
|
|
| 40 |
"""
|
| 41 |
)
|
| 42 |
# Compatibilidad con BDs existentes creadas sin columna de valoracion.
|
| 43 |
+
columns = conn.execute("PRAGMA table_info(historial_peliculas)").fetchall()
|
| 44 |
column_names = {row[1] for row in columns}
|
| 45 |
if "user_rating" not in column_names:
|
| 46 |
+
conn.execute("ALTER TABLE historial_peliculas ADD COLUMN user_rating REAL")
|
| 47 |
|
| 48 |
# Indices para lecturas frecuentes por usuario + fecha.
|
| 49 |
conn.execute(
|
| 50 |
"""
|
| 51 |
+
CREATE INDEX IF NOT EXISTS idx_historial_peliculas_user_viewed_at
|
| 52 |
+
ON historial_peliculas (user_id, viewed_at DESC)
|
| 53 |
"""
|
| 54 |
)
|
| 55 |
+
# Crea una tabla para eventos de emocion detectada con texto analizado.
|
| 56 |
+
# Tiene para los distintos usuarios un registro del texto a analizar, la emocion detectada y cuando se hizo
|
| 57 |
conn.execute(
|
| 58 |
"""
|
| 59 |
+
CREATE TABLE IF NOT EXISTS eventos_emociones (
|
| 60 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 61 |
user_id TEXT NOT NULL,
|
| 62 |
text TEXT,
|
|
|
|
| 67 |
)
|
| 68 |
conn.execute(
|
| 69 |
"""
|
| 70 |
+
CREATE INDEX IF NOT EXISTS idx_eventos_emociones_user_analyzed_at
|
| 71 |
+
ON eventos_emociones (user_id, analyzed_at DESC)
|
| 72 |
"""
|
| 73 |
)
|
| 74 |
+
# Tabla para ciclos de recomendacion con datos pre y post recomendacion.
|
| 75 |
conn.execute(
|
| 76 |
"""
|
| 77 |
+
CREATE TABLE IF NOT EXISTS ciclos_recomendaciones (
|
| 78 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 79 |
user_id TEXT NOT NULL,
|
| 80 |
pre_text TEXT,
|
|
|
|
| 93 |
)
|
| 94 |
conn.execute(
|
| 95 |
"""
|
| 96 |
+
CREATE INDEX IF NOT EXISTS idx_ciclos_recomendaciones_user_created_at
|
| 97 |
+
ON ciclos_recomendaciones (user_id, created_at DESC)
|
| 98 |
"""
|
| 99 |
)
|
| 100 |
conn.commit()
|
backend/repositories/__init__.py
CHANGED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#Se utiliza para marcar el paquete de Python
|
| 2 |
+
#Asi los imports relativos dentro del backend funcionan
|
backend/repositories/history_repository.py
CHANGED
|
@@ -1,45 +1,85 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
# Se ejecuta DELETE para borrar historial de visionado del usuario.
|
| 7 |
cur = conn.execute(
|
| 8 |
"""
|
| 9 |
-
DELETE FROM
|
| 10 |
WHERE user_id = ?
|
| 11 |
""",
|
| 12 |
(user_id,),
|
| 13 |
)
|
| 14 |
-
conn.commit()
|
| 15 |
return int(cur.rowcount or 0)
|
| 16 |
|
| 17 |
|
| 18 |
-
def
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
# Consulta base para perfilar recomendaciones con movie_id y user_rating.
|
| 21 |
-
|
| 22 |
"""
|
| 23 |
SELECT movie_id, user_rating
|
| 24 |
-
FROM
|
| 25 |
WHERE user_id = ?
|
| 26 |
ORDER BY viewed_at DESC
|
| 27 |
LIMIT ?
|
| 28 |
""",
|
| 29 |
(user_id, limit),
|
| 30 |
).fetchall()
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
def save_emotion_event(user_id: str, text: str, emotion: str, analyzed_at: str) -> int | None:
|
| 35 |
if not user_id:
|
| 36 |
return None
|
| 37 |
|
| 38 |
-
with
|
| 39 |
# Registro temporal de cada analisis emocional del usuario.
|
| 40 |
cur = conn.execute(
|
| 41 |
"""
|
| 42 |
-
INSERT INTO
|
| 43 |
VALUES (?, ?, ?, ?)
|
| 44 |
""",
|
| 45 |
(user_id, text, emotion, analyzed_at),
|
|
@@ -48,7 +88,7 @@ def save_emotion_event(user_id: str, text: str, emotion: str, analyzed_at: str)
|
|
| 48 |
return cur.lastrowid
|
| 49 |
|
| 50 |
|
| 51 |
-
def
|
| 52 |
user_id: str,
|
| 53 |
pre_text: str,
|
| 54 |
pre_emotion: str,
|
|
@@ -56,14 +96,30 @@ def create_recommendation_cycle(
|
|
| 56 |
recommendation_mode: str,
|
| 57 |
created_at: str,
|
| 58 |
) -> int | None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
if not user_id:
|
| 60 |
return None
|
| 61 |
|
| 62 |
-
with
|
| 63 |
# Se guarda el estado previo a la recomendacion para seguimiento posterior.
|
| 64 |
cur = conn.execute(
|
| 65 |
"""
|
| 66 |
-
INSERT INTO
|
| 67 |
user_id,
|
| 68 |
pre_text,
|
| 69 |
pre_emotion,
|
|
@@ -79,23 +135,33 @@ def create_recommendation_cycle(
|
|
| 79 |
return cur.lastrowid
|
| 80 |
|
| 81 |
|
| 82 |
-
def
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
"""
|
| 86 |
SELECT id, user_id, pre_text, pre_emotion, pre_valence, recommendation_mode,
|
| 87 |
created_at, selected_movie_id, selected_movie_title,
|
| 88 |
post_text, post_emotion, post_valence, post_analyzed_at
|
| 89 |
-
FROM
|
| 90 |
WHERE id = ? AND user_id = ?
|
| 91 |
LIMIT 1
|
| 92 |
""",
|
| 93 |
(cycle_id, user_id),
|
| 94 |
-
).fetchone()
|
| 95 |
-
return dict(
|
| 96 |
|
| 97 |
|
| 98 |
-
def
|
| 99 |
cycle_id: int,
|
| 100 |
user_id: str,
|
| 101 |
post_text: str,
|
|
@@ -105,11 +171,28 @@ def save_post_recommendation_state(
|
|
| 105 |
movie_id: str,
|
| 106 |
movie_title: str,
|
| 107 |
) -> None:
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
# Actualiza el ciclo con pelicula elegida y estado emocional posterior.
|
| 110 |
conn.execute(
|
| 111 |
"""
|
| 112 |
-
UPDATE
|
| 113 |
SET selected_movie_id = ?,
|
| 114 |
selected_movie_title = ?,
|
| 115 |
post_text = ?,
|
|
@@ -123,15 +206,26 @@ def save_post_recommendation_state(
|
|
| 123 |
conn.commit()
|
| 124 |
|
| 125 |
|
| 126 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
if not user_id:
|
| 128 |
return None
|
| 129 |
|
| 130 |
-
with
|
| 131 |
-
|
|
|
|
| 132 |
"""
|
| 133 |
SELECT id, user_id, text, emotion, analyzed_at
|
| 134 |
-
FROM
|
| 135 |
WHERE user_id = ?
|
| 136 |
ORDER BY analyzed_at DESC
|
| 137 |
LIMIT 1
|
|
@@ -139,15 +233,26 @@ def get_last_emotion_event(user_id: str) -> dict | None:
|
|
| 139 |
(user_id,),
|
| 140 |
).fetchone()
|
| 141 |
|
| 142 |
-
return dict(
|
|
|
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
"""
|
| 149 |
SELECT movie_id, title, viewed_at
|
| 150 |
-
FROM
|
| 151 |
WHERE user_id = ?
|
| 152 |
AND viewed_at > ?
|
| 153 |
AND viewed_at <= ?
|
|
@@ -157,10 +262,10 @@ def get_last_viewed_between(user_id: str, start_iso: str, end_iso: str) -> dict
|
|
| 157 |
(user_id, start_iso, end_iso),
|
| 158 |
).fetchone()
|
| 159 |
|
| 160 |
-
return dict(
|
| 161 |
-
|
| 162 |
|
| 163 |
-
def
|
| 164 |
user_id: str,
|
| 165 |
movie_id: str,
|
| 166 |
title: str,
|
|
@@ -169,10 +274,24 @@ def insert_view_history(
|
|
| 169 |
session_text: str,
|
| 170 |
viewed_at: str,
|
| 171 |
) -> int:
|
| 172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
cur = conn.execute(
|
| 174 |
"""
|
| 175 |
-
INSERT INTO
|
| 176 |
VALUES (?, ?, ?, ?, ?, ?, ?)
|
| 177 |
""",
|
| 178 |
(user_id, movie_id, title, emotion, user_rating, session_text, viewed_at),
|
|
@@ -181,28 +300,51 @@ def insert_view_history(
|
|
| 181 |
return int(cur.lastrowid)
|
| 182 |
|
| 183 |
|
| 184 |
-
def
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
"""
|
| 188 |
SELECT id, user_id, movie_id, title, emotion, user_rating, session_text, viewed_at
|
| 189 |
-
FROM
|
| 190 |
WHERE user_id = ?
|
| 191 |
ORDER BY viewed_at DESC
|
| 192 |
LIMIT ?
|
| 193 |
""",
|
| 194 |
(user_id, limit),
|
| 195 |
).fetchall()
|
| 196 |
-
return [dict(
|
|
|
|
| 197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
"""
|
| 204 |
SELECT id, user_id, text, emotion, analyzed_at
|
| 205 |
-
FROM
|
| 206 |
WHERE user_id = ?
|
| 207 |
ORDER BY analyzed_at ASC
|
| 208 |
LIMIT 500
|
|
@@ -210,10 +352,11 @@ def get_transition_items(user_id: str, limit: int) -> list[dict]:
|
|
| 210 |
(user_id,),
|
| 211 |
).fetchall()
|
| 212 |
|
| 213 |
-
|
|
|
|
| 214 |
"""
|
| 215 |
SELECT movie_id, title, viewed_at
|
| 216 |
-
FROM
|
| 217 |
WHERE user_id = ?
|
| 218 |
ORDER BY viewed_at ASC
|
| 219 |
LIMIT 1000
|
|
@@ -221,40 +364,50 @@ def get_transition_items(user_id: str, limit: int) -> list[dict]:
|
|
| 221 |
(user_id,),
|
| 222 |
).fetchall()
|
| 223 |
|
| 224 |
-
|
| 225 |
-
|
| 226 |
|
|
|
|
| 227 |
# (movie_id, title, emocion_origen, emocion_destino) -> conteo.
|
| 228 |
transition_counter: dict[tuple[str, str, str, str], int] = {}
|
| 229 |
|
| 230 |
-
for idx in range(1, len(
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
|
|
|
|
|
|
| 234 |
continue
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
|
|
|
|
|
|
|
|
|
| 239 |
matched_movie = None
|
| 240 |
-
for
|
| 241 |
-
viewed_at =
|
| 242 |
-
if
|
| 243 |
-
matched_movie =
|
| 244 |
break
|
| 245 |
|
| 246 |
if not matched_movie:
|
| 247 |
continue
|
| 248 |
-
|
|
|
|
|
|
|
|
|
|
| 249 |
key = (
|
| 250 |
str(matched_movie.get("movie_id", "")),
|
| 251 |
matched_movie.get("title") or "",
|
| 252 |
-
|
| 253 |
-
|
| 254 |
)
|
| 255 |
transition_counter[key] = transition_counter.get(key, 0) + 1
|
| 256 |
|
| 257 |
items = []
|
|
|
|
| 258 |
for (movie_id, title, from_emotion, to_emotion), count in transition_counter.items():
|
| 259 |
items.append(
|
| 260 |
{
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Este archivo contiene funciones para realizar sobre la BD operaciones sobre el
|
| 3 |
+
historial de visionado del usuario y el seguimiento del ciclo de recomendacion
|
| 4 |
+
siguiendo eventos emocionales del usuario antes y despues de la recomendacion.
|
| 5 |
+
|
| 6 |
+
Solo hay funciones que acceden a la informacion de las tablas de la BD
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
# Se importan la funcion que conecta con la BD de Sqlite para ejecutar las consultas
|
| 10 |
+
from db import obtener_conexion_bd
|
| 11 |
+
|
| 12 |
+
def borrar_historial_usuario(user_id: str) -> int:
|
| 13 |
+
"""
|
| 14 |
+
Borra el historial de visionado de un usuario.
|
| 15 |
+
|
| 16 |
+
Args:
|
| 17 |
+
- user_id: El ID del usuario cuyo historial se desea borrar.
|
| 18 |
+
Returns:
|
| 19 |
+
- El número de registros eliminados del historial.
|
| 20 |
+
"""
|
| 21 |
+
with obtener_conexion_bd() as conn:
|
| 22 |
# Se ejecuta DELETE para borrar historial de visionado del usuario.
|
| 23 |
cur = conn.execute(
|
| 24 |
"""
|
| 25 |
+
DELETE FROM historial_peliculas
|
| 26 |
WHERE user_id = ?
|
| 27 |
""",
|
| 28 |
(user_id,),
|
| 29 |
)
|
| 30 |
+
conn.commit() # Se hace commit para confirmar cambios con insert, update o delete
|
| 31 |
return int(cur.rowcount or 0)
|
| 32 |
|
| 33 |
|
| 34 |
+
def obtener_historial_usuario(user_id: str, limit: int = 200) -> list[dict]:
|
| 35 |
+
"""
|
| 36 |
+
Obtiene las filas del historial de visionado de un usuario.
|
| 37 |
+
|
| 38 |
+
Args:
|
| 39 |
+
- user_id: El ID del usuario cuyo historial se desea obtener.
|
| 40 |
+
- limit: El número máximo de registros a obtener.
|
| 41 |
+
|
| 42 |
+
Returns:
|
| 43 |
+
- Una lista de diccionarios con los registros del historial.
|
| 44 |
+
"""
|
| 45 |
+
with obtener_conexion_bd() as conn:
|
| 46 |
# Consulta base para perfilar recomendaciones con movie_id y user_rating.
|
| 47 |
+
filas = conn.execute(
|
| 48 |
"""
|
| 49 |
SELECT movie_id, user_rating
|
| 50 |
+
FROM historial_peliculas
|
| 51 |
WHERE user_id = ?
|
| 52 |
ORDER BY viewed_at DESC
|
| 53 |
LIMIT ?
|
| 54 |
""",
|
| 55 |
(user_id, limit),
|
| 56 |
).fetchall()
|
| 57 |
+
|
| 58 |
+
# Devuelve un diccionario con (movie_id, user_rating) para cada fila del historial del usuario
|
| 59 |
+
return [dict(fila) for fila in filas]
|
| 60 |
+
|
| 61 |
|
| 62 |
+
def añadir_evento_emocional(user_id: str, text: str, emotion: str, analyzed_at: str) -> int | None:
|
| 63 |
+
"""
|
| 64 |
+
Añade cuando se le detecta una emocion al usuario
|
| 65 |
+
|
| 66 |
+
Args:
|
| 67 |
+
- user_id: El ID del usuario al que se le detecta la emocion.
|
| 68 |
+
- text: El texto analizado para detectar la emocion.
|
| 69 |
+
- emotion: La emocion detectada.
|
| 70 |
+
- analyzed_at: La fecha y hora en formato ISO cuando se analizo el texto.
|
| 71 |
+
Returns:
|
| 72 |
+
- El ID del evento emocional insertado o None si no se pudo insertar.
|
| 73 |
+
"""
|
| 74 |
|
|
|
|
| 75 |
if not user_id:
|
| 76 |
return None
|
| 77 |
|
| 78 |
+
with obtener_conexion_bd() as conn:
|
| 79 |
# Registro temporal de cada analisis emocional del usuario.
|
| 80 |
cur = conn.execute(
|
| 81 |
"""
|
| 82 |
+
INSERT INTO eventos_emociones (user_id, text, emotion, analyzed_at)
|
| 83 |
VALUES (?, ?, ?, ?)
|
| 84 |
""",
|
| 85 |
(user_id, text, emotion, analyzed_at),
|
|
|
|
| 88 |
return cur.lastrowid
|
| 89 |
|
| 90 |
|
| 91 |
+
def crear_ciclo_recomendacion(
|
| 92 |
user_id: str,
|
| 93 |
pre_text: str,
|
| 94 |
pre_emotion: str,
|
|
|
|
| 96 |
recommendation_mode: str,
|
| 97 |
created_at: str,
|
| 98 |
) -> int | None:
|
| 99 |
+
"""
|
| 100 |
+
Crea un nuevo ciclo de recomendacion para seguimiento posterior.
|
| 101 |
+
Un ciclo de recomendacion contiene el estado emocional previo a la recomendacion, el modo de recomendacion aplicado,
|
| 102 |
+
y luego se actualiza con el estado posterior y pelicula elegida por el usuario.
|
| 103 |
+
|
| 104 |
+
Args:
|
| 105 |
+
- user_id: El ID del usuario para el que se crea el ciclo de recomendacion
|
| 106 |
+
- pre_text: El texto analizado para detectar la emocion previa a la recomendacion
|
| 107 |
+
- pre_emotion: La emocion detectada previa a la recomendacion
|
| 108 |
+
- pre_valence: La valencia detectada previa a la recomendacion
|
| 109 |
+
- recommendation_mode: El modo de recomendacion aplicado (exploracion o zona conocida)
|
| 110 |
+
- created_at: La fecha y hora en formato ISO cuando se creo el ciclo de recomendación
|
| 111 |
+
Returns:
|
| 112 |
+
- El ID del ciclo de recomendacion creado o None si no se pudo crear
|
| 113 |
+
|
| 114 |
+
"""
|
| 115 |
if not user_id:
|
| 116 |
return None
|
| 117 |
|
| 118 |
+
with obtener_conexion_bd() as conn:
|
| 119 |
# Se guarda el estado previo a la recomendacion para seguimiento posterior.
|
| 120 |
cur = conn.execute(
|
| 121 |
"""
|
| 122 |
+
INSERT INTO ciclos_recomendaciones (
|
| 123 |
user_id,
|
| 124 |
pre_text,
|
| 125 |
pre_emotion,
|
|
|
|
| 135 |
return cur.lastrowid
|
| 136 |
|
| 137 |
|
| 138 |
+
def obtener_ciclo_recomendacion(cycle_id: int, user_id: str) -> dict | None:
|
| 139 |
+
"""
|
| 140 |
+
Funcion que devuelve un ciclo de recomendacion de la BD
|
| 141 |
+
|
| 142 |
+
Args:
|
| 143 |
+
- cycle_id: El ID del ciclo de recomendacion a obtener
|
| 144 |
+
- user_id: El ID del usuario al que pertenece el ciclo de recomendacion
|
| 145 |
+
Returns:
|
| 146 |
+
- Un diccionario con los datos del ciclo de recomendacion o None si no se encuentra
|
| 147 |
+
"""
|
| 148 |
+
|
| 149 |
+
with obtener_conexion_bd() as conn:
|
| 150 |
+
fila = conn.execute(
|
| 151 |
"""
|
| 152 |
SELECT id, user_id, pre_text, pre_emotion, pre_valence, recommendation_mode,
|
| 153 |
created_at, selected_movie_id, selected_movie_title,
|
| 154 |
post_text, post_emotion, post_valence, post_analyzed_at
|
| 155 |
+
FROM ciclos_recomendaciones
|
| 156 |
WHERE id = ? AND user_id = ?
|
| 157 |
LIMIT 1
|
| 158 |
""",
|
| 159 |
(cycle_id, user_id),
|
| 160 |
+
).fetchone() # Se usa fetchone porque solo hay un ciclo correspondiente
|
| 161 |
+
return dict(fila) if fila else None
|
| 162 |
|
| 163 |
|
| 164 |
+
def guardar_estado_posterior(
|
| 165 |
cycle_id: int,
|
| 166 |
user_id: str,
|
| 167 |
post_text: str,
|
|
|
|
| 171 |
movie_id: str,
|
| 172 |
movie_title: str,
|
| 173 |
) -> None:
|
| 174 |
+
"""
|
| 175 |
+
Funcion que añade a un ciclo de recomendacion el estado posterior a la recomendacion
|
| 176 |
+
Se le preguntara al usuario que introduzca un rating de la pelicula vista
|
| 177 |
+
Asi como que exprese su emocion una vez vista la pelicula, para medir el cambio emocional y de valencia
|
| 178 |
+
|
| 179 |
+
Args:
|
| 180 |
+
- cycle_id: El ID del ciclo de recomendacion a actualizar
|
| 181 |
+
- user_id: El ID del usuario al que pertenece el ciclo de recomendacion
|
| 182 |
+
- post_text: El texto analizado para detectar la emocion posterior a la recomendacion
|
| 183 |
+
- post_emotion: La emocion detectada posterior a la recomendacion
|
| 184 |
+
- post_valence: La valencia detectada posterior a la recomendacion
|
| 185 |
+
- post_analyzed_at: La fecha y hora en formato ISO cuando se analizo el texto posterior a la recomendacion
|
| 186 |
+
- movie_id: El ID de la pelicula vista que se le pregunta al usuario
|
| 187 |
+
- movie_title: El titulo de la pelicula vista que se le pregunta al usuario
|
| 188 |
+
Returns:
|
| 189 |
+
- None (es un update de una instancia ya creada en la BD)
|
| 190 |
+
"""
|
| 191 |
+
with obtener_conexion_bd() as conn:
|
| 192 |
# Actualiza el ciclo con pelicula elegida y estado emocional posterior.
|
| 193 |
conn.execute(
|
| 194 |
"""
|
| 195 |
+
UPDATE ciclos_recomendaciones
|
| 196 |
SET selected_movie_id = ?,
|
| 197 |
selected_movie_title = ?,
|
| 198 |
post_text = ?,
|
|
|
|
| 206 |
conn.commit()
|
| 207 |
|
| 208 |
|
| 209 |
+
def obtener_ultima_emocion(user_id: str) -> dict | None:
|
| 210 |
+
"""
|
| 211 |
+
Funcion que devuelve el ultimo evento emocional registrado para un usuario
|
| 212 |
+
Se utiliza para detectar la emocion previa a una recomendacion y medir transiciones emocionales
|
| 213 |
+
|
| 214 |
+
Args:
|
| 215 |
+
- user_id: El ID del usuario del que se desea obtener el ultimo evento emocional
|
| 216 |
+
Returns:
|
| 217 |
+
- Un diccionario con los datos del ultimo evento emocional o None si no se encuentra. El diccionario contiene las claves: id, user_id, text, emotion, analyzed_at
|
| 218 |
+
"""
|
| 219 |
+
|
| 220 |
if not user_id:
|
| 221 |
return None
|
| 222 |
|
| 223 |
+
with obtener_conexion_bd() as conn:
|
| 224 |
+
# Se realiza la consulta, se ordena descendentemente por fecha y nos quedamos con la ultima instancia
|
| 225 |
+
fila = conn.execute(
|
| 226 |
"""
|
| 227 |
SELECT id, user_id, text, emotion, analyzed_at
|
| 228 |
+
FROM eventos_emociones
|
| 229 |
WHERE user_id = ?
|
| 230 |
ORDER BY analyzed_at DESC
|
| 231 |
LIMIT 1
|
|
|
|
| 233 |
(user_id,),
|
| 234 |
).fetchone()
|
| 235 |
|
| 236 |
+
return dict(fila) if fila else None
|
| 237 |
+
|
| 238 |
|
| 239 |
+
def obtener_pelicula_vista_entre(user_id: str, start_iso: str, end_iso: str) -> dict | None:
|
| 240 |
+
"""
|
| 241 |
+
Funcion que obtiene la pelicula vista por el usuario entre dos momentos determinados
|
| 242 |
+
Se usa para detectar la pelicula vista entre dos eventos de recogida de estado emocional
|
| 243 |
|
| 244 |
+
Args:
|
| 245 |
+
- user_id: El ID del usuario del que se desea obtener la pelicula vista
|
| 246 |
+
- start_iso: El momento inicial en formato ISO entre el que se desea obtener la pelicula vista
|
| 247 |
+
- end_iso: El momento final en formato ISO entre el que se desea obtener la pelicula vista
|
| 248 |
+
Returns:
|
| 249 |
+
- Un diccionario con los datos de la pelicula vista entre ambos momentos o None si no se encuentra. El diccionario contiene las claves: movie_id, title, viewed_at
|
| 250 |
+
"""
|
| 251 |
+
with obtener_conexion_bd() as conn:
|
| 252 |
+
fila = conn.execute(
|
| 253 |
"""
|
| 254 |
SELECT movie_id, title, viewed_at
|
| 255 |
+
FROM historial_peliculas
|
| 256 |
WHERE user_id = ?
|
| 257 |
AND viewed_at > ?
|
| 258 |
AND viewed_at <= ?
|
|
|
|
| 262 |
(user_id, start_iso, end_iso),
|
| 263 |
).fetchone()
|
| 264 |
|
| 265 |
+
return dict(fila) if fila else None
|
| 266 |
+
|
| 267 |
|
| 268 |
+
def añadir_pelicula_a_historial(
|
| 269 |
user_id: str,
|
| 270 |
movie_id: str,
|
| 271 |
title: str,
|
|
|
|
| 274 |
session_text: str,
|
| 275 |
viewed_at: str,
|
| 276 |
) -> int:
|
| 277 |
+
"""
|
| 278 |
+
Funcion que añade al historial de visualizaciones una nueva pelicula, junto con su valoracion y emocion
|
| 279 |
+
|
| 280 |
+
Args:
|
| 281 |
+
- user_id: El ID del usuario al que se le añade la pelicula al historial
|
| 282 |
+
- movie_id: El ID de la pelicula vista
|
| 283 |
+
- title: El titulo de la pelicula vista
|
| 284 |
+
- emotion: La emocion asociada a la pelicula vista (puede ser la emocion detectada en el texto del usuario)
|
| 285 |
+
- user_rating: La valoracion que el usuario da a la pelicula vista (puede ser None si no se proporciona)
|
| 286 |
+
- session_text: El texto de la sesion que se asocia a la pelicula vista
|
| 287 |
+
- viewed_at: La fecha y hora en formato ISO cuando se visualizo la pelicula
|
| 288 |
+
Returns:
|
| 289 |
+
- El ID del registro insertado en el historial de visualizaciones
|
| 290 |
+
"""
|
| 291 |
+
with obtener_conexion_bd() as conn:
|
| 292 |
cur = conn.execute(
|
| 293 |
"""
|
| 294 |
+
INSERT INTO historial_peliculas (user_id, movie_id, title, emotion, user_rating, session_text, viewed_at)
|
| 295 |
VALUES (?, ?, ?, ?, ?, ?, ?)
|
| 296 |
""",
|
| 297 |
(user_id, movie_id, title, emotion, user_rating, session_text, viewed_at),
|
|
|
|
| 300 |
return int(cur.lastrowid)
|
| 301 |
|
| 302 |
|
| 303 |
+
def obtener_peliculas_del_historial(user_id: str, limit: int) -> list[dict]:
|
| 304 |
+
"""
|
| 305 |
+
Funcion que devuelve las ultimas peliculas vistas por el usuario junto con su emocion asociada
|
| 306 |
+
Se utiliza para detectar transiciones emocionales entre peliculas vistas y medir su frecuencia
|
| 307 |
+
|
| 308 |
+
Args:
|
| 309 |
+
- user_id: El ID del usuario del que se desea obtener las peliculas vistas
|
| 310 |
+
- limit: El número máximo de registros a obtener
|
| 311 |
+
|
| 312 |
+
Returns:
|
| 313 |
+
- Una lista de diccionarios con los datos de las peliculas vistas por el usuario. Cada diccionario contiene las claves: movie_id, title, emotion, viewed_at; ordenado por fecha de visualizacion empezando por la mas reciente
|
| 314 |
+
"""
|
| 315 |
+
with obtener_conexion_bd() as conn:
|
| 316 |
+
filas = conn.execute(
|
| 317 |
"""
|
| 318 |
SELECT id, user_id, movie_id, title, emotion, user_rating, session_text, viewed_at
|
| 319 |
+
FROM historial_peliculas
|
| 320 |
WHERE user_id = ?
|
| 321 |
ORDER BY viewed_at DESC
|
| 322 |
LIMIT ?
|
| 323 |
""",
|
| 324 |
(user_id, limit),
|
| 325 |
).fetchall()
|
| 326 |
+
return [dict(fila) for fila in filas]
|
| 327 |
+
|
| 328 |
|
| 329 |
+
def obtener_relacion_pelicula_emocion(user_id: str, limit: int) -> list[dict]:
|
| 330 |
+
"""
|
| 331 |
+
Funcion que calcula que peliculas han sido vistas por el usuario entre eventos emocionales con cambio de emocion
|
| 332 |
+
De esta manera se detecta que peliculas estan asociadas a que transiciones emocionales y con que frecuencia
|
| 333 |
|
| 334 |
+
Args:
|
| 335 |
+
- user_id: El ID del usuario del que se desea obtener las transiciones emocionales asociadas a peliculas vistas
|
| 336 |
+
- limit: El número máximo de registros a obtener
|
| 337 |
+
|
| 338 |
+
Returns:
|
| 339 |
+
- Una lista de diccionarios con los datos de las transiciones emocionales asociadas a peliculas vistas por el usuario. Cada diccionario contiene las claves: movie_id, title, from_emotion, to_emotion, count; ordenado por frecuencia de la transicion empezando por la mas frecuente
|
| 340 |
+
"""
|
| 341 |
+
|
| 342 |
+
with obtener_conexion_bd() as conn:
|
| 343 |
+
# Se obtiene el historial de eventos emocionales (texto y emocion asociada)
|
| 344 |
+
filas_emociones = conn.execute(
|
| 345 |
"""
|
| 346 |
SELECT id, user_id, text, emotion, analyzed_at
|
| 347 |
+
FROM eventos_emociones
|
| 348 |
WHERE user_id = ?
|
| 349 |
ORDER BY analyzed_at ASC
|
| 350 |
LIMIT 500
|
|
|
|
| 352 |
(user_id,),
|
| 353 |
).fetchall()
|
| 354 |
|
| 355 |
+
# Se obtiene el historial de peliculas vistas (movie_id, title y momento del visionado)
|
| 356 |
+
filas_peliculas = conn.execute(
|
| 357 |
"""
|
| 358 |
SELECT movie_id, title, viewed_at
|
| 359 |
+
FROM historial_peliculas
|
| 360 |
WHERE user_id = ?
|
| 361 |
ORDER BY viewed_at ASC
|
| 362 |
LIMIT 1000
|
|
|
|
| 364 |
(user_id,),
|
| 365 |
).fetchall()
|
| 366 |
|
| 367 |
+
emociones = [dict(fila) for fila in filas_emociones]
|
| 368 |
+
peliculas = [dict(fila) for fila in filas_peliculas]
|
| 369 |
|
| 370 |
+
# Se recorren los eventos emocionales buscando transiciones de emocion entre eventos consecutivos
|
| 371 |
# (movie_id, title, emocion_origen, emocion_destino) -> conteo.
|
| 372 |
transition_counter: dict[tuple[str, str, str, str], int] = {}
|
| 373 |
|
| 374 |
+
for idx in range(1, len(emociones)):
|
| 375 |
+
emocion_previa = emociones[idx - 1]
|
| 376 |
+
emocion_actual = emociones[idx]
|
| 377 |
+
|
| 378 |
+
#Si son la misma emocion no se ha transicionado
|
| 379 |
+
if emocion_previa.get("emotion") == emocion_actual.get("emotion"):
|
| 380 |
continue
|
| 381 |
+
|
| 382 |
+
#En caso de haber cambiado de estado emocional se obtiene el momento de ambos eventos para buscar la pelicula vista
|
| 383 |
+
#entre ambos momentos
|
| 384 |
+
momento_inicio = emocion_previa.get("analyzed_at", "")
|
| 385 |
+
momento_fin = emocion_actual.get("analyzed_at", "")
|
| 386 |
+
|
| 387 |
+
# Se busca la pelicula vista en ese momento en especifico
|
| 388 |
matched_movie = None
|
| 389 |
+
for pelicula in reversed(peliculas):
|
| 390 |
+
viewed_at = pelicula.get("viewed_at", "")
|
| 391 |
+
if momento_inicio < viewed_at <= momento_fin:
|
| 392 |
+
matched_movie = pelicula
|
| 393 |
break
|
| 394 |
|
| 395 |
if not matched_movie:
|
| 396 |
continue
|
| 397 |
+
|
| 398 |
+
# Si se encuentra una pelicula vista entre ambos eventos emocionales
|
| 399 |
+
# se cuenta la transicion emocional asociada a esa pelicula, para detectar
|
| 400 |
+
# que peliculas estan mas asociadas a que transiciones emocionales
|
| 401 |
key = (
|
| 402 |
str(matched_movie.get("movie_id", "")),
|
| 403 |
matched_movie.get("title") or "",
|
| 404 |
+
emocion_previa.get("emotion", ""),
|
| 405 |
+
emocion_actual.get("emotion", ""),
|
| 406 |
)
|
| 407 |
transition_counter[key] = transition_counter.get(key, 0) + 1
|
| 408 |
|
| 409 |
items = []
|
| 410 |
+
# Se construye la lista de diccionarios con los datos de las peliculas asociadas a transiciones emocionales y su frecuencia
|
| 411 |
for (movie_id, title, from_emotion, to_emotion), count in transition_counter.items():
|
| 412 |
items.append(
|
| 413 |
{
|
backend/services/__init__.py
CHANGED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#Se utiliza para marcar el paquete de Python
|
| 2 |
+
#Asi los imports relativos dentro del backend funcionan
|