## 转账闭环识别

- **DDS-Section**: 7.4 链路合并（转账闭环 + 订单链路）
- **DDS-Lines**: L820-L871

### Extract

#### 典型场景

银行卡支出 1000 元（流向支付宝），支付宝收入 1000 元 → 合并为一笔内部转账。

#### 转账闭环识别规则（5 项全部满足）

| # | 条件 | 说明 |
|---|------|------|
| 1 | 金额一致 | `a.Amount.Equal(b.Amount)` |
| 2 | 方向互补 | 一条 expense + 一条 income |
| 3 | 时间窗口内 | 默认 ±30 分钟（`transfer_time_window` 可配置） |
| 4 | 不同平台 | `a.SourcePlatform != b.SourcePlatform` |
| 5 | 非退款/手续费 | `direction` 不是 `refund` 或 `fee` |

#### 实现骨架

```go
func (l *TransferLinker) Detect(a, b *Transaction) *LinkResult {
    // 条件 1: 金额一致
    if !a.Amount.Equal(b.Amount) { return nil }

    // 条件 2: 方向互补
    if !((a.Direction == "expense" && b.Direction == "income") ||
         (a.Direction == "income" && b.Direction == "expense")) { return nil }

    // 条件 3: 时间窗口
    if math.Abs(a.TradeTime.Sub(b.TradeTime).Minutes()) > l.TimeWindow { return nil }

    // 条件 4: 不同平台
    if a.SourcePlatform == b.SourcePlatform { return nil }

    // 条件 5: 非退款/手续费
    if a.Direction == "refund" || b.Direction == "refund" { return nil }
    if a.Direction == "fee" || b.Direction == "fee" { return nil }

    return &LinkResult{
        ParentTransactionID: selectPrimary(a, b).ID,
        ChildTransactionID:  selectSecondary(a, b).ID,
        LinkType:            "transfer",
        FromAccount:         mapToAccount(getExpenseSide(a, b)),
        ToAccount:           mapToAccount(getIncomeSide(a, b)),
    }
}
```

#### 订单链路合并

**典型场景**：京东订单 + 微信支付，一笔真实消费产生多条流水。

**合并策略**：
- 保留更完整的业务记录为主交易（优先保留有商品详情的记录）
- 其他记录挂为关联来源
- 形成 `parent_order_id` 聚合链路

#### LinkResult 数据结构

```go
type LinkResult struct {
    ParentTransactionID string
    ChildTransactionID  string
    LinkType            string // transfer / order / refund / fee
    FromAccount         string
    ToAccount           string
}
```

#### 配置项

| 配置键 | 默认值 | 说明 |
|--------|--------|------|
| `transfer_time_window` | 30 | 转账闭环时间窗口（分钟） |
