package statistics import ( "fmt" "context" "database/sql" "gitea.pena/SQuiz/common/dal/sqlcgen" ) type Deps struct { Queries *sqlcgen.Queries Pool *sql.DB } type StatisticsRepository struct { queries *sqlcgen.Queries pool *sql.DB } func NewStatisticsRepo(deps Deps) *StatisticsRepository { return &StatisticsRepository{ queries: deps.Queries, pool: deps.Pool, } } type DeviceStatReq struct { QuizId int64 From uint64 To uint64 } type DeviceStatResp struct { //ключ DeviceType значение процент Device map[string]float64 // процентное соотношение DeviceType по всем ответам на опроc c res==true // тоже самое тут только по OS и BROWSER OS map[string]float64 Browser map[string]float64 } func (r *StatisticsRepository) GetDeviceStatistics(ctx context.Context, req DeviceStatReq) (DeviceStatResp, error) { resp := DeviceStatResp{ Device: make(map[string]float64), OS: make(map[string]float64), Browser: make(map[string]float64), } allStatistics, err := r.queries.DeviceStatistics(ctx, sqlcgen.DeviceStatisticsParams{ QuizID: req.QuizId, ToTimestamp: float64(req.From), ToTimestamp_2: float64(req.To), }) if err != nil { return resp, err } for _, stat := range allStatistics { resp.Device[stat.DeviceType] = stat.DevicePercentage resp.OS[stat.Os] = stat.OsPercentage resp.Browser[stat.Browser] = stat.BrowserPercentage } return resp, nil } type GeneralStatsResp struct { Open map[int64]int64 // количество ответов с полем start == true за период от одного пункта разбиения и до другого Result map[int64]int64 // количество ответов с полем result == true за период от одного пункта разбиения и до другого AvTime map[int64]uint64 // среднее время между ответом с полем result == true и start == true. в рамках сессии Conversion map[int64]float64 // Result/Open за период от одного пункта разбиения и до другого } func (r *StatisticsRepository) GetGeneralStatistics(ctx context.Context, req DeviceStatReq) (GeneralStatsResp, error) { resp := GeneralStatsResp{ Open: make(map[int64]int64), Result: make(map[int64]int64), AvTime: make(map[int64]uint64), Conversion: make(map[int64]float64), } // todo затестить запрос нужно, когда на один тру ответ приходится один тру старт апдейтнуть запрос allStatistics, err := r.queries.GeneralStatistics(ctx, sqlcgen.GeneralStatisticsParams{ QuizID: req.QuizId, ToTimestamp: float64(req.From), ToTimestamp_2: float64(req.To), }) if err != nil { return resp, err } for _, stat := range allStatistics { resp.Open[stat.TimeBucket.Unix()] = stat.OpenCount resp.Result[stat.TimeBucket.Unix()] = stat.TrueResultCount resp.AvTime[stat.TimeBucket.Unix()] = uint64(stat.AvgTime) resp.Conversion[stat.TimeBucket.Unix()] = stat.Conversion } return resp, nil } type QuestionsStatsResp struct { // PS это / не или а делить а то я спустя пару часов только догнал //Funnel 3 отдельных метрики // 0 - количество сессий с любым ответом кроме start == true / количество сессий с ответом start == true // 1 - количество сессий с result == false, но тип вопроса, на который ответ == result / количество сессий с ответом start == true // 2 - количество сессий с ответом result == true / количество сессий с ответом start == true Funnel [3]float64 FunnelData [4]float64 // ключ - заголовок вопроса найденного по айдишнику вопроса в ответе result == true, // значение - процент ответов с result == true и таким айдишником вопроса Results map[string]float64 // ключ - заголовок вопроса, а значение - map, где ключ - вариант ответа на этот вопрос, // т.е. группировка по полю Контент, а значение - процент таких ответов Questions map[string]map[string]float64 } func (r *StatisticsRepository) GetQuestionsStatistics(ctx context.Context, req DeviceStatReq) (QuestionsStatsResp, error) { resp := QuestionsStatsResp{ Funnel: [3]float64{}, FunnelData: [4]float64{}, Results: make(map[string]float64), Questions: make(map[string]map[string]float64), } queStatistics, err := r.queries.QuestionsStatistics(ctx, sqlcgen.QuestionsStatisticsParams{ QuizID: req.QuizId, ToTimestamp: float64(req.From), ToTimestamp_2: float64(req.To), }) if err != nil { return resp, err } for _, row := range queStatistics { if row.CountStartTrue != 0 { resp.Funnel[0] = float64(row.CountStartFalse) / float64(row.CountStartTrue) resp.Funnel[1] = float64(row.CountFResultWithTQuestion) / float64(row.CountStartTrue) resp.Funnel[2] = float64(row.CountTResult) / float64(row.CountStartTrue) resp.FunnelData[0] = float64(row.CountStartTrue) resp.FunnelData[1] = float64(row.CountStartFalse) resp.FunnelData[2] = float64(row.CountFResultWithTQuestion) resp.FunnelData[3] = float64(row.CountTResult) } resp.Results[row.ResultsTitle] = row.ResultsPercentage questionTitle := fmt.Sprintf("%s (%d)", row.QuestionsTitle, row.QuestionsPage) if resp.Questions[questionTitle] == nil { resp.Questions[questionTitle] = make(map[string]float64) } resp.Questions[questionTitle][row.AnswerContent] = row.QuestionsPercentage } return resp, nil } type StatisticResp struct { // от from до to Registrations int64 // количество зарегестрированных аккаунтов Quizes int64 // количество созданных не удаленных квизов Results int64 // количество ответов с result = true } func (r *StatisticsRepository) AllServiceStatistics(ctx context.Context, from, to uint64) (StatisticResp, error) { allSvcStats, err := r.queries.AllServiceStatistics(ctx, sqlcgen.AllServiceStatisticsParams{ ToTimestamp: float64(from), ToTimestamp_2: float64(to), }) if err != nil { return StatisticResp{}, err } resp := StatisticResp{ Registrations: allSvcStats.Registrations, Quizes: allSvcStats.Quizes, Results: allSvcStats.Results, } return resp, nil }