Skip to content

Latest commit

 

History

History
127 lines (93 loc) · 8.93 KB

File metadata and controls

127 lines (93 loc) · 8.93 KB

stocklens/notification/ — 股票领域通知壳

职责

包装 pulsefan.Notifier股票领域薄壳:daily / batch / metadata 报告生成、12 段语义渲染、Bot 会话回复、本地报告归档。

通用多渠道扇出能力(10 个 sender / ChannelRegistry / ParallelDispatcher / formatting helpers)已经下沉到 pulsefan,此处不重复。

文件清单

业务层(保留在 stocklens)

文件 职责 行数
orchestrator.py ReportOrchestrator(兼容别名 NotificationService)— 包装 pulsefan.Notifier + 业务调度 ~190
config_adapter.py 单一映射表:stocklens.Config → pulsefan.NotificationConfig ~95
report_builder.py _ReportMixin — 通道检测 + 报告生成 + summary-only 处理
metadata_report.py 元数据报告编排入口(仅 setup + 顺序调用 12 个 sub-renderer) 134
metadata_sections/ 元数据报告的 12 个语义段子包(详见下方子表)
report_generators.py 多种报告格式生成(dashboard / brief / single / daily) 920+
report_storage.py 报告文件存储与索引管理
context_sender.py 钉钉/飞书 Bot 会话回复
channel_types.py NotificationChannel enum + ChannelDetector + NotificationBuilder 80
__init__.py 重新导出 ReportOrchestrator + NotificationService 别名 + pulsefan.formatting 工具(chunk_content_by_max_bytes, format_feishu_markdown) ~30

旧的 dispatcher.py 已在 pulsefan 拆分(Commit 3)中重命名为 orchestrator.py 并彻底重构,不留 shim;旧的 senders/ 子包整体搬入 pulsefan/senders/

metadata_sections/ 子包

历史 1394 行单函数 generate_metadata_report 已按 12 个语义段拆分。每段是独立 render_<name>(lines, enhanced_context, _fmt, *, code, stock_name, news_context, report_date) -> None 函数,自行从 enhanced_context 解构需要的字段。metadata_report.py 仅负责 setup 与编排。

文件 段名 内容 行数
scoring_overview.py 量化评分概览 ScoringService.format_overview_table() 渲染 36
insider_sector.py 内部交易+板块 ETF 内部人交易 + 板块 ETF 比较 72
dashboard.py 决策仪表盘 关键指标速览 98
market_overview.py 行情概览 realtime + OHLCV + MA 状态 + 筹码 120
technical.py 技术分析 趋势 + Bollinger + ATR/OBV/VWAP + K 线 372
fundamentals.py 基本面 DB 基本面 + finviz EPS/营收/估值/盈利/派息 321
industry_capital.py 行业与资金 板块 / 行业 / Peer / 做空 / 持股 / 内部人 197
macro_env.py 宏观环境 宏观 + 美债 + 情绪 + CNN F&G + PE 百分位 219
company.py 公司概况 描述 + 公司信息 53
news.py 新闻情报 finviz 英文 + AkshareNews 中文 56
openbb_extra.py OpenBB 增量 OpenBB 数据扩展 49
footer.py 页脚 数据采集时间戳 28

核心类

ReportOrchestrator(orchestrator.py)

2026-05 实现细节_history_compare_cache 从无界 Dict[Tuple, ...] 升级为 cachetools.LRUCache(maxsize=128),避免长生命周期实例(FastAPI lifespan singleton) 持续累积旧 batch 的对比结果。

构造

from pulsefan import Notifier
from stocklens.notification import ReportOrchestrator
from stocklens.notification.config_adapter import build_notification_config

# 显式注入 Notifier(推荐 —— api/app.py lifespan 用法)
notifier = Notifier(build_notification_config(get_config()))
orchestrator = ReportOrchestrator(notifier=notifier)

# 兜底懒加载(CLI / Bot 入口未走 lifespan 时)
orchestrator = ReportOrchestrator()  # 自动 build_notification_config(get_config())

关键方法(继承自 _ReportMixin)

  • generate_daily_report(results) -> str — 多股汇总 markdown
  • generate_metadata_report(code, name, enhanced_context, ...) -> str — 12 段元数据报告
  • is_available() -> boolget_available_channels()get_channel_names() -> str

兼容性

  • NotificationService = ReportOrchestratororchestrator.py 末尾保留为别名;旧 from stocklens.notification import NotificationService 仍可用。
  • notifier.send_to_wechat(...) / send_to_feishu(...) 等 10 个直通方法通过动态属性委托保留(pipeline 的 push_handler.py 依赖)。新代码应使用 orchestrator.notifier.send_text(...)

config_adapter.py

单一映射函数 build_notification_config(config) -> NotificationConfig,把 stocklens 的 130+ 字段 Config 投射成 pulsefan 的 10 个 ChannelConfig。所有 getattr(config, ...) 均带默认值,对 mock config 友好。

channel_types.py

业务枚举层 —— NotificationChannel enum + ChannelDetector(业务名称翻译)+ NotificationBuilder(简单告警快捷构造)。保留在 stocklens 因为这些是业务概念(区分"WECHAT" vs "WechatSender" 实例)。

依赖关系

  • 依赖:pulsefanstocklens.config.settings.Configstocklens.contracts.BotMessagestocklens.analyzer.llm_analyzer.AnalysisResultmarkdown2jinja2(可选)、imgkit(可选)
  • 被依赖:
    • stocklens/pipeline/push_handler.py — 调用 11 个 send_to_<channel> shortcut
    • stocklens/pipeline/orchestrator.py — 构造 ReportOrchestrator(source_message=...)
    • stocklens/pipeline/stages/metadata_stage.py — 调用 generate_metadata_report
    • api/v1/endpoints/agent.pychat/send endpoint
    • api/app.py lifespan — 启动时构造 pulsefan.Notifier 存入 app.state.notifier

配置项

.env.example 中"通知推送"部分。pulsefan 的 NotificationConfigconfig_adapter.build_notification_config(config) 从这些字段生成;新增渠道字段时,记得同步 config_adapter.py 中的映射表。

关键通用配置:

变量 默认值 说明
SINGLE_STOCK_NOTIFY false 单股立即推送
REPORT_TYPE simple 报告格式
REPORT_SUMMARY_ONLY false 仅推送摘要
MARKDOWN_TO_IMAGE_CHANNELS 渲染为图片的渠道
MARKDOWN_TO_IMAGE_MAX_CHARS 15000 触发图片渲染的字符上限

注意事项

  • 不要直接 from pulsefan.senders.* import —— CLAUDE.md 已禁止;所有业务推送一律走 ReportOrchestrator

  • pulsefan 不读 .env;所有配置通过 config_adapter.build_notification_config(config) 注入。新增渠道字段时同步更新 adapter。

  • 微信企业号 Webhook 有频率限制(每分钟 20 条);pulsefan 的 dispatcher 不限速,业务层需要自己管。

  • Email SMTP 服务器自动检测:在 pulsefan/senders/email.pySMTP_CONFIGS 表里写死了 10 大邮箱(gmail / qq / 163 等);显式 EmailConfig.smtp_server 优先生效。

  • 飞书 Webhook 和 App Bot 是两种不同的推送方式,pulsefan 的 FeishuSender 走 webhook;App Bot 上下文回复在 stocklens/notification/context_sender.py

  • REPORT_DIR 可覆盖默认报告输出目录(report_storage.py 处理)。

  • 2026-05 修复 report_storage.py 静态拼写错误_resolve_stock_dir_ReportMixin._stock_short_name(code) 是真 bug —— 类实际叫 _ReportStorageMixin,运行时如果走到该分支会 NameError。改正为 _ReportStorageMixin._stock_short_name(code)

  • 2026-05 删除死变量metadata_sections/fundamentals.pyname = stock_name(赋值后从未使用);metadata_sections/technical.pyrealtime = enhanced_context.get("realtime")(同样未使用)。同时把 technical.py"""渲染 技术分析 段...""" docstring 翻译成英文。

  • 2026-05 M4 — channel dispatch table(report_builder.send)_ReportMixin.send() 之前 130 行的 if channel == X: ... elif channel == Y: ... 长链已重写为声明式 dispatch table。每个渠道有专属 _dispatch_<channel>(*, content, image_bytes, ...) 方法(10 个);_channel_dispatch_table 是 lazy-built 的 {NotificationChannel: handler} 映射;send() 主体只剩 dispatch 循环 + 失败计数。新增渠道只需 (i) 写一个 _dispatch_xxx (ii) 在 _channel_dispatch_table 加一行。 另外抽出 _convert_markdown_to_image_once(content) 集中处理 Markdown→图片转换 + 安装提示日志(先前在 send() 里内联 ~50 行)。 send() 行数从 130 降到 ~50。 stocklens/pipeline/push_handler.py::_send_notifications 同步收敛:原 180 行 if/elif ladder 拆为 _convert_to_image_with_hint / _push_wechat / _push_email_groups / _push_other_channel(后者携带 5 个 text-only 渠道的 dispatch table)。Email 单独保留分组路由(Issue #268),WeChat 单独保留 brief 转换(平台 4096 char 限制)。