Fundamental_Analysis/docs/data_provider_refactoring_and_trait_design.md
Lv, Qi 0e45dd4a3f docs: 更新路线图—勾选光荣退役并归档旧Python服务
- 勾选阶段五第3项下线Python服务
- 描述将 backend/ 与 services/config-service/ 归档至 archive/python/
- 明确无Python组件下系统运行的验收标准
2025-11-16 20:54:01 +08:00

34 KiB
Raw Blame History

设计文档: 面向Rust的事件驱动数据微服务架构

1. 引言

1.1. 文档目的

本文档旨在为“基本面选股系统”设计一个完全基于Rust的、事件驱动的、去中心化的微服务架构。此设计将作为彻底替换现有Python组件、并构建下一代数据处理生态系统的核心技术蓝图。

新的架构目标是:

  1. 服务独立化将每个外部数据源Tushare, Finnhub等封装成独立的、可独立部署和运行的微服务。
  2. 事件驱动引入消息总线Message Bus作为服务间通信的主干实现服务的高度解耦和异步协作。
  3. 数据中心化:所有微服务将标准化的数据写入一个由data-persistence-service独占管理的中央数据库,实现“数据写入即共享”。
  4. 纯Rust生态从前端网关到最末端的数据提供商整个后端生态系统将100%使用Rust构建确保端到端的类型安全、高性能和健壮性。

1.2. 核心架构理念

  • 独立单元 (Independent Units): 每个服务都是一个完整的、自包含的应用程序,拥有自己的配置、逻辑和生命周期。
  • 异步协作 (Asynchronous Collaboration): 服务之间通过发布/订阅消息进行通信而非紧耦合的直接API调用。
  • 单一事实源 (Single Source of Truth): 数据库是所有结构化数据的唯一事实源。服务通过向数据库写入数据来“广播”其工作成果。

2. 目标架构 (Target Architecture)

2.1. 架构图

+-------------+      +------------------+      +---------------------------+
|             | HTTP |                  |      |                           |
|  Frontend   |----->|   API Gateway    |----->|      Message Bus          |
|  (Next.js)  |      |     (Rust)       |      | (e.g., RabbitMQ, NATS)    |
|             |      |                  |      |                           |
+-------------+      +-------+----------+      +-------------+-------------+
                             |                                |
        (Read operations)    |                                | (Pub/Sub Commands & Events)
                             |                                |
           +-----------------v------------------+      +------v------+  +----------------+  +----------------+
           |                                    |      | Tushare     |  | Finnhub        |  | iFind          |
           |   Data Persistence Service (Rust)  |<---->| Provider    |  | Provider       |  | Provider       |
           |                                    |      | Service     |  | Service        |  | Service        |
           +-----------------+------------------+      | (Rust)      |  | (Rust)         |  | (Rust)         |
                             |                         +-------------+  +----------------+  +----------------+
                             v
+-----------------------------------------------------+
|                                                     |
|                PostgreSQL Database                  |
|                                                     |
+-----------------------------------------------------+

2.2. 服务职责划分

  • API Gateway (Rust):

    • 面向前端的唯一入口 (BFF - Backend for Frontend)。
    • 负责处理用户请求、认证鉴权。
    • 将前端的查询请求转化为对Data Persistence Service的数据读取调用。
    • 将前端的操作请求如“生成新报告”转化为命令Command并发布到Message Bus
  • *_provider-service (Rust):

    • 一组独立的微服务每个服务对应一个外部数据APItushare-provider-service)。
    • 订阅Message Bus上的相关命令FetchFinancialsRequest)。
    • 独立调用外部API对返回数据进行清洗、标准化。
    • 调用Data Persistence Service的接口,将标准化后的数据写入数据库。
    • 操作完成后可以向Message Bus发布事件EventFinancialsDataReady
  • Data Persistence Service (Rust):

    • (职责不变) 数据库的唯一守门人。
    • 为所有其他内部微服务提供稳定、统一的数据库读写gRPC/HTTP接口。
  • Message Bus (e.g., RabbitMQ, NATS):

    • 整个系统的神经中枢,负责所有服务间的异步通信。
    • 传递命令(“做什么”)和事件(“发生了什么”)。

3. 核心抽象与数据契约

3.1. DataProvider Trait (内部实现蓝图)

此Trait依然是构建每个独立Provider微服务内部逻辑的核心蓝图。它定义了一个Provider应该具备的核心能力。

// This trait defines the internal logic blueprint for each provider microservice.
#[async_trait]
pub trait DataProvider: Send + Sync {
    // ... (trait definition remains the same as previous version) ...
    fn get_id(&self) -> &'static str;
    async fn get_company_profile(&self, symbol: &str) -> Result<CompanyProfile, DataProviderError>;
    async fn get_historical_financials(&self, symbol: &str, years: &[u16]) -> Result<Vec<FinancialStatement>, DataProviderError>;
    // ... etc ...
}

3.2. 标准化数据模型 (共享的数据契约)

这些模型是服务间共享的“通用语言”,也是存入数据库的最终形态,其重要性在新架构下更高。

// These structs are the shared "Data Contracts" across all services.
// Their definitions remain the same as the previous version.

#[derive(Debug, Clone, Serialize, Deserialize)] // Add Serialize/Deserialize for messaging
pub struct CompanyProfile { ... }

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FinancialStatement { ... }

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketDataPoint { ... }

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealtimeQuote { ... }

3.3. 消息/事件定义 (Message/Event Contracts)

这是新架构的核心定义了在Message Bus上传递的消息格式。

use uuid::Uuid;
use serde::{Serialize, Deserialize};

// --- Commands (Instructions to do something) ---

#[derive(Serialize, Deserialize)]
pub struct FetchCompanyDataCommand {
    pub request_id: Uuid,
    pub symbol: String,
    pub market: String, // To help providers route to the correct API endpoint
}

// --- Events (Notifications that something has happened) ---

#[derive(Serialize, Deserialize)]
pub struct CompanyProfilePersistedEvent {
    pub request_id: Uuid,
    pub symbol: String,
    // We don't need to carry the full data, as it's now in the database.
    // Interested services can query it.
}

#[derive(Serialize, Deserialize)]
pub struct FinancialsPersistedEvent {
    pub request_id: Uuid,
    pub symbol: String,
    pub years_updated: Vec<u16>,
}

4. 数据工作流示例 (Example Data Workflow)

  1. 请求发起: 用户在前端请求AAPL的分析报告。请求到达API Gateway
  2. 命令发布: API Gateway生成一个唯一的request_id然后向Message Bus发布一个FetchCompanyDataCommand命令。
  3. 命令消费: tushare-providerfinnhub-provider等所有订阅了此命令的服务都会收到消息。
  4. 独立执行:
    • finnhub-provider根据marketsymbol调用Finnhub API获取公司简介、财务、行情数据。
    • 数据获取成功后,它将数据转换为标准化的CompanyProfile, Vec<FinancialStatement>等模型。
    • 它调用Data Persistence Service的接口,将这些标准化的数据写入数据库。
    • 写入成功后它向Message Bus发布CompanyProfilePersistedEventFinancialsPersistedEvent等事件。
    • tushare-provider收到命令后,可能因为市场不匹配而直接忽略该消息。
  5. 下游响应: 一个潜在的report-generator-service(图中未画出,属于业务层)可以订阅...PersistedEvent。当它收到了生成一份完整报告所需的所有数据事件后便开始从数据库中拉取这些数据进行AI分析并将最终报告存回数据库。
  6. 前端轮询/通知: API Gateway可以通过WebSocket或长轮询等方式将最终报告的完成状态通知给前端。

5. 实施路线图 (Roadmap) - 更新于 2025-11-15

基于对项目现状的调研,本路线图已更新,明确标识了已完成的工作和接下来的行动计划。


✔ 阶段-1容器化与初步服务拆分 (已完成)

  • 核心服务已容器化: data-persistence-service 已完全开发、容器化并通过Docker Compose与数据库和现有Python后端集成。
  • 数据库已初始化: Rust服务的 migrations 目录证实了数据库表结构已通过 sqlx-cli 创建和管理。
  • Python后端部分重构: Python backend 服务已经作为客户端通过HTTP API调用data-persistence-service来读写数据。
  • 配置服务已拆分: config-service 作为一个独立的Python微服务也已存在并运行。
  • 开发环境已建立: 整个系统可以通过Docker ComposeTilt一键启动。

阶段〇:奠定新架构的基石 (Laying the New Foundation)

  • 1. 部署消息总线: 在docker-compose.yml中添加一个消息总线服务 (NATS)。这是实现事件驱动架构的先决条件
  • 2. 创建共享契约库 (common-contracts): 在services/下创建一个新的Rust common-contracts crate。
    • data-persistence-service/src/dtos.rsmodels.rs中的核心数据结构(如CompanyProfile, FinancialStatement等)迁移至此。
    • 添加architecture_module_specification.md中定义的消息契约 (FetchCompanyDataCommand等) 和可观测性结构 (HealthStatus, TaskProgress)。
  • 3. 升级 data-persistence-service:
    • 使其依赖新的common-contracts crate替换掉本地的数据模型定义。
    • 为其实现SystemModule规范,即添加/health/tasks端点。

阶段一:开发 alphavantage-provider-service (精确实现蓝图)

目标: 创建并实现 alphavantage-provider-service,使其成为我们新架构下的第一个功能完备、可独立运行的数据提供商微服务。

  • 1. 项目初始化与依赖配置

    • 任务: 基于我们的微服务模板创建新的Rust项目 services/alphavantage-provider-service
    • 任务: 在其Cargo.toml中添加核心依赖。
      # Cargo.toml
      [dependencies]
      # ... other dependencies like axum, tokio, etc.
      common-contracts = { path = "../common-contracts" }
      
      # Generic MCP Client
      rmcp = "0.8.5"
      
      # Message Queue (NATS)
      async-nats = "0.33"
      
    • 验收标准: 项目可以成功编译 (cargo check)。
  • 2. 实现 SystemModule 规范

    • 任务: 在main.rs中启动一个Axum HTTP服务器。
    • 任务: 实现强制的/health端点,返回当前服务的健康状态。
    • 任务: 实现强制的/tasks端点。此端点需要从一个线程安全的内存存储(例如 Arc<DashMap<Uuid, TaskProgress>>)中读取并返回所有正在进行的任务。
    • 验收标准: 启动服务后,可以通过curl或浏览器访问http://localhost:port/healthhttp://localhost:port/tasks并得到正确的JSON响应。
  • 3. 实现核心业务逻辑:事件驱动的数据处理

    • 任务: 实现连接到Message Bus并订阅FetchCompanyDataCommand命令的逻辑。
    • 任务: 当收到FetchCompanyDataCommand命令时,执行以下异步工作流:
      1. 在任务存储中创建并插入一个新的TaskProgress记录。
      2. 从配置中读取ALPHAVANTAGE_API_KEY并构建MCP端点URL。
      3. 初始化通用的rmcp客户端: let client = rmcp::mcp::Client::new(mcp_endpoint_url);
      4. 使用tokio::try_join!并行执行多个数据获取任务。注意:函数名是字符串,返回的是serde_json::Value
        // 伪代码示例
        let symbol = &command.symbol;
        
        let overview_task = client.query("OVERVIEW", &[("symbol", symbol)]);
        let income_task = client.query("INCOME_STATEMENT", &[("symbol", symbol)]);
        // ... 其他任务
        
        match tokio::try_join!(overview_task, income_task, /*...*/) {
            Ok((overview_json, income_json, /*...*/)) => {
                // overview_json and income_json are of type serde_json::Value
                // ... 进入步骤 4
            },
            Err(e) => { /* ... */ }
        }
        
      5. try_join!前后,精确地更新内存中TaskProgress的状态。
    • 验收标准: 在Message Bus中发布命令后服务的日志能正确打印出从Alpha Vantage获取到的原始JSON数据。
  • 4. 实现数据转换与持久化 (强类型映射)

    • 任务: (关键变更) 实现 TryFrom<serde_json::Value> Trait完成从动态JSON到我们common-contracts模型的带错误处理的转换。
      // alphavantage-provider-service/src/mapping.rs
      use serde_json::Value;
      use common_contracts::models as our;
      
      impl TryFrom<Value> for our::CompanyProfile {
          type Error = anyhow::Error; // Or a more specific parsing error
      
          fn try_from(v: Value) -> Result<Self, Self::Error> {
              Ok(our::CompanyProfile {
                  symbol: v["Symbol"].as_str().ok_or_else(|| anyhow!("Missing Symbol"))?.to_string(),
                  name: v["Name"].as_str().ok_or_else(|| anyhow!("Missing Name"))?.to_string(),
                  // ... 其他字段的安全解析和转换
              })
          }
      }
      
    • 任务: 创建一个类型化的HTTP客户端 (Data Persistence Client),用于与data-persistence-service通信。
    • 任务: 在所有数据转换成功后,调用上述客户端进行持久化。
    • 验收标准: 数据库中查询到的数据,结构完全符合common-contracts定义。
  • 5. 实现事件发布与任务完成

    • 任务: 在数据成功持久化到数据库后向Message Bus发布相应的数据就绪事件CompanyProfilePersistedEventFinancialsPersistedEvent
    • 任务: 在所有流程执行完毕(无论成功或失败)后,从内存存储中移除对应的TaskProgress对象或将其标记为“已完成”并设置TTL
    • 验收标准: 能够在Message Bus中监听到本服务发布的事件。/tasks接口不再显示已完成的任务。

阶段二重构API网关与请求流程 (精确实现蓝图)

目标: 创建一个纯Rust的api-gateway服务,它将作为前端的唯一入口(BFF),负责发起数据获取任务、查询持久化数据以及追踪分布式任务进度。

  • 1. 项目初始化与 SystemModule 规范实现

    • 任务: 基于我们的微服务模板创建新的Rust项目 services/api-gateway
    • 任务: 在其Cargo.toml中添加核心依赖: axum, tokio, common-contracts, async-nats, reqwest, tracing, config
    • 任务: 实现强制的/health端点。
    • 任务: 实现强制的/tasks端点。由于网关本身是无状态的、不执行长任务,此端点当前可以简单地返回一个空数组[]
    • 验收标准: api-gateway服务可以独立编译和运行,并且/health接口按预期工作。
  • 2. 实现数据触发流程 (发布命令)

    • 任务: 在api-gateway中创建一个新的HTTP端点 POST /v1/data-requests它应接收一个JSON体例如: {"symbol": "AAPL", "market": "US"}
    • 任务: 为此端点实现处理逻辑:
      1. 生成一个全局唯一的 request_id (UUID)。
      2. 创建一个common_contracts::messages::FetchCompanyDataCommand消息,填入请求参数和request_id
      3. 连接到Message Bus并将此命令发布到data_fetch_commands队列。
      4. 向前端立即返回 202 Accepted 状态码,响应体中包含 { "request_id": "..." },以便前端后续追踪。
    • 验收标准: 通过工具如Postman调用此端点后能够在NATS的管理界面看到相应的消息被发布同时alphavantage-provider-service的日志显示它已接收并开始处理该命令。
  • 3. 实现数据查询流程 (读取持久化数据)

    • 任务: 在api-gateway中创建一个类型化的HTTP客户端 (Persistence Client),用于与data-persistence-service通信。
    • 任务: 实现 GET /v1/companies/{symbol}/profile 端点。该端点接收股票代码通过Persistence Client调用data-persistence-service的相应接口,并将查询到的CompanyProfile数据返回给前端。
    • 任务: (可选) 根据需要,实现查询财务报表、行情数据等其他数据类型的端点。
    • 验收标准: 在alphavantage-provider-service成功写入数据后,通过浏览器或curl调用这些新端点可以查询到预期的JSON数据。
  • 4. 实现分布式任务进度追踪

    • 任务: 在api-gateway的配置中,增加一个provider_services字段,用于列出所有数据提供商服务的地址,例如: ["http://alphavantage-provider-service:8000"]
    • 任务: 实现 GET /v1/tasks/{request_id} 端点。
    • 任务: 该端点的处理逻辑需要:
      1. 读取配置中的provider_services列表。
      2. 使用tokio::join!futures::future::join_all并行地向所有provider服务的/tasks端点发起HTTP GET请求。
      3. 聚合所有服务的返回结果(一个Vec<Vec<TaskProgress>>并从中线性搜索与URL路径中request_id匹配的TaskProgress对象。
      4. 如果找到匹配的任务将其作为JSON返回。如果遍历完所有结果都未找到则返回404 Not Found
    • 验收标准: 当alphavantage-provider-service正在处理一个任务时,通过api-gateway的这个新端点(并传入正确的request_id),能够实时查询到该任务的进度详情。

阶段三:逐步迁移与替换 (精确实现蓝图)

目标: 将前端应用无缝对接到新的Rust api-gateway并随着数据提供商的逐步完善最终彻底移除旧的Python backend服务,完成整个系统的架构升级。

  • 1. 将 api-gateway 集成到开发环境

    • 任务: 在根目录的 docker-compose.yml 文件中,为我们新创建的 api-gateway 服务添加入口定义。
      # docker-compose.yml
      services:
        # ... other services
        api-gateway:
          build:
            context: ./services/api-gateway
            dockerfile: Dockerfile
          container_name: api-gateway
          environment:
            # 注入所有必要的配置
            SERVER_PORT: 4000
            NATS_ADDR: nats://nats:4222
            DATA_PERSISTENCE_SERVICE_URL: http://data-persistence-service:3000/api/v1
            # 注意: provider_services需要包含所有provider的内部地址
            PROVIDER_SERVICES: '["http://alphavantage-provider-service:8000"]'
          ports:
            - "14000:4000"
          depends_on:
            - nats
            - data-persistence-service
            - alphavantage-provider-service
      
    • 任务: (如果尚未完成) 在docker-compose.yml中添加nats服务。
    • 验收标准: 运行 docker-compose up (或 tilt up) 后,api-gateway 服务能够成功启动并连接到消息总线。
  • 2. 迁移前端应用的API调用逻辑

    • 任务: 修改前端项目的环境变量将API请求的目标从旧backend指向新api-gateway
      # frontend/.env.local (or in docker-compose.yml)
      NEXT_PUBLIC_BACKEND_URL=http://api-gateway:4000/v1
      
    • 任务: 重构前端的数据获取Hooks例如 useApi.ts)。
      • 旧逻辑: 发起一个长轮询GET请求等待完整数据返回。
      • 新逻辑:
        1. 触发: 发起一个 POST 请求到 /data-requests,并从响应中获取 request_id
        2. 轮询: 使用 useSWRreact-query 的轮询功能每隔2-3秒调用一次 GET /tasks/{request_id} 端点来获取任务进度。
        3. 展示: 根据任务进度更新UI例如显示加载条和状态信息
        4. 完成: 当任务状态变为 "completed" (或类似状态),或 GET /tasks/{request_id} 返回 404 时,停止轮询,并调用 GET /companies/{symbol}/profile 等数据查询端点来获取最终数据并渲染。
    • 验收标准: 在前端页面输入股票代码并点击“生成报告”后能够触发新的异步工作流并在UI上看到实时进度的更新最终成功展示由 alphavantage-provider-service 获取的数据。

阶段四:数据提供商生态系统扩展 (Data Provider Ecosystem Expansion)

目标: 将现有Python backend中的核心data_providers逐一重写为独立的Rust微服务丰富我们的数据维度。

  • 0. (前置任务) 完成所有Provider的适配性分析

    • 任务: 在开始大规模编码前,完成 附录A 中所有待迁移数据提供商的适配性分析,确保common-contracts模型的完备性并明确每个Provider的实现关键点。
  • 1. 实现 tushare-provider-service (中国市场核心)

    • 任务: 基于 alphavantage-provider-service 模板,创建并实现服务的基本框架。
    • 任务: 完成 Tushare 8个核心API的并行数据获取、聚合与报告期筛选逻辑。
    • 任务: 在 mapping.rs精确复刻Python版本 _calculate_derived_metrics 方法中近20个派生财务指标的计算逻辑。
    • 任务: 在 docker-compose.yml中添加此服务,并将其地址加入到api-gatewayPROVIDER_SERVICES环境变量中。
    • 验收标准: 收到market: "CN"FetchCompanyDataCommand命令时该服务能被触发并成功将与Python版本逻辑一致的A股数据包含所有派生指标)写入数据库。
  • 2. (可选) 迁移其他数据提供商

    • 任务: 基于各自的适配性分析,创建并实现finnhub-provider-service
    • 任务: 基于各自的适配性分析,创建并实现yfinance-provider-service
    • 任务: 基于各自的适配性分析,创建并实现ifind-provider-service

阶段五:业务逻辑迁移与最终替换 (Business Logic Migration & Final Replacement)

目标: 将Python backend中剩余的AI分析和配置管理逻辑迁移到Rust生态并最终彻底下线Python服务。

  • 1. 创建 report-generator-service

    • 任务: 创建一个新的Rust微服务report-generator-service
    • 任务: 实现对Message Bus事件FinancialsPersistedEvent)的订阅。
    • 任务: 将原Python backend中的analysis_client.pycompany_profile_client.py的逻辑迁移至此服务。
    • 验收标准: 当所有数据提供商完成数据写入后此服务能被自动触发并成功生成AI分析报告。
  • 2. (可选) 创建 config-service-rs

    • 任务: 用Rust重写现有的Python config-service
    • 验收标准: 所有Rust微服务都能从新的配置服务中获取配置并正常启动。
  • 3. 光荣退役下线所有Python服务

    • 前提条件: 所有数据获取和AI分析功能均已由新的Rust微服务完全承载。
    • 任务: 在 docker-compose.yml 中,删除 backendconfig-service 的服务定义。
    • 任务: 将backend/services/config-service/目录移动至archive/python/进行归档保留。
    • 验收标准: 整个系统在没有任何Python组件的情况下依然能够完整、正常地运行所有核心功能。架构升级正式完成。

附录A: 数据提供商适配性分析

本附录用于详细记录每个待迁移的数据提供商API与我们common-contracts标准模型之间的适配性。

A.1 Tushare 适配性分析

核心结论: 适配完全可行,但计算逻辑复杂common-contracts无需调整。迁移工作的核心是精确复刻Python版本中近400行的财务数据聚合与派生指标计算逻辑。

1. 数据模型适配概览

common-contracts 模型 适配可行性 关键实现要点
CompanyProfile 使用 stock_basicstock_company 接口。
DailyMarketData 关联 dailydaily_basic 接口。
RealtimeQuote ⚠️ Tushare无直接对应接口可使用最新日线数据作为“准实时”替代。
FinancialStatement 高,但复杂 (核心难点) 需聚合 balancesheet, income, cashflow, fina_indicator 等8个API并复刻近20个派生指标的计算。

2. 关键迁移逻辑

  • 多表聚合: Rust版本需实现并行调用多个Tushare API并以end_date为主键将结果聚合。
  • 报告期筛选: 需复刻“今年的最新报告 + 往年所有年报”的筛选逻辑。
  • 派生指标计算: 必须用Rust精确实现_calculate_derived_metrics方法中的所有计算公式。

A.2 Finnhub 适配性分析

核心结论: 适配可行。Finnhub作为美股和全球市场的主要数据源数据较为规范但同样涉及多API聚合少量派生计算common-contracts无需调整。

1. 数据模型适配概览

common-contracts 模型 适配可行性 关键实现要点
CompanyProfile 使用 /stock/profile2 接口。
DailyMarketData 使用 /stock/candle 接口获取OHLCV使用 /stock/metric 获取PE/PB等指标。
RealtimeQuote 使用 /quote 接口。
FinancialStatement 高,但需聚合 需聚合 /stock/financials-reported (按ic, bs, cf查询)返回的三张报表,并进行少量派生计算。

2. 关键迁移逻辑

  • 多API聚合: FinancialStatement的构建需要组合/stock/financials-reported接口的三次调用结果。DailyMarketData的构建也需要组合/stock/candle/stock/metric
  • 派生指标计算: Python代码 (finnhub.py) 中包含了自由现金流 (__free_cash_flow) 和其他一些比率的计算这些需要在Rust中复刻。
  • 字段名映射: Finnhub返回的字段名netIncome)需要被映射到我们标准模型的字段名(如net_income)。

A.3 YFinance 适配性分析

核心结论: 适配可行,主要作为行情数据的补充或备用源。yfinance库的封装使得数据获取相对简单。

1. 数据模型适配概览

common-contracts 模型 适配可行性 关键实现要点
CompanyProfile ticker.info 字典提供了大部分信息但字段不如Finnhub或Tushare规范。
DailyMarketData ticker.history() 方法是主要数据来源可直接提供OHLCV。
RealtimeQuote ⚠️ yfinance本身不是为实时流式数据设计的,获取的数据有延迟。
FinancialStatement ticker.financials, ticker.balance_sheet, ticker.cashflow 提供了数据,但需要手动将多年度的数据列转换为按年份的记录行。

2. 关键迁移逻辑

  • 数据结构转换: yfinance返回的DataFrame需要被转换为我们期望的Vec<Record>结构。特别是财务报表,需要将列式(多年份)数据转换为行式(单年份)记录。
  • 库的替代: Rust中没有yfinance库。我们需要找到一个替代的Rust库 (如 yahoo_finance_api)或者直接模拟其HTTP请求来获取数据。这将是迁移此模块的主要工作。

  • 2. (可选) 迁移其他数据提供商
    • 任务: 基于各自的适配性分析,创建并实现finnhub-provider-service
    • 任务: 基于各自的适配性分析,创建并实现yfinance-provider-service
    • 任务: (已暂停/待独立规划) 实现ifind-provider-service

A.4 iFind 适配性分析 - 更新于 2025-11-16

核心结论: 当前阶段纯Rust迁移复杂度极高任务已暂停。iFind的Python接口 (iFinDPy.py) 是一个基于 ctypes 的薄封装它直接调用了底层的C/C++动态链接库 (.so 文件)。这意味着没有任何可见的网络协议或HTTP请求可供我们在Rust中直接模拟。

1. 迁移路径评估

基于对 ref/ifind 库文件的调研,我们确认了迁移此模块面临两个选择:

  1. HTTP API方案 (首选,待调研):

    • 描述: 您提到iFind存在一个HTTP API版本。这是最符合我们纯Rust、去中心化架构的理想路径。
    • 工作量评估: 中等。如果该HTTP API文档齐全且功能满足需求那么开发此服务的工作量将与 finnhub-provider-service 类似。
    • 规划: 此路径应作为一个独立的、后续的调研与开发任务。当前置于暂停状态。
  2. FFI方案 (备选,不推荐):

    • 描述: 在Rust服务中通过FFIpyo3rust-cpython crate嵌入Python解释器直接调用 iFinDPy 库。
    • 工作量评估: 。虽然可以复用逻辑但这会引入技术栈污染破坏我们纯Rust的目标并显著增加部署和维护的复杂度需要在容器中管理Python环境和iFind的二进制依赖。这与我们“rustic”的确定性原则相悖。

2. 最终决定

  • 暂停实现: ifind-provider-service 的开发工作已正式暂停
  • 更新路线图: 在主路线图中,此任务已被标记为“已暂停/待独立规划”。
  • 未来方向: 当项目进入下一阶段时,我们将启动一个独立的任务来专门调研其HTTP API,并基于调研结果决定最终的实现策略。

附录B: 业务逻辑模块迁移分析

本附录用于分析backend/app/services/中包含的核心业务逻辑并为将其迁移至Rust服务制定策略。

B.1 analysis_client.py & company_profile_client.py

  • 核心功能: 这两个模块是AI分析的核心负责与大语言模型如GeminiAPI进行交互。

    • analysis_client.py: 提供一个通用的分析框架,可以根据不同的prompt_template执行任意类型的分析。它还包含一个SafeFormatter来安全地填充模板。
    • company_profile_client.py: 是一个特化的版本包含了用于生成公司简介的、具体的、硬编码的长篇Prompt。
  • 迁移策略:

    1. 统一并重写为 report-generator-service: 这两个模块的功能应被合并并迁移到一个全新的Rust微服务——report-generator-service中。
    2. 订阅事件: 该服务将订阅Message Bus上的数据就绪事件FinancialsPersistedEvent而不是被HTTP直接调用。
    3. Prompt管理: 硬编码在company_profile_client.py中的Prompt以及analysis_client.py所依赖的、从analysis-config.json加载的模板,都应该由report-generator-service统一管理。在初期可以从配置文件加载未来可以由Rust版的config-service-rs提供。
    4. 复刻SafeFormatter: Python版本中用于安全填充模板的SafeFormatter逻辑需要在Rust中被等价复刻以确保在上下文不完整时系统的健壮性。
    5. AI客户端: 使用reqwest或其他HTTP客户端库在Rust中重新实现与大模型API的交互逻辑。
  • 结论: 迁移完全可行。核心工作是将Python中的Prompt管理和API调用逻辑用Rust的异步方式重写。这将使AI分析任务成为一个独立的、可扩展的、事件驱动的后台服务。


B.2 config_manager.py

  • 核心功能: 作为Python backend内部的一个组件,它负责从config-service拉取配置,并与本地config.json文件进行合并。它还包含了测试各种配置有效性的逻辑如测试数据库连接、Tushare Token等

  • 迁移策略:

    • 功能分散化: ConfigManager本身不会作为一个独立的Rust服务存在它的功能将被分散到每个需要它的微服务中。
    • 配置拉取: 每个Rust微服务api-gateway, tushare-provider等)在启动时,都将负责独立地从环境变量或未来的config-service-rs中获取自己的配置。我们为每个服务编写的config.rs模块已经实现了这一点。
    • 配置测试逻辑: 测试配置的逻辑(如_test_database, _test_tushare)非常有用,但不属于运行时功能。这些逻辑可以被迁移到:
      1. 独立的CLI工具: 创建一个Rust CLI工具专门用于测试和验证整个系统的配置。
      2. 服务的/health端点: 在每个服务的/health检查中可以包含对其依赖服务数据库、外部API连通性的检查从而在运行时提供健康状况反馈。
  • 结论: ConfigManager的功能将被**“肢解”并吸收**到新的Rust微服务生态中而不是直接迁移。


B.3 data_persistence_client.py

  • 核心功能: 这是一个HTTP客户端用于让Python backend与其他微服务(data-persistence-service)通信。

  • 迁移策略:

    • 模式复用: 这个模块本身就是我们新架构模式的一个成功范例。
    • Rust等价实现: 我们在alphavantage-provider-service中创建的persistence.rs客户端,以及即将在api-gatewayreport-generator-service中创建的类似客户端,正是data_persistence_client.py的Rust等价物。
    • 最终废弃: 当Python backend最终被下线时,这个客户端模块也将随之被废弃。
  • 结论: 该模块无需迁移其设计思想已被我们的Rust服务所采纳和实现。