iagofp commited on
Commit
e2c5ba5
·
1 Parent(s): 9a3b82e

1 boceto de v3

Browse files
backend/app_factory.py CHANGED
@@ -25,6 +25,7 @@ from repositories.history_repository import (
25
  from services.chatbot_service import generar_texto_chatbot
26
  from services.emotion_service import (
27
  analizar_texto,
 
28
  calculo_arousal,
29
  crear_clasificador_emociones,
30
  estimar_arousal_emocion_es,
@@ -72,6 +73,7 @@ def create_app() -> Flask:
72
  # Se analiza el texto y se recomiendan peliculas en base a la emocion dominante detectada.
73
  resultado, dominant_es, dominant_valence = analizar_texto(modelo, texto)
74
  arousal_actual = calculo_arousal(resultado)
 
75
 
76
  historial_eventos = obtener_historial_emocional(user_id=user_id, limit=200)
77
  historico_arousal = [
@@ -91,6 +93,7 @@ def create_app() -> Flask:
91
  estrategia_recomendacion = estrategia,
92
  arousal_actual = arousal_actual,
93
  historico_arousal = historico_arousal,
 
94
  )
95
  # Se indica un modo de recomendacion
96
  recommendation_mode = "diferente" if dominant_es in POSITIVE_EMOTIONS else "similar"
@@ -131,6 +134,7 @@ def create_app() -> Flask:
131
  "emociones": resultado,
132
  "emocion_dominante": dominant_es,
133
  "valencia_dominante": dominant_valence,
 
134
  "arousal_actual": arousal_actual,
135
  "historico_arousal_size": len(historico_arousal),
136
  "emocion_anterior": emocion_previa.get("emotion") if emocion_previa else None,
 
25
  from services.chatbot_service import generar_texto_chatbot
26
  from services.emotion_service import (
27
  analizar_texto,
28
+ calcular_valencia_continua,
29
  calculo_arousal,
30
  crear_clasificador_emociones,
31
  estimar_arousal_emocion_es,
 
73
  # Se analiza el texto y se recomiendan peliculas en base a la emocion dominante detectada.
74
  resultado, dominant_es, dominant_valence = analizar_texto(modelo, texto)
75
  arousal_actual = calculo_arousal(resultado)
76
+ valencia_continua = calcular_valencia_continua(resultado)
77
 
78
  historial_eventos = obtener_historial_emocional(user_id=user_id, limit=200)
79
  historico_arousal = [
 
93
  estrategia_recomendacion = estrategia,
94
  arousal_actual = arousal_actual,
95
  historico_arousal = historico_arousal,
96
+ valencia_actual = valencia_continua,
97
  )
98
  # Se indica un modo de recomendacion
99
  recommendation_mode = "diferente" if dominant_es in POSITIVE_EMOTIONS else "similar"
 
134
  "emociones": resultado,
135
  "emocion_dominante": dominant_es,
136
  "valencia_dominante": dominant_valence,
137
+ "valencia_continua": valencia_continua,
138
  "arousal_actual": arousal_actual,
139
  "historico_arousal_size": len(historico_arousal),
140
  "emocion_anterior": emocion_previa.get("emotion") if emocion_previa else None,
backend/services/emotion_service.py CHANGED
@@ -31,6 +31,16 @@ AROUSAL_BY_EMOTION_ES = {
31
  "tristeza": 0.3,
32
  }
33
 
 
 
 
 
 
 
 
 
 
 
34
  def mapeo_emocion_valencia(emocion: str) -> str:
35
  """
36
  Funcion que mapea una emocion puntual a su valencia (positivo, negativo o neutro) segun las listas de emociones positivas y negativas.
@@ -127,3 +137,27 @@ def calculo_arousal(resultado: list[dict], historical_data: list | None = None)
127
  return round(max(0.0, min(1.0, arousal_final)), 4)
128
 
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  "tristeza": 0.3,
32
  }
33
 
34
+ VALENCE_BY_MODEL_LABEL = {
35
+ "joy": 1.0,
36
+ "surprise": 1.0,
37
+ "others": 0.5,
38
+ "sadness": 0.0,
39
+ "anger": 0.0,
40
+ "fear": 0.0,
41
+ "disgust": 0.0,
42
+ }
43
+
44
  def mapeo_emocion_valencia(emocion: str) -> str:
45
  """
46
  Funcion que mapea una emocion puntual a su valencia (positivo, negativo o neutro) segun las listas de emociones positivas y negativas.
 
137
  return round(max(0.0, min(1.0, arousal_final)), 4)
138
 
139
 
140
+ def calcular_valencia_continua(resultado: list[dict]) -> float:
141
+ """
142
+ Calcula valencia continua en [0,1] usando toda la distribucion de emociones.
143
+ 0: totalmente negativa, 0.5: neutra, 1: totalmente positiva.
144
+ """
145
+ if not resultado:
146
+ return 0.5
147
+
148
+ suma_ponderada = 0.0
149
+ suma_scores = 0.0
150
+ for item in resultado:
151
+ label = str(item.get("label", "others") or "others").strip().lower()
152
+ score = float(item.get("score", 0.0) or 0.0)
153
+ peso_valencia = VALENCE_BY_MODEL_LABEL.get(label, 0.5)
154
+ suma_ponderada += score * peso_valencia
155
+ suma_scores += score
156
+
157
+ if suma_scores <= 0:
158
+ return 0.5
159
+
160
+ valencia = suma_ponderada / suma_scores
161
+ return round(max(0.0, min(1.0, valencia)), 4)
162
+
163
+
backend/services/recommender_service.py CHANGED
@@ -380,6 +380,92 @@ def _percentil_arousal(arousal_actual: float, historico_arousal: list[float] | N
380
 
381
  menores_o_iguales = sum(1 for v in valores if v <= arousal_actual)
382
  return menores_o_iguales / len(valores)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
 
384
  def recomendar_peliculas(
385
  emotion_es: str,
@@ -390,6 +476,7 @@ def recomendar_peliculas(
390
  estrategia_recomendacion: str,
391
  arousal_actual: float = 0.5,
392
  historico_arousal: list[float] | None = None,
 
393
  ) -> list[dict]:
394
  """
395
  Funcion principal que recomienda peliculas a un usuario dado su estado emocional y su historial de visualizacion.
@@ -528,7 +615,41 @@ def recomendar_peliculas(
528
 
529
 
530
  case "v3":
531
- return "v3"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
 
533
  case _:
534
  # Se ordenan por calidad global
 
380
 
381
  menores_o_iguales = sum(1 for v in valores if v <= arousal_actual)
382
  return menores_o_iguales / len(valores)
383
+
384
+ def _percentil_desde_cero(arousal_actual: float, historico_arousal: list[float] | None) -> float:
385
+ """
386
+ Calcula el percentil considerando el 0.0 (calma total) como el ancla inferior.
387
+ Mide la intensidad absoluta dentro del rango histórico del usuario.
388
+ """
389
+ if historico_arousal is None:
390
+ historico_arousal = []
391
+
392
+ # Incluimos el 0.0 explícitamente para que la escala siempre parta de la calma
393
+ valores = [float(v) for v in historico_arousal if v is not None]
394
+ valores.append(0.0)
395
+
396
+ # Ordenamos para entender el rango
397
+ valores.sort()
398
+
399
+ # Si el valor actual es mayor que el máximo histórico, es un 1.0 (máxima intensidad)
400
+ if arousal_actual >= valores[-1]:
401
+ return 1.0
402
+
403
+ # Si es menor o igual a 0, es 0.0 (calma total)
404
+ if arousal_actual <= 0:
405
+ return 0.0
406
+
407
+ # Contamos cuántos valores (incluyendo el 0) quedan por debajo
408
+ menores = sum(1 for v in valores if v < arousal_actual)
409
+
410
+ # El percentil ahora refleja la distancia desde el "suelo" (0)
411
+ percentil = menores / len(valores)
412
+
413
+ return round(percentil, 4)
414
+
415
+
416
+ def _normalizar_valencia_actual(valencia_actual: str | float | None, emotion_es: str) -> str:
417
+ """
418
+ Normaliza valencia a {'positivo', 'negativo', 'neutro'}.
419
+ Si no se recibe valencia, se infiere desde la emocion actual.
420
+ """
421
+ if isinstance(valencia_actual, (int, float)):
422
+ if float(valencia_actual) > 0.5:
423
+ return "positivo"
424
+ if float(valencia_actual) < 0.5:
425
+ return "negativo"
426
+ return "neutro"
427
+
428
+ val = str(valencia_actual or "").strip().lower()
429
+ if val in {"positivo", "negativo", "neutro"}:
430
+ return val
431
+
432
+ emo = str(emotion_es or "").strip().lower()
433
+ if emo == "neutral":
434
+ return "neutro"
435
+ if emo in POSITIVE_EMOTIONS:
436
+ return "positivo"
437
+ return "negativo"
438
+
439
+
440
+ def _valencia_a_factor(valencia_normalizada: str | float | None) -> float:
441
+ """
442
+ Mapea valencia a factor numerico V en [0, 1].
443
+ negativo -> 0.0, neutro -> 0.5, positivo -> 1.0
444
+ """
445
+ if isinstance(valencia_normalizada, (int, float)):
446
+ return max(0.0, min(1.0, float(valencia_normalizada)))
447
+
448
+ if valencia_normalizada == "positivo":
449
+ return 1.0
450
+ if valencia_normalizada == "negativo":
451
+ return 0.0
452
+ return 0.5
453
+
454
+ def _calcular_pertenencia_zona_confort(peli: dict, zona_confort_ponderada: dict[str, float]) -> float:
455
+ """
456
+ Calcula un grado de pertenencia a la zona de confort basado en los generos de la pelicula y su peso en el perfil del usuario.
457
+ Se devuelve un valor entre 0 y 1, donde 1 significa que la pelicula es completamente dentro de la zona de confort (tiene generos muy preferidos) y 0 significa que esta completamente fuera (no tiene generos preferidos).
458
+ """
459
+ generos = _obtener_generos_pelicula(peli)
460
+ if not generos or not zona_confort_ponderada:
461
+ return 0.0
462
+
463
+ # Se calcula el promedio del peso de los generos de la pelicula segun el perfil del usuario
464
+ pesos = [zona_confort_ponderada.get(g, 0.0) for g in generos]
465
+ if not pesos:
466
+ return 0.0
467
+
468
+ return sum(pesos) / len(pesos)
469
 
470
  def recomendar_peliculas(
471
  emotion_es: str,
 
476
  estrategia_recomendacion: str,
477
  arousal_actual: float = 0.5,
478
  historico_arousal: list[float] | None = None,
479
+ valencia_actual: str | float | None = None,
480
  ) -> list[dict]:
481
  """
482
  Funcion principal que recomienda peliculas a un usuario dado su estado emocional y su historial de visualizacion.
 
615
 
616
 
617
  case "v3":
618
+ # A = percentil de arousal anclado a calma 0.
619
+ a = _percentil_desde_cero(arousal_actual, historico_arousal)
620
+ # V = valencia numerica: negativo=0, neutro=0.5, positivo=1.
621
+ # Si llega continua, se usa directamente; si llega categorica, se normaliza.
622
+ if isinstance(valencia_actual, (int, float)):
623
+ v = _valencia_a_factor(valencia_actual)
624
+ else:
625
+ valencia_normalizada = _normalizar_valencia_actual(valencia_actual, emotion_es)
626
+ v = _valencia_a_factor(valencia_normalizada)
627
+
628
+ # Formula propuesta: Cd = 0.5 + A * (0.5 - V)
629
+ target_confort = 0.5 + a * (0.5 - v)
630
+ target_confort = max(0.0, min(1.0, target_confort))
631
+
632
+ zona_confort_ponderada: dict[str, float] = probabilidades_generos
633
+
634
+ def ranking_v3(peli):
635
+ # A. Grado de confort real de la película (0.0 a 1.0)
636
+ # Basado en la probabilidad de los géneros en el historial
637
+ confort_real = _calcular_pertenencia_zona_confort(peli, zona_confort_ponderada)
638
+
639
+ # B. Distancia al Target (Diferencia absoluta)
640
+ # Queremos que esta distancia sea mínima
641
+ distancia_al_target = abs(confort_real - target_confort)
642
+
643
+ # C. Factores secundarios
644
+ emo_score = _score_emo_peli(peli, emotional_scores)
645
+ calidad = _puntuacion_calidad_global(peli, media_global_ratings)
646
+
647
+ # Score Final: Penalizamos la distancia al target y sumamos calidad/emoción
648
+ # Usamos 1 - distancia para que "menor distancia" sume más
649
+ return (1 - distancia_al_target) * 0.5 + (emo_score * 0.3) + (calidad * 0.2)
650
+
651
+ ranked = sorted(peliculas_candidatas, key=ranking_v3, reverse=True)
652
+ return ranked[:limit]
653
 
654
  case _:
655
  # Se ordenan por calidad global
readme.txt CHANGED
@@ -38,4 +38,9 @@ e esto axuda a definir a zona de confort como os generos mais vistos que supoña
38
  -v2, definense en emotion_service duas funcions (unha para calcular o arousal e outra para pasar a percentil)
39
  - calculo arousal usase uns pesos predeterminados por emocion, calcula a polaridad do texto usando o que nos devolve a ia e se normaliza
40
  - ese arousal pasase a un percentil, permitindo saber se a polaridad detectada é habitual no usuario ou non
41
- - ahora a maior arousal maior se busca sair da zona de confort se a emocion e positiva (definese unha funcion de forma que o arousal fai que pondere ms) e ao reves se e negativa
 
 
 
 
 
 
38
  -v2, definense en emotion_service duas funcions (unha para calcular o arousal e outra para pasar a percentil)
39
  - calculo arousal usase uns pesos predeterminados por emocion, calcula a polaridad do texto usando o que nos devolve a ia e se normaliza
40
  - ese arousal pasase a un percentil, permitindo saber se a polaridad detectada é habitual no usuario ou non
41
+ - ahora a maior arousal maior se busca sair da zona de confort se a emocion e positiva (definese unha funcion de forma que o arousal fai que pondere ms) e ao reves se e negativa
42
+
43
+ -v3, pasamos de ver a lista de confort como un array de generos, ahora para cada pelicula hai un indice de confort entre 0 e 1.
44
+ - con percentil desde cero, aseguramos q nn se sempre se esta estresado nn se teña en conta
45
+ - se e positiva ou negativa, en funcion do arousal buscase un target de indice de confort a buscar nas peliculas
46
+ - calculase o score como a distancia entre o target e o indice de confort da pelicula