# ADR-005：KM 品質分數加入時間衰減

- **Status**: Accepted
- **Date**: 2026-04-18
- **Decision Maker**: 統帥（顧問補充建議第 3 條）
- **Author**: Claude

## Context

`ai_insights` 表中的 KM 記錄有一個「品質分數（`avg_quality`）」欄位，由用戶的 👍/👎 回饋計算。
問題：原始設計只取回饋的 **靜態平均分**，沒有考慮時間因素。

**失效場景**：
- 半年前的「換季保濕策略洞察」被判定為高品質（當時收到 5 個 👍）
- 但今天是夏天，RAG 仍優先抓取它 → OpenClaw 回答品質下降
- 競品策略也在演化，6 個月前的競品分析今日已完全無效

## Decision

**KM 品質分數採用時間衰減公式**：

```
Effective_Score = Base_Score × exp(−λ × days_since_created)
```

| 參數 | 值 | 說明 |
|---|---|---|
| `λ` (decay_rate) | `0.01` | 半衰期約 70 天（ln(2)/0.01） |
| `Base_Score` | `avg_quality`（0~1） | 原始回饋分數平均 |
| `days_since_created` | `NOW() - created_at` | 洞察建立至今天數 |

**實際效果**：

| 天數 | Effective Score（假設 Base=1.0） |
|---|---|
| 0 天（剛建立） | 1.00 |
| 30 天 | 0.74 |
| 70 天 | 0.50（半衰期） |
| 180 天 | 0.16 |
| 365 天 | 0.03（幾乎不被選中） |

**例外**（不套時間衰減）：
- `insight_type = 'structural'`（結構性規則，如「MOMO 商品代碼格式永遠是 I...」）
- `insight_type = 'constitutional'`（專案憲法類）
- 手動標記 `decay_exempt = true` 的記錄

## Alternatives Considered

| 方案 | 拒絕原因 |
|---|---|
| 固定天數截斷（只看近 90 天） | 硬截斷損失部分有效的長期記憶 |
| 不做衰減（原始設計） | 舊記錄永遠佔據 RAG top 5，導致回答跑偏 |
| 每隔一段時間批次刪除老記錄 | 不可逆，人工操作風險大 |

## Consequences

### Positive
- RAG 自動偏好新鮮知識，回答隨業務演化自我更新
- 舊洞察不消失（審計/回溯仍可查），只是自然淡出 ranking
- 半衰期可調（λ 改小 = 記得更久，λ 改大 = 遺忘更快）

### Negative / Trade-offs
- `ORDER BY` 子句需在 SQL 中即時計算 exponential，有輕微效能影響
  - 緩解：pgvector HNSW 先做 ANN 近似過濾，再在 Python 層做衰減重排
- 開發者需理解「分數不是靜態的」，debug 時要考慮時間因素

## Implementation Notes

```sql
-- RAG 查詢時計算有效分數
SELECT
    insight_text,
    created_at,
    avg_quality,
    avg_quality * EXP(-0.01 * EXTRACT(EPOCH FROM (NOW() - created_at)) / 86400.0)
        AS effective_score
FROM ai_insights
WHERE insight_type NOT IN ('structural', 'constitutional')
  AND decay_exempt IS NOT TRUE
ORDER BY embedding <-> :query_vector  -- 先做語意過濾
LIMIT 20;
-- 然後在 Python 層取 effective_score 前 5
```

```python
# services/openclaw_learning_service.py
import math
from datetime import datetime, timezone

def compute_effective_score(base_score: float, created_at: datetime, decay_rate: float = 0.01) -> float:
    """時間衰減品質分數"""
    days = (datetime.now(timezone.utc) - created_at.replace(tzinfo=timezone.utc)).days
    return base_score * math.exp(-decay_rate * days)
```

**新欄位需求**（Migration 005）：
```sql
ALTER TABLE ai_insights ADD COLUMN decay_exempt BOOLEAN DEFAULT FALSE;
CREATE INDEX ON ai_insights (insight_type, decay_exempt);
```

## Related ADRs

- ADR-001：三 Agent 分工（OpenClaw 應用層 RAG 的取數來源）
- ADR-002：pgvector（儲存層，含 HNSW 索引）
- ADR-003：本地 embedding（採集層，與品質分數無關但同表）
