common/repository/statistics/click_statistics.go
2024-06-14 21:06:01 +03:00

207 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package statistics
import (
"context"
"database/sql"
"fmt"
)
type DepsClick struct {
Conn *sql.DB
}
type StatisticClick struct {
conn *sql.DB
}
func NewClickStatistic(ctx context.Context, deps DepsClick) (*StatisticClick, error) {
s := &StatisticClick{
conn: deps.Conn,
}
err := s.checkMW(ctx)
if err != nil {
fmt.Println("error check material view existing")
return nil, err
}
return s, nil
}
// todo toanaliz for keydevice,keydevicetype,keybrowser,
func (s *StatisticClick) checkMW(ctx context.Context) error {
// в мат вью получаем последний ссесионный координат, то есть тот момент на котором сессия в квизе завершилась
// не зависит какой это тип вопроса, страт, обычный, резулт, получаем всегда последний у сессии
// он определят конечное положение пользователя в опросе
query := `
CREATE MATERIALIZED VIEW IF NOT EXISTS mv_last_answers_events
ENGINE = MergeTree()
ORDER BY (ctxsession, event_time) AS
SELECT
event_time, ctxsession, ctxquizid, ctxquestionid, ctxidint, message, keyos,
ctxuserip, ctxuserport, keydomain, keypath, ctxquiz, ctxreferrer
FROM (SELECT
event_time, ctxsession, ctxquizid, ctxquestionid, ctxidint, message, keyos,
ctxuserip, ctxuserport, keydomain, keypath, ctxquiz, ctxreferrer,
row_number() OVER (PARTITION BY ctxsession ORDER BY event_time DESC) as row_num
FROM statistics
WHERE message IN ('InfoQuizOpen', 'InfoAnswer', 'InfoResult') AND event_level = 'info') AS sorted
WHERE row_num = 1;
`
_, err := s.conn.ExecContext(ctx, query)
if err != nil {
return err
}
return nil
}
type Statistic struct {
Count int64
QuestionID int64
}
type PipeLineStatsResp [][]Statistic
type FunnelData struct {
Session string
QuestionIDs []int64
}
func (s *StatisticClick) funnelData(ctx context.Context, quizID int64, from uint64, to uint64) ([]FunnelData, error) {
// получили все уникальные сессии из мат вью
sessionQuery := `
SELECT DISTINCT ctxsession
FROM mv_last_answers_events
WHERE ctxquizid = ? AND event_time BETWEEN ? AND ?
`
sessionRows, err := s.conn.QueryContext(ctx, sessionQuery, quizID, from, to)
if err != nil {
return nil, err
}
defer sessionRows.Close()
var funnelData []FunnelData
for sessionRows.Next() {
var ctxsession string
err := sessionRows.Scan(&ctxsession)
if err != nil {
return nil, err
}
// получаем вопросы уникальные, последние в рамках сессии
questionQuery := `
SELECT DISTINCT ctxquestionid
FROM statistics WHERE ctxsession = ? ORDER BY event_time DESC;
`
questionRows, err := s.conn.QueryContext(ctx, questionQuery, ctxsession)
if err != nil {
return nil, err
}
defer questionRows.Close()
var questions []int64
for questionRows.Next() {
var ctxquestionid int64
err := questionRows.Scan(&ctxquestionid)
if err != nil {
return nil, err
}
questions = append(questions, ctxquestionid)
}
fmt.Println("questions", questions)
if err := questionRows.Err(); err != nil {
return nil, err
}
funnelData = append(funnelData, FunnelData{
Session: ctxsession,
QuestionIDs: questions,
})
}
if err := sessionRows.Err(); err != nil {
return nil, err
}
return funnelData, nil
}
// todo еще раз понять подумать осознать как подписывать воронку сейчас если массив соддержит 1 элемент это старт ответ
func (s *StatisticClick) getOneFunnelQuestions(ctx context.Context, quizID int64, from uint64, to uint64) ([]int64, error) {
var result []int64
funnelData, err := s.funnelData(ctx, quizID, from, to)
if err != nil {
return nil, err
}
for _, fd := range funnelData {
if len(fd.QuestionIDs) == 1 {
result = append(result, fd.QuestionIDs...)
}
}
return result, nil
}
func (s *StatisticClick) GetPipelinesStatistics(ctx context.Context, quizID int64, from uint64, to uint64) (PipeLineStatsResp, error) {
// тут нужно подписать все вопросы, которые принадлежат окончанию сессии, то есть массив ответов до окончания сессии
// это делается для каждой из сессии которая имеется в мат вью, это отношение к результату которого достиг респондент
// для того чтобы получить индентификатор воронки, то есть тот который последний этап прохождения был, то на чем все закончилось
// нужно из всех подписанных которые подписываются массивом и нужно выбрать из всех подписанных ответов на вопросы, выбрать те
// которые имеет всего один элемент в списке, то есть достижение никакого другого вопроса или результата не проходило через
// этот ответа кроме попытки достигнуть этотго оттвета или результата, это и являются конечные точки прохождения вопросов и от них считаются воронки и являются индентификаторами воронки
// именно те которые с массивом с 1 элементом
// нужно вернуть в каждом списке пару, id вопроса и количество ответов на этот вопрос, count - уникальные сессии которые прошли через этот вопрос
var pipelines PipeLineStatsResp
idS, err := s.getOneFunnelQuestions(ctx, quizID, from, to)
if err != nil {
return nil, err
}
for _, questionID := range idS {
query := `
SELECT ctxquestionid, COUNT(DISTINCT ctxsession) AS session_count
FROM mv_last_answers_events
WHERE ctxquizid = ? AND ctxquestionid = ? AND event_time BETWEEN ? AND ?
GROUP BY ctxquestionid
`
rows, err := s.conn.QueryContext(ctx, query, quizID, questionID, from, to)
if err != nil {
return nil, err
}
defer rows.Close()
var currentPipeline []Statistic
for rows.Next() {
var id int64
var count int64
err := rows.Scan(&id, &count)
if err != nil {
return nil, err
}
currentPipeline = append(currentPipeline, Statistic{
QuestionID: id,
Count: count,
})
}
if err := rows.Err(); err != nil {
return nil, err
}
if len(currentPipeline) > 0 {
pipelines = append(pipelines, currentPipeline)
}
}
return pipelines, nil
}