# 供应商隔离的数据新鲜度与缓存设计方案

## 1. 背景 (Background)

当前系统使用 `company_profiles` 表中的全局 `updated_at` 时间戳来判断某个股票的数据是否“新鲜”（例如：过去 24 小时内更新过）。

**现有问题：**
这种方法在多供应商（Multi-Provider）环境中会导致严重的竞态条件（Race Condition）：
1.  **Tushare**（A股数据源）通常响应较快，获取数据并更新了 `company_profiles` 表的 `updated_at`。
2.  `updated_at` 时间戳被更新为 `NOW()`。
3.  **YFinance** 或 **AlphaVantage**（全球数据源）稍后启动任务。
4.  它们检查 `company_profiles` 表，发现 `updated_at` 非常新，因此错误地认为**自己的**数据也是最新的。
5.  结果：YFinance/AlphaVantage 跳过执行，导致这些特定字段的数据为空或陈旧。

## 2. 目标 (Objective)

实现一个**供应商隔离的缓存机制**，允许每个数据供应商（Tushare, YFinance, AlphaVantage, Finnhub）能够：
1.  独立追踪其最后一次成功更新数据的时间。
2.  仅根据**自己的**数据新鲜度来决定是否执行任务。
3.  避免干扰其他供应商的执行逻辑。

## 3. 设计原则 (Design Principles)

1.  **不新增数据表**：利用数据库现有的文档-关系混合特性（Document-Relational）。具体来说，使用 `company_profiles` 表中的 `additional_info` (JSONB) 字段。
2.  **服务层抽象**：解析和管理这些元数据的复杂性应封装在 `Data Persistence Service` 内部，向各 Provider Service 暴露简洁的 API。
3.  **并发安全**：确保不同供应商的并发更新不会覆盖彼此的元数据状态。

## 4. 数据结构设计 (Data Structure Design)

我们将利用现有的 `company_profiles.additional_info` 字段（类型：`JSONB`）来存储一个供应商状态字典。

### `additional_info` JSON Schema 设计

```json
{
  "provider_status": {
    "tushare": {
      "last_updated": "2025-11-19T10:00:00Z",
      "data_version": "v1",
      "status": "success"
    },
    "yfinance": {
      "last_updated": "2025-11-18T09:30:00Z",
      "status": "success"
    },
    "alphavantage": {
      "last_updated": "2025-11-15T14:00:00Z",
      "status": "partial_success" // 例如：触发了速率限制
    }
  },
  "other_metadata": "..." // 保留其他现有元数据
}
```

## 5. 实施计划 (Implementation Plan)

### 5.1. 数据持久化服务更新 (Data Persistence Service)

我们需要扩展 `PersistenceClient` 及其底层 API，以支持细粒度的元数据更新。

**新增/更新 API 端点：**

1.  **`PUT /companies/{symbol}/providers/{provider_id}/status`** (新增)
    *   **目的**：原子更新特定供应商的状态，无需读取/写入完整的 profile。
    *   **实现**：使用 Postgres 的 `jsonb_set` 函数，直接更新 JSON 路径 `['provider_status', provider_id]`。
    *   **Payload**:
        ```json
        {
          "last_updated": "2025-11-19T12:00:00Z",
          "status": "success"
        }
        ```

2.  **`GET /companies/{symbol}/providers/{provider_id}/status`** (新增)
    *   **目的**：辅助接口，用于获取特定供应商的当前缓存状态。

### 5.2. 供应商服务工作流更新 (Provider Service)

每个 Provider Service（例如 `yfinance-provider-service`）将修改其 `worker.rs` 中的逻辑：

**现有逻辑（有缺陷）：**
```rust
let profile = client.get_company_profile(symbol).await?;
if profile.updated_at > 24h_ago { return; } // 全局检查
```

**新逻辑：**
```rust
// 1. 检查 Provider 专属缓存
let status = client.get_provider_status(symbol, "yfinance").await?;
if let Some(s) = status {
    if s.last_updated > 24h_ago {
        info!("YFinance 数据较新，跳过执行。");
        return;
    }
}

// 2. 获取并持久化数据
// ... fetch ...
client.upsert_company_profile(profile).await?; // 更新基本信息
client.batch_insert_financials(financials).await?;

// 3. 更新 Provider 状态
client.update_provider_status(symbol, "yfinance", ProviderStatus {
    last_updated: Utc::now(),
    status: "success"
}).await?;
```

## 6. 风险管理与迁移 (Risk Management & Migration)

*   **竞态条件 (Race Conditions)**：通过在数据库层使用 `jsonb_set` 进行部分更新，我们避免了“读-改-写”的竞态条件，确保 Provider A 的更新不会覆盖 Provider B 同时写入的状态。
*   **数据迁移 (Migration)**：
    *   **策略**：**Lazy Migration (懒迁移)**。
    *   现有数据中没有 `provider_status` 字段。代码将优雅地处理 `null` 或缺失键的情况（将其视为“陈旧/从未运行”，触发重新获取）。
    *   **无需**编写专门的 SQL 迁移脚本去清洗历史数据。旧数据会随着新的抓取任务运行而自动补充上状态信息。
    *   如果必须清理，可以直接执行 `UPDATE company_profiles SET additional_info = additional_info - 'provider_status';` 来重置所有缓存状态。

## 7. 实施清单 (Implementation Checklist)

- [x] **Phase 1: Common Contracts & DTOs**
    - [x] 在 `services/common-contracts/src/dtos.rs` 中定义 `ProviderStatusDto`.

- [x] **Phase 2: Data Persistence Service API**
    - [x] 实现 DB 层逻辑: `get_provider_status` (读取 JSONB).
    - [x] 实现 DB 层逻辑: `update_provider_status` (使用 `jsonb_set`).
    - [x] 添加 API Handler: `GET /companies/{symbol}/providers/{provider_id}/status`.
    - [x] 添加 API Handler: `PUT /companies/{symbol}/providers/{provider_id}/status`.
    - [x] 注册路由并测试接口.

- [x] **Phase 3: Client Logic Update**
    - [x] 更新各服务中的 `PersistenceClient` (如 `services/yfinance-provider-service/src/persistence.rs` 等)，增加 `get_provider_status` 和 `update_provider_status` 方法.

- [x] **Phase 4: Provider Services Integration**
    - [x] **Tushare Service**: 更新 `worker.rs`，集成新的缓存检查逻辑.
    - [x] **YFinance Service**: 更新 `worker.rs`，集成新的缓存检查逻辑.
    - [x] **AlphaVantage Service**: 更新 `worker.rs`，集成新的缓存检查逻辑.
    - [x] **Finnhub Service**: 更新 `worker.rs`，集成新的缓存检查逻辑.

- [ ] **Phase 5: Verification (验证)**
    - [ ] 运行 `scripts/test_data_fetch.py` 验证全流程.
    - [ ] 验证不同 Provider 的状态互不干扰.

- [ ] **Phase 6: Caching Logic Abstraction (缓存逻辑抽象 - 智能客户端)**
    - [ ] 将 `PersistenceClient` 迁移至 `services/common-contracts/src/persistence_client.rs`（或新建 `service-sdk` 库），消除重复代码。
    - [ ] 在共享客户端中实现高层方法 `should_fetch_data(symbol, provider, ttl)`。
    - [ ] 重构所有 Provider Service 以使用共享的 `PersistenceClient`。
    - [ ] 验证所有 Provider 的缓存逻辑是否一致且无需手动实现。
