website/pkg/models/demographic/demographic.go

196 lines
3.9 KiB
Go
Raw Normal View History

// 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
}