Add per-song per-station playback counts to dashboard
This commit is contained in:
1 parent
fac4b3ebb8
commit
21d34b7562
3 files changed
+43
-5
No files matched your search
@@ -11,6 +11,7 @@ HISTORY_COLUMNS = {
|
|||||||
"duplicate_count": "INTEGER DEFAULT 0",
|
"duplicate_count": "INTEGER DEFAULT 0",
|
||||||
"skipped_count": "INTEGER DEFAULT 0",
|
"skipped_count": "INTEGER DEFAULT 0",
|
||||||
"station_breakdown": "TEXT DEFAULT '[]'",
|
"station_breakdown": "TEXT DEFAULT '[]'",
|
||||||
|
"scraped_songs": "TEXT DEFAULT '[]'",
|
||||||
"skipped_songs": "TEXT DEFAULT '[]'",
|
"skipped_songs": "TEXT DEFAULT '[]'",
|
||||||
"error_message": "TEXT DEFAULT ''",
|
"error_message": "TEXT DEFAULT ''",
|
||||||
}
|
}
|
||||||
@@ -37,6 +38,10 @@ def init_history_db(db_path=None):
|
|||||||
for column_name, definition in HISTORY_COLUMNS.items():
|
for column_name, definition in HISTORY_COLUMNS.items():
|
||||||
if column_name not in existing_columns:
|
if column_name not in existing_columns:
|
||||||
cursor.execute(f"ALTER TABLE history ADD COLUMN {column_name} {definition}")
|
cursor.execute(f"ALTER TABLE history ADD COLUMN {column_name} {definition}")
|
||||||
|
|
||||||
|
# store scraped song detail for per-song/per-station analysis
|
||||||
|
if 'scraped_songs' not in existing_columns:
|
||||||
|
cursor.execute("ALTER TABLE history ADD COLUMN scraped_songs TEXT DEFAULT '[]'")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -50,8 +55,8 @@ def save_history_entry(result, db_path=None):
|
|||||||
INSERT INTO history (
|
INSERT INTO history (
|
||||||
date, added_count, added_songs, missing_count, missing_songs,
|
date, added_count, added_songs, missing_count, missing_songs,
|
||||||
status, scraped_count, matched_count, duplicate_count, skipped_count,
|
status, scraped_count, matched_count, duplicate_count, skipped_count,
|
||||||
station_breakdown, skipped_songs, error_message
|
station_breakdown, scraped_songs, skipped_songs, error_message
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
datetime.now().isoformat(),
|
datetime.now().isoformat(),
|
||||||
@@ -65,6 +70,7 @@ def save_history_entry(result, db_path=None):
|
|||||||
result.get("duplicate_count", 0),
|
result.get("duplicate_count", 0),
|
||||||
result.get("skipped_count", 0),
|
result.get("skipped_count", 0),
|
||||||
json.dumps(result.get("station_breakdown", [])),
|
json.dumps(result.get("station_breakdown", [])),
|
||||||
|
json.dumps(result.get("scraped_songs", [])),
|
||||||
json.dumps(result.get("skipped_songs", [])),
|
json.dumps(result.get("skipped_songs", [])),
|
||||||
result.get("error_message", ""),
|
result.get("error_message", ""),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ def run_update_job(config=None, dry_run=False, persist_history=True, write_buy_l
|
|||||||
songs = scrape_result["songs"]
|
songs = scrape_result["songs"]
|
||||||
result["station_breakdown"] = scrape_result["station_results"]
|
result["station_breakdown"] = scrape_result["station_results"]
|
||||||
result["scraped_count"] = len(songs)
|
result["scraped_count"] = len(songs)
|
||||||
|
# Keep scraped songs for history analytics (song play counts per station)
|
||||||
|
result["scraped_songs"] = songs
|
||||||
|
|
||||||
try:
|
try:
|
||||||
plex = connect_to_plex_server(token, server_ip)
|
plex = connect_to_plex_server(token, server_ip)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ def load_recent_stats(db_path=None):
|
|||||||
"last_attempted": None,
|
"last_attempted": None,
|
||||||
"last_success": None,
|
"last_success": None,
|
||||||
"station_counts": {},
|
"station_counts": {},
|
||||||
|
"song_counts": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
if not Path(db_path).exists():
|
if not Path(db_path).exists():
|
||||||
@@ -52,16 +53,28 @@ def load_recent_stats(db_path=None):
|
|||||||
stats["last_attempted"] = last_row[0]
|
stats["last_attempted"] = last_row[0]
|
||||||
stats["last_success"] = last_row[1]
|
stats["last_success"] = last_row[1]
|
||||||
|
|
||||||
c.execute("SELECT station_breakdown FROM history")
|
c.execute("SELECT station_breakdown, scraped_songs FROM history")
|
||||||
station_rows = c.fetchall()
|
station_rows = c.fetchall()
|
||||||
for (station_json,) in station_rows:
|
for station_json, scraped_songs_json in station_rows:
|
||||||
try:
|
try:
|
||||||
for station in json.loads(station_json or "[]"):
|
for station in json.loads(station_json or "[]"):
|
||||||
if station.get("success"):
|
if station.get("success"):
|
||||||
key = station.get("display_name") or station.get("station") or "Unknown"
|
key = station.get("display_name") or station.get("station") or "Unknown"
|
||||||
stats["station_counts"][key] = stats["station_counts"].get(key, 0) + int(station.get("scraped_count", 0))
|
stats["station_counts"][key] = stats["station_counts"].get(key, 0) + int(station.get("scraped_count", 0))
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
songs = json.loads(scraped_songs_json or "[]")
|
||||||
|
for song in songs:
|
||||||
|
station_name = song.get("source", "Unknown")
|
||||||
|
title = song.get("title", "?")
|
||||||
|
artist = song.get("artist", "?")
|
||||||
|
key = f"{artist} - {title}"
|
||||||
|
stats["song_counts"].setdefault(station_name, {})
|
||||||
|
stats["song_counts"][station_name][key] = stats["song_counts"][station_name].get(key, 0) + 1
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -75,6 +88,14 @@ def render_dashboard_html(stats):
|
|||||||
)
|
)
|
||||||
overall_count = sum(stats["station_counts"].values())
|
overall_count = sum(stats["station_counts"].values())
|
||||||
|
|
||||||
|
# Build a flattened station-song ranking table for display
|
||||||
|
top_song_rows = []
|
||||||
|
for station_name, songs in sorted(stats.get("song_counts", {}).items()):
|
||||||
|
sorted_songs = sorted(songs.items(), key=lambda kv: kv[1], reverse=True)
|
||||||
|
for song_name, count in sorted_songs[:10]:
|
||||||
|
top_song_rows.append(f"<tr><td>{station_name}</td><td>{song_name}</td><td>{count}</td></tr>")
|
||||||
|
top_song_rows_html = ''.join(top_song_rows) or '<tr><td colspan="3">No song counts yet</td></tr>'
|
||||||
|
|
||||||
template = """
|
template = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@@ -123,6 +144,14 @@ def render_dashboard_html(stats):
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3>Top tracks per station (historical play counts)</h3>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Station</th><th>Song</th><th>Count</th></tr></thead>
|
||||||
|
<tbody><!--TOP_SONG_ROWS--></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
Last attempted run: <!--LAST_ATTEMPTED--><br>
|
Last attempted run: <!--LAST_ATTEMPTED--><br>
|
||||||
Last successful run: <!--LAST_SUCCESS-->
|
Last successful run: <!--LAST_SUCCESS-->
|
||||||
@@ -173,6 +202,7 @@ def render_dashboard_html(stats):
|
|||||||
.replace("<!--TOTAL_DUPLICATES-->", f"{stats['total_duplicates']:,}")
|
.replace("<!--TOTAL_DUPLICATES-->", f"{stats['total_duplicates']:,}")
|
||||||
.replace("<!--TOTAL_UPDATES-->", f"{stats['total_updates']:,}")
|
.replace("<!--TOTAL_UPDATES-->", f"{stats['total_updates']:,}")
|
||||||
.replace("<!--STATION_ROWS-->", station_rows or '<tr><td colspan="2">No station data yet</td></tr>')
|
.replace("<!--STATION_ROWS-->", station_rows or '<tr><td colspan="2">No station data yet</td></tr>')
|
||||||
|
.replace("<!--TOP_SONG_ROWS-->", top_song_rows_html)
|
||||||
.replace("<!--LAST_ATTEMPTED-->", stats['last_attempted'] or 'N/A')
|
.replace("<!--LAST_ATTEMPTED-->", stats['last_attempted'] or 'N/A')
|
||||||
.replace("<!--LAST_SUCCESS-->", stats['last_success'] or 'N/A')
|
.replace("<!--LAST_SUCCESS-->", stats['last_success'] or 'N/A')
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in new issue
Block a user