Skip to content

Commit

Permalink
add csv generation for stats
Browse files Browse the repository at this point in the history
  • Loading branch information
codez committed Sep 2, 2019
1 parent c19355e commit 4586cf1
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 80 deletions.
55 changes: 55 additions & 0 deletions app/controllers/admin/stats_controller.rb
@@ -0,0 +1,55 @@
module Admin
class StatsController < ApplicationController

include Admin::Authenticatable

swagger_path '/admin/stats/{year}/{month}' do
operation :get do
key :description, 'Returns a CSV with various show and track statistics.'
key :tags, [:stats, :admin]
key :produces, ['text/csv']

parameter name: :year,
in: :path,
description: 'The four-digit year to get the stats for.',
required: true,
type: :integer
parameter name: :month,
in: :path,
description: 'Optional two-digit month to get the stats for.',
required: true, # false, actually. Swagger path params must be required.
type: :integer

response 200 do
key :description,
'CSV with header row, overall row and one row for each show ' \
'that was broadcasted in the given period.'
schema do
key :type, :string
end
end

security jwt_token: []
end
end

def index
send_data Stats::Csv.new(stats).generate, filename: filename
end

private

def filename
if params[:month]
"stats_#{params[:year]}_#{params[:month].to_s.rjust(2, '0')}.csv"
else
"stats_#{params[:year]}.csv"
end
end

def stats
@stats ||= Stats.for(params[:year], params[:month])
end

end
end
1 change: 1 addition & 0 deletions app/controllers/apidocs_controller.rb
Expand Up @@ -17,6 +17,7 @@ class ApidocsController < ApplicationController
Admin::PlaybackFormatsController,
Admin::ProfilesController,
Admin::ShowsController,
Admin::StatsController,
Admin::UsersController,
Admin::Shows::MergeController,
# entities
Expand Down
6 changes: 3 additions & 3 deletions app/controllers/concerns/swaggerable.rb
Expand Up @@ -39,7 +39,7 @@ def parameter_attrs(model_name, action, data_class)
required: true do
schema do
property :data do
key '$ref', data_class
key '$ref', URI.encode_www_form_component(data_class)
end
end
end
Expand All @@ -50,7 +50,7 @@ def response_entity(data_class, status = 200)
key :description, 'successfull operation'
schema do
property :data do
key '$ref', data_class
key '$ref', URI.encode_www_form_component(data_class)
end
end
end
Expand All @@ -61,7 +61,7 @@ def response_entities(data_class, status = 200)
key :description, 'successfull operation'
schema do
property :data, type: :array do
items '$ref' => data_class
items '$ref' => URI.encode_www_form_component(data_class)
end
end
end
Expand Down
132 changes: 113 additions & 19 deletions app/services/stats.rb
Expand Up @@ -4,50 +4,56 @@ class Stats

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

def initialize(date_range)
@date_range = date_range
end

def tracks
Track.within(date_range.first, date_range.last).joins(:broadcast)
def shows
Show.where(id: broadcast_durations.keys)
end

def broadcasts
Broadcast.within(date_range.first, date_range.last)
def broadcast_counts
@broadcast_counts ||= sort_counts(broadcasts.group(:show_id).count)
end

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

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

def broadcast_counts
sort_counts(broadcasts.group(:show_id).count)
def overall_broadcast_duration
@overall_broadcast_duration ||= broadcast_durations.values.sum
end

def overall_broadcast_duration
broadcast_durations.values.sum
def broadcasts?
overall_broadcast_duration > 0
end

def tracks_durations
@tracks_durations ||= sum_hours_by_show(tracks, 'tracks')
end

def track_ratios
def overall_tracks_duration
tracks_durations.values.sum
end

def tracks_ratios
sort_counts(
track_durations.each_with_object({}) do |(show_id, value), hash|
tracks_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
def overall_tracks_ratio
broadcasts? ? (overall_tracks_duration / overall_broadcast_duration.to_f) : nil
end

def track_counts
Expand Down Expand Up @@ -117,13 +123,27 @@ def overall_uniq_single_artists
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)
hash[show_id] = count / broadcast_durations[show_id]
end
)
end

private

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 sum_hours_by_show(scope, table)
seconds = scope.group(:show_id).sum(duration_in_seconds(table))
hours = seconds.transform_values { |v| v / 1.hour.to_f }
sort_counts(hours)
end

def track_property_counts(scope)
hash = Hash.new { |h, k| h[k] = {} }
scope.count.each do |values, count|
Expand Down Expand Up @@ -160,4 +180,78 @@ def db_adapter
Track.connection.adapter_name.downcase
end

class Csv

delegate_missing_to :@stats

def initialize(stats)
@stats = stats
end

def generate
require 'csv'
CSV.generate do |csv|
csv << csv_headers
csv << overall_columns
shows.includes(:profile).list.each do |show|
csv << show_columns(show)
end
end
end

private

def csv_headers # rubocop:disable Metrics/MethodLength
[
'Show',
'Profile',
'Broadcast Count',
'Broadcast Duration',
'Tracks Duration',
'Tracks Ratio',
'Uniq Tracks Count',
'Unique Artist Combo Count',
'Unique Single Artist Count',
'Unique Tracks per hour',
'Unique Artist Combos per hour',
'Unique Single Artists per hour'
]
end

def overall_columns # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
[
'Overall',
nil,
overall_broadcast_count,
overall_broadcast_duration,
overall_tracks_duration,
overall_tracks_ratio,
overall_uniq_tracks,
overall_uniq_artist_combos,
overall_uniq_single_artists,
broadcasts? ? overall_uniq_tracks / overall_broadcast_duration : nil,
broadcasts? ? overall_uniq_artist_combos / overall_broadcast_duration : nil,
broadcasts? ? overall_uniq_single_artists / overall_broadcast_duration : nil
]
end

def show_columns(show) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
[
show.name,
show.profile.name,
broadcast_counts[show.id],
broadcast_durations[show.id],
tracks_durations[show.id],
tracks_ratios[show.id],
uniq_tracks[show.id],
uniq_artist_combos[show.id],
uniq_single_artists[show.id],
per_hour(uniq_tracks)[show.id],
per_hour(uniq_artist_combos)[show.id],
per_hour(uniq_single_artists)[show.id]
]
end

end

end
2 changes: 2 additions & 0 deletions config/routes.rb
Expand Up @@ -49,6 +49,8 @@
post 'merge/:target_id', to: 'shows/merge#create', on: :member
end

get 'stats/:year(/:month)', to: 'stats#index'

resources :users
end

Expand Down

0 comments on commit 4586cf1

Please sign in to comment.