Skip to content

Commit 0247b38

Browse files
committed
clustering Sonar cube review
1 parent 87dd756 commit 0247b38

2 files changed

Lines changed: 79 additions & 49 deletions

File tree

tasks/clustering.py

Lines changed: 19 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
from .clustering_helper import (
3434
_get_stratified_song_subset,
3535
get_job_result_safely,
36-
_perform_single_clustering_iteration
36+
_perform_single_clustering_iteration,
37+
_shuffle_playlist_songs,
38+
_assign_playlist_chunks,
39+
_try_ai_name_playlist,
3740
)
3841
# Import post-processing functions from dedicated module
3942
from .clustering_postprocessing import (
@@ -910,37 +913,29 @@ def _name_and_prepare_playlists(best_result, ai_provider, ollama_url, ollama_mod
910913
Returns a dictionary mapping final playlist names to lists of song tuples (id, title, author).
911914
"""
912915
final_playlists = {}
913-
centroids = best_result.get("playlist_centroids", {})
914916
named_playlists = best_result.get("named_playlists", {})
915917
max_songs = best_result.get("parameters", {}).get("max_songs_per_cluster", MAX_SONGS_PER_CLUSTER)
916918

917919
for original_name, songs in named_playlists.items():
918920
if not songs:
919921
continue
920922

921-
final_name = original_name
922-
if ai_provider in ["OLLAMA", "OPENAI", "GEMINI", "MISTRAL"]:
923+
if ai_provider in ("OLLAMA", "OPENAI", "GEMINI", "MISTRAL"):
923924
try:
924-
925-
ai_config = {
926-
'provider': ai_provider,
927-
'ollama_url': ollama_url, 'ollama_model': ollama_model,
928-
'openai_url': openai_url, 'openai_model': openai_model, 'openai_key': openai_key,
929-
'gemini_key': gemini_key, 'gemini_model': gemini_model,
930-
'mistral_key': mistral_key, 'mistral_model': mistral_model,
931-
}
932-
ai_name = get_ai_playlist_name(
933-
creative_prompt_template,
934-
[{'title': s_title, 'author': s_author} for _, s_title, s_author in songs],
935-
centroids.get(original_name, {}),
936-
ai_config,
925+
final_name = _try_ai_name_playlist(
926+
original_name, songs,
927+
best_result.get("playlist_centroids", {}),
928+
ai_provider,
929+
ollama_url, ollama_model,
930+
openai_url, openai_model, openai_key,
931+
gemini_key, gemini_model,
932+
mistral_key, mistral_model,
937933
)
938-
if ai_name and "Error" not in ai_name:
939-
final_name = ai_name.strip().replace("\n", " ")
940-
else:
941-
logger.warning(f"AI naming failed for '{original_name}': {ai_name}. Using original name.")
942934
except Exception as e:
943935
logger.warning(f"AI naming failed for '{original_name}': {e}. Using original name.")
936+
final_name = original_name
937+
else:
938+
final_name = original_name
944939

945940
# Ensure unique names
946941
temp_name = final_name
@@ -950,34 +945,9 @@ def _name_and_prepare_playlists(best_result, ai_provider, ollama_url, ollama_mod
950945
temp_name = f"{final_name} ({suffix})"
951946
final_name = temp_name
952947

953-
# Add suffix and handle chunking
954-
base_name_with_suffix = f"{final_name}_automatic"
955-
956-
# The 'songs' variable is already the list of tuples: [(item_id, title, author), ...]
957-
# *** FINAL SAFETY SHUFFLE: Ensure songs are randomized in final playlists ***
958-
final_songs = songs.copy()
959-
n = len(final_songs)
960-
961-
if n > 1:
962-
# FISHER-YATES MANUAL SHUFFLE - GUARANTEED TO RANDOMIZE
963-
current_time_seed = int(time.time() * 1000000) % 1000000
964-
965-
for i in range(n - 1, 0, -1):
966-
j = (random.randint(0, i) + current_time_seed + i * 7) % (i + 1)
967-
final_songs[i], final_songs[j] = final_songs[j], final_songs[i]
968-
current_time_seed = (current_time_seed * 1103515245 + 12345) % (2**31)
969-
970-
logger.info(f"FINAL FISHER-YATES SHUFFLE applied to '{base_name_with_suffix}': {len(final_songs)} songs")
971-
logger.info(f"FINAL ORDER: First song = '{final_songs[0][1]}', Last song = '{final_songs[-1][1]}'")
972-
else:
973-
logger.info(f"FINAL: '{base_name_with_suffix}' has only {n} songs - no shuffling needed")
974-
975-
if max_songs > 0 and len(final_songs) > max_songs:
976-
chunks = [final_songs[i:i+max_songs] for i in range(0, len(final_songs), max_songs)]
977-
for idx, chunk in enumerate(chunks, 1):
978-
final_playlists[f"{base_name_with_suffix} ({idx})"] = chunk # Store the chunk of tuples
979-
else:
980-
final_playlists[base_name_with_suffix] = final_songs # Store the list of tuples
948+
base_name = f"{final_name}_automatic"
949+
shuffled = _shuffle_playlist_songs(songs, base_name)
950+
_assign_playlist_chunks(shuffled, max_songs, base_name, final_playlists)
981951

982952
return final_playlists
983953

tasks/clustering_helper.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import random
55
import logging
6+
import time
67
import numpy as np
78
from collections import defaultdict
89
# time, re, and cdist imports moved to clustering_postprocessing.py
@@ -38,6 +39,65 @@
3839
USE_GPU_CLUSTERING)
3940
from .commons import score_vector
4041

42+
# Import AI naming for playlist helpers
43+
from tasks.ai.api import get_ai_playlist_name
44+
from tasks.ai.prompts import creative_prompt_template
45+
46+
47+
# --- Playlist Naming & Shuffling Helpers ---
48+
49+
def _shuffle_playlist_songs(songs, playlist_name):
50+
"""Fisher-Yates shuffle a list of song tuples; log the result."""
51+
final_songs = songs.copy()
52+
n = len(final_songs)
53+
if n <= 1:
54+
logger.info(f"FINAL: '{playlist_name}' has only {n} songs - no shuffling needed")
55+
return final_songs
56+
57+
current_time_seed = int(time.time() * 1000000) % 1000000
58+
for i in range(n - 1, 0, -1):
59+
j = (random.randint(0, i) + current_time_seed + i * 7) % (i + 1)
60+
final_songs[i], final_songs[j] = final_songs[j], final_songs[i]
61+
current_time_seed = (current_time_seed * 1103515245 + 12345) % (2 ** 31)
62+
63+
logger.info(f"FINAL FISHER-YATES SHUFFLE applied to '{playlist_name}': {len(final_songs)} songs")
64+
logger.info(f"FINAL ORDER: First song = '{final_songs[0][1]}', Last song = '{final_songs[-1][1]}'")
65+
return final_songs
66+
67+
68+
def _assign_playlist_chunks(final_songs, max_songs, base_name, final_playlists):
69+
"""Chunk oversized playlists or store the list as-is."""
70+
if max_songs > 0 and len(final_songs) > max_songs:
71+
chunks = [final_songs[i:i + max_songs] for i in range(0, len(final_songs), max_songs)]
72+
for idx, chunk in enumerate(chunks, 1):
73+
final_playlists[f"{base_name} ({idx})"] = chunk
74+
else:
75+
final_playlists[base_name] = final_songs
76+
77+
78+
def _try_ai_name_playlist(original_name, songs, centroids, ai_provider,
79+
ollama_url, ollama_model, openai_url, openai_model, openai_key,
80+
gemini_key, gemini_model, mistral_key, mistral_model):
81+
"""Attempt AI naming; return the original name on failure."""
82+
ai_config = {
83+
'provider': ai_provider,
84+
'ollama_url': ollama_url, 'ollama_model': ollama_model,
85+
'openai_url': openai_url, 'openai_model': openai_model, 'openai_key': openai_key,
86+
'gemini_key': gemini_key, 'gemini_model': gemini_model,
87+
'mistral_key': mistral_key, 'mistral_model': mistral_model,
88+
}
89+
ai_name = get_ai_playlist_name(
90+
creative_prompt_template,
91+
[{'title': s_title, 'author': s_author} for _, s_title, s_author in songs],
92+
centroids.get(original_name, {}),
93+
ai_config,
94+
)
95+
if ai_name and "Error" not in ai_name:
96+
return ai_name.strip().replace("\n", " ")
97+
logger.warning(f"AI naming failed for '{original_name}': {ai_name}. Using original name.")
98+
return original_name
99+
100+
41101
# --- Main Orchestrator for a Single Iteration ---
42102

43103
def _perform_single_clustering_iteration(

0 commit comments

Comments
 (0)