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

177 lines
5.4 KiB
Markdown
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.

# 全 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
```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 → 0100
- **預測互動率**`EngagementPredicted`like+reply → 對數正規化 0100
- **受眾品質**`AudienceQualityScore``computeAudienceQuality()` heuristic
權重:語意 20% / 互動 30% / 品質 10% / 舊有 productFit + solvedBy + recency 40%。
```go
func computeAudienceQuality(followers int) int
```
Heuristicfollowers 在 50050k 之間品質最佳,過低或過高遞減。
### 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非 Redisthread-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`