package insightsapi import ( "context" "fmt" "sort" "time" "apps/backend/internal/middleware" "apps/backend/internal/module/growth/usecase" "apps/backend/internal/svc" "apps/backend/internal/types" "github.com/zeromicro/go-zero/core/logx" ) type GetInsightsSummaryLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewGetInsightsSummaryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetInsightsSummaryLogic { return &GetInsightsSummaryLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx} } func (l *GetInsightsSummaryLogic) GetInsightsSummary(req *types.InsightsSummaryReq) (*types.InsightsSummaryData, error) { uid, ok := middleware.UIDFrom(l.ctx) if !ok || uid <= 0 { return nil, fmt.Errorf("unauthorized") } if l.svcCtx.Studio == nil { return &types.InsightsSummaryData{Conclusion: "尚無創作資料", Suggestions: []string{"先同步已發貼文"}}, nil } posts, err := l.svcCtx.Studio.ListOwnPosts(l.ctx, uid, req.AccountId) if err != nil { return nil, err } months := req.Months if months <= 0 { months = 3 } cutoff := time.Now().UTC().AddDate(0, -months, 0).UnixNano() type agg struct{ posts, views, likes, replies int } byMonth := map[string]*agg{} var totalV, totalL, totalR, n int var top []types.InsightsPostPublic for _, p := range posts { if p == nil { continue } ts := p.PublishedAt if ts > 0 && ts < cutoff { continue } n++ totalV += p.ViewCount totalL += p.LikeCount totalR += p.ReplyCount mk := time.Unix(0, ts).UTC().Format("2006-01") if ts == 0 { mk = time.Now().UTC().Format("2006-01") } a := byMonth[mk] if a == nil { a = &agg{} byMonth[mk] = a } a.posts++ a.views += p.ViewCount a.likes += p.LikeCount a.replies += p.ReplyCount eng := 0.0 if p.ViewCount > 0 { eng = float64(p.LikeCount+p.ReplyCount) / float64(p.ViewCount) } prev := p.Text if len([]rune(prev)) > 80 { prev = string([]rune(prev)[:80]) + "…" } top = append(top, types.InsightsPostPublic{ Id: p.ID, TextPreview: prev, PublishedAt: ts, ViewCount: p.ViewCount, LikeCount: p.LikeCount, ReplyCount: p.ReplyCount, EngRate: usecase.RoundRate(eng), }) } sort.Slice(top, func(i, j int) bool { return top[i].EngRate > top[j].EngRate }) if len(top) > 5 { top = top[:5] } avgEng := 0.0 if totalV > 0 { avgEng = float64(totalL+totalR) / float64(totalV) } var buckets []types.InsightsMonthBucket for mk, a := range byMonth { e := 0.0 if a.views > 0 { e = float64(a.likes+a.replies) / float64(a.views) } buckets = append(buckets, types.InsightsMonthBucket{ MonthKey: mk, Posts: a.posts, Views: a.views, Likes: a.likes, Replies: a.replies, AvgEngRate: usecase.RoundRate(e), }) } sort.Slice(buckets, func(i, j int) bool { return buckets[i].MonthKey < buckets[j].MonthKey }) conclusion := fmt.Sprintf("近 %d 個月同步貼文 %d 則,平均互動率 %.2f%%。", months, n, avgEng*100) sugs := []string{"維持高互動題材節奏", "把表現佳貼文結構拿去仿寫"} if n == 0 { conclusion = "尚無足夠已同步貼文" sugs = []string{"到創作 → 我的貼文按同步", "連好 Threads 帳號後再看成效"} } else if avgEng < 0.02 { sugs = append([]string{"互動率偏低:可加強開頭鉤子與提問 CTA"}, sugs...) } // also feed benchmark if n > 0 { _ = l.svcCtx.Growth.ContributeBenchmark(l.ctx, uid, "general", avgEng, float64(totalV)/float64(n)) } return &types.InsightsSummaryData{ AccountId: req.AccountId, PostCount: n, TotalViews: totalV, TotalLikes: totalL, TotalReplies: totalR, AvgEngRate: usecase.RoundRate(avgEng), TopPosts: top, MonthBuckets: buckets, Conclusion: conclusion, Suggestions: sugs, }, nil }