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