mirror of
https://github.com/bnair123/MusicAnalyser.git
synced 2026-02-25 11:46:07 +00:00
Add skip tracking, compressed heatmap, listening log, docs, tests, and OpenAI support
Major changes: - Add skip tracking: poll currently-playing every 15s, detect skips (<30s listened) - Add listening-log and sessions API endpoints - Fix ReccoBeats client to extract spotify_id from href response - Compress heatmap from 24 hours to 6 x 4-hour blocks - Add OpenAI support in narrative service (use max_completion_tokens for new models) - Add ListeningLog component with timeline and list views - Update all frontend components to use real data (album art, play counts) - Add docker-compose external network (dockernet) support - Add comprehensive documentation (API, DATA_MODEL, ARCHITECTURE, FRONTEND) - Add unit tests for ingest and API endpoints
This commit is contained in:
@@ -1,35 +1,50 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, JSON, ForeignKey, Float, Table, Text
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
Column,
|
||||
Integer,
|
||||
String,
|
||||
DateTime,
|
||||
JSON,
|
||||
ForeignKey,
|
||||
Float,
|
||||
Table,
|
||||
Text,
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
from .database import Base
|
||||
|
||||
# Association Table for Many-to-Many Relationship between Track and Artist
|
||||
track_artists = Table(
|
||||
'track_artists',
|
||||
"track_artists",
|
||||
Base.metadata,
|
||||
Column('track_id', String, ForeignKey('tracks.id'), primary_key=True),
|
||||
Column('artist_id', String, ForeignKey('artists.id'), primary_key=True)
|
||||
Column("track_id", String, ForeignKey("tracks.id"), primary_key=True),
|
||||
Column("artist_id", String, ForeignKey("artists.id"), primary_key=True),
|
||||
)
|
||||
|
||||
|
||||
class Artist(Base):
|
||||
__tablename__ = "artists"
|
||||
|
||||
id = Column(String, primary_key=True, index=True) # Spotify ID
|
||||
id = Column(String, primary_key=True, index=True) # Spotify ID
|
||||
name = Column(String)
|
||||
genres = Column(JSON, nullable=True) # List of genre strings
|
||||
image_url = Column(String, nullable=True) # Artist profile image
|
||||
genres = Column(JSON, nullable=True) # List of genre strings
|
||||
image_url = Column(String, nullable=True) # Artist profile image
|
||||
|
||||
# Relationships
|
||||
tracks = relationship("Track", secondary=track_artists, back_populates="artists")
|
||||
|
||||
|
||||
class Track(Base):
|
||||
__tablename__ = "tracks"
|
||||
|
||||
id = Column(String, primary_key=True, index=True) # Spotify ID
|
||||
id = Column(String, primary_key=True, index=True) # Spotify ID
|
||||
name = Column(String)
|
||||
artist = Column(String) # Display string (e.g. "Drake, Future") - kept for convenience
|
||||
artist = Column(
|
||||
String
|
||||
) # Display string (e.g. "Drake, Future") - kept for convenience
|
||||
album = Column(String)
|
||||
image_url = Column(String, nullable=True) # Album art
|
||||
image_url = Column(String, nullable=True) # Album art
|
||||
duration_ms = Column(Integer)
|
||||
popularity = Column(Integer, nullable=True)
|
||||
|
||||
@@ -55,7 +70,7 @@ class Track(Base):
|
||||
genres = Column(JSON, nullable=True)
|
||||
|
||||
# AI Analysis fields
|
||||
lyrics = Column(Text, nullable=True) # Full lyrics from Genius
|
||||
lyrics = Column(Text, nullable=True) # Full lyrics from Genius
|
||||
lyrics_summary = Column(String, nullable=True)
|
||||
genre_tags = Column(String, nullable=True)
|
||||
|
||||
@@ -71,11 +86,13 @@ class PlayHistory(Base):
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
track_id = Column(String, ForeignKey("tracks.id"))
|
||||
played_at = Column(DateTime, index=True) # The timestamp from Spotify
|
||||
|
||||
# Context (album, playlist, etc.)
|
||||
played_at = Column(DateTime, index=True)
|
||||
context_uri = Column(String, nullable=True)
|
||||
|
||||
listened_ms = Column(Integer, nullable=True)
|
||||
skipped = Column(Boolean, nullable=True)
|
||||
source = Column(String, nullable=True)
|
||||
|
||||
track = relationship("Track", back_populates="plays")
|
||||
|
||||
|
||||
@@ -84,16 +101,19 @@ class AnalysisSnapshot(Base):
|
||||
Stores the computed statistics and LLM analysis for a given period.
|
||||
Allows for trend analysis over time.
|
||||
"""
|
||||
|
||||
__tablename__ = "analysis_snapshots"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
date = Column(DateTime, default=datetime.utcnow, index=True) # When the analysis was run
|
||||
date = Column(
|
||||
DateTime, default=datetime.utcnow, index=True
|
||||
) # When the analysis was run
|
||||
period_start = Column(DateTime)
|
||||
period_end = Column(DateTime)
|
||||
period_label = Column(String) # e.g., "last_30_days", "monthly_nov_2023"
|
||||
period_label = Column(String) # e.g., "last_30_days", "monthly_nov_2023"
|
||||
|
||||
# The heavy lifting: stored as JSON blobs
|
||||
metrics_payload = Column(JSON) # The input to the LLM (StatsService output)
|
||||
narrative_report = Column(JSON) # The output from the LLM (NarrativeService output)
|
||||
metrics_payload = Column(JSON) # The input to the LLM (StatsService output)
|
||||
narrative_report = Column(JSON) # The output from the LLM (NarrativeService output)
|
||||
|
||||
model_used = Column(String, nullable=True) # e.g. "gemini-1.5-flash"
|
||||
model_used = Column(String, nullable=True) # e.g. "gemini-1.5-flash"
|
||||
|
||||
Reference in New Issue
Block a user