iagofp commited on
Commit
57f27d3
·
1 Parent(s): f2bead0

Comentarios en parte BD

Browse files
backend/db.py CHANGED
@@ -1,21 +1,33 @@
 
 
 
 
 
 
1
  import sqlite3
2
 
3
  from config import HISTORY_DB_PATH
4
 
5
 
6
- def get_db_connection() -> sqlite3.Connection:
 
 
 
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 init_history_db() -> None:
14
- with get_db_connection() as conn:
 
 
 
15
  # Historial de visionado con rating opcional por usuario.
16
  conn.execute(
17
  """
18
- CREATE TABLE IF NOT EXISTS view_history (
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(view_history)").fetchall()
32
  column_names = {row[1] for row in columns}
33
  if "user_rating" not in column_names:
34
- conn.execute("ALTER TABLE view_history ADD COLUMN user_rating REAL")
35
 
36
  # Indices para lecturas frecuentes por usuario + fecha.
37
  conn.execute(
38
  """
39
- CREATE INDEX IF NOT EXISTS idx_view_history_user_viewed_at
40
- ON view_history (user_id, viewed_at DESC)
41
  """
42
  )
 
 
43
  conn.execute(
44
  """
45
- CREATE TABLE IF NOT EXISTS emotion_events (
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 idx_emotion_events_user_analyzed_at
57
- ON emotion_events (user_id, analyzed_at DESC)
58
  """
59
  )
 
60
  conn.execute(
61
  """
62
- CREATE TABLE IF NOT EXISTS recommendation_cycles (
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 idx_recommendation_cycles_user_created_at
82
- ON recommendation_cycles (user_id, created_at DESC)
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
- from db import get_db_connection
2
-
3
-
4
- def delete_user_history(user_id: str) -> int:
5
- with get_db_connection() as conn:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  # Se ejecuta DELETE para borrar historial de visionado del usuario.
7
  cur = conn.execute(
8
  """
9
- DELETE FROM view_history
10
  WHERE user_id = ?
11
  """,
12
  (user_id,),
13
  )
14
- conn.commit()
15
  return int(cur.rowcount or 0)
16
 
17
 
18
- def get_user_history_rows(user_id: str, limit: int = 200) -> list[dict]:
19
- with get_db_connection() as conn:
 
 
 
 
 
 
 
 
 
 
20
  # Consulta base para perfilar recomendaciones con movie_id y user_rating.
21
- rows = conn.execute(
22
  """
23
  SELECT movie_id, user_rating
24
- FROM view_history
25
  WHERE user_id = ?
26
  ORDER BY viewed_at DESC
27
  LIMIT ?
28
  """,
29
  (user_id, limit),
30
  ).fetchall()
31
- return [dict(row) for row in rows]
 
 
 
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 get_db_connection() as conn:
39
  # Registro temporal de cada analisis emocional del usuario.
40
  cur = conn.execute(
41
  """
42
- INSERT INTO emotion_events (user_id, text, emotion, analyzed_at)
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 create_recommendation_cycle(
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 get_db_connection() as conn:
63
  # Se guarda el estado previo a la recomendacion para seguimiento posterior.
64
  cur = conn.execute(
65
  """
66
- INSERT INTO recommendation_cycles (
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 get_recommendation_cycle(cycle_id: int, user_id: str) -> dict | None:
83
- with get_db_connection() as conn:
84
- row = conn.execute(
 
 
 
 
 
 
 
 
 
 
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 recommendation_cycles
90
  WHERE id = ? AND user_id = ?
91
  LIMIT 1
92
  """,
93
  (cycle_id, user_id),
94
- ).fetchone()
95
- return dict(row) if row else None
96
 
97
 
98
- def save_post_recommendation_state(
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
- with get_db_connection() as conn:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  # Actualiza el ciclo con pelicula elegida y estado emocional posterior.
110
  conn.execute(
111
  """
112
- UPDATE recommendation_cycles
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 get_last_emotion_event(user_id: str) -> dict | None:
 
 
 
 
 
 
 
 
 
 
127
  if not user_id:
128
  return None
129
 
130
- with get_db_connection() as conn:
131
- row = conn.execute(
 
132
  """
133
  SELECT id, user_id, text, emotion, analyzed_at
134
- FROM emotion_events
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(row) if row else None
 
143
 
 
 
 
 
144
 
145
- def get_last_viewed_between(user_id: str, start_iso: str, end_iso: str) -> dict | None:
146
- with get_db_connection() as conn:
147
- row = conn.execute(
 
 
 
 
 
 
148
  """
149
  SELECT movie_id, title, viewed_at
150
- FROM view_history
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(row) if row else None
161
-
162
 
163
- def insert_view_history(
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
- with get_db_connection() as conn:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  cur = conn.execute(
174
  """
175
- INSERT INTO view_history (user_id, movie_id, title, emotion, user_rating, session_text, viewed_at)
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 get_history_items(user_id: str, limit: int) -> list[dict]:
185
- with get_db_connection() as conn:
186
- rows = conn.execute(
 
 
 
 
 
 
 
 
 
 
 
187
  """
188
  SELECT id, user_id, movie_id, title, emotion, user_rating, session_text, viewed_at
189
- FROM view_history
190
  WHERE user_id = ?
191
  ORDER BY viewed_at DESC
192
  LIMIT ?
193
  """,
194
  (user_id, limit),
195
  ).fetchall()
196
- return [dict(row) for row in rows]
 
197
 
 
 
 
 
198
 
199
- def get_transition_items(user_id: str, limit: int) -> list[dict]:
200
- with get_db_connection() as conn:
201
- # Se toman eventos y vistas en orden cronologico para detectar transiciones.
202
- event_rows = conn.execute(
 
 
 
 
 
 
 
203
  """
204
  SELECT id, user_id, text, emotion, analyzed_at
205
- FROM emotion_events
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
- view_rows = conn.execute(
 
214
  """
215
  SELECT movie_id, title, viewed_at
216
- FROM view_history
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
- events = [dict(row) for row in event_rows]
225
- views = [dict(row) for row in view_rows]
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(events)):
231
- prev_event = events[idx - 1]
232
- curr_event = events[idx]
233
- if prev_event.get("emotion") == curr_event.get("emotion"):
 
 
234
  continue
235
-
236
- start = prev_event.get("analyzed_at", "")
237
- end = curr_event.get("analyzed_at", "")
238
-
 
 
 
239
  matched_movie = None
240
- for view in reversed(views):
241
- viewed_at = view.get("viewed_at", "")
242
- if start < viewed_at <= end:
243
- matched_movie = view
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
- prev_event.get("emotion", ""),
253
- curr_event.get("emotion", ""),
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