thread-master/backend/docs/phase1-methods.md

5.4 KiB
Raw Blame History

全 Phase 新增功能說明

Phase 1 — TA 精準度

1. Semantic Embedding Reranker

ScanPost entity 新增欄位internal/model/scan_post/domain/entity/post.go:

欄位 型別 用途
Embedding []float32 OpenAI text-embedding-3-small 向量
SemanticScore int 語意相似度分數0100
EngagementPredicted int 預測互動率
AudienceQualityScore int 受眾品質分數0100
AuthorID string 作者 IDThreads user ID
AuthorAvatar string 作者頭像 URL
AuthorFollowers int 作者粉絲數

ScanCandidate struct 同步新增對應欄位(internal/library/placement/dual_track.go)。

2. Embedding Client

// internal/library/embedding/client.go
func NewClient(apiKey, model string) *Client
func (c *Client) Generate(ctx context.Context, text string) ([]float32, error)
  • 預設使用 OpenAI text-embedding-3-small(可降級 local BGE-M3
  • Scorer: CosineSimilarity(a, b []float32) float64

3. Placement Score V2

// internal/library/placement/dual_track.go
func computePlacementScore(post *ScanCandidate, member productContext, brand productMeta,
    semanticScore, predictedEngagement, audienceQuality *int) int

整合三項 pointer 參數nil 表示欄位不存在,跳過該項):

  • 語意相似度SemanticScoreEmbedding cosine → 0100
  • 預測互動率EngagementPredictedlike+reply → 對數正規化 0100
  • 受眾品質AudienceQualityScorecomputeAudienceQuality() heuristic

權重:語意 20% / 互動 30% / 品質 10% / 舊有 productFit + solvedBy + recency 40%。

func computeAudienceQuality(followers int) int

Heuristicfollowers 在 50050k 之間品質最佳,過低或過高遞減。

4. Creator Lookalike

端點: POST /api/v1/scan-post/lookalike(需 JWT

{ "source_post_id": "uuid", "brand_id": "...", "limit": 10 }

 { "list": [{ "post_id", "author_id", "author_name", "similarity", ... }] }

演算法: cosine similarity門檻 ≥0.3),同 brand 內找 embedding 最接近的貼文回傳。

檔案
Handler internal/handler/scan_post/lookalike_handler.go
Logic internal/logic/scan_post/lookalike_logic.go
cosineSimilarity internal/logic/scan_post/lookalike_logic.go — 全 Go 實作

Phase 2 — 營運流程

2.1 Creator CRM

完整四層 model crm_contact

internal/model/crm_contact/
  domain/entity/entity.go
  domain/repository/repository.go
  domain/usecase/usecase.go
  usecase/usecase.go
  repository/mongo.go

端點: /api/v1/crm/contacts(需 JWT

Method Path 用途
GET / Listpage/pageSize/brand_id/status/tag
POST / 新增聯絡人(author_id + author_name
GET /:id 取得單筆
PATCH /:id 更新(notes/tags/status
DELETE /:id 刪除

CRMContact 狀態機: leadcontactedrespondedconverted / archived

2.2 Publish Analytics

完整四層 model publish_analytics

Checkpoint 常數internal/model/publish_analytics/usecase/usecase.go: +1h+24h+7d

方法:

RecordCheckpoint(ctx, tenantID, ownerUID, summary) (*PublishAnalyticsSummary, error)
ListByMedia(ctx, tenantID, ownerUID, mediaID) ([]PublishAnalyticsSummary, error)

Worker handler publish-analyticsstep check

呼叫 Threads API GET /{media-id}?fields=like_count,reply_count,repost_count,quote_count → 存入 publish_analytics

2.3 Threads API Token 刷新

底層 APIinternal/library/threadsapi/media.go:

RefreshAccessToken(ctx, currentToken) (*TokenRefreshResult, error)
// GET /v1.0/refresh_access_token?grant_type=th_refresh_token

儲存 Token:

// Repository
SaveAPIAccessToken(ctx, accountID, accessToken, expiresAt) (*entity.Secrets, error)
// Usecase
SaveAPIAccessToken(ctx, accountID, accessToken, expiresAt) (*SecretsData, error)

Worker handler refresh-threads-tokenstep refresh):讀取 secrets → refresh → 儲存。

2.4 Rate Limiter

// internal/library/ratelimit/token_bucket.go
func NewTokenBucket(rate float64, burst int) *TokenBucket
func (tb *TokenBucket) Allow() bool
func (tb *TokenBucket) AllowN(n int) bool
  • In-memory token bucket非 Redisthread-safe
  • rate = tokens/secburst = 最大 burst

Infrastructure

Job Templates

internal/model/job/usecase/usecase.go 新增兩個 template constants + 對應 Ensure* methods + template() functions

Template Type Steps Timeout Retry
refresh-threads-token refreshgo 120s 3 次30/120/300s
publish-analytics checkgo 120s 2 次60s

Worker Handlers 註冊

internal/svc/service_context.go 中註冊:

jobworker.RegisterRefreshThreadsTokenHandler(runner, RefreshThreadsTokenDeps{...})
jobworker.RegisterPublishAnalyticsHandler(runner, PublishAnalyticsDeps{...})

Worker Helper

// internal/worker/job/expand_graph.go
func int64Field(payload map[string]any, key string) int64

從 job payload 讀 int64,支援 float64 / int64 / int / json.Number