Files
news/docs/architecture.md
T
simon 6dede790c6 docs: 新增 MCP 工具说明文档 docs/mcp_tools.md
- 5 个工具参数/返回格式/降级策略/工作原理/调试连接/测试/数据前提
- user-guide §8.3 与 architecture §3.8 挂链接
2026-09-01 18:00:42 +08:00

638 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 项目架构文档
> 版本:v2.0 | 最后更新:2026-08-22
---
## 目录
1. [整体架构](#1-整体架构)
2. [数据流全景](#2-数据流全景)
3. [包职责与导出 API](#3-包职责与导出-api)
4. [核心数据模型](#4-核心数据模型)
5. [配置体系](#5-配置体系)
6. [产物目录结构](#6-产物目录结构)
7. [运行环境](#7-运行环境)
---
## 1. 整体架构
```
┌─────────────────────────────────────────────────────────────────┐
│ 统一 CLI (a_share_cli) │
│ crawl | extract | dedup | events | embed | ingest | pipeline │
│ search | status | report | cninfo | watchlist | discover │
└──────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────┼──────────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌────────┐ ┌──────────┐ ┌──────────┐
│ crawler │──▶│extractor │──▶│ dedup │──▶│ llm │──▶│embedding │
│ (M1) │ │ (M2) │ │ (M3) │ │ (M4) │ │ (M5) │
└─────────┘ └──────────┘ └────────┘ └──────────┘ └─────┬────┘
│
▼
┌──────────┐
│vectorstore│
│ (M6) │
└─────┬────┘
│
┌──────────────────────────────────────┤
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ mcp_server│ │scheduler │
│ (M8) │ │ (M7) │
└──────────┘ └─────┬────┘
│
┌─────────────────┤
▼ ▼
┌──────────┐ ┌──────────────┐
│ reporter │ │stock_reporter │
│ (日报) │ │ (个股日报) │
└─────┬────┘ └──────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│report_db │ │report_ │ │ MySQL │
│ (M10) │ │ import │ │(myquant) │
└──────────┘ └──────────┘ └──────────┘
```
**11 个包**(按管道顺序):
| 序号 | 包 | 模块 | 概述 |
|------|-----|------|------|
| 1 | `crawler/` | M1 | 新闻抓取:httpx 静态直连 + Playwright JS 渲染 + cninfo 公告 API |
| 2 | `extractor/` | M2 | GNE 中文新闻正文提取,输出 Article |
| 3 | `dedup/` | M3 | 三层去重:URL Hash → Content Hash → SimHash |
| 4 | `llm/` | M4 | DeepSeek/Qwen 投资事件抽取,输出 ExtractedEvent |
| 5 | `embedding/` | M5 | DashScope 远程 / 本地 BGE-M3 向量化 |
| 6 | `vectorstore/` | M6 | Qdrant 本地文件模式,语义检索 |
| 7 | `scheduler/` | M7 | APScheduler 定时任务 + pipeline 编排 + 日报生成 |
| 8 | `mcp_server/` | M8 | MCP 5 工具,供 Cherry Studio/Claude Code 调用 |
| 9 | `a_share_cli/` | CLI | 统一命令行入口(argparse),所有子命令 |
| 10 | `report_db/` | M10 | 日报 MySQL 结构化入库(pymysql) |
| 11 | `report_import/` | M10 | 历史日报 HTML 解析与批量导入 |
辅助目录:
| 目录 | 用途 |
|------|------|
| `configs/` | sources.yaml(新闻源)、watchlist.yaml(关注列表)、llm_models.yaml(LLM 场景配置)、loader.py |
| `prompts/` | 4 个 LLM Prompt 模板(event_extraction/company_analysis/industry_analysis/risk_analysis) |
| `scripts/` | 独立运行脚本(每个模块一个入口) + systemd 服务文件 |
| `tests/` | pytest 测试(225+ passed) |
| `api/` | 占位(空包,预留给未来 API 服务) |
| `app/` | 空目录(预留) |
---
## 2. 数据流全景
```
财经网站 / cninfo API
│
▼
┌───────────┐
│ M1 抓取 │ → data/raw/{source}/{YYYYMMDD}/*.html + index.jsonl
└─────┬─────┘
│
▼
┌───────────┐
│ M2 提取 │ → data/processed/{source}/{YYYYMMDD}/{url_hash}.json (Article)
└─────┬─────┘
│
▼
┌───────────┐
│ M3 去重 │ → data/deduped/{YYYYMMDD}/uniques/{url_hash}.json
└─────┬─────┘ data/dedup/fingerprints.sqlite3 (指纹库)
│
▼
┌───────────┐
│ M4 LLM │ → data/events/{YYYYMMDD}/{url_hash}.json (ExtractedEvent)
└─────┬─────┘
│
▼
┌───────────┐
│ M5 向量化 │ → data/embeddings/{YYYYMMDD}/{url_hash}.json (1024 维向量)
└─────┬─────┘
│
▼
┌───────────┐
│ M6 入库 │ → data/qdrant_storage/ (Qdrant 本地文件)
└─────┬─────┘
│
┌────┴────┐
▼ ▼
┌──────┐ ┌──────┐
│ MCP │ │ 日报 │
│ 检索 │ │ 生成 │
└──────┘ └──┬───┘
│
▼
┌────────┐
│ MySQL │ → news_report / news_event (myquant 库)
└────────┘
```
**关键设计**:
- **增量处理**:M2/M4/M5 产物存在即跳过(2026-08-12 新增),`--force` 全量重建
- **断点续跑**:`pipeline --once --resume` 从上次失败步骤继续,状态文件 `data/pipeline/state.json`
- **去重多源记录**:M3 指纹库 `source_ids` 列记录同一篇新闻的多个来源
---
## 3. 包职责与导出 API
### 3.1 `crawler/` — M1 新闻抓取
| 文件 | 职责 |
|------|------|
| `engine.py` | Crawl4AI 异步引擎:httpx 静态直连(js_render=false) / Playwright(js_render=true) |
| `config.py` | 加载 `configs/sources.yaml` |
| `models.py` | SourceConfig / CrawlerConfig / CrawlResult / ArticleLink / CninfoItem / CrawlStage |
| `storage.py` | 保存 raw HTML + index.jsonl,URL 去重 |
| `cninfo.py` | cninfo 公告/调研/互动易 API 抓取 |
**公开 API**:
```python
from crawler import (
crawl_all, crawl_source, extract_article_links,
load_crawler_config,
SourceConfig, CrawlerConfig, CrawlResult, CrawlStage, ArticleLink, CninfoItem,
)
```
**抓取分流规则**:
| 条件 | 引擎 | 特点 |
|------|------|------|
| `js_render=False` 且无 `wait_for` | httpx 直连 | 快,不触发反爬 |
| `js_render=True` 或有 `wait_for` | Playwright | 支持 JS 渲染 |
**新闻源**:14 个(13 Web + 1 API:新闻联播),见 `configs/sources.yaml`
### 3.2 `extractor/` — M2 正文提取
| 文件 | 职责 |
|------|------|
| `parser.py` | GNE 中文正文提取,模板文本过滤 |
| `models.py` | Article / ExtractError |
**公开 API**:
```python
from extractor import (
extract_article, Article, ExtractError, MIN_CONTENT_LENGTH, SOURCE_NAME_MAP,
)
```
**Article 模型**(M2 输出,M3+ 输入的唯一格式):
| 字段 | 类型 | 说明 |
|------|------|------|
| `source_id` | str | M1 源 ID |
| `url` | str | 文章 URL |
| `url_hash` | str | SHA1 前 16 位,主键 |
| `title` | str | 标题 |
| `content` | str | 清理后纯文本正文 |
| `author` | str\|None | 作者 |
| `publish_time` | datetime\|None | 标准化发布时间 |
| `word_count` | int | 中文字符数 |
| `item_type` | str\|None | cninfo 类型:announcement/research/irm |
### 3.3 `dedup/` — M3 三层去重
| 文件 | 职责 |
|------|------|
| `deduper.py` | Deduper 主类:check / ingest / stats |
| `hasher.py` | SimHash 64 位 + 汉明距离 + content_hash |
| `store.py` | FingerprintStore,SQLite 持久化 |
| `models.py` | DedupLayer / DedupResult / DedupStats / Fingerprint |
**公开 API**:
```python
from dedup import (
Deduper, article_to_fingerprint, FingerprintStore,
DedupLayer, DedupResult, DedupStats, Fingerprint,
simhash64, hamming, content_hash, normalize_content,
DEFAULT_HAMMING_THRESHOLD, DEFAULT_TIME_WINDOW_DAYS,
)
```
**三层去重逻辑**:
| 层 | 算法 | 命中条件 |
|----|------|----------|
| L1 URL | URL Hash (SHA1) | 完全相同 URL |
| L2 Content | 标准化正文 SHA1 | 正文完全一致 |
| L3 SimHash | 64 位 SimHash + 汉明距离 | 距离 ≤ 3 (默认) |
### 3.4 `llm/` — M4 投资事件抽取
| 文件 | 职责 |
|------|------|
| `client.py` | OpenAI 兼容客户端,指数退避重试,LLMConfig 加载 |
| `extractor.py` | Prompt 模板加载,JSON 解析,extract_event / extract_event_async |
| `models.py` | EventExtraction / ExtractedEvent / Sentiment / LLMCallError / EVENT_TYPES |
**公开 API**:
```python
from llm import (
extract_event, extract_event_async, parse_event_json,
PromptTemplate, load_llm_config, LLMConfig,
make_sync_client, make_async_client,
EventExtraction, ExtractedEvent, Sentiment, LLMCallError,
EVENT_TYPES, MAX_CONTENT_CHARS, MAX_IMPORTANCE, MIN_IMPORTANCE,
SCENE_EVENT_EXTRACTION, SCENE_DAILY_REPORT, SCENE_STOCK_REPORT,
)
```
**ExtractedEvent 模型**(M4 落盘格式):
| 字段 | 类型 | 说明 |
|------|------|------|
| `source_id` | str | 主源 |
| `url` | str | 原文 URL |
| `url_hash` | str | SHA1 前 16 位 |
| `title` | str | 标题 |
| `publish_time` | datetime\|None | 发布时间 |
| `sources` | list[str] | 全部来源(去重合并) |
| `event` | EventExtraction | LLM 抽取结果 |
| `provider` | str | deepseek / qwen |
| `model` | str | 模型名 |
| `attempts` | int | LLM 实际调用次数(含重试) |
**EventExtraction**(LLM 输出 JSON 结构):
| 字段 | 类型 | 说明 |
|------|------|------|
| `stock_codes` | list[str] | 6 位代码,可带 .SH/.SZ/.BJ |
| `company_names` | list[str] | 公司中文简称 |
| `industries` | list[str] | 行业(申万二级) |
| `sentiment` | Sentiment | positive/neutral/negative |
| `importance` | int | 1-5 重要程度 |
| `event_type` | str | 23 种事件类型之一 |
| `summary` | str | 一句话摘要(≤200 字) |
**23 种事件类型**:业绩预告/业绩快报/财报披露/合作签约/投资并购/重大合同/产品发布/技术突破/监管处罚/诉讼仲裁/股东减持/股东增持/回购/分红/高管变动/资产重组/停牌复牌/ST警示/退市风险/宏观政策/行业政策/国际局势/其他
**LLM 场景配置**(`configs/llm_models.yaml`):
| 场景 | 用途 | 调用方 |
|------|------|--------|
| `event_extraction` | 投资事件抽取(M4) | llm/extractor.py |
| `daily_report` | 日报 AI 摘要 | scheduler/reporter.py |
| `stock_report` | 个股 AI 要点 | scheduler/stock_reporter.py |
| `embedding` | 文本向量化(M5) | embedding/factory.py |
### 3.5 `embedding/` — M5 向量化
| 文件 | 职责 |
|------|------|
| `base.py` | EmbeddingProvider / AsyncEmbeddingProvider ABC + compose_text |
| `remote.py` | DashScope 远程 Embedding(OpenAI 兼容) |
| `local.py` | 本地 BGE-M3(可选,需 `uv sync --extra local-embedding`) |
| `factory.py` | resolve_provider_type / make_sync_provider / make_async_provider |
| `models.py` | EmbeddingProviderType / EmbeddingResult / EmbeddingError |
**公开 API**:
```python
from embedding import (
make_sync_provider, make_async_provider, resolve_provider_type,
compose_text, EmbeddingProvider, AsyncEmbeddingProvider,
EmbeddingResult, EmbeddingError, EmbeddingProviderType,
DashScopeEmbeddingProvider, DashScopeAsyncEmbeddingProvider,
DASHSCOPE_DEFAULT_DIM, DASHSCOPE_DEFAULT_MODEL, DASHSCOPE_BATCH_LIMIT,
MAX_TEXT_CHARS,
)
```
**向量维度**:1024(DashScope `text-embedding-v3` / 本地 BGE-M3)
**文本组装**:`compose_text()` 统一策略——标题 + 正文 + 事件摘要,最大 8000 字符
### 3.6 `vectorstore/` — M6 Qdrant 知识库
| 文件 | 职责 |
|------|------|
| `client.py` | VectorStore 封装(init/upsert/query/count/info) + make_qdrant_client 工厂 |
| `models.py` | SearchFilter / SearchResult / CollectionInfo |
**公开 API**:
```python
from vectorstore import (
VectorStore, make_qdrant_client,
SearchFilter, SearchResult, CollectionInfo,
DEFAULT_COLLECTION, DEFAULT_VECTOR_DIM,
)
```
**Qdrant 模式**:默认本地文件模式(`data/qdrant_storage/`),零依赖,ARM64 兼容
**SearchFilter 支持**:source_id / stock_codes / company_names / industries / sentiment / importance_min / event_types / publish_date_from / publish_date_to
### 3.7 `scheduler/` — M7 定时任务与 Pipeline
| 文件 | 职责 |
|------|------|
| `pipeline.py` | run_pipeline / run_step,编排 M1→M6,断点续跑 |
| `reporter.py` | 日报生成(收集→AI 摘要→MySQL 入库) |
| `stock_reporter.py` | 个股日报生成(关注列表) |
**公开 API**:
```python
from scheduler import (
run_pipeline, run_step, PipelineResult, StepResult,
STEP_COMMANDS, STEP_TIMEOUTS,
)
```
**Pipeline 步骤**:
| 步骤名 | 中文 | 默认超时 | 说明 |
|--------|------|----------|------|
| `crawler` | M1 新闻抓取 | 900s | 13 源,含 Playwright |
| `xwlb` | M1 新闻联播 | 60s | 纯 HTTP API |
| `extractor` | M2 正文提取 | 300s | GNE 提取 |
| `dedup` | M3 新闻去重 | 120s | 三层去重 |
| `llm` | M4 LLM 抽取 | 900s | API 调用 |
| `embedding` | M5 向量化 | 300s | DashScope |
| `qdrant` | M6 Qdrant 入库 | 300s | 本地文件写入 |
| `report` | 日报生成 | 30s | 仅 07:00 执行 |
| `cninfo_crawl` | cninfo 公告抓取 | 900s | 06:30 执行 |
| `cninfo_extract` | cninfo 正文提取 | 300s | — |
| `cninfo_pdf` | cninfo PDF 补充 | — | — |
**超时优先级**:`TIMEOUT_{NAME}` 环境变量 > `PIPELINE_STEP_TIMEOUT` > 硬编码默认值 > 1800s
**调度时间表**:
| 时间 | 步骤 |
|------|------|
| 06:30 | cninfo 公告管道 |
| 07:00 | crawler→xwlb→extractor→dedup→llm→embedding→qdrant→**日报** |
| 07:30 | 个股日报 |
| 12:00 | crawler→xwlb→extractor→dedup→llm→embedding→qdrant |
| 18:00 | crawler→xwlb→extractor→dedup→llm→embedding→qdrant |
| 22:00 | crawler→xwlb→extractor→dedup→llm→embedding→qdrant |
### 3.8 `mcp_server/` — M8 MCP 服务
| 文件 | 职责 |
|------|------|
| `tools.py` | FastMCP 服务器,5 个 MCP 工具 |
**5 个 MCP 工具**:
| 工具 | 参数 | 说明 |
|------|------|------|
| `search_news` | query, top_k | 通用语义检索 |
| `search_company_news` | query, company, top_k | 按公司过滤 |
| `search_industry_news` | query, industry, top_k | 按行业过滤 |
| `search_stock_events` | query, stock_code, top_k | 按股票代码过滤 |
| `search_sentiment_trend` | query, sentiment, top_k | 情绪趋势 + 统计 |
> 工具参数/返回格式/降级策略详见 [`docs/mcp_tools.md`](mcp_tools.md)
**启动方式**:stdio 模式(Cherry Studio/Claude Code 自动管理进程)或 SSE 模式(调试:`--sse 8765`)
### 3.9 `a_share_cli/` — 统一 CLI
| 文件 | 职责 |
|------|------|
| `main.py` | argparse 子命令路由,所有命令行操作 |
**全部子命令**:
| 命令 | 功能 | 关键参数 |
|------|------|----------|
| `crawl` | M1 抓取 | `--source`, `--no-save` |
| `extract` | M2 提取 | `--date`, `--source` |
| `dedup` | M3 去重 | `--date`, `--reset` |
| `events` | M4 LLM 抽取 | `--date`, `--provider`, `--model`, `--limit`, `--concurrency` |
| `embed` | M5 向量化 | `--date`, `--provider`, `--model` |
| `ingest` | M6 入库 | `--date`, `--recreate` |
| `pipeline` | 全链路/守护 | `--once`, `--resume`, `--steps`, `--report`, `--cninfo-once` |
| `search` | 检索知识库 | `query`, `--top`, `--source`, `--sentiment`, `--stock`, `--industry`, `--min-importance` |
| `status` | 数据总览 | 无参数 |
| `report` | 生成日报 | `--date`, `--no-upload` |
| `report-import` | 历史日报导入 | `--dir`, `--date`, `--type`, `--force` |
| `cninfo` | 公告抓取 | `--no-save`, `--enrich-pdf`, `--pdf-limit` |
| `stock-report` | 个股日报 | `--no-upload` |
| `watchlist` | 关注列表管理 | `add`/`remove`/`list` |
| `discover` | 站点分析 | `url`, `--name`, `--extra`, `--add` |
| `add-entry` | 追加入口 | `source`, `urls...` |
### 3.10 `report_db/` — M10 日报 DB 层
| 文件 | 职责 |
|------|------|
| `db.py` | 连接/事务/建表/保存/查询 |
| `models.py` | EventRow / ReportData (Pydantic) |
| `schema.py` | DDL(CREATE TABLE) |
**公开 API**:
```python
from report_db import (
connect, init_schema, save_report, load_db_config,
exists_report, fetch_report,
EventRow, ReportData,
)
```
### 3.11 `report_import/` — M10 历史日报导入
| 文件 | 职责 |
|------|------|
| `parser.py` | BeautifulSoup 解析 finance/intl 历史 HTML |
| `importer.py` | 批量导入,幂等,ImportStats 统计 |
**公开 API**:
```python
from report_import import (
import_history, ImportStats,
parse_report, parse_finance_report, parse_intl_report, ReportParseError,
)
```
---
## 4. 核心数据模型
### 4.1 管道数据模型关系
```
CrawlResult (M1) ──→ Article (M2) ──→ DedupResult (M3)
│
▼
ExtractedEvent (M4)
│
▼
EmbeddingResult (M5)
│
▼
SearchResult (M6)
```
### 4.2 日报数据模型
```
ReportData (Pydantic)
├── report_date: date
├── report_type: str (finance | intl)
├── file_name: str
├── generated_at: datetime
├── ai_summary: str | None
├── stats: dict (JSON)
└── events: list[EventRow]
├── section: str (xwlb | news | cninfo | intl)
├── rank: int
├── importance: int | None
├── event_type: str | None
├── title: str
├── summary: str | None
├── sentiment: str | None
└── source: str | None
```
### 4.3 全部 Pydantic 模型清单
| 包 | 模型 | 用途 |
|----|------|------|
| crawler | SourceConfig | 单个新闻源配置 |
| crawler | CrawlerConfig | 全局抓取配置 |
| crawler | CrawlerSettings | 抓取设置(并发/重试/UA) |
| crawler | CrawlResult | 单次抓取结果 |
| crawler | ArticleLink | 列表页发现的链接 |
| crawler | CninfoItem | 公告/调研/互动易条目 |
| extractor | Article | 标准化文章(M2 输出) |
| dedup | Fingerprint | 指纹记录(SQLite) |
| dedup | DedupResult | 判重结果 |
| dedup | DedupStats | 指纹库统计 |
| llm | EventExtraction | LLM 输出 JSON 结构 |
| llm | ExtractedEvent | M4 落盘格式(文章+事件) |
| embedding | EmbeddingResult | 向量化结果 |
| vectorstore | SearchFilter | 检索过滤条件 |
| vectorstore | SearchResult | 单条检索结果 |
| vectorstore | CollectionInfo | Qdrant Collection 信息 |
| scheduler | StepResult | 单步执行结果 |
| scheduler | PipelineResult | 全链路执行结果 |
| report_db | EventRow | 日报事件行 |
| report_db | ReportData | 完整日报数据 |
---
## 5. 配置体系
### 5.1 配置文件
| 文件 | 格式 | 用途 | 热更新 |
|------|------|------|--------|
| `configs/sources.yaml` | YAML | 14 个新闻源配置 | 每次抓取重读 |
| `configs/llm_models.yaml` | YAML | 4 个 LLM 场景配置 | 每次调用重读 |
| `configs/watchlist.yaml` | YAML | cninfo 公告关注列表 | 每次操作重读 |
| `.env` | dotenv | API Key + 调度/超时/DB 配置 | 重启服务生效 |
### 5.2 配置优先级(LLM 场景)
```
CLI 显式参数 (--provider / --model)
↓
configs/llm_models.yaml scenes.<场景>.xxx
↓
.env 环境变量 (LLM_PROVIDER / DEEPSEEK_MODEL 等)
↓
代码内置默认值 (仅超时/温度等参数)
```
> **模型名**无内置兜底:缺失即报错,绝不静默使用错误模型。
### 5.3 关键环境变量
| 变量 | 用途 | 生产值示例 |
|------|------|-----------|
| `DASHSCOPE_API_KEY` | 百炼 LLM + Embedding | sk-xxx |
| `DEEPSEEK_API_KEY` | DeepSeek LLM | sk-xxx |
| `SCHEDULE_TIMES` | 调度时间 | `07:00,12:00,18:00,22:00` |
| `PIPELINE_STEP_TIMEOUT` | 全局超时 | `1800` |
| `NEWS_DB_HOST` | 日报 DB 主机 | `192.168.1.10`(pi5) / `127.0.0.1`(Mac) |
| `NEWS_DB_PORT` | 日报 DB 端口 | `13306` |
---
## 6. 产物目录结构
```
data/
├── raw/ # M1 原始抓取
│ ├── cls/{YYYYMMDD}/ # 按源分目录
│ │ ├── index.jsonl # 文章列表(CrawlResult 摘要)
│ │ └── *.html # 原始 HTML
│ ├── eastmoney/{YYYYMMDD}/
│ ├── ...
│ └── cninfo/{YYYYMMDD}/ # 公告独立目录
│
├── processed/ # M2 正文提取
│ ├── cls/{YYYYMMDD}/
│ │ └── {url_hash}.json # Article
│ └── ...
│
├── dedup/ # M3 指纹库(跨日)
│ └── fingerprints.sqlite3
│
├── deduped/ # M3 去重产物
│ └── {YYYYMMDD}/
│ ├── uniques/
│ │ └── {url_hash}.json # 唯一文章
│ ├── duplicates.jsonl # 重复记录
│ └── sources.json # 多源映射
│
├── events/ # M4 事件抽取
│ └── {YYYYMMDD}/
│ └── {url_hash}.json # ExtractedEvent
│
├── embeddings/ # M5 向量化
│ └── {YYYYMMDD}/
│ └── {url_hash}.json # 1024 维向量 + 事件
│
├── qdrant_storage/ # M6 Qdrant 本地文件
│
├── pipeline/ # 断点续跑状态
│ └── state.json
│
└── reports_history/ # 历史日报 HTML(可选)
```
---
## 7. 运行环境
| 项目 | 值 |
|------|-----|
| Python | 3.11 |
| 包管理器 | uv |
| 虚拟环境 | `.venv/` |
| 生产服务器 | 树莓派 5 (ARM64), `pi@192.168.1.160` |
| 数据库 | MySQL/MariaDB (myquant 库), 通过 `pi@192.168.1.10` autossh 隧道 |
| 系统服务 | systemd `a-share-research` + `a-share-db-tunnel` |
| 测试 | pytest (asyncio_mode=auto, integration 标记默认跳过) |
| 静态检查 | ruff + mypy |
| LLM 模型 | `deepseek-v4-flash`(锁定,不可修改) |
---
—— 架构文档结束 ——