# ProjectMoneyX — 系统详细设计说明书（DDS）

> **版本**：v1.0 | **日期**：2026-03-03 | **状态**：综合定稿
> **技术栈**：后端 Golang + GIN + GORM | 前端 Vue3 + TypeScript + Vuetify | 数据库 SQLite

---

## 1. 设计目标与范围

### 1.1 设计目标

ProjectMoneyX 定位为 Firefly III 生态的**本地化多源账单数据治理中间件**，核心目标是将分散在支付平台、银行、生活服务及加密货币交易所的零散账单，经过标准化清洗、智能去重与链路合并后，无缝推送至 Firefly III / Data Importer。

本系统需实现以下完整处理链路：

1. 原始文件接入与批次化管理
2. 多平台账单解析与字段标准化（插件化架构）
3. 统一交易模型入库（SQLite 持久化）
4. 严格去重 → 模糊去重 → 链路合并（三层递进）
5. 分类 / 账户 / 标签 / 商户归一化规则映射
6. 导出为 Firefly III / Data Importer 可消费的数据格式（API / CSV）
7. 提供预览、审计、失败重试与全链路可回溯能力

### 1.2 本期范围（V1.0 / MVP）

| 范围项 | 说明 |
|--------|------|
| 账单来源 | 支付宝、微信支付（优先）；建设银行、工商银行（次优先） |
| 文件格式 | CSV / Excel / 常见文本格式 |
| 持久化 | 本地 SQLite |
| 去重 | 基础去重（严格去重）+ 转账闭环链路合并 |
| 规则映射 | 基础规则映射（分类 / 账户） |
| 导入方式 | API 推送 + 中间文件导出 |
| 交互 | 导入预览与确认、失败重试 |

### 1.3 非目标（Out of Scope）

- 不直接登录第三方平台自动抓取账单
- 不实现完整 BI 分析平台
- 不替代 Firefly III 本体记账能力
- 不做高频实时同步（本期以"批量导入"为主）

---

## 2. 需求分析与架构判断

### 2.1 核心业务意图

PRD 的真实意图不是"导入工具"，而是一个**本地账单数据治理层**。系统重心在于：

- **多源异构字段收敛**：屏蔽支付宝、微信、银行等平台字段差异
- **多因子去重与链路恢复**：消除重复流水，还原真实交易链路
- **可沉淀规则体系**：分类/账户/标签映射可长期积累、可解释、可迁移

### 2.2 关键设计约束

| # | 约束 | 说明 |
|---|------|------|
| 1 | 本地优先 | 财务数据敏感，必须本地部署，默认不上传云端 |
| 2 | 插件化解析器 | 平台格式变化频繁，适配逻辑必须隔离 |
| 3 | 统一交易模型稳定 | 避免下游 Firefly III 或上游平台格式污染核心域模型 |
| 4 | 支付宝分类为主标准 | 支付宝拥有最丰富的 22 种交易分类，其他平台应映射到此分类体系 |
| 5 | 微信分类需推断 | 微信"交易类型"较粗，需结合"商品"字段推断细分分类 |

### 2.3 技术风险识别

| # | 风险 | 影响 | 应对策略 |
|---|------|------|----------|
| 1 | 平台格式变更 | 解析器失效 | 插件化 Parser + 版本化适配 |
| 2 | 模糊去重误判 | 误合并 / 漏合并 | 分层策略 + 低置信度进人工确认队列 |
| 3 | 转账闭环识别不准 | 内部转账漏匹配 | 多因子评分 + 可配置参数 |
| 4 | Firefly III 映射不完整 | 导入失败 | 导入前校验 + 失败可重试 |
| 5 | 规则增长后性能退化 | 响应变慢 | 规则预筛选 + 索引优化 |

---

## 3. 总体架构设计

### 3.1 系统分层架构图

系统采用经典的领域驱动设计（DDD）分层思想，严格隔离各平台差异：

```mermaid
graph TD
    subgraph "展现层 (Vue3 + TypeScript + Vuetify)"
        UI_Import["导入中心<br/>文件上传 / 批次管理"]
        UI_Preview["数据清洗预览<br/>标准化结果 / 字段对比"]
        UI_Dedup["去重处理<br/>重复确认 / 链路合并"]
        UI_Rule["规则管理<br/>分类 / 账户 / 标签配置"]
        UI_Task["导入任务<br/>执行 / 结果 / 重试"]
        UI_Audit["数据审计<br/>全链路追溯"]
        UI_Setting["系统设置<br/>Firefly 连接 / 参数配置"]
    end

    subgraph "接入层 (GIN RESTful API)"
        API_Import["导入 API"]
        API_Data["数据流转 API"]
        API_Rule["规则管理 API"]
        API_Export["导出 API"]
        API_Config["配置 API"]
    end

    subgraph "应用服务层 (Application Service)"
        SVC_Batch["ImportBatchService<br/>批次编排"]
        SVC_Pipeline["PipelineService<br/>ETL 流水线"]
    end

    subgraph "业务逻辑层 (Core Domain)"
        subgraph "Adapter 解析层 (插件化)"
            Parser_Alipay["支付宝解析器"]
            Parser_WeChat["微信解析器"]
            Parser_CCB["建行解析器"]
            Parser_ICBC["工行解析器"]
        end
        Norm["Normalize 标准化引擎"]
        Match["Match 去重引擎"]
        Link["Link 链路合并引擎"]
        Rule["Rule 规则映射引擎"]
        Export["Export 导出推送引擎"]
    end

    subgraph "数据持久层 (GORM + SQLite)"
        DB[(SQLite Database)]
    end

    subgraph "外部系统 (External)"
        FF["Firefly III"]
        DI["Data Importer"]
    end

    UI_Import & UI_Preview & UI_Dedup & UI_Rule & UI_Task & UI_Audit & UI_Setting --> API_Import & API_Data & API_Rule & API_Export & API_Config
    API_Import & API_Data & API_Rule & API_Export --> SVC_Batch & SVC_Pipeline
    SVC_Pipeline --> Parser_Alipay & Parser_WeChat & Parser_CCB & Parser_ICBC
    Parser_Alipay & Parser_WeChat & Parser_CCB & Parser_ICBC --> Norm
    Norm --> Match --> Link --> Rule --> Export
    Export --> FF & DI
    Norm & Match & Link & Rule & Export -.-> DB
```

### 3.2 分层职责定义

| 层级 | 技术选型 | 核心职责 |
|------|----------|----------|
| **展现层** | Vue3 + TS + Vuetify | 上传文件、批次管理、预览确认、规则配置、人工确认、导入结果展示 |
| **接入层** | GIN Framework | RESTful 接口，统一参数校验、错误处理、响应封装 |
| **应用服务层** | Go Service | 编排完整业务流程（ETL Pipeline），不承载具体解析规则 |
| **Adapter 层** | Go Plugin Interface | 按平台解析原始文件，输出平台原始记录 DTO |
| **Normalize 层** | Go Service | 统一字段、金额、方向、时间、分类原始值 |
| **Match 层** | Go Service | 严格去重、模糊去重（多因子评分） |
| **Link 层** | Go Service | 转账闭环、订单链路聚合 |
| **Rule 层** | Go Service | 分类、账户、对手方、标签映射 |
| **Export 层** | Go Service | 适配 Firefly III / Data Importer API 或 CSV/JSON |
| **Repository 层** | GORM | 隔离数据库访问，面向领域对象持久化 |
| **数据持久层** | SQLite | 本地数据库，存储全量数据与审计链路 |

### 3.3 数据流转拓扑

```
┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  多源账单导入  │ -> │  解析器(Parser) │ -> │ 标准化入库     │ -> │ 去重与链路合并  │ -> │  规则映射      │ -> │ 导出/推送      │
│  (文件上传)   │    │  (Adapter层)   │    │ (SQLite)      │    │ (Dedup+Link)  │    │ (Rule Engine) │    │ (API/CSV)     │
└──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘
```

---

## 4. 系统模块划分

### 4.1 模块清单

| 模块 | 包名 | 职责 | 优先级 |
|------|------|------|--------|
| 导入中心 | `import-center` | 文件上传、批次管理、来源识别 | P0 |
| 解析引擎 | `parser-engine` | 平台解析器注册、装载与执行 | P0 |
| 标准化引擎 | `normalize-engine` | 统一模型转换 | P0 |
| 去重引擎 | `dedup-engine` | 严格去重与模糊去重 | P0/P1 |
| 链路引擎 | `link-engine` | 转账闭环与订单链路合并 | P0 |
| 规则引擎 | `rule-engine` | 分类/账户/标签/商户归一化 | P0/P1 |
| 导入编排 | `import-orchestrator` | 导入预览、执行、重试 | P0 |
| 审计中心 | `audit-center` | 审计日志、处理链追溯 | P0 |
| 系统设置 | `settings-center` | Firefly 配置、阈值参数 | P1 |

### 4.2 后端包结构（Go）

```text
projectmoneyx-server/
├── cmd/
│   └── server/
│       └── main.go                 # 程序入口
├── internal/
│   ├── config/                     # 配置管理
│   │   └── config.go
│   ├── handler/                    # GIN Handler（接入层）
│   │   ├── import_handler.go
│   │   ├── transaction_handler.go
│   │   ├── dedup_handler.go
│   │   ├── rule_handler.go
│   │   ├── export_handler.go
│   │   └── audit_handler.go
│   ├── middleware/                  # 中间件
│   │   ├── error_handler.go
│   │   └── cors.go
│   ├── service/                    # 应用服务层
│   │   ├── import_batch_service.go
│   │   ├── pipeline_service.go
│   │   ├── dedup_service.go
│   │   ├── rule_apply_service.go
│   │   ├── export_service.go
│   │   └── audit_service.go
│   ├── domain/                     # 领域层
│   │   ├── entity/                 # 领域实体
│   │   │   ├── source_file.go
│   │   │   ├── raw_record.go
│   │   │   ├── transaction.go
│   │   │   ├── rule.go
│   │   │   ├── import_task.go
│   │   │   └── audit_log.go
│   │   ├── valueobject/            # 值对象
│   │   │   ├── direction.go
│   │   │   ├── platform.go
│   │   │   └── batch_status.go
│   │   └── repository/            # 仓储接口
│   │       ├── source_file_repo.go
│   │       ├── raw_record_repo.go
│   │       ├── transaction_repo.go
│   │       └── rule_repo.go
│   ├── parser/                     # Adapter 解析层（插件化）
│   │   ├── interface.go            # BillParser 接口定义
│   │   ├── registry.go             # 解析器注册中心
│   │   ├── alipay/
│   │   │   └── alipay_parser.go
│   │   ├── wechat/
│   │   │   └── wechat_parser.go
│   │   ├── ccb/
│   │   │   └── ccb_parser.go
│   │   └── icbc/
│   │       └── icbc_parser.go
│   ├── normalize/                  # 标准化引擎
│   │   └── normalizer.go
│   ├── matcher/                    # 去重引擎
│   │   ├── strict_dedup.go
│   │   ├── fuzzy_dedup.go
│   │   └── scorer.go
│   ├── linker/                     # 链路合并引擎
│   │   ├── transfer_linker.go
│   │   └── order_linker.go
│   ├── rule/                       # 规则引擎
│   │   ├── engine.go
│   │   ├── category_mapper.go
│   │   ├── account_mapper.go
│   │   └── merchant_normalizer.go
│   ├── exporter/                   # 导出引擎
│   │   ├── firefly_exporter.go
│   │   └── csv_exporter.go
│   ├── dao/                        # 数据访问层（GORM 实现）
│   │   ├── source_file_dao.go
│   │   ├── raw_record_dao.go
│   │   ├── transaction_dao.go
│   │   └── rule_dao.go
│   └── dto/                        # 数据传输对象
│       ├── request/
│       └── response/
├── migrations/                     # 数据库迁移
├── web/                            # 前端打包产物
└── go.mod
```

### 4.3 前端项目结构（Vue3）

```text
projectmoneyx-web/
├── src/
│   ├── api/                        # API 调用封装
│   │   ├── import.ts
│   │   ├── transaction.ts
│   │   ├── dedup.ts
│   │   ├── rule.ts
│   │   ├── export.ts
│   │   └── audit.ts
│   ├── views/                      # 页面视图
│   │   ├── ImportCenterView.vue    # 导入中心
│   │   ├── BatchDetailView.vue     # 批次详情
│   │   ├── PreviewView.vue         # 清洗结果预览
│   │   ├── DedupReviewView.vue     # 去重处理
│   │   ├── RuleConfigView.vue      # 规则配置
│   │   ├── ImportTaskView.vue      # 导入任务
│   │   ├── AuditTraceView.vue      # 审计追溯
│   │   └── SettingsView.vue        # 系统设置
│   ├── components/                 # 可复用组件
│   │   ├── FileUploader.vue
│   │   ├── TransactionTable.vue
│   │   ├── RuleEditor.vue
│   │   ├── DedupCompare.vue
│   │   └── AuditTimeline.vue
│   ├── stores/                     # Pinia 状态管理
│   │   ├── importStore.ts
│   │   ├── transactionStore.ts
│   │   └── ruleStore.ts
│   ├── types/                      # TypeScript 类型定义
│   │   ├── transaction.ts
│   │   ├── rule.ts
│   │   └── common.ts
│   ├── router/                     # Vue Router
│   │   └── index.ts
│   ├── plugins/                    # Vuetify 等插件
│   │   └── vuetify.ts
│   ├── App.vue
│   └── main.ts
├── package.json
├── tsconfig.json
└── vite.config.ts
```

---

## 5. 核心业务流程设计

### 5.1 主流程时序图

```mermaid
sequenceDiagram
    participant U as 用户
    participant FE as Web UI (Vue3)
    participant API as GIN API
    participant SVC as Pipeline Service
    participant PARSER as Parser Engine
    participant NORM as Normalize Engine
    participant MATCH as Match/Link Engine
    participant RULE as Rule Engine
    participant EXP as Export Engine
    participant DB as SQLite (GORM)
    participant FF as Firefly III / Data Importer

    U->>FE: 上传账单文件（拖拽/选择）
    FE->>API: POST /api/v1/import/batches (multipart)
    API->>SVC: createBatch(files, platform)
    SVC->>DB: 保存 source_files + 创建 import_batch
    SVC->>PARSER: 自动识别来源 → 加载对应 Parser
    PARSER->>PARSER: 逐行解析原始数据
    PARSER->>DB: 批量保存 raw_records
    SVC->>NORM: 标准化转换
    NORM->>NORM: 字段映射、金额归一、时区对齐
    NORM->>DB: 批量保存 transactions (status=PENDING_CLEAN)
    SVC->>MATCH: 执行去重流程
    MATCH->>MATCH: 1. 严格去重（精确匹配）
    MATCH->>MATCH: 2. 模糊去重（多因子评分）
    MATCH->>MATCH: 3. 链路合并（转账闭环）
    MATCH->>DB: 保存 dedup_relations / link_relations
    SVC->>RULE: 应用映射规则
    RULE->>RULE: 对手方归一 → 分类映射 → 账户映射 → 标签映射
    RULE->>DB: 更新 category_mapped / account / tag
    API-->>FE: 返回预览数据
    U->>FE: 查看预览，确认无误
    FE->>API: POST /api/v1/import/tasks (确认导入)
    API->>EXP: 执行导出推送
    EXP->>FF: 调用 API 推送 / 生成 CSV 下载
    FF-->>EXP: 返回导入结果
    EXP->>DB: 保存 import_results, 更新任务状态
    API-->>FE: 展示导入结果（成功/失败统计）
```

### 5.2 批次状态机

```mermaid
stateDiagram-v2
    [*] --> CREATED: 创建批次
    CREATED --> UPLOADED: 文件上传完成
    UPLOADED --> PARSING: 触发解析
    PARSING --> PARSED: 解析完成
    PARSED --> NORMALIZING: 触发标准化
    NORMALIZING --> NORMALIZED: 标准化完成
    NORMALIZED --> MATCHING: 触发去重/链路
    MATCHING --> MATCHED: 去重/链路完成
    MATCHED --> RULE_APPLYING: 触发规则映射
    RULE_APPLYING --> PREVIEW_READY: 规则映射完成，可预览
    PREVIEW_READY --> IMPORTING: 用户确认导入
    IMPORTING --> IMPORT_SUCCESS: 全部导入成功
    IMPORTING --> PARTIAL_FAILED: 部分失败
    IMPORTING --> IMPORT_FAILED: 全部失败
    PARTIAL_FAILED --> RETRYING: 用户重试失败项
    IMPORT_FAILED --> RETRYING: 用户重试
    RETRYING --> IMPORT_SUCCESS: 重试成功
    RETRYING --> PARTIAL_FAILED: 仍有失败

    note right of PARSING: 解析失败时\n状态回退至 UPLOADED\n记录错误信息
    note right of PREVIEW_READY: 用户可在此阶段\n查看预览结果\n人工确认去重
```

### 5.3 ETL Pipeline 编排逻辑

```go
// PipelineService 负责编排整个 ETL 流程
type PipelineService struct {
    parserRegistry  *parser.Registry
    normalizer      *normalize.Normalizer
    strictDedup     *matcher.StrictDedup
    fuzzyDedup      *matcher.FuzzyDedup
    transferLinker  *linker.TransferLinker
    ruleEngine      *rule.Engine
    batchRepo       repository.ImportBatchRepo
    transactionRepo repository.TransactionRepo
}

func (s *PipelineService) Process(ctx context.Context, batchID string) error {
    // 阶段 1: 解析
    rawRecords, err := s.parse(ctx, batchID)
    if err != nil { return err }

    // 阶段 2: 标准化
    transactions, err := s.normalize(ctx, rawRecords)
    if err != nil { return err }

    // 阶段 3: 严格去重
    transactions, err = s.strictDedup.Execute(ctx, transactions)
    if err != nil { return err }

    // 阶段 4: 模糊去重（P1 阶段）
    transactions, err = s.fuzzyDedup.Execute(ctx, transactions)
    if err != nil { return err }

    // 阶段 5: 链路合并
    transactions, err = s.transferLinker.Execute(ctx, transactions)
    if err != nil { return err }

    // 阶段 6: 规则映射
    err = s.ruleEngine.Apply(ctx, transactions)
    if err != nil { return err }

    // 更新批次状态为 PREVIEW_READY
    return s.batchRepo.UpdateStatus(ctx, batchID, "PREVIEW_READY")
}
```

---

## 6. 账单解析与标准化设计

### 6.1 解析器接口设计

```go
// BillParser 是所有平台解析器必须实现的接口
type BillParser interface {
    // Platform 返回平台标识符，如 "alipay", "wechat", "ccb"
    Platform() string

    // Detect 根据文件元信息和表头判断是否为本平台文件
    Detect(fileMeta FileMeta, header []string) bool

    // Parse 解析指定文件，返回原始记录列表
    Parse(ctx context.Context, reader io.Reader) ([]RawBillRecord, error)
}

// FileMeta 文件元信息
type FileMeta struct {
    FileName string
    FileType string // csv, xlsx, txt
    FileHash string
    FileSize int64
}

// RawBillRecord 原始账单记录（平台相关的中间结构）
type RawBillRecord struct {
    SourcePlatform  string
    SourceRecordID  string
    RawFields       map[string]string // 原始 K-V 字段
    RowNo           int
    RowFingerprint  string
}
```

### 6.2 解析器注册中心

```go
// Registry 解析器注册中心
type Registry struct {
    parsers []BillParser
}

func NewRegistry() *Registry {
    r := &Registry{}
    // 注册所有解析器
    r.Register(&alipay.Parser{})
    r.Register(&wechat.Parser{})
    r.Register(&ccb.Parser{})
    r.Register(&icbc.Parser{})
    return r
}

// Detect 自动检测文件对应的解析器
func (r *Registry) Detect(meta FileMeta, header []string) (BillParser, error) {
    for _, p := range r.parsers {
        if p.Detect(meta, header) {
            return p, nil
        }
    }
    return nil, ErrUnknownPlatform
}
```

### 6.3 支付宝解析规则

支付宝账单数据维度：

```
交易时间 | 交易分类 | 交易对方 | 对方账号 | 商品说明 | 收/支 | 金额 | 收/付款方式 | 交易状态 | 交易订单号 | 商家订单号 | 备注
```

#### 支付宝字段映射表

| 原字段 | 目标字段 | 映射说明 |
|--------|----------|----------|
| 交易时间 | `trade_time` | 解析为 `time.Time`，统一 UTC+8 |
| 交易分类 | `category_raw` | 直接作为原始分类（22 种标准分类） |
| 交易对方 | `counterparty` | 交易对手名称 |
| 对方账号 | `counterparty_account` | 存入扩展字段 |
| 商品说明 | `merchant_name` / `note` | 优先作为商户名，辅助作为备注 |
| 收/支 | `direction` | "收入" → income, "支出" → expense, "其他" → other |
| 金额 | `amount` | 去除 ¥ 符号，解析为 Decimal 正数 |
| 收/付款方式 | `payment_method` | 存入扩展字段，可用于账户映射 |
| 交易状态 | `trade_status` | "交易成功" / "退款成功" 等 |
| 交易订单号 | `source_record_id` / `order_id` | 去除空格，作为唯一标识 |
| 商家订单号 | `merchant_order_id` / `parent_order_id` | 可用于链路关联 |
| 备注 | `note` | 补充备注 |

> **关键设计决策**：支付宝拥有最丰富的 22 种交易分类，系统将其作为**全局统一分类基准字典**。所有其他平台的交易分类最终都应映射到此套分类枚举。

#### 支付宝标准分类枚举（22 类）

| 编号 | 分类名称 | 编号 | 分类名称 |
|------|----------|------|----------|
| 1 | 餐饮美食 | 12 | 退款 |
| 2 | 投资理财 | 13 | 教育培训 |
| 3 | 日用百货 | 14 | 住房物业 |
| 4 | 数码电器 | 15 | 酒店旅游 |
| 5 | 交通出行 | 16 | 文化休闲 |
| 6 | 充值缴费 | 17 | 运动户外 |
| 7 | 信用借还 | 18 | 爱车养车 |
| 8 | 转账红包 | 19 | 商业服务 |
| 9 | 生活服务 | 20 | 母婴亲子 |
| 10 | 家居家装 | 21 | 收入 |
| 11 | 医疗健康 | 22 | 其他 |

### 6.4 微信解析规则

微信账单数据维度：

```
交易时间 | 交易类型 | 交易对方 | 商品 | 收/支 | 金额(元) | 支付方式 | 当前状态 | 交易单号 | 商户单号 | 备注
```

#### 微信字段映射表

| 原字段 | 目标字段 | 映射说明 |
|--------|----------|----------|
| 交易时间 | `trade_time` | 解析为 `time.Time`，统一 UTC+8 |
| 交易类型 | `category_raw` | 存储原始类型，需通过推断映射到标准分类 |
| 交易对方 | `counterparty` | 交易对手名称 |
| 商品 | `merchant_name` / `product_desc` | **关键字段**，用于推断实际消费分类 |
| 收/支 | `direction` | "收入" → income, "支出" → expense |
| 金额(元) | `amount` | 去除 ¥ 符号，解析为 Decimal 正数 |
| 支付方式 | `payment_method` | 存入扩展字段 |
| 当前状态 | `trade_status` | "支付成功" / "已退款" 等 |
| 交易单号 | `source_record_id` / `order_id` | 去除空格 |
| 商户单号 | `merchant_order_id` / `parent_order_id` | 链路关联 |
| 备注 | `note` | 补充备注 |

#### 微信分类推断规则

微信"交易类型"多为支付动作（商户消费、扫二维码付款、转账、红包等），无实际消费语义。需结合"商品"字段进行关键词推断：

```mermaid
flowchart TD
    A[微信原始记录] --> B{交易类型判断}
    B -->|转账| C[转账红包]
    B -->|微信红包| C
    B -->|微信红包-退款| D[退款]
    B -->|xxx-退款| D
    B -->|商户消费 / 扫二维码付款| E{商品关键词推断}
    E -->|美团/外卖/餐厅/咖啡/麦当劳/肯德基| F[餐饮美食]
    E -->|滴滴/打车/地铁/高铁/火车/机票/加油| G[交通出行]
    E -->|京东/超市/便利店/百货| H[日用百货]
    E -->|电费/水费/话费/燃气/宽带| I[充值缴费]
    E -->|医院/药店/门诊| J[医疗健康]
    E -->|电影/KTV/游戏/书籍| K[文化休闲]
    E -->|酒店/景点/旅行| L[酒店旅游]
    E -->|无法识别| M[其他 → 待人工补充]
```

#### 推断规则配置示例

| 微信交易类型 | 商品关键词 | 推断标准分类 |
|-------------|-----------|-------------|
| 商户消费 | 美团 / 外卖 / 餐厅 / 咖啡 / 面包 | 餐饮美食 |
| 商户消费 | 滴滴 / 打车 / 地铁 / 高铁 / 加油 | 交通出行 |
| 商户消费 | 京东 / 超市 / 便利店 / 百货 | 日用百货 |
| 商户消费 | 电费 / 水费 / 话费 / 燃气 | 充值缴费 |
| 商户消费 | 医院 / 药店 / 体检 | 医疗健康 |
| 商户消费 | 电影 / 游戏 / 书籍 | 文化休闲 |
| 转账 | — | 转账红包 |
| 微信红包 | — | 转账红包 |
| xxx-退款 | — | 退款 |

> **设计原则**：若商品内容无法识别关键词，先落入"其他"分类，并允许用户通过规则管理补充映射。系统应记录未命中规则的记录，便于后续规则完善。

### 6.5 统一交易模型

```mermaid
classDiagram
    class Transaction {
        +string ID
        +string TransactionID
        +string BatchID
        +string SourcePlatform
        +string SourceRecordID
        +time.Time TradeTime
        +decimal Amount
        +string Currency
        +string Direction
        +string Counterparty
        +string MerchantName
        +string CategoryRaw
        +string CategoryMapped
        +string OrderID
        +string ParentOrderID
        +string PaymentMethod
        +string Note
        +string RawPayload
        +string Status
        +time.Time CreatedAt
        +time.Time UpdatedAt
    }

    class Direction {
        <<enumeration>>
        INCOME
        EXPENSE
        TRANSFER
        REFUND
        FEE
        OTHER
    }

    class TransactionStatus {
        <<enumeration>>
        PENDING_CLEAN
        CLEANED
        PENDING_REVIEW
        READY_TO_IMPORT
        IMPORTING
        IMPORTED
        FAILED
        DUPLICATE
    }

    Transaction --> Direction
    Transaction --> TransactionStatus
```

#### 标准化规则

| 规则项 | 说明 |
|--------|------|
| 时间 | 统一存储为 `Asia/Shanghai (UTC+8)` |
| 金额 | 统一使用正数（`decimal(18,6)`），方向独立用 `direction` 表达 |
| 币种 | 默认 `CNY`，后续可扩展多币种 |
| 状态 | 初始为 `PENDING_CLEAN` |
| 原始快照 | 完整写入 `raw_payload`（JSON），确保审计可追溯 |
| 指纹 | 对关键字段做 SHA256，用于严格去重 |

---

## 7. 去重与链路合并设计

### 7.1 三层递进处理模型

系统采用"**基础去重 → 模糊去重 → 链路合并**"三层递进去重模型：

```mermaid
flowchart TD
    A[新标准化交易入库] --> B{第一层：严格去重}
    B -->|命中| X[标记为 DUPLICATE<br/>建立 dedup_relation]
    B -->|未命中| C{第二层：模糊去重}
    C -->|高置信度 ≥85| X
    C -->|中置信度 60-84| D[标记为 PENDING_REVIEW<br/>进入人工确认队列]
    C -->|低置信度 <60| E{第三层：链路合并}
    E -->|命中转账闭环| F[合并为 Transfer<br/>建立 link_relation]
    E -->|命中订单链路| G[聚合为订单链<br/>建立 parent_order]
    E -->|未命中| H[保留为独立交易<br/>status=CLEANED]

    style X fill:#ff6b6b,color:#fff
    style D fill:#ffd93d,color:#333
    style F fill:#6bcb77,color:#fff
    style G fill:#6bcb77,color:#fff
    style H fill:#4d96ff,color:#fff
```

### 7.2 严格去重（基础去重 — 精确匹配）

#### 唯一判定键

采用三级唯一性判定，优先级从高到低：

| 优先级 | 判定键 | 适用场景 |
|--------|--------|----------|
| 1 | `source_platform` + `source_record_id` | 同一平台重复导入 |
| 2 | `source_file_hash` + `row_fingerprint` | 同一文件重复上传 |
| 3 | `order_id`（若可信） | 跨批次订单号匹配 |

#### 行指纹算法

对以下字段标准化后做 SHA256 哈希，生成 `row_fingerprint`：

```go
func GenerateRowFingerprint(t *Transaction) string {
    raw := fmt.Sprintf("%s|%s|%s|%s|%s|%s",
        t.TradeTime.Format("2006-01-02 15:04"),  // 分钟粒度
        t.Amount.String(),
        t.Direction,
        normalizeString(t.Counterparty),
        normalizeString(t.MerchantName),
        t.OrderID,
    )
    hash := sha256.Sum256([]byte(raw))
    return hex.EncodeToString(hash[:])
}
```

#### 严格去重流程

```go
func (s *StrictDedup) Execute(ctx context.Context, txns []*Transaction) ([]*Transaction, error) {
    var result []*Transaction
    for _, txn := range txns {
        // 判定键 1: platform + record_id
        exists, existingID := s.repo.FindByPlatformAndRecordID(
            ctx, txn.SourcePlatform, txn.SourceRecordID)
        if exists {
            s.createDedupRelation(ctx, txn.ID, existingID, "strict", 100)
            txn.Status = "DUPLICATE"
            continue
        }
        // 判定键 2: file_hash + fingerprint
        exists, existingID = s.repo.FindByFingerprint(ctx, txn.RowFingerprint)
        if exists {
            s.createDedupRelation(ctx, txn.ID, existingID, "strict", 100)
            txn.Status = "DUPLICATE"
            continue
        }
        result = append(result, txn)
    }
    return result, nil
}
```

### 7.3 模糊去重（多因子评分 — P1 阶段）

#### 多因子评分模型

| 因子 | 分值 | 评分说明 |
|------|------|----------|
| 时间在 ±5 分钟内 | 30 | 时间差越小得分越高，超出窗口直接 0 分 |
| 金额精确一致 | 30 | 金额一致得满分，差额在手续费容差内得部分分 |
| 交易方向一致 | 10 | 方向相同得满分 |
| 订单号相同/相近 | 15 | 完全一致 15 分，包含关系 10 分 |
| 对手方相似 | 10 | Levenshtein 相似度 + contains 判定 |
| 来源关联规则命中 | 5 | 预配置的平台关联规则 |

#### 判定阈值（可配置）

| 分值范围 | 判定结果 | 处理方式 |
|----------|----------|----------|
| ≥ 85 | 自动判定重复 | 自动标记 DUPLICATE |
| 60 ~ 84 | 疑似重复 | 标记 PENDING_REVIEW，进入人工确认队列 |
| < 60 | 不判定重复 | 保留独立交易 |

#### 评分算法伪代码

```go
type FuzzyScorer struct {
    TimeWindow    time.Duration  // 默认 5 分钟
    AmountEpsilon float64        // 金额容差（手续费）
}

func (s *FuzzyScorer) Score(a, b *Transaction) int {
    score := 0

    // 时间因子 (30分)
    timeDiff := math.Abs(a.TradeTime.Sub(b.TradeTime).Minutes())
    if timeDiff <= s.TimeWindow.Minutes() {
        score += int(30 * (1 - timeDiff/s.TimeWindow.Minutes()))
    }

    // 金额因子 (30分)
    if a.Amount.Equal(b.Amount) {
        score += 30
    } else if a.Amount.Sub(b.Amount).Abs().LessThan(decimal.NewFromFloat(s.AmountEpsilon)) {
        score += 20
    }

    // 方向因子 (10分)
    if a.Direction == b.Direction { score += 10 }

    // 订单号因子 (15分)
    if a.OrderID != "" && a.OrderID == b.OrderID {
        score += 15
    } else if strings.Contains(a.OrderID, b.OrderID) || strings.Contains(b.OrderID, a.OrderID) {
        score += 10
    }

    // 对手方因子 (10分)
    score += int(10 * counterpartySimilarity(a.Counterparty, b.Counterparty))

    // 来源规则因子 (5分)
    if s.platformLinked(a.SourcePlatform, b.SourcePlatform) { score += 5 }

    return score
}
```

### 7.4 链路合并（转账闭环 + 订单链路）

#### 7.4.1 转账闭环识别

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

**识别规则（5 项全部满足）**：

1. 金额一致
2. 一条为支出（expense），一条为收入（income）
3. 时间在可配置窗口内（默认 ±30 分钟）
4. 来源平台不同但账户映射可闭环
5. 非退款、非手续费类型

```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)),
    }
}
```

#### 7.4.2 订单链路合并

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

**合并策略**：

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

### 7.5 人工确认机制

```mermaid
sequenceDiagram
    participant U as 用户
    participant FE as Web UI
    participant API as GIN API
    participant DB as SQLite

    FE->>API: GET /api/v1/dedup/reviews?batchId=xxx
    API->>DB: 查询 status=PENDING_REVIEW 的记录
    DB-->>API: 返回疑似重复关系列表
    API-->>FE: 展示疑似重复对比（评分、命中因子、原始数据）
    U->>FE: 确认合并 / 拒绝合并
    FE->>API: POST /api/v1/dedup/reviews/{id}/confirm 或 /reject
    API->>DB: 更新 dedup_relation 状态
    API->>DB: 更新 transaction 状态
    API-->>FE: 返回处理结果
```

---

## 8. 规则引擎设计

### 8.1 规则模型

```mermaid
classDiagram
    class Rule {
        +string ID
        +string RuleType
        +int Priority
        +string PlatformScope
        +JSON Conditions
        +JSON Actions
        +bool Enabled
        +string Description
        +time.Time CreatedAt
        +time.Time UpdatedAt
    }

    class RuleType {
        <<enumeration>>
        CATEGORY_MAPPING
        ACCOUNT_MAPPING
        COUNTERPARTY_NORMALIZE
        TAG_MAPPING
        MERCHANT_NORMALIZE
        FIREFLY_FIELD_MAPPING
    }

    class RuleHit {
        +string ID
        +string TransactionID
        +string RuleID
        +string MatchedCondition
        +string BeforeValue
        +string AfterValue
        +time.Time CreatedAt
    }

    Rule --> RuleType
    Rule "1" --> "*" RuleHit : produces
```

### 8.2 规则匹配条件

规则条件采用 JSON 结构化存储，支持以下匹配维度：

| 条件类型 | 字段 | 说明 | 示例 |
|----------|------|------|------|
| 平台过滤 | `platform` | 指定生效平台 | `"alipay"` |
| 原始分类 | `category_raw` | 原始分类匹配 | `"餐饮美食"` |
| 关键词 | `keywords` | 商品/商户名关键词 | `["美团", "外卖"]` |
| 正则 | `regex` | 正则表达式匹配 | `"^滴滴.*出行$"` |
| 金额范围 | `amount_range` | [min, max] | `[0, 50]` |
| 方向 | `direction` | 收支方向 | `"expense"` |
| 对手方 | `counterparty` | 对手方包含 | `"支付宝"` |

#### 规则条件 JSON 示例

```json
{
  "platform": "wechat",
  "conditions": {
    "category_raw": "商户消费",
    "keywords": ["美团", "外卖", "饿了么"],
    "direction": "expense"
  },
  "actions": {
    "category_mapped": "餐饮美食",
    "merchant_normalized": "外卖平台"
  }
}
```

### 8.3 规则执行顺序

规则引擎按以下**严格顺序**执行，确保前置归一化提升后续规则命中率：

```mermaid
flowchart LR
    A["1. 对手方归一化<br/>COUNTERPARTY_NORMALIZE"] --> B["2. 商户归一化<br/>MERCHANT_NORMALIZE"]
    B --> C["3. 分类映射<br/>CATEGORY_MAPPING"]
    C --> D["4. 账户映射<br/>ACCOUNT_MAPPING"]
    D --> E["5. 标签映射<br/>TAG_MAPPING"]
    E --> F["6. Firefly 字段映射<br/>FIREFLY_FIELD_MAPPING"]
```

**执行原则**：
- 同一类型内按 `priority` 升序执行（数字越小优先级越高）
- 先做归一再做分类，可提升规则命中率与稳定性
- 每条交易记录所有命中的规则 ID 和前后字段对比

### 8.4 规则引擎核心实现

```go
type Engine struct {
    ruleRepo repository.RuleRepo
    hitRepo  repository.RuleHitRepo
}

func (e *Engine) Apply(ctx context.Context, txns []*Transaction) error {
    // 按类型分组加载启用的规则
    ruleGroups := e.loadRulesGroupByType(ctx)

    executionOrder := []string{
        "COUNTERPARTY_NORMALIZE",
        "MERCHANT_NORMALIZE",
        "CATEGORY_MAPPING",
        "ACCOUNT_MAPPING",
        "TAG_MAPPING",
        "FIREFLY_FIELD_MAPPING",
    }

    for _, txn := range txns {
        for _, ruleType := range executionOrder {
            rules := ruleGroups[ruleType]
            for _, rule := range rules {
                if !rule.MatchPlatform(txn.SourcePlatform) { continue }
                if rule.MatchConditions(txn) {
                    before := txn.Snapshot()
                    rule.ApplyActions(txn)
                    after := txn.Snapshot()
                    // 记录命中日志
                    e.hitRepo.Save(ctx, &RuleHit{
                        TransactionID:  txn.ID,
                        RuleID:         rule.ID,
                        MatchedCondition: rule.ConditionsJSON,
                        BeforeValue:    before,
                        AfterValue:     after,
                    })
                    break // 同类型首条命中即停止（优先级控制）
                }
            }
        }
    }
    return nil
}
```

### 8.5 可解释性设计

每条交易保留完整的规则命中记录，用于前端"为何被分到餐饮/交通"的解释展示：

| 信息项 | 说明 |
|--------|------|
| 命中规则 ID | 关联 rules 表 |
| 命中条件摘要 | 匹配的具体关键词/正则 |
| 变更前值 | 规则执行前的字段值 |
| 变更后值 | 规则执行后的字段值 |
| 命中时间 | 规则执行时间戳 |

---

## 9. Firefly III / Data Importer 适配设计

### 9.1 两段式规则映射策略

```mermaid
flowchart LR
    subgraph "第一阶段 — ProjectMoneyX 负责"
        A1["字段级映射<br/>异构字段 → 统一模型"]
        A2["业务分类映射<br/>对手方/描述 → 分类/标签"]
        A3["商户名归一化<br/>别名 → 统一名称"]
    end

    subgraph "第二阶段 — Firefly III 负责"
        B1["最后一层字段适配"]
        B2["临时补充规则"]
        B3["导入格式兼容"]
    end

    A1 --> A2 --> A3 --> B1 --> B2 --> B3
```

### 9.2 导出模式

#### 模式 A：API 推送模式（优先）

```mermaid
sequenceDiagram
    participant EXP as Export Engine
    participant DI as Data Importer
    participant FF as Firefly III

    EXP->>EXP: 构建 Firefly 兼容 DTO
    EXP->>DI: POST /api/v1/import (batch)
    DI->>FF: 转发至 Firefly III
    FF-->>DI: 返回导入结果
    DI-->>EXP: 返回成功/失败明细
    EXP->>EXP: 更新 import_results 表
```

#### 模式 B：中间文件导出模式

- 生成完全符合 Data Importer 规范的标准 CSV / JSON
- 用户手动下载后在 Data Importer 中执行导入
- 适合 API 不可用或权限受限场景

### 9.3 Firefly 交易类型映射

| 内部 Direction | Firefly Type | 说明 |
|----------------|--------------|------|
| `expense` | `withdrawal` | 支出 |
| `income` | `deposit` | 收入 |
| `transfer` | `transfer` | 内部转账 |
| `refund` | `deposit` | 退款（作为收入处理） |
| `fee` | `withdrawal` | 手续费（作为支出处理） |

### 9.4 导入前校验清单

| # | 校验项 | 说明 |
|---|--------|------|
| 1 | 必填字段完整性 | amount, trade_time, direction 不可为空 |
| 2 | 金额格式合法性 | 必须为正数，精度不超过 6 位小数 |
| 3 | 时间格式合法性 | 必须为有效日期时间 |
| 4 | 账户映射完整性 | 来源平台必须有对应的 Firefly 账户映射 |
| 5 | 重复导入拦截 | 检查 transaction_id 是否已在 Firefly III 中存在 |
| 6 | 未确认记录检查 | 是否存在 PENDING_REVIEW 状态的疑似重复记录 |

### 9.5 导入后反馈

```go
type ImportResult struct {
    TaskID        string
    TransactionID string
    Status        string    // success / failed
    ErrorCode     string    // 错误码
    ErrorMessage  string    // 错误描述
    FireflyTxnID  string    // Firefly III 返回的交易 ID
    RetryCount    int       // 已重试次数
    CreatedAt     time.Time
}
```

- 导入成功/失败数量统计
- 失败原因分类展示（字段缺失、格式错误、账户不存在等）
- 失败记录可单独重试（无需整批重做）

---

## 10. 数据库详细设计（SQLite + GORM）

### 10.1 ER 关系图

```mermaid
erDiagram
    IMPORT_BATCHES ||--o{ SOURCE_FILES : contains
    SOURCE_FILES ||--o{ RAW_RECORDS : contains
    IMPORT_BATCHES ||--o{ TRANSACTIONS : generates
    RAW_RECORDS ||--|| TRANSACTIONS : normalizes_to
    TRANSACTIONS ||--o{ DEDUP_RELATIONS : source
    TRANSACTIONS ||--o{ LINK_RELATIONS : linked
    TRANSACTIONS ||--o{ RULE_HITS : matched
    RULES ||--o{ RULE_HITS : referenced
    IMPORT_BATCHES ||--o{ IMPORT_TASKS : owns
    IMPORT_TASKS ||--o{ IMPORT_RESULTS : produces
    TRANSACTIONS ||--o{ AUDIT_LOGS : traced

    IMPORT_BATCHES {
        string id PK "UUID 主键"
        string status "批次状态"
        int total_files "文件总数"
        int total_records "记录总数"
        int success_count "成功数"
        int failed_count "失败数"
        int duplicate_count "重复数"
        datetime created_at "创建时间"
        datetime updated_at "更新时间"
    }

    SOURCE_FILES {
        string id PK "UUID 主键"
        string batch_id FK "批次 ID"
        string file_name "原始文件名"
        string file_hash "文件 SHA256 哈希"
        string source_platform "来源平台"
        string file_type "csv/xlsx/txt"
        int file_size "文件大小(bytes)"
        datetime uploaded_at "上传时间"
    }

    RAW_RECORDS {
        string id PK "UUID 主键"
        string source_file_id FK "来源文件 ID"
        int row_no "行号"
        string source_platform "平台"
        string source_record_id "原始流水号"
        string row_fingerprint "行指纹 SHA256"
        text raw_payload "原始 JSON 快照"
        string parse_status "解析状态"
        text parse_error "错误信息"
    }

    TRANSACTIONS {
        string id PK "UUID 主键"
        string transaction_id UK "业务唯一 ID"
        string batch_id FK "导入批次"
        string raw_record_id FK "原始记录 ID"
        string source_platform "来源平台"
        string source_record_id "原始记录号"
        datetime trade_time "交易时间"
        decimal amount "金额 decimal(18,6)"
        string currency "币种"
        string direction "方向"
        string counterparty "对手方"
        string merchant_name "商户名"
        string category_raw "原始分类"
        string category_mapped "映射分类"
        string account_mapped "映射账户"
        string tags "标签(逗号分隔)"
        string order_id "订单号"
        string parent_order_id "父链路号"
        string payment_method "支付方式"
        text note "备注"
        text raw_payload "原始记录 JSON"
        string status "状态"
        string firefly_txn_id "Firefly 交易 ID"
        datetime imported_at "导入时间"
        datetime created_at "创建时间"
        datetime updated_at "更新时间"
    }

    DEDUP_RELATIONS {
        string id PK "UUID 主键"
        string src_transaction_id FK "原交易 ID"
        string target_transaction_id FK "目标交易 ID"
        string relation_type "strict/fuzzy"
        int confidence "置信度 0-100"
        string status "auto/confirmed/rejected"
        text reason_json "判定依据 JSON"
        datetime created_at "创建时间"
    }

    LINK_RELATIONS {
        string id PK "UUID 主键"
        string parent_transaction_id FK "主交易 ID"
        string child_transaction_id FK "子交易 ID"
        string link_type "transfer/order/refund/fee"
        text reason_json "关联依据 JSON"
        datetime created_at "创建时间"
    }

    RULES {
        string id PK "UUID 主键"
        string rule_type "规则类型"
        int priority "优先级(越小越高)"
        string platform_scope "平台范围(all/alipay/wechat)"
        text conditions_json "条件 JSON"
        text actions_json "动作 JSON"
        bool enabled "是否启用"
        string description "规则描述"
        datetime created_at "创建时间"
        datetime updated_at "更新时间"
    }

    RULE_HITS {
        string id PK "UUID 主键"
        string transaction_id FK "交易 ID"
        string rule_id FK "规则 ID"
        text matched_condition "命中条件摘要"
        text before_value "变更前值"
        text after_value "变更后值"
        datetime created_at "执行时间"
    }

    IMPORT_TASKS {
        string id PK "UUID 主键"
        string batch_id FK "批次 ID"
        string export_mode "api/csv"
        string status "pending/running/success/partial_failed/failed"
        int total_count "总记录数"
        int success_count "成功数"
        int failed_count "失败数"
        datetime started_at "开始时间"
        datetime finished_at "完成时间"
    }

    IMPORT_RESULTS {
        string id PK "UUID 主键"
        string task_id FK "任务 ID"
        string transaction_id FK "交易 ID"
        string status "success/failed"
        string error_code "错误码"
        text error_message "错误描述"
        string firefly_txn_id "Firefly 返回 ID"
        int retry_count "重试次数"
        datetime created_at "创建时间"
    }

    AUDIT_LOGS {
        string id PK "UUID 主键"
        string entity_type "实体类型"
        string entity_id "实体 ID"
        string action "操作类型"
        text before_snapshot "变更前快照"
        text after_snapshot "变更后快照"
        string operator "操作者"
        datetime created_at "操作时间"
    }
```

### 10.2 关键索引设计

| 表名 | 索引名 | 索引列 | 用途 |
|------|--------|--------|------|
| `transactions` | `idx_txn_platform_record` | `source_platform, source_record_id` | 严格去重判定键 1 |
| `transactions` | `idx_txn_fingerprint` | `row_fingerprint` | 严格去重判定键 2 |
| `transactions` | `idx_txn_batch` | `batch_id` | 批次查询 |
| `transactions` | `idx_txn_trade_time` | `trade_time` | 模糊去重时间分桶 |
| `transactions` | `idx_txn_order` | `order_id` | 订单号匹配 |
| `transactions` | `idx_txn_status` | `status` | 状态过滤 |
| `source_files` | `idx_sf_hash` | `file_hash` | 文件重复上传拦截 |
| `raw_records` | `idx_rr_fingerprint` | `row_fingerprint` | 行级去重 |
| `rules` | `idx_rule_type_priority` | `rule_type, priority` | 规则执行顺序 |
| `dedup_relations` | `idx_dedup_src` | `src_transaction_id` | 去重关系查询 |

### 10.3 GORM Model 定义示例

```go
// Transaction GORM 模型
type Transaction struct {
    ID               string          `gorm:"primaryKey;type:varchar(36)"`
    TransactionID    string          `gorm:"uniqueIndex;type:varchar(64)"`
    BatchID          string          `gorm:"index;type:varchar(36)"`
    RawRecordID      string          `gorm:"type:varchar(36)"`
    SourcePlatform   string          `gorm:"type:varchar(32);index:idx_platform_record"`
    SourceRecordID   string          `gorm:"type:varchar(128);index:idx_platform_record"`
    TradeTime        time.Time       `gorm:"index;not null"`
    Amount           decimal.Decimal `gorm:"type:decimal(18,6);not null"`
    Currency         string          `gorm:"type:varchar(16);default:'CNY'"`
    Direction        string          `gorm:"type:varchar(16);not null"`
    Counterparty     string          `gorm:"type:varchar(255)"`
    MerchantName     string          `gorm:"type:varchar(255)"`
    CategoryRaw      string          `gorm:"type:varchar(128)"`
    CategoryMapped   string          `gorm:"type:varchar(128)"`
    AccountMapped    string          `gorm:"type:varchar(128)"`
    Tags             string          `gorm:"type:varchar(512)"`
    OrderID          string          `gorm:"index;type:varchar(128)"`
    ParentOrderID    string          `gorm:"type:varchar(128)"`
    PaymentMethod    string          `gorm:"type:varchar(128)"`
    Note             string          `gorm:"type:text"`
    RawPayload       string          `gorm:"type:text"`
    RowFingerprint   string          `gorm:"index;type:varchar(64)"`
    Status           string          `gorm:"index;type:varchar(32);default:'PENDING_CLEAN'"`
    FireflyTxnID     string          `gorm:"type:varchar(128)"`
    ImportedAt       *time.Time
    CreatedAt        time.Time
    UpdatedAt        time.Time
}

func (Transaction) TableName() string { return "transactions" }
```

---

## 11. API 接口设计（GIN RESTful）

### 11.1 统一响应格式

```go
type Response struct {
    Code    int         `json:"code"`    // 0=成功, 非0=错误码
    Message string      `json:"message"` // 成功/错误说明
    Data    interface{} `json:"data"`    // 业务数据
}

type PageResponse struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data"`
    Total   int64       `json:"total"`
    Page    int         `json:"page"`
    Size    int         `json:"size"`
}
```

### 11.2 导入中心 API

| 方法 | 路径 | 说明 |
|------|------|------|
| `POST` | `/api/v1/import/batches` | 上传账单文件，创建批次 |
| `GET` | `/api/v1/import/batches` | 获取批次列表 |
| `GET` | `/api/v1/import/batches/:batchId` | 获取批次详情 |
| `POST` | `/api/v1/import/batches/:batchId/process` | 触发解析与清洗流水线 |
| `GET` | `/api/v1/import/batches/:batchId/preview` | 获取清洗预览结果 |
| `DELETE` | `/api/v1/import/batches/:batchId` | 删除批次 |

#### 上传账单文件

```
POST /api/v1/import/batches
Content-Type: multipart/form-data

参数:
  - files[]            (必填) 账单文件
  - sourcePlatform     (可选) 指定来源平台, 如 "alipay", "wechat"
  - autoDetect         (可选) 是否自动识别, 默认 true

响应:
{
  "code": 0,
  "message": "ok",
  "data": {
    "batchId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "UPLOADED",
    "filesCount": 2,
    "detectedPlatforms": ["alipay", "wechat"]
  }
}
```

### 11.3 交易记录 API

| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/transactions` | 分页查询交易记录 |
| `GET` | `/api/v1/transactions/:id` | 获取交易详情（含规则命中记录） |
| `GET` | `/api/v1/transactions/:id/trace` | 获取交易完整处理链路 |

### 11.4 去重确认 API

| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/dedup/reviews` | 获取疑似重复列表 |
| `GET` | `/api/v1/dedup/reviews/:reviewId` | 获取重复详情（含评分因子） |
| `POST` | `/api/v1/dedup/reviews/:reviewId/confirm` | 确认合并 |
| `POST` | `/api/v1/dedup/reviews/:reviewId/reject` | 拒绝合并 |

### 11.5 规则管理 API

| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/rules` | 获取规则列表（支持类型/平台过滤） |
| `POST` | `/api/v1/rules` | 创建规则 |
| `PUT` | `/api/v1/rules/:id` | 更新规则 |
| `DELETE` | `/api/v1/rules/:id` | 删除规则 |
| `POST` | `/api/v1/rules/evaluate` | 重新评估规则（修改规则后触发） |
| `POST` | `/api/v1/rules/:id/test` | 测试规则命中预览 |

### 11.6 导入/导出 API

| 方法 | 路径 | 说明 |
|------|------|------|
| `POST` | `/api/v1/import/tasks` | 创建导入任务（确认导入到 Firefly） |
| `GET` | `/api/v1/import/tasks/:taskId` | 获取导入任务详情 |
| `POST` | `/api/v1/import/tasks/:taskId/retry` | 重试失败项 |
| `GET` | `/api/v1/export/csv/:batchId` | 导出批次为 CSV 文件 |

### 11.7 审计与系统 API

| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/audit/logs` | 获取操作日志列表 |
| `GET` | `/api/v1/settings` | 获取系统配置 |
| `PUT` | `/api/v1/settings` | 更新系统配置（Firefly 连接等） |
| `POST` | `/api/v1/settings/test-connection` | 测试 Firefly III 连接 |

---

## 12. 前端页面设计（Vue3 + Vuetify）

### 12.1 信息架构与导航

```mermaid
graph LR
    NAV[侧边导航栏] --> A[导入中心]
    NAV --> B[数据清洗]
    NAV --> C[去重处理]
    NAV --> D[规则管理]
    NAV --> E[导入任务]
    NAV --> F[数据审计]
    NAV --> G[系统设置]

    A --> A1[文件上传页]
    A --> A2[批次列表页]
    A --> A3[批次详情页]

    B --> B1[清洗结果预览页]

    C --> C1[重复记录处理页]

    D --> D1[规则列表页]
    D --> D2[规则编辑页]

    E --> E1[导入任务列表页]
    E --> E2[导入结果详情页]

    F --> F1[审计追溯页]
    F --> F2[交易处理链路页]

    G --> G1[Firefly 连接配置]
    G --> G2[去重参数配置]
```

### 12.2 页面职责与交互说明

#### 12.2.1 导入中心 — 文件上传页

| 元素 | 说明 |
|------|------|
| 拖拽上传区域 | 支持拖拽或点击选择文件，支持批量选择 |
| 来源识别结果 | 自动检测文件来源平台，允许手动修改 |
| 批量文件列表 | 显示已选文件名、大小、检测到的平台 |
| 处理按钮 | "开始处理"触发 ETL Pipeline |
| 历史批次入口 | 快速跳转至批次列表 |

**核心组件**：`FileUploader.vue`
- 使用 Vuetify `v-file-input` + 自定义拖拽区域
- 文件选择后立即调用 `POST /api/v1/import/batches`
- 上传进度条实时展示

#### 12.2.2 清洗结果预览页

| 元素 | 说明 |
|------|------|
| 交易列表表格 | 分页展示标准化后的交易记录 |
| 字段对比面板 | 点击任一行展开：原始字段 vs 标准字段对比 |
| 规则命中说明 | 显示该条交易命中的具体规则和分类依据 |
| 状态标记 | 以颜色标签区分：待清洗/已清洗/重复/待确认 |
| 筛选器 | 支持按来源平台、分类、方向、状态过滤 |

**核心组件**：`TransactionTable.vue`
- 使用 Vuetify `v-data-table` 实现分页排序
- 行展开（`expanded`）显示详情面板
- 支持批量选择操作

#### 12.2.3 去重处理页

| 元素 | 说明 |
|------|------|
| 疑似重复列表 | 成对展示疑似重复的交易记录 |
| 评分展示 | 展示模糊去重的总分和各因子得分 |
| 对比视图 | 左右并排对比两条记录的关键字段 |
| 操作按钮 | 确认合并 / 拒绝合并 / 暂时跳过 |
| 链路视图 | 展示已识别的转账闭环和订单链路 |

**核心组件**：`DedupCompare.vue`
- 左右分栏对比布局
- 差异字段高亮显示
- 评分因子可展开查看

#### 12.2.4 规则配置页

| 元素 | 说明 |
|------|------|
| 规则列表 | 按类型分组展示，支持拖拽排序优先级 |
| 规则编辑器 | 可视化编辑条件（关键词/正则/金额范围等） |
| 动作配置 | 选择映射目标（分类/账户/标签） |
| 测试预览 | 输入测试数据，预览规则命中结果 |
| 启用/禁用 | 一键切换规则状态 |

**核心组件**：`RuleEditor.vue`
- 条件构建器：支持多条件 AND/OR 组合
- 使用 Vuetify `v-select`、`v-text-field`、`v-chip` 构建
- 测试按钮触发 `POST /api/v1/rules/:id/test`

#### 12.2.5 导入结果页

| 元素 | 说明 |
|------|------|
| 统计概览 | 成功/失败/总计数量，饼图展示 |
| 失败记录列表 | 展示失败原因分类 |
| 重试操作 | 支持单条重试和批量重试 |
| 导出按钮 | 下载 CSV 中间文件 |

#### 12.2.6 审计追溯页

| 元素 | 说明 |
|------|------|
| 交易搜索 | 按交易 ID / 订单号 / 时间范围搜索 |
| 处理链路时间线 | 纵向时间线展示完整处理链路 |
| 各阶段快照 | 原始文件 → 原始记录 → 标准化 → 规则命中 → 导入结果 |
| 操作日志 | 展示人工干预操作记录 |

**核心组件**：`AuditTimeline.vue`
- 使用 Vuetify `v-timeline` 组件
- 每个节点可展开查看详细快照数据

### 12.3 前端路由设计

```typescript
// router/index.ts
const routes = [
  { path: '/', redirect: '/import' },
  {
    path: '/import',
    name: 'ImportCenter',
    component: () => import('@/views/ImportCenterView.vue'),
    meta: { title: '导入中心', icon: 'mdi-upload' }
  },
  {
    path: '/import/batch/:batchId',
    name: 'BatchDetail',
    component: () => import('@/views/BatchDetailView.vue'),
    meta: { title: '批次详情' }
  },
  {
    path: '/preview/:batchId',
    name: 'Preview',
    component: () => import('@/views/PreviewView.vue'),
    meta: { title: '清洗预览' }
  },
  {
    path: '/dedup',
    name: 'DedupReview',
    component: () => import('@/views/DedupReviewView.vue'),
    meta: { title: '去重处理', icon: 'mdi-content-duplicate' }
  },
  {
    path: '/rules',
    name: 'RuleConfig',
    component: () => import('@/views/RuleConfigView.vue'),
    meta: { title: '规则管理', icon: 'mdi-cog-outline' }
  },
  {
    path: '/tasks',
    name: 'ImportTask',
    component: () => import('@/views/ImportTaskView.vue'),
    meta: { title: '导入任务', icon: 'mdi-export' }
  },
  {
    path: '/audit',
    name: 'AuditTrace',
    component: () => import('@/views/AuditTraceView.vue'),
    meta: { title: '数据审计', icon: 'mdi-history' }
  },
  {
    path: '/settings',
    name: 'Settings',
    component: () => import('@/views/SettingsView.vue'),
    meta: { title: '系统设置', icon: 'mdi-tune' }
  }
]
```

---

## 13. 非功能设计

### 13.1 性能设计

**目标**：单次导入 1 万条记录在主流程内完成解析与清洗，去重计算应在 30 秒内完成。

| 优化策略 | 说明 |
|----------|------|
| 批量插入 | `raw_records` 和 `transactions` 使用 GORM `CreateInBatches`，每批 500 条 |
| 关键索引 | `source_platform + source_record_id`、`batch_id`、`trade_time`、`order_id` |
| 模糊去重分桶 | 按 `trade_time` 时间分桶，避免全表扫描 |
| 规则预筛选 | 按平台和启用状态预加载规则，减少无效匹配 |
| 异步处理 | ETL Pipeline 使用 goroutine 异步执行，前端轮询状态 |
| 连接池 | SQLite 使用 WAL 模式提升并发读写性能 |

#### SQLite 性能配置

```go
func initDB(dbPath string) *gorm.DB {
    db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
    sqlDB, _ := db.DB()
    sqlDB.SetMaxOpenConns(1)    // SQLite 单写
    sqlDB.SetMaxIdleConns(10)
    // 启用 WAL 模式
    db.Exec("PRAGMA journal_mode=WAL")
    db.Exec("PRAGMA synchronous=NORMAL")
    db.Exec("PRAGMA cache_size=-64000") // 64MB cache
    return db
}
```

### 13.2 安全设计

| 安全措施 | 说明 |
|----------|------|
| 本地部署 | 默认本地运行，敏感账单数据不上传云端 |
| API Token 加密 | Firefly III API Token 使用 AES 加密存储 |
| 审计日志脱敏 | 日志中账号、订单号局部遮罩（如 `138****1234`） |
| 文件安全 | 上传文件限制类型和大小（默认最大 50MB） |
| CORS 配置 | 仅允许本地来源访问 API |

### 13.3 可维护性设计

| 设计原则 | 说明 |
|----------|------|
| 解析器插件化 | 新增平台只需实现 `BillParser` 接口并注册 |
| 规则条件 JSON 化 | 规则存储为 JSON，灵活扩展匹配条件 |
| 导入器解耦 | Export 层独立，可替换下游目标（不限于 Firefly III） |
| 分层 DTO/VO/Entity | Handler → DTO → Service → Entity → DAO，职责清晰 |
| 事务分阶段 | 每个 ETL 阶段独立事务，避免超长事务 |

### 13.4 可追溯性设计

| 追溯能力 | 实现方式 |
|----------|----------|
| 任一导入结果 → 原始文件 | 通过 `transaction.raw_record_id → raw_record.source_file_id → source_file` |
| 任一规则命中 → 解释说明 | 通过 `rule_hits` 表记录命中条件和前后字段值对比 |
| 任一合并操作 → 判定依据 | 通过 `dedup_relations.reason_json` 和 `link_relations.reason_json` |
| 任一操作 → 操作日志 | 通过 `audit_logs` 表记录实体变更和操作者信息 |

### 13.5 部署架构

```mermaid
graph TD
    subgraph "Docker 容器 / 本地部署"
        FE["前端静态资源<br/>(Vue3 Build → /web)"]
        BE["后端服务<br/>(Go Binary :8080)"]
        DB["SQLite 数据库<br/>(/data/projectmoneyx.db)"]
    end

    subgraph "外部依赖"
        FF["Firefly III<br/>(可选, API 推送)"]
        DI["Data Importer<br/>(可选, API 推送)"]
    end

    FE -->|"嵌入式静态服务"| BE
    BE --> DB
    BE -->|"HTTP API"| FF
    BE -->|"HTTP API"| DI
```

**部署方式**：

1. **Docker 部署**（推荐）：单容器包含前后端 + SQLite
2. **二进制部署**：交叉编译为单体可执行文件，前端资源使用 `embed` 嵌入

```dockerfile
# Dockerfile 示例
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o projectmoneyx ./cmd/server

FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/projectmoneyx .
COPY --from=builder /app/web ./web
VOLUME /data
EXPOSE 8080
CMD ["./projectmoneyx", "--db", "/data/projectmoneyx.db"]
```

---

## 14. 关键实现建议（Go + GIN + GORM）

### 14.1 分层编码规范

| 层级 | 职责 | 规范 |
|------|------|------|
| `handler` | 仅做参数绑定、校验、返回 | 不包含业务逻辑，调用 service 方法 |
| `service` | 编排业务流程 | 不直接操作数据库，调用 repository |
| `domain/entity` | 领域实体定义 | 包含业务方法和校验逻辑 |
| `domain/repository` | 仓储接口定义 | 仅定义接口，不含实现 |
| `dao` | GORM 数据访问实现 | 实现 repository 接口 |
| `parser` / `matcher` / `rule` / `exporter` | 独立可测试组件 | 纯函数风格，易于单元测试 |

### 14.2 推荐核心 Service 对象

```go
// 核心 Service 清单
type ImportBatchService struct { ... }        // 批次管理
type PipelineService struct { ... }           // ETL 流水线编排
type ParserRegistry struct { ... }            // 解析器注册中心
type TransactionNormalizeService struct { ... } // 标准化服务
type DedupMatchService struct { ... }          // 去重匹配服务
type TransferLinkService struct { ... }        // 转账链路合并服务
type RuleApplyService struct { ... }           // 规则应用服务
type FireflyExportService struct { ... }       // Firefly 导出服务
type AuditTraceService struct { ... }          // 审计追溯服务
```

### 14.3 事务边界设计

建议以下阶段分别事务化，避免超长事务导致 SQLite 锁定：

```mermaid
flowchart LR
    T1["事务 1<br/>文件入库 + 原始记录入库"] --> T2["事务 2<br/>标准化结果落库"]
    T2 --> T3["事务 3<br/>去重/链路关系落库"]
    T3 --> T4["事务 4<br/>规则命中落库"]
    T4 --> T5["事务 5<br/>导入结果落库"]
```

```go
// 事务示例
func (s *PipelineService) processNormalize(ctx context.Context, batchID string) error {
    return s.db.Transaction(func(tx *gorm.DB) error {
        // 在事务内完成标准化 + 批量入库
        transactions := s.normalizer.NormalizeAll(ctx, rawRecords)
        return tx.CreateInBatches(transactions, 500).Error
    })
}
```

### 14.4 错误处理策略

```go
// 统一错误码定义
const (
    ErrCodeSuccess        = 0
    ErrCodeBadRequest     = 40000
    ErrCodeFileParseError = 40001
    ErrCodeUnknownPlatform = 40002
    ErrCodeDuplicateFile  = 40003
    ErrCodeRuleInvalid    = 40004
    ErrCodeExportFailed   = 40005
    ErrCodeInternal       = 50000
)

// 全局错误处理中间件
func ErrorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        if len(c.Errors) > 0 {
            err := c.Errors.Last()
            // 根据错误类型返回对应错误码和消息
            c.JSON(http.StatusOK, Response{
                Code:    mapErrorCode(err),
                Message: err.Error(),
            })
        }
    }
}
```

### 14.5 配置管理

```go
// config/config.go
type Config struct {
    Server   ServerConfig   `yaml:"server"`
    Database DatabaseConfig `yaml:"database"`
    Firefly  FireflyConfig  `yaml:"firefly"`
    Dedup    DedupConfig    `yaml:"dedup"`
}

type ServerConfig struct {
    Port int    `yaml:"port" default:"8080"`
    Mode string `yaml:"mode" default:"release"` // debug/release
}

type DatabaseConfig struct {
    Path string `yaml:"path" default:"./data/projectmoneyx.db"`
}

type FireflyConfig struct {
    BaseURL       string `yaml:"base_url"`
    APIToken      string `yaml:"api_token"`       // AES 加密存储
    ImporterURL   string `yaml:"importer_url"`
    Enabled       bool   `yaml:"enabled"`
}

type DedupConfig struct {
    FuzzyTimeWindow    int     `yaml:"fuzzy_time_window" default:"5"`     // 分钟
    FuzzyThresholdHigh int     `yaml:"fuzzy_threshold_high" default:"85"` // 自动判定
    FuzzyThresholdLow  int     `yaml:"fuzzy_threshold_low" default:"60"`  // 疑似判定
    TransferTimeWindow int     `yaml:"transfer_time_window" default:"30"` // 分钟
    AmountEpsilon      float64 `yaml:"amount_epsilon" default:"0.01"`     // 金额容差
}
```

---

## 15. 版本演进规划

### 15.1 V1.0（MVP — 优先上线）

```mermaid
gantt
    title V1.0 MVP 开发计划
    dateFormat  YYYY-MM-DD
    section 基础架构
    项目骨架搭建           :a1, 2026-03-10, 3d
    数据库表结构建模         :a2, after a1, 2d
    GIN 路由与中间件        :a3, after a2, 2d
    section 解析引擎
    支付宝 Parser          :b1, after a3, 3d
    微信 Parser            :b2, after b1, 3d
    解析器注册中心          :b3, after a3, 1d
    section 核心流程
    标准化引擎              :c1, after b2, 3d
    严格去重                :c2, after c1, 2d
    转账闭环合并            :c3, after c2, 3d
    基础规则映射            :c4, after c3, 3d
    section 导入导出
    CSV 导出               :d1, after c4, 2d
    Firefly API 推送       :d2, after d1, 3d
    导入预览与确认          :d3, after d2, 2d
    section 前端
    导入中心页面            :e1, after a3, 5d
    清洗预览页面            :e2, after e1, 4d
    规则配置页面            :e3, after e2, 4d
    导入结果页面            :e4, after e3, 3d
```

**核心交付物**：

- 支付宝 / 微信 Parser
- 严格去重 + 转账闭环
- 基础规则映射（分类 / 账户）
- 导入预览与确认
- CSV 导出 + Firefly API 推送
- 基础前端（导入 → 预览 → 确认 → 结果）

### 15.2 V1.5（增强版）

| 功能 | 说明 |
|------|------|
| 建行 / 工行 Parser | 扩展银行账单支持 |
| 模糊去重评分 | 多因子评分模型上线 |
| 人工确认流程 | 疑似重复的人工确认队列 |
| 商户别名归一化 | 商户别名库与归一化规则 |
| 批次管理增强 | 批次列表、详情、删除 |
| 手动合并/拆分 | 支持用户手动操作交易链路 |
| 标签映射 | 规则引擎支持标签维度 |

### 15.3 V2.0（进阶版）

| 功能 | 说明 |
|------|------|
| 京东 / 美团 Parser | 电商与生活服务平台支持 |
| 订单链路增强 | 京东订单 + 微信支付链路聚合 |
| 多币种支持 | 加密货币交易所流水适配 |
| 可视化规则调试 | 规则测试与命中预览 |
| 简易统计报表 | 导入概览、分类占比、来源分布 |
| 对账可视化 | 孤儿记录展示 + 手动干预 |
| 多账本支持 | 对接多个 Firefly III 实例 |

---

## 16. 结论

ProjectMoneyX 不是一个"简单导入工具"，而是一个面向 Firefly III 的**本地账单数据治理中台**。

### 核心价值

> 1. **汇聚多源账单** — 一站式接入支付平台、银行、生活服务、交易所
> 2. **统一交易语义** — 屏蔽平台差异，建立标准化交易模型
> 3. **智能去重合并** — 消除重复，还原真实交易链路与转账闭环
> 4. **本地规则沉淀** — 分类/账户/标签映射可长期积累、可解释、可迁移
> 5. **无缝对接导入** — 丝滑推送至 Firefly III / Data Importer

### 核心架构决策

| 决策 | 理由 |
|------|------|
| **插件化 Adapter 架构** | 平台差异收敛在 Adapter 层，格式变更只需更新对应 Parser |
| **支付宝分类字典作为统一基准** | 支付宝 22 种分类最丰富，其他平台映射到此体系 |
| **微信"交易类型 + 商品"联合推断** | 微信交易类型粗粒度，需结合商品字段推断实际消费分类 |
| **规则映射以本地为主** | 规则可沉淀、可解释、可迁移，不绑定在 Data Importer 实例 |
| **三层递进去重策略** | 严格 → 模糊 → 链路合并，兼顾准确性与覆盖率 |
| **SQLite 作为核心数据底座** | 不仅是缓存，更是清洗结果与审计链路的持久化存储 |
| **ETL 分阶段事务** | 避免超长事务，每个阶段独立可重试 |

### 技术栈总结

```
┌─────────────────────────────────────────────────────┐
│  前端: Vue3 + TypeScript + Vuetify (Material Design) │
├─────────────────────────────────────────────────────┤
│  后端: Golang + GIN (RESTful API) + GORM (ORM)      │
├─────────────────────────────────────────────────────┤
│  数据库: SQLite (WAL 模式, 本地优先)                   │
├─────────────────────────────────────────────────────┤
│  部署: Docker 容器化 / 跨平台二进制单体                 │
└─────────────────────────────────────────────────────┘
```
