2024-06-13 12:39:18 +00:00
|
|
|
package statistics
|
|
|
|
|
2024-06-13 15:37:58 +00:00
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
2025-05-10 20:41:38 +00:00
|
|
|
"fmt"
|
|
|
|
"time"
|
2024-06-13 15:37:58 +00:00
|
|
|
)
|
2024-06-13 12:39:18 +00:00
|
|
|
|
|
|
|
type DepsClick struct {
|
|
|
|
Conn *sql.DB
|
|
|
|
}
|
|
|
|
|
|
|
|
type StatisticClick struct {
|
|
|
|
conn *sql.DB
|
|
|
|
}
|
|
|
|
|
2024-07-02 08:18:00 +00:00
|
|
|
func NewClickStatistic(deps DepsClick) *StatisticClick {
|
|
|
|
return &StatisticClick{
|
2024-06-13 12:39:18 +00:00
|
|
|
conn: deps.Conn,
|
|
|
|
}
|
2024-06-13 15:37:58 +00:00
|
|
|
}
|
|
|
|
|
2025-05-10 20:41:38 +00:00
|
|
|
type PipelineAndFormStatistic struct {
|
|
|
|
PipelineStatistic PipeLineStatsResp
|
|
|
|
ContactFormStatistic map[int64]int
|
|
|
|
}
|
|
|
|
|
2024-06-13 19:03:41 +00:00
|
|
|
type Statistic struct {
|
2024-06-13 15:37:58 +00:00
|
|
|
Count int64
|
|
|
|
QuestionID int64
|
|
|
|
}
|
|
|
|
|
2024-06-19 14:51:34 +00:00
|
|
|
type PipeLineStatsResp map[int64][]Statistic
|
2024-06-13 19:03:41 +00:00
|
|
|
|
2025-05-10 20:41:38 +00:00
|
|
|
func (s *StatisticClick) GetPipelinesStatistics(ctx context.Context, quizID int64, from uint64, to uint64) (*PipelineAndFormStatistic, error) {
|
2024-06-30 08:42:21 +00:00
|
|
|
pipelines := make(PipeLineStatsResp)
|
2024-06-19 14:29:20 +00:00
|
|
|
|
2024-06-17 11:37:03 +00:00
|
|
|
query := `
|
2024-06-30 08:42:21 +00:00
|
|
|
select last_quesion, questions, count() from (select last_quesion, questions, target_quiz
|
|
|
|
from (select last_quesion, questions, target_quiz from view_pipelines_signs
|
|
|
|
join view_respondent_paths on long_sess = session array join questions)
|
|
|
|
as pipelines join mv_answers on questions=questionid and quizid=target_quiz
|
|
|
|
where target_quiz = ? AND event_time BETWEEN ? AND ? ) group by last_quesion, questions;
|
2024-06-17 11:37:03 +00:00
|
|
|
`
|
|
|
|
|
|
|
|
rows, err := s.conn.QueryContext(ctx, query, quizID, from, to)
|
2024-06-13 15:37:58 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-06-17 11:37:03 +00:00
|
|
|
defer rows.Close()
|
2024-06-14 18:06:01 +00:00
|
|
|
|
2024-06-17 11:37:03 +00:00
|
|
|
for rows.Next() {
|
2024-06-30 08:42:21 +00:00
|
|
|
var lastQuestionID int64
|
|
|
|
var questionID int64
|
|
|
|
var count int64
|
2024-06-17 11:37:03 +00:00
|
|
|
|
2024-06-30 08:42:21 +00:00
|
|
|
if err := rows.Scan(&lastQuestionID, &questionID, &count); err != nil {
|
2024-06-19 14:51:34 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2024-06-14 13:51:47 +00:00
|
|
|
|
2024-06-30 08:42:21 +00:00
|
|
|
stat := Statistic{
|
|
|
|
Count: count,
|
|
|
|
QuestionID: questionID,
|
2024-06-14 13:51:47 +00:00
|
|
|
}
|
2024-06-30 08:42:21 +00:00
|
|
|
|
|
|
|
pipelines[lastQuestionID] = append(pipelines[lastQuestionID], stat)
|
2024-06-17 11:37:03 +00:00
|
|
|
}
|
2024-06-14 13:51:47 +00:00
|
|
|
|
2024-06-17 11:37:03 +00:00
|
|
|
if err := rows.Err(); err != nil {
|
|
|
|
return nil, err
|
2024-06-14 13:51:47 +00:00
|
|
|
}
|
|
|
|
|
2025-05-10 20:41:38 +00:00
|
|
|
contactQuery := `
|
|
|
|
SELECT questionid, count() FROM mv_quiz_open WHERE quizid = ? AND event_time BETWEEN ? AND ?
|
|
|
|
GROUP BY questionid;
|
|
|
|
`
|
|
|
|
|
|
|
|
contactRows, err := s.conn.QueryContext(ctx, contactQuery, quizID, from, to)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer contactRows.Close()
|
|
|
|
|
|
|
|
contactForms := make(map[int64]int)
|
|
|
|
for contactRows.Next() {
|
|
|
|
var questionID int64
|
|
|
|
var count int
|
|
|
|
if err := contactRows.Scan(&questionID, &count); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
contactForms[questionID] = count
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := contactRows.Err(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &PipelineAndFormStatistic{
|
|
|
|
PipelineStatistic: pipelines,
|
|
|
|
ContactFormStatistic: contactForms,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StatisticClick) CalculateCustomerLTV(ctx context.Context, req ChStatDeps) (int64, error) {
|
|
|
|
timeFilter := ""
|
|
|
|
if req.From != 0 {
|
|
|
|
fromTime := time.Unix(req.From, 0).UTC().Format("2006-01-02 15:04:05")
|
|
|
|
timeFilter += fmt.Sprintf(" AND create_time >= '%s'", fromTime)
|
|
|
|
}
|
|
|
|
if req.To != 0 {
|
|
|
|
toTime := time.Unix(req.To, 0).UTC().Format("2006-01-02 15:04:05")
|
|
|
|
timeFilter += fmt.Sprintf(" AND create_time <= '%s'", toTime)
|
|
|
|
}
|
|
|
|
|
|
|
|
query := fmt.Sprintf(`
|
|
|
|
SELECT avg(lifeTimeInDays) AS averageLTV
|
|
|
|
FROM (
|
|
|
|
SELECT
|
|
|
|
(dateDiff('day', min(create_time), max(create_time))) AS lifeTimeInDays
|
|
|
|
FROM ltv_view WHERE 1 = 1 %s GROUP BY ctxuserid)
|
|
|
|
`, timeFilter)
|
|
|
|
|
|
|
|
var result struct{ AverageLTV float64 }
|
|
|
|
if err := s.conn.QueryRowContext(ctx, query).Scan(&result.AverageLTV); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return int64(result.AverageLTV), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type ChStatDeps struct {
|
|
|
|
From int64
|
|
|
|
To int64
|
|
|
|
}
|
|
|
|
|
|
|
|
type QuizLogoStats struct {
|
|
|
|
ID string
|
|
|
|
Regs int
|
|
|
|
Money int64
|
|
|
|
Quizes []Quiz
|
|
|
|
}
|
|
|
|
|
|
|
|
type Quiz struct {
|
|
|
|
QuizID string
|
|
|
|
Regs uint64
|
|
|
|
Money int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StatisticClick) QuizLogoStat(ctx context.Context, req ChStatDeps) ([]QuizLogoStats, error) {
|
|
|
|
timeFilter := ""
|
|
|
|
if req.From != 0 && req.To != 0 {
|
|
|
|
timeFilter = fmt.Sprintf("AND lv.create_time >= toDate(%d) AND lv.create_time <= toDate(%d)", req.From, req.To)
|
|
|
|
}
|
|
|
|
query := fmt.Sprintf(`
|
|
|
|
SELECT
|
|
|
|
sub.partner AS partner,
|
|
|
|
count() AS regs,
|
|
|
|
sum(sub.rowPrice) AS money,
|
|
|
|
groupArray((sub.fromID, sub.regCount, sub.sumPrice)) AS quizes
|
|
|
|
FROM (
|
|
|
|
SELECT
|
|
|
|
uv.keyfrompartner AS partner,
|
|
|
|
uv.keyfromid AS fromID,
|
|
|
|
count() AS regCount,
|
|
|
|
sum(lv.ctxprice) AS sumPrice,
|
|
|
|
sum(lv.ctxprice) AS rowPrice
|
|
|
|
FROM ltv_view lv
|
|
|
|
JOIN user_regs_view uv ON lv.ctxuserid = uv.ctxuserid
|
|
|
|
WHERE uv.keyfrompartner != '' AND uv.keyfromid != ''
|
|
|
|
%s
|
|
|
|
GROUP BY uv.keyfrompartner, uv.keyfromid
|
|
|
|
) AS sub
|
|
|
|
GROUP BY sub.partner;
|
|
|
|
`, timeFilter)
|
|
|
|
|
|
|
|
var results []QuizLogoStats
|
|
|
|
rows, err := s.conn.QueryContext(ctx, query)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
var stats QuizLogoStats
|
|
|
|
var quizesData [][]interface{}
|
|
|
|
|
|
|
|
err := rows.Scan(&stats.ID, &stats.Regs, &stats.Money, &quizesData)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var quizes []Quiz
|
|
|
|
for _, q := range quizesData {
|
|
|
|
quiz := Quiz{
|
|
|
|
QuizID: q[0].(string),
|
|
|
|
Regs: q[1].(uint64),
|
|
|
|
Money: q[2].(int64),
|
|
|
|
}
|
|
|
|
quizes = append(quizes, quiz)
|
|
|
|
}
|
|
|
|
|
|
|
|
stats.Quizes = quizes
|
|
|
|
results = append(results, stats)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := rows.Err(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type PromoLtvResp struct {
|
|
|
|
Regs int
|
|
|
|
Money int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StatisticClick) PromocodeLTV(ctx context.Context, req ChStatDeps) (map[string]PromoLtvResp, error) {
|
|
|
|
timeFilterFirstPromo := ""
|
|
|
|
timeFilterFirstData := ""
|
|
|
|
timeFilterMainQuery := ""
|
|
|
|
if req.From != 0 && req.To != 0 {
|
|
|
|
timeFilterFirstPromo = fmt.Sprintf("AND create_time BETWEEN toDateTime(%d) AND toDateTime(%d)", req.From, req.To)
|
|
|
|
timeFilterFirstData = fmt.Sprintf("AND promo.create_time BETWEEN toDateTime(%d) AND toDateTime(%d)", req.From, req.To)
|
|
|
|
timeFilterMainQuery = fmt.Sprintf("AND ltv.create_time BETWEEN toDateTime(%d) AND toDateTime(%d)", req.From, req.To)
|
|
|
|
}
|
|
|
|
|
|
|
|
query := fmt.Sprintf(`
|
|
|
|
WITH first_promo AS (
|
|
|
|
SELECT
|
|
|
|
ctxuserid, min(create_time) AS first_promo_time
|
|
|
|
FROM promo_view
|
|
|
|
WHERE 1=1 %s
|
|
|
|
GROUP BY ctxuserid
|
|
|
|
),
|
|
|
|
first_data AS (
|
|
|
|
SELECT
|
|
|
|
promo.ctxid, promo.ctxuserid, promo.create_time AS first_time
|
|
|
|
FROM
|
|
|
|
promo_view AS promo JOIN first_promo AS first
|
|
|
|
ON promo.ctxuserid = first.ctxuserid AND promo.create_time = first.first_promo_time
|
|
|
|
WHERE 1=1 %s
|
|
|
|
)
|
|
|
|
SELECT
|
|
|
|
promo_data.ctxid AS promoID,
|
|
|
|
count(DISTINCT promo_data.ctxuserid) AS regs,
|
|
|
|
sum(if(ltv.create_time > promo_data.first_time %s, ltv.ctxprice, 0)) AS totalSpent
|
|
|
|
FROM
|
|
|
|
first_data AS promo_data LEFT JOIN ltv_view AS ltv
|
|
|
|
ON promo_data.ctxuserid = ltv.ctxuserid
|
|
|
|
GROUP BY promo_data.ctxid;
|
|
|
|
`, timeFilterFirstPromo, timeFilterFirstData, timeFilterMainQuery)
|
|
|
|
|
|
|
|
rows, err := s.conn.QueryContext(ctx, query)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
results := make(map[string]PromoLtvResp)
|
|
|
|
for rows.Next() {
|
|
|
|
var promoID string
|
|
|
|
var resp PromoLtvResp
|
|
|
|
if err := rows.Scan(&promoID, &resp.Regs, &resp.Money); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
results[promoID] = resp
|
|
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return results, nil
|
2024-06-13 12:39:18 +00:00
|
|
|
}
|