Skip to content

Commit

Permalink
add track stats
Browse files Browse the repository at this point in the history
  • Loading branch information
codez committed Aug 19, 2019
1 parent e263eef commit 6698733
Show file tree
Hide file tree
Showing 6 changed files with 379 additions and 16 deletions.
163 changes: 163 additions & 0 deletions app/services/stats.rb
@@ -0,0 +1,163 @@
class Stats

attr_reader :date_range

class << self
def for(year, month = nil)
new(Date.new(year, month || 1, 1)..Date.new(year, month || 12, -1))
end
end

def initialize(date_range)
@date_range = date_range
end

def tracks
Track.within(date_range.first, date_range.last).joins(:broadcast)
end

def broadcasts
Broadcast.within(date_range.first, date_range.last)
end

def track_durations
@track_durations ||=
sort_counts(tracks.group(:show_id).sum(duration_in_seconds('tracks')))
end

def broadcast_durations
@broadcast_durations ||=
sort_counts(broadcasts.group(:show_id).sum(duration_in_seconds('broadcasts')))
end

def broadcast_counts
sort_counts(broadcasts.group(:show_id).count)
end

def overall_broadcast_duration
broadcast_durations.values.sum
end

def track_ratios
sort_counts(
track_durations.each_with_object({}) do |(show_id, value), hash|
hash[show_id] = value / broadcast_durations[show_id].to_f
end
)
end

def overall_track_ratio
track_durations.values.sum / overall_broadcast_duration.to_f
end

def track_counts
@track_counts ||=
track_property_counts(tracks.group(:show_id, :artist, :title)) do |values|
[values.first, values[1..2]]
end
end

def overall_track_counts
@overall_track_counts ||= overall_counts(track_counts)
end

def uniq_tracks
sort_counts(track_counts.transform_values(&:size))
end

def overall_uniq_tracks
overall_track_counts.size
end

def artist_combo_counts
@artist_combo_counts ||=
track_property_counts(tracks.group(:show_id, :artist)) do |values|
[values.first, values.last]
end
end

def overall_artist_combo_counts
@overall_artist_combo_counts ||= overall_counts(artist_combo_counts)
end

def uniq_artist_combos
sort_counts(artist_combo_counts.transform_values(&:size))
end

def overall_uniq_artist_combos
overall_artist_combo_counts.size
end

def single_artist_counts
artist_combo_counts.transform_values do |subhash|
sort_counts(
subhash.each_with_object(Hash.new(0)) do |(combo, count), hash|
combo.split(',').map(&:strip).each do |artist|
hash[artist] += count
end
end
)
end
end

def overall_single_artist_counts
@overall_single_artist_counts ||= overall_counts(single_artist_counts)
end

def uniq_single_artists
sort_counts(single_artist_counts.transform_values(&:size))
end

def overall_uniq_single_artists
overall_single_artist_counts.size
end

# Aggregate uniq_tracks, uniq_artist_combos and uniq_single_artists
# per hour of broadcasts duration
def per_hour(show_counts)
sort_counts(
show_counts.each_with_object({}) do |(show_id, count), hash|
hash[show_id] = count / (broadcast_durations[show_id] / 1.hour.to_f)
end
)
end

private

def track_property_counts(scope)
hash = Hash.new { |h, k| h[k] = {} }
scope.count.each do |values, count|
show_id, key = yield values
hash[show_id][key] = count
end
hash.transform_values { |subhash| sort_counts(subhash) }
end

def overall_counts(counts)
sort_counts(
counts.values.each_with_object(Hash.new(0)) do |subhash, hash|
subhash.each { |key, count| hash[key] += count }
end
)
end

def sort_counts(counts)
Hash[counts.sort_by { |key, value| [-value, key] }]
end

def duration_in_seconds(table)
case db_adapter
when /postgres/
"extract(epoch from (#{table}.finished_at - #{table}.started_at))"
when /sqlite/
"strftime('%s', #{table}.finished_at) - strftime('%s', #{table}.started_at)"
else
raise "Unsupported DB adapter #{db_adapter}"
end
end

def db_adapter
Track.connection.adapter_name.downcase
end

end
2 changes: 1 addition & 1 deletion test/controllers/broadcasts_controller_test.rb
Expand Up @@ -43,7 +43,7 @@ class BroadcastsControllerTest < ActionController::TestCase
test 'GET index with search param finds tracks' do
broadcasts(:klangbecken_mai1).update(label: 'Klangbecken Mai')
get :index, params: { q: 'loco' }
assert_equal ['Klangbecken Mai', 'G9S Shizzle Edition'], json_attrs(:label)
assert_equal ['Klangbecken Mai', 'G9S Shizzle Edition', "G9S Shizzle Edition II"], json_attrs(:label)
end

test 'GET index with day time range returns filtered list' do
Expand Down
17 changes: 10 additions & 7 deletions test/controllers/tracks_controller_test.rb
Expand Up @@ -9,17 +9,20 @@ class TracksControllerTest < ActionController::TestCase

test 'GET index returns list of all tracks of the given show' do
get :index, params: { show_id: shows(:g9s).id }
assert_equal ['Jay-Z', 'Chocolococolo', 'Göldin & Bit-Tuner'], json_attrs(:artist)
assert_equal ['Jay-Z', 'Chocolococolo', 'Göldin, Bit-Tuner', 'Bit-Tuner', 'Chocolococolo'],
json_attrs(:artist)
end

test 'GET index returns list of all tracks of the given show, respecting descending sort order' do
get :index, params: { show_id: shows(:g9s).id, sort: '-started_at' }
assert_equal ['Göldin & Bit-Tuner', 'Chocolococolo', 'Jay-Z'], json_attrs(:artist)
assert_equal ['Chocolococolo', 'Bit-Tuner', 'Göldin, Bit-Tuner', 'Chocolococolo', 'Jay-Z'],
json_attrs(:artist)
end

test 'GET index returns list of all tracks of the given show, respecting ascending sort order' do
get :index, params: { show_id: shows(:g9s).id, sort: 'artist' }
assert_equal ['Chocolococolo', 'Göldin & Bit-Tuner', 'Jay-Z'], json_attrs(:artist)
assert_equal ['Bit-Tuner', 'Chocolococolo', 'Chocolococolo', 'Göldin, Bit-Tuner', 'Jay-Z'],
json_attrs(:artist)
end

test 'GET index returns bad request if sort is invalid' do
Expand All @@ -34,17 +37,17 @@ class TracksControllerTest < ActionController::TestCase

test 'GET index with search param returns filtered list' do
get :index, params: { show_id: shows(:g9s).id, q: 'loco' }
assert_equal ['Chocolococolo'], json_attrs(:artist)
assert_equal ['Chocolococolo', 'Chocolococolo'], json_attrs(:artist)
end

test 'GET index without show with search param returns filtered list' do
get :index, params: { q: 'loco' }
assert_equal ['Shakira', 'Chocolococolo'], json_attrs(:artist)
assert_equal ['Shakira', 'Chocolococolo', 'Chocolococolo'], json_attrs(:artist)
end

test 'GET index with day time range returns filtered list' do
get :index, params: { year: 2013, month: 5, day: 20 }
assert_equal ['Shakira', 'Jay-Z', 'Chocolococolo'], json_attrs(:artist)
assert_equal ['Shakira', 'Jay-Z', 'Jay-Z', 'Chocolococolo'], json_attrs(:artist)
end

test 'GET index with hour time range returns filtered list' do
Expand Down Expand Up @@ -174,7 +177,7 @@ class TracksControllerTest < ActionController::TestCase
private

def entry
tracks(:choco)
tracks(:choco1)
end

end
32 changes: 30 additions & 2 deletions test/fixtures/tracks.yml
Expand Up @@ -17,7 +17,7 @@ jayz:
finished_at: <%= Time.zone.local(2013, 5, 20, 20, 04, 51).to_s(:db) %>
broadcast: g9s_mai

choco:
choco1:
title: Schwenger
artist: Chocolococolo
started_at: <%= Time.zone.local(2013, 5, 20, 20, 10, 44).to_s(:db) %>
Expand All @@ -26,14 +26,42 @@ choco:

goeldin:
title: Liebi i Zyte vom kommende Ufstand
artist: "Göldin & Bit-Tuner"
artist: "Göldin, Bit-Tuner"
started_at: <%= Time.zone.local(2013, 6, 12, 20, 36, 21).to_s(:db) %>
finished_at: <%= Time.zone.local(2013, 6, 12, 20, 39, 9).to_s(:db) %>
broadcast: g9s_juni

bit:
title: Sacre du printemps
artist: "Bit-Tuner"
started_at: <%= Time.zone.local(2013, 6, 12, 20, 39, 21).to_s(:db) %>
finished_at: <%= Time.zone.local(2013, 6, 12, 20, 54, 59).to_s(:db) %>
broadcast: g9s_juni

choco2:
title: Schwenger
artist: Chocolococolo
started_at: <%= Time.zone.local(2013, 6, 12, 21, 10, 44).to_s(:db) %>
finished_at: <%= Time.zone.local(2013, 6, 12, 21, 13, 5).to_s(:db) %>
broadcast: g9s_juni

shakira:
title: Loco
artist: Shakira
started_at: <%= Time.zone.local(2013, 5, 20, 11, 45, 13).to_s(:db) %>
finished_at: <%= Time.zone.local(2013, 5, 20, 11, 48).to_s(:db) %>
broadcast: klangbecken_mai1

jayz_klangbecken_1:
title: "99 problems"
artist: "Jay-Z"
started_at: <%= Time.zone.local(2013, 5, 20, 13, 24, 1).to_s(:db) %>
finished_at: <%= Time.zone.local(2013, 5, 20, 13, 27, 39).to_s(:db) %>
broadcast: klangbecken_mai1

jayz_klangbecken_2:
title: "101 problems"
artist: "Jay-Z"
started_at: <%= Time.zone.local(2013, 5, 21, 3, 39, 13).to_s(:db) %>
finished_at: <%= Time.zone.local(2013, 5, 21, 3, 43, 4).to_s(:db) %>
broadcast: klangbecken_mai2
12 changes: 6 additions & 6 deletions test/models/track_test.rb
Expand Up @@ -21,27 +21,27 @@ class TrackTest < ActiveSupport::TestCase
end

test 'duration is in seconds' do
assert_equal 141, tracks(:choco).duration
assert_equal 141, tracks(:choco1).duration
end

test '.within contains started at and finished_at' do
t = tracks(:choco)
t = tracks(:choco1)
assert_equal [t], Track.within(t.started_at, t.finished_at)
end

test '.within contains the broadcast around the duration' do
t = tracks(:choco)
t = tracks(:choco1)
assert_equal [t], Track.within(t.started_at + 1.minute, t.finished_at - 1.minute)
end

test '.within contains all broadcasts around the duration' do
t = tracks(:choco)
assert_equal tracks(:jayz, :choco),
t = tracks(:choco1)
assert_equal tracks(:jayz, :choco1),
Track.within(t.started_at - 10.minute, t.finished_at + 10.minute).list
end

test '.for_show contains all tracks for show' do
assert_equal tracks(:jayz, :choco, :goeldin),
assert_equal tracks(:jayz, :choco1, :goeldin, :bit, :choco2),
Track.for_show(shows(:g9s).id).list
end

Expand Down

0 comments on commit 6698733

Please sign in to comment.