# 全 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` | 語意相似度分數(0–100) | | `EngagementPredicted` | `int` | 預測互動率 | | `AudienceQualityScore` | `int` | 受眾品質分數(0–100) | | `AuthorID` | `string` | 作者 ID(Threads user ID) | | `AuthorAvatar` | `string` | 作者頭像 URL | | `AuthorFollowers` | `int` | 作者粉絲數 | `ScanCandidate` struct 同步新增對應欄位(`internal/library/placement/dual_track.go`)。 ### 2. Embedding Client ```go // 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 ```go // internal/library/placement/dual_track.go func computePlacementScore(post *ScanCandidate, member productContext, brand productMeta, semanticScore, predictedEngagement, audienceQuality *int) int ``` 整合三項 pointer 參數(nil 表示欄位不存在,跳過該項): - **語意相似度**(`SemanticScore`):Embedding cosine → 0–100 - **預測互動率**(`EngagementPredicted`):like+reply → 對數正規化 0–100 - **受眾品質**(`AudienceQualityScore`):`computeAudienceQuality()` heuristic 權重:語意 20% / 互動 30% / 品質 10% / 舊有 productFit + solvedBy + recency 40%。 ```go func computeAudienceQuality(followers int) int ``` Heuristic:followers 在 500–50k 之間品質最佳,過低或過高遞減。 ### 4. Creator Lookalike **端點**: `POST /api/v1/scan-post/lookalike`(需 JWT) ```json { "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 | `/` | List(`page`/`pageSize`/`brand_id`/`status`/`tag`) | | POST | `/` | 新增聯絡人(`author_id` + `author_name`) | | GET | `/:id` | 取得單筆 | | PATCH | `/:id` | 更新(`notes`/`tags`/`status`) | | DELETE | `/:id` | 刪除 | **CRMContact 狀態機**: `lead` → `contacted` → `responded` → `converted` / `archived` ### 2.2 Publish Analytics **完整四層 model** `publish_analytics`: **Checkpoint 常數**(`internal/model/publish_analytics/usecase/usecase.go`): `+1h`、`+24h`、`+7d` **方法**: ```go RecordCheckpoint(ctx, tenantID, ownerUID, summary) (*PublishAnalyticsSummary, error) ListByMedia(ctx, tenantID, ownerUID, mediaID) ([]PublishAnalyticsSummary, error) ``` **Worker handler** `publish-analytics`(step `check`): 呼叫 Threads API `GET /{media-id}?fields=like_count,reply_count,repost_count,quote_count` → 存入 `publish_analytics`。 ### 2.3 Threads API Token 刷新 **底層 API**(`internal/library/threadsapi/media.go`): ```go RefreshAccessToken(ctx, currentToken) (*TokenRefreshResult, error) // GET /v1.0/refresh_access_token?grant_type=th_refresh_token ``` **儲存 Token**: ```go // Repository SaveAPIAccessToken(ctx, accountID, accessToken, expiresAt) (*entity.Secrets, error) // Usecase SaveAPIAccessToken(ctx, accountID, accessToken, expiresAt) (*SecretsData, error) ``` **Worker handler** `refresh-threads-token`(step `refresh`):讀取 secrets → refresh → 儲存。 ### 2.4 Rate Limiter ```go // 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(非 Redis),thread-safe - `rate` = tokens/sec,`burst` = 最大 burst --- ## Infrastructure ### Job Templates `internal/model/job/usecase/usecase.go` 新增兩個 template constants + 對應 `Ensure*` methods + `template()` functions: | Template Type | Steps | Timeout | Retry | |---------------|-------|---------|-------| | `refresh-threads-token` | `refresh`(go) | 120s | 3 次(30/120/300s) | | `publish-analytics` | `check`(go) | 120s | 2 次(60s) | ### Worker Handlers 註冊 `internal/svc/service_context.go` 中註冊: ```go jobworker.RegisterRefreshThreadsTokenHandler(runner, RefreshThreadsTokenDeps{...}) jobworker.RegisterPublishAnalyticsHandler(runner, PublishAnalyticsDeps{...}) ``` ### Worker Helper ```go // internal/worker/job/expand_graph.go func int64Field(payload map[string]any, key string) int64 ``` 從 job payload 讀 `int64`,支援 `float64` / `int64` / `int` / `json.Number`。