Admin analytics: heatmap per device, anteprima staff e analisi costi.
Separa heatmap mobile/desktop, opt-out analytics per operatori, dashboard costi con KPI e trend mensili. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Dati simulati per la pagina Admin → Costi (trend 12 mesi, società, revenue).
|
||||
# Uso: bundle exec rails runner db/seeds/cost_analytics_demo.rb
|
||||
|
||||
load Rails.root.join("db/seeds/plans.rb")
|
||||
|
||||
coach = User.find_or_create_by!(email: "coach@matchlivetv.test") do |u|
|
||||
u.name = "Coach Demo"
|
||||
u.password = "Password123"
|
||||
u.role = "coach"
|
||||
end
|
||||
|
||||
clubs_data = [
|
||||
{ name: "Tigers Volley", sport: "volleyball", plan: "premium_full", sessions: 14, hours: 18.5, revenue_yearly: 19900 },
|
||||
{ name: "ASD Eagles Milano", sport: "volleyball", plan: "premium_light", sessions: 9, hours: 11.0, revenue_yearly: 5900 },
|
||||
{ name: "Volley Stars Roma", sport: "volleyball", plan: "premium_full", sessions: 6, hours: 8.0, revenue_yearly: 19900 },
|
||||
{ name: "Basket Juventus U18", sport: "basketball", plan: "premium_light", sessions: 4, hours: 5.5, revenue_yearly: 5900 },
|
||||
{ name: "Padova Beach", sport: "volleyball", plan: "free", sessions: 2, hours: 2.0, revenue_yearly: 0 }
|
||||
]
|
||||
|
||||
sport_keys = {
|
||||
"volleyball" => "pallavolo",
|
||||
"basketball" => "basket"
|
||||
}
|
||||
|
||||
clubs = clubs_data.map do |row|
|
||||
club = Club.find_or_create_by!(name: row[:name]) { |c| c.sport = row[:sport] }
|
||||
Billing::AssignPlan.call(club: club, plan_slug: row[:plan])
|
||||
ClubMembership.find_or_create_by!(user: coach, club: club) { |m| m.role = "owner" }
|
||||
team = club.teams.find_or_create_by!(name: "Prima squadra") do |t|
|
||||
t.sport_key = sport_keys.fetch(row[:sport], "pallavolo")
|
||||
end
|
||||
{ club: club, team: team, **row }
|
||||
end
|
||||
|
||||
month_costs = [
|
||||
{ offset: 11, hetzner: 89.00, stream: 12.50, s3: 8.20 },
|
||||
{ offset: 10, hetzner: 89.00, stream: 18.00, s3: 9.10 },
|
||||
{ offset: 9, hetzner: 89.00, stream: 24.50, s3: 10.40 },
|
||||
{ offset: 8, hetzner: 89.00, stream: 31.00, s3: 11.80 },
|
||||
{ offset: 7, hetzner: 89.00, stream: 22.00, s3: 12.20 },
|
||||
{ offset: 6, hetzner: 89.00, stream: 15.50, s3: 13.50 },
|
||||
{ offset: 5, hetzner: 89.00, stream: 28.00, s3: 14.80 },
|
||||
{ offset: 4, hetzner: 89.00, stream: 35.50, s3: 15.60 },
|
||||
{ offset: 3, hetzner: 89.00, stream: 42.00, s3: 16.90 },
|
||||
{ offset: 2, hetzner: 89.00, stream: 38.00, s3: 18.20 },
|
||||
{ offset: 1, hetzner: 89.00, stream: 45.00, s3: 19.50 },
|
||||
{ offset: 0, hetzner: 89.00, stream: 52.00, s3: 21.00 }
|
||||
]
|
||||
|
||||
PlatformCostEntry.delete_all
|
||||
|
||||
month_costs.each do |row|
|
||||
month = Time.zone.today.beginning_of_month - row[:offset].months
|
||||
[
|
||||
{ label: "Hetzner home + backup", amount: row[:hetzner] },
|
||||
{ label: "Nodi stream CPX (overflow)", amount: row[:stream] },
|
||||
{ label: "Garage/S3 replay storage", amount: row[:s3] }
|
||||
].each do |entry|
|
||||
PlatformCostEntry.create!(
|
||||
month: month,
|
||||
label: entry[:label],
|
||||
amount_cents: (entry[:amount] * 100).round,
|
||||
notes: "Dato demo seed"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Sessioni e pagamenti nel mese corrente (distribuiti tra società)
|
||||
current_month = Time.zone.today.beginning_of_month
|
||||
clubs.each_with_index do |row, index|
|
||||
club = row[:club]
|
||||
team = row[:team]
|
||||
sessions_count = row[:sessions]
|
||||
total_secs = (row[:hours] * 3600).round
|
||||
|
||||
sessions_count.times do |n|
|
||||
secs = (total_secs / sessions_count.to_f).round
|
||||
day = current_month + (n % 27).days
|
||||
started = day.change(hour: 10 + (n % 6), min: 0)
|
||||
ended = started + secs.seconds
|
||||
match = team.matches.create!(
|
||||
opponent_name: "Avversario demo #{n + 1}",
|
||||
sport_key: team.sport_key,
|
||||
scheduled_at: started
|
||||
)
|
||||
StreamSession.create!(
|
||||
match: match,
|
||||
user: coach,
|
||||
platform: "matchlivetv",
|
||||
status: "ended",
|
||||
started_at: started,
|
||||
ended_at: ended,
|
||||
total_duration_secs: secs
|
||||
)
|
||||
end
|
||||
|
||||
if row[:revenue_yearly].positive?
|
||||
payment = Billing::Payment.find_or_initialize_by(club: club, paid_at: current_month + (index + 3).days)
|
||||
payment.assign_attributes(
|
||||
status: "paid",
|
||||
provider: "stripe",
|
||||
amount_cents: row[:revenue_yearly],
|
||||
currency: "EUR",
|
||||
plan_slug: row[:plan]
|
||||
)
|
||||
payment.save!
|
||||
end
|
||||
|
||||
# Storage simulato (cumulativo)
|
||||
sample_session = team.matches.last&.stream_sessions&.first
|
||||
next unless sample_session
|
||||
|
||||
rec = Recording.find_or_initialize_by(stream_session: sample_session)
|
||||
rec.assign_attributes(
|
||||
team: team,
|
||||
status: "ready",
|
||||
privacy_status: "unlisted",
|
||||
storage_backend: "s3",
|
||||
storage_policy: "retained",
|
||||
byte_size: (500_000_000 + index * 180_000_000),
|
||||
duration_secs: 3600
|
||||
)
|
||||
rec.save!
|
||||
end
|
||||
|
||||
puts "Cost analytics demo OK:"
|
||||
puts " Voci costo: #{PlatformCostEntry.count} (12 mesi)"
|
||||
puts " Sessioni demo mese corrente: #{StreamSession.where(status: 'ended', ended_at: current_month..current_month.end_of_month).count}"
|
||||
puts " Admin: http://localhost:3000/admin/costs"
|
||||
puts " Login admin: admin / AdminPass123"
|
||||
Reference in New Issue
Block a user