2f31d678d0
Adds two new features to collect and show useful analytics. Usage Statistics: * Begin tracking daily active users who log in and interact with major features of the website each day, such as the chat room, forum and gallery. Demographics page: * For marketing, the home page now shows live statistics about the breakdown of content (explicit vs. non-explicit) on the site, and the /insights page gives a lot more data in detail. * Show the percent split in photo gallery content and how many users opt-in or share explicit content on the site. * Show high-level demographics of the members (by age range, gender, orientation) Misc cleanup: * Rearrange model list in data export to match the auto-create statements. * In data exports, include the forum_memberships, push_notifications and usage_statistics tables.
196 lines
3.9 KiB
Go
196 lines
3.9 KiB
Go
// Package demographic handles periodic report pulling for high level website statistics.
|
|
//
|
|
// It powers the home page and insights page, where a prospective new user can get a peek inside
|
|
// the website to see the split between regular vs. explicit content and membership statistics.
|
|
//
|
|
// These database queries could get slow so the demographics are pulled and cached in this package.
|
|
package demographic
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sort"
|
|
"time"
|
|
|
|
"code.nonshy.com/nonshy/website/pkg/config"
|
|
)
|
|
|
|
// Demographic is the top level container struct with all the insights needed for front-end display.
|
|
type Demographic struct {
|
|
Computed bool
|
|
LastUpdated time.Time
|
|
Photo Photo
|
|
People People
|
|
}
|
|
|
|
// Photo statistics show the split between explicit and non-explicit content.
|
|
type Photo struct {
|
|
Total int64
|
|
NonExplicit int64
|
|
Explicit int64
|
|
}
|
|
|
|
// People statistics.
|
|
type People struct {
|
|
Total int64
|
|
ExplicitOptIn int64
|
|
ExplicitPhoto int64
|
|
ByAgeRange map[string]int64
|
|
ByGender map[string]int64
|
|
ByOrientation map[string]int64
|
|
}
|
|
|
|
// MemberDemographic of members.
|
|
type MemberDemographic struct {
|
|
Label string // e.g. age range "18-25" or gender
|
|
Count int64
|
|
Percent int
|
|
}
|
|
|
|
/**
|
|
* Dynamic calculation methods on the above types (percentages, etc.)
|
|
*/
|
|
|
|
func (d Demographic) PrettyPrint() string {
|
|
b, err := json.MarshalIndent(d, "", "\t")
|
|
if err != nil {
|
|
return err.Error()
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func (p Photo) PercentExplicit() int {
|
|
if p.Total == 0 {
|
|
return 0
|
|
}
|
|
return int((float64(p.Explicit) / float64(p.Total)) * 100)
|
|
}
|
|
|
|
func (p Photo) PercentNonExplicit() int {
|
|
if p.Total == 0 {
|
|
return 0
|
|
}
|
|
return int((float64(p.NonExplicit) / float64(p.Total)) * 100)
|
|
}
|
|
|
|
func (p People) PercentExplicit() int {
|
|
if p.Total == 0 {
|
|
return 0
|
|
}
|
|
return int((float64(p.ExplicitOptIn) / float64(p.Total)) * 100)
|
|
}
|
|
|
|
func (p People) PercentExplicitPhoto() int {
|
|
if p.Total == 0 {
|
|
return 0
|
|
}
|
|
return int((float64(p.ExplicitPhoto) / float64(p.Total)) * 100)
|
|
}
|
|
|
|
func (p People) IterAgeRanges() []MemberDemographic {
|
|
var (
|
|
result = []MemberDemographic{}
|
|
values = []string{}
|
|
unique = map[string]struct{}{}
|
|
)
|
|
|
|
for age := range p.ByAgeRange {
|
|
if _, ok := unique[age]; !ok {
|
|
values = append(values, age)
|
|
}
|
|
unique[age] = struct{}{}
|
|
}
|
|
|
|
sort.Strings(values)
|
|
for _, age := range values {
|
|
var (
|
|
count = p.ByAgeRange[age]
|
|
pct int
|
|
)
|
|
if p.Total > 0 {
|
|
pct = int((float64(count) / float64(p.Total)) * 100)
|
|
}
|
|
|
|
result = append(result, MemberDemographic{
|
|
Label: age,
|
|
Count: p.ByAgeRange[age],
|
|
Percent: pct,
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (p People) IterGenders() []MemberDemographic {
|
|
var (
|
|
result = []MemberDemographic{}
|
|
values = append(config.Gender, "")
|
|
unique = map[string]struct{}{}
|
|
)
|
|
|
|
for _, option := range values {
|
|
unique[option] = struct{}{}
|
|
}
|
|
|
|
for gender := range p.ByGender {
|
|
if _, ok := unique[gender]; !ok {
|
|
values = append(values, gender)
|
|
unique[gender] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for _, gender := range values {
|
|
var (
|
|
count = p.ByGender[gender]
|
|
pct int
|
|
)
|
|
if p.Total > 0 {
|
|
pct = int((float64(count) / float64(p.Total)) * 100)
|
|
}
|
|
|
|
result = append(result, MemberDemographic{
|
|
Label: gender,
|
|
Count: p.ByGender[gender],
|
|
Percent: pct,
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (p People) IterOrientations() []MemberDemographic {
|
|
var (
|
|
result = []MemberDemographic{}
|
|
values = append(config.Orientation, "")
|
|
unique = map[string]struct{}{}
|
|
)
|
|
|
|
for _, option := range values {
|
|
unique[option] = struct{}{}
|
|
}
|
|
|
|
for orientation := range p.ByOrientation {
|
|
if _, ok := unique[orientation]; !ok {
|
|
values = append(values, orientation)
|
|
unique[orientation] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for _, gender := range values {
|
|
var (
|
|
count = p.ByOrientation[gender]
|
|
pct int
|
|
)
|
|
if p.Total > 0 {
|
|
pct = int((float64(count) / float64(p.Total)) * 100)
|
|
}
|
|
|
|
result = append(result, MemberDemographic{
|
|
Label: gender,
|
|
Count: p.ByOrientation[gender],
|
|
Percent: pct,
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|