Skip to content

feat: 新增 OpenQuakeAPI 气象预警接入、聚合推送服务、细粒度日志级别控制及多项修复 - #180

Open
DBJD-CR wants to merge 27 commits into
dev/1.6.0from
dev/local
Open

feat: 新增 OpenQuakeAPI 气象预警接入、聚合推送服务、细粒度日志级别控制及多项修复#180
DBJD-CR wants to merge 27 commits into
dev/1.6.0from
dev/local

Conversation

@DBJD-CR

@DBJD-CR DBJD-CR commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

📝 描述 / Description

feat #42
feat #109

🛠️ 改动点 / Modifications

  • 不是一个破坏性变更 / This is NOT a breaking change.

📸 运行截图或测试结果 / Screenshots or Test Results

✅ 检查清单 / Checklist

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“运行截图”或“测试日志”。/ My changes have been well-tested, and "Screenshots" or "Test Logs" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txt 文件中。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to requirements.txt.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

❤️ CONTRIBUTING

  • 🥳 我已阅读并同意遵守该项目的 贡献指南 / I have read and agree to abide by the CONTRIBUTING of this project.

Summary by Sourcery

通过 OpenQuakeAPI 集成 CMA 气象预警,新增会话级天气预警聚合(可配置时间窗口与速率限制),并在多种灾害数据源上优化日志记录、过滤、轮询与展示行为。

New Features:

  • 添加 OpenQuakeAPI CMA 天气预警数据源,配套专用解析器,并集成到灾害服务处理管线中。
  • 引入基于会话的天气预警聚合与限速投递服务,支持前向消息批处理以及关闭阶段的队列刷新。

Bug Fixes:

  • 通过跟踪实际抓取的开始与完成状态,避免 HTTP 轮询服务在启动静默超时时被错误分类。
  • 修正 PLUM 和假定震中烈度过滤逻辑,避免造成误导性的震级阈值失败和非预期的数据丢弃。
  • 纠正 EQSC 和 S-Net 的数据源健康状况及子源状态上报,避免将已禁用的数据源误标为错误。

Enhancements:

  • 增加更细粒度的事件流日志等级控制,并在解析器、路由器、去重以及推送流日志之间传播流标签。
  • 改进天气图标处理,通过将 CMA p-code 映射到 Fan Studio 11B 图标代码、降级伪图片错误等级,并优先使用本地回退以降低日志噪音。
  • 增强天气查询命令输出,支持对大型结果集进行批量前向消息发送并提高节点数量上限。
  • 收紧 EQSC HTTP 客户端超时时间,以在网络抖动与大负载场景下提供更稳健的访问能力。
  • 对齐区域概要视图与城镇详情视图中的 JMA 烈度展示选项,确保一致遵循 detailed_jma_intensity

Documentation:

  • 扩展 OpenQuakeAPI 文档,增加 CMA 天气预警 WebSocket 端点、事件类型以及负载结构说明。
Original summary in English

Summary by Sourcery

Integrate CMA meteorological warnings via OpenQuakeAPI, add session-level weather alert aggregation with configurable windows and rate limiting, and refine logging, filtering, polling, and presentation behavior across multiple disaster data sources.

New Features:

  • Add OpenQuakeAPI CMA weather warning source with dedicated parser and integration into the disaster service pipeline.
  • Introduce a per-session weather alert aggregation and rate-limited delivery service with forward-message batching and shutdown flushing.

Bug Fixes:

  • Prevent startup silence timeout misclassification for HTTP polling services by tracking actual fetch start and completion states.
  • Fix PLUM and assumed epicenter intensity filtering to avoid misleading magnitude threshold failures and unintended drops.
  • Correct data source health and sub-source status reporting for EQSC and S-Net to avoid mislabeling disabled sources as errors.

Enhancements:

  • Add fine-grained event stream log level controls and propagate stream tags across parsers, routers, deduplication, and push flow logging.
  • Improve weather icon handling by mapping CMA p-codes to Fan Studio 11B icon codes, downgrading pseudo-image errors, and preferring local fallbacks with reduced log noise.
  • Enhance weather query command output by supporting batched forward messages for large result sets and increasing node limits.
  • Tighten EQSC HTTP client timeouts for more robust access under network jitter and large payloads.
  • Align JMA intensity presentation options between region-summary and town-detail views to honor detailed_jma_intensity consistently.

Documentation:

  • Extend OpenQuakeAPI documentation with CMA weather warning WebSocket endpoint, event types, and payload schema.

Summary by CodeRabbit

  • 新功能

    • 新增中国气象局气象预警数据源及相关展示支持。
    • 支持气象预警按时间窗口聚合、合并转发,并可配置红色预警即时推送与限流策略。
    • 气象预警消息支持官方图标、最终预警颜色和发布机构展示。
    • 新增按事件类型分别配置日志级别的能力。
  • 改进

    • 优化启动静默期间的数据抓取状态判断,避免过早恢复推送。
    • 改善气象预警查询的分段发送及合并转发回退体验。
    • 延长部分网络请求超时时间,提升连接稳定性。
  • Bug 修复

    • 修正地震震度展示及占位震级过滤逻辑。
    • 改善数据源连接状态展示,避免未启用连接被误报为异常。

DBJD-CR added 27 commits July 31, 2026 04:30
在 SOURCE_CATALOG 中注册 OpenQuakeAPI CMA 气象预警数据源,
priority=3(高于 Fan=1),与 Fan 不做跨源去重,可双推。
payload 直接透传 CMA 预警地图 API 字段。
/ws/all 聚合推送的 source 字段新增 cma 映射到 china_weather_openquake,
更新文档注释说明当前已接入 Global Quake 与 CMA 气象预警。
source_compat 新增 openquake_cma/cma_weather/cma → china_weather_openquake
别名映射;_DISPLAY_MAP 区分 Fan/OQ 气象预警展示名(- Fan / - OQ)。
WeatherAlarmParser 构造时按 source_id 区分数据源,各源维护独立
短窗去重队列。新增 _extract_realtime_payload 方法解包 OpenQuakeAPI
外层结构(source/type/action/payload),仅处理 type=weather 且
action=new 的事件,非 RealtimeEvent 结构回退到 FAN Studio 扁平载荷。
source_family 改从 source_entry 动态读取。
- parser_registry: 新增 weather_alarm_parser 细化分派,按 source_id
  注入正确的解析器实例,避免硬编码 china_weather_fanstudio
- base_parser: critical_fields 新增 china_weather_openquake 关键字段校验
OpenQuakeAPI 聚合连接的 JSON 消息,从仅放行 earthquake 业务类型
扩展为同时放行 weather(CMA 气象)业务类型,其余仍过滤。
新增 /ws/cma 路径说明、weather/new 事件列表、payload 数据示例
及字段说明表(id/headline/effective/description/longitude/latitude/type/title)。
source 列表表新增 cma 行。
新增 WeatherAggregationService:按会话维度独立缓冲气象预警事件,
时间窗口到期后合并推送。支持合并转发(Comp.Nodes),不支持时降级
为逐条文本发送并启用限流(按颜色级别排序,超出配额丢弃低级别)。
红色预警可配置立即触发推送,缓冲区为空时直接走常规推送流。
- event_pipeline: 按会话级配置判断是否聚合,启用聚合的会话进入
  缓冲区,未启用的走常规推送;新增 _flush_weather_buffer 回调,
  为每条预警构建含图标的完整消息链后发送,先通过规则链复核
- disaster_service: 装配 WeatherAggregationService 并注入流水线
- lifecycle: 停机前 flush_all 推送缓冲区中尚未发送的事件,避免丢失
- 新增 weather_aggregation 配置组(enabled/time_window_seconds/
  max_batch_size/flush_on_red/rate_limit_enabled/rate_limit_max_messages/
  rate_limit_window_seconds)
- 新增 event_stream_log_level 配置组(all/weather_alarm/global_quake/
  earthquake/tsunami/typhoon 独立日志级别覆盖)
- OpenQuakeAPI 分组新增 china_weather_alarm 子源开关
- FAN Studio 气象预警默认关闭(由 OQ 高优先级源接管)
- 全部 hint 统一补充句号,push_frequency_control hint 措辞修正
- 新增 weather_aggregation 配置项的布尔/数值范围校验
  (time_window 60-3600s, max_batch 1-50, rate_limit_max 1-20 等)
- OpenQuakeAPI 分组新增 china_weather_alarm 子源开关校验与旧配置回填
PluginLogger 新增 event_stream 参数与 _resolve_stream_action 方法,
支持按事件流类型(weather_alarm/global_quake/earthquake/tsunami/typhoon)
独立控制日志级别,优先级高于 log_mode 总开关。all 全局覆盖优先级最高,
其次按事件流类型匹配,最后回退到总开关。info/warning 方法签名
新增 event_stream 可选参数。
CENC/CEAEEW/烈度速报/FSSN CMT/JMA 地震与预警/S-Net/CWA 报告与预警
等地震类解析器的 plugin_logger.info 调用统一补充
event_stream="earthquake" 标签,支持细粒度日志级别控制。
Global Quake/USGS/ShakeAlert 解析器补充 event_stream="global_quake"/"earthquake";
海啸解析器补充 "tsunami";台风解析器补充 "typhoon";
- source_message_router: 新增 _resolve_stream_by_source_id 静态方法,
  按数据源标识解析事件流标签
- event_deduplication_service: 新增 _resolve_event_stream_by_source_id,
  确保去重器报数日志与解析器日志流标签一致
- cenc/cwa_eew_fusion_service: 融合策略日志补充 "earthquake" 标签
- push_flow_handler: 新增 _resolve_event_stream 按事件类型解析流标签
- EQSC 轮询服务日志补充对应事件流标签
新增 weather_alarm_code_map 模块,将 CMA 预警地图 API 的 p 编码
(如 p0002003)转换为 Fan Studio 图标接口兼容的 11B 编码
(如 11B03_yellow)。支持 p 编码通用规则解析、特殊短码跳过、
标题文本兜底映射,提供 resolve_weather_icon_code 与
build_weather_icon_url 公共接口。
- 通过 resolve_weather_icon_code 统一解析图标码(p 编码经映射表转换),
  替代直接拼接 URL
- 识别气象预警图标接口返回的伪图片(HTML 错误页伪装为 image/png),
  稳定触发的失败降级为 DEBUG 避免控制台刷屏
- 图标下载失败/无可用回退图标的日志从 WARNING 降级为 DEBUG
- weather_constants: 新增 extract_final_weather_color 函数,优先从
  "升级为/降级为/变更为"关键词后的文本提取最终颜色
- weather_presenter: 颜色提示图标改用 extract_final_weather_color,
  避免显示变更前旧颜色;标题补充"中国气象局"来源标识
- weather_query_service: detect_weather_color 同步改用统一提取逻辑;
  extract_weather_org 新增气象台/气象局/气象站/气象中心/气象分局
  机构名正则,覆盖升级/降级类预警 headline 无"发布"关键词的场景
- startup_silence_coordinator: GateState 新增 fetch_started_at/fetching
  字段,超时从实际开始抓取时间起算,避免初始化耗时被误计入;
  fetching=True 时不触发超时放行,避免首轮抓取耗时较长时被误放行
- 各轮询服务统一调用 note_poll_fetch_started/completed,抽取
  _notify_silence_fetch_completed 消除重复代码,确保失败路径通知协调器
- 台风静默期调用 _handle_disaster_event 播种去重指纹,避免重载后
  首次推送因去重缓存为空而被误放行(重复推送根因)
_evaluate_dual_threshold_filter 新增 skip_primary/skip_secondary 参数,
被跳过的检查项不参与 any/all 组合判定。震度模式下检测
is_assumption/magnitude_is_placeholder 元数据标记,跳过震级阈值检查,
仅按震度过滤,避免 all 模式误杀 PLUM 报或 any 模式产生误导性失败描述。
connect/sock_connect 从 8s 放宽到 15s(国内到 equake.top 的 TCP+TLS
握手在网络抖动时容易超过 8 秒);sock_read 从 15s 放宽到 30s
(台风列表接口含 20 个台风完整轨迹,响应体较大);total 从 25s
放宽到 45s(connect+sock_read 上限)。
- 从 connections 视图提取各连接分组的配置启用状态,避免将未启用的
  连接(如 FAN Studio 烈度速报)误显示为"异常"
- EQSC/S-Net 子源改由 build_sub_source_status 按 config_key 正确归组,
  移除硬编码注入逻辑,避免 key 名与 grouped_sources 漂移导致计数错误
- 数据源分组之间增加空行提升可读性
- 补充 FSSN CMT、china_weather_alarm(OpenQuakeAPI) 等缺失的数据源展示
正常区域搜索结果 >1 条时优先尝试合并转发分批发送(max_nodes 从 8
提升到 10),失败时回退文本概要发送。补充 delivery_mode 遥测字段
区分 forward_batches/text_blocks 两种投递模式。
- ConnectionsGrid: OpenQuakeAPI 分组新增 china_weather_alarm/
  china_weather_openquake 连接展示
- formatters: normalizeSourceName 新增 openquake_cma/cma_weather/cma
  别名归一化;formatSourceName 区分 Fan/OQ 气象预警展示名
地域汇总分支此前完全不读取 detailed_jma_intensity,导致 detailed=false
时仍输出全部震度档地域,与开关描述"仅显示最大震度区域"的语义矛盾。

现于地域汇总输出循环前根据 detailed_jma_intensity 截取 sorted_scales,
false 时仅保留最大震度档,使该开关在 region 开启/关闭两种模式下均生效,
实现两个开关的正交双维度控制。
@DBJD-CR
DBJD-CR requested a review from Aloys233 July 30, 2026 22:18
@dosubot dosubot Bot added size:XXL 修改了 1000+ 行代码 (忽略生成文件) type/feat ✨ 新功能 / New Feature labels Jul 30, 2026
@sourcery-ai

sourcery-ai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

集成来自 OpenQuakeAPI 的 CMA 气象预警,新增按会话维度的天气聚合/转发推送服务,引入基于事件流的细粒度日志级别控制,优化启动静默协调和 EQSC/S-Net 轮询行为,并修复若干展示、解析、查询与配置相关问题。

CMA 气象事件聚合与推送的时序图

sequenceDiagram
    actor OpenQuakeAPI
    participant SourceMessageRouter
    participant WeatherAlarmParser
    participant EventPipeline
    participant WeatherAggregationService
    participant MessageManager
    participant MessageBuildService
    participant SessionSender

    OpenQuakeAPI->>SourceMessageRouter: WebSocket message (source=cma, type=weather)
    SourceMessageRouter->>WeatherAlarmParser: _parse_data(data)
    WeatherAlarmParser->>WeatherAlarmParser: _extract_realtime_payload(data)
    WeatherAlarmParser-->>SourceMessageRouter: EventEnvelope(weather_alarm)
    SourceMessageRouter->>EventPipeline: handle(event)

    loop per session
        EventPipeline->>WeatherAggregationService: should_aggregate(event, session, runtime_config)
        alt should_aggregate == False
            EventPipeline->>MessageManager: push_event(event, target_sessions)
        else should_aggregate == True
            EventPipeline->>WeatherAggregationService: buffer event
        end
    end

    rect rgb(230,230,250)
    note over WeatherAggregationService: later, when flush conditions met
    WeatherAggregationService->>EventPipeline: _flush_weather_buffer(session, entries, config, mode="forward")
    loop each WeatherBufferEntry
        EventPipeline->>MessageManager: evaluate_push_decision(entry.event, runtime_config)
        alt decision.accepted
            EventPipeline->>MessageBuildService: build_message_async(entry.event, runtime_config)
            MessageBuildService-->>EventPipeline: MessageChain
        else decision.rejected
            EventPipeline-->>EventPipeline: skip entry
        end
    end

    alt mode == "forward"
        EventPipeline->>SessionSender: send(session, MessageChain(Comp.Nodes))
    else mode == "single"
        loop each built message
            EventPipeline->>SessionSender: send(session, message)
        end
    end
    end
Loading

File-Level Changes

Change Details Files
引入按会话维度的气象预警聚合与转发推送流程,并在关闭时优雅地刷新缓冲区。
  • EventPipeline 现在注入 WeatherAggregationService,并根据会话配置将天气事件分流到聚合路径或普通推送路径。
  • WeatherAggregationService 维护按会话划分的缓冲区,支持红色等级的立即刷新、forward-merge 模式、受限速的单消息回退,并暴露由 EventPipeline 使用的 flush 回调。
  • 灾害服务生命周期在关闭时会刷新所有剩余的聚合天气缓冲,以避免丢失预警。
core/app/pipeline/event_pipeline.py
core/message/push/weather_aggregation_service.py
core/app/runtime/disaster_service_lifecycle.py
新增 OpenQuakeAPI CMA 气象数据源,并调整天气解析、路由和图标构建以同时支持 Fan 与 CMA 负载。
  • 数据源目录注册新的 china_weather_openquake SourceEntry,归属 OpenQuake 分发 family,并使用 CMA 提供方名称。
  • WeatherAlarmParser 增加针对 type=weather/action=new 的 RealtimeEvent 负载解包,支持按数据源的去重队列、source_family 传递,以及在日志中添加 event_stream 标签。
  • 解析器注册器向 WeatherAlarmParser 传入 source_id,基础解析器将 china_weather_openquake 视为非心跳源,原始消息过滤器对 OpenQuake 仅保留 earthquake/weather 类型的 RealtimeEvent。
  • 天气图标 presenter 现在通过 weather_alarm_code_map 解析 CMA p-code 与标题为 11B 图标代码,构建 Fan Studio URL,将伪图像错误视为 debug,并在本地图标回退时降低日志级别。
  • 管理/状态 UI 和存储兼容映射已更新,以展示并规范化来自 OpenQuake 的新 CMA 气象数据源。
core/sources/source_catalog.py
core/parsers/weather_parser.py
core/parsers/parser_registry.py
core/message/logging/filters/raw_message_filter.py
core/message/push/message_build_service.py
core/message/presenters/weather_alarm_code_map.py
admin/js/utils/formatters.js
core/storage/source_compat.py
admin/js/components/status/ConnectionsGrid.jsx
docs/OpenQuakeAPI.md
实现按事件流维度的细粒度日志级别控制,并在地震、天气、台风、海啸以及 Global Quake 流中统一日志标签。
  • PluginLogger 现在在 debug_config 中支持 event_stream_log_level 覆盖(整体或按事件流),与现有的 log_mode/downgrade 行为并存。
  • 大量解析、融合、去重、推送流、路由和轮询日志调用已更新,增加天气预警(weather_alarm)、地震(earthquake)、global_quake、台风(typhoon)、海啸(tsunami)等 event_stream 标签。
  • 事件流标签由 EventDeduplicationService、SourceMessageRouter、PushFlowHandler 及其他组件基于 source_id 或 event_type 派生。
utils/plugin_logger.py
core/services/identity/event_deduplication_service.py
core/network/source_message_router.py
core/message/push/push_flow_handler.py
core/message/fusion/cwa_eew_fusion_service.py
core/message/fusion/cenc_fusion_service.py
core/parsers/global_sources_parser.py
core/parsers/japan_eew_parser.py
core/parsers/tsunami_parser.py
core/parsers/china_eew_parser.py
core/parsers/japan_earthquake_parser.py
core/parsers/taiwan_eew_parser.py
core/parsers/china_earthquake_parser.py
core/parsers/china_intensity_report_eqsc_parser.py
core/parsers/china_intensity_report_parser.py
core/parsers/fssn_cmt_parser.py
core/parsers/taiwan_earthquake_parser.py
core/parsers/typhoon_parser.py
core/parsers/snet_parser.py
优化启动静默协调及 EQSC/S-Net 轮询行为,避免过早取消静默,并在静默期间预热去重缓存。
  • StartupSilenceCoordinator 追踪各 gate 的 fetch_started_at 和 fetching 标志;轮询 gate 现在基于实际抓取开始时间进行预热,并在抓取期间避免因超时而结束静默。
  • EQSC 台风、CENC 烈度和海啸轮询服务在抓取开始/完成时通知静默协调器,封装完成通知逻辑,并为台风/地震日志添加 event_stream 标签。
  • EQSC 台风轮询通过 _handle_disaster_event 在静默期间也向去重缓存写入事件,以避免静默结束后出现重复推送。
  • S-Net 轮询服务同样集成静默协调器通知机制,区分首次抓取失败和后续失败。
core/app/runtime/startup_silence_coordinator.py
core/services/eqsc/eqsc_typhoon_poll_service.py
core/services/eqsc/eqsc_cenc_intensity_poll_service.py
core/services/eqsc/eqsc_tsunami_poll_service.py
core/services/snet/snet_poll_service.py
加强对数据源、天气聚合、EQSC、S-Net 和 OpenQuake 的配置校验及管理状态上报。
  • ConfigValidationService 校验 weather_aggregation 的按会话选项(启用标志、时间窗口、批量大小、限速阈值)。
  • OpenQuake global_quake 配置增加 china_weather_alarm 子数据源开关;S-Net 配置改用 enabled 键;EQSC 数据源校验逻辑简化。
  • Admin 灾害状态命令更新数据源名称,使用 connections 视图区分禁用与异常连接,移除 EQSC/S-Net 子数据源的硬编码注入,并改进分组格式。
  • Admin/ConnectionsGrid 添加 CMA 气象(经 OpenQuake)的映射,并区分 Fan 与 OQ 的天气数据源。
core/services/config/config_validation_service.py
plugin/commands/plugin_admin_command_service.py
admin/js/components/status/ConnectionsGrid.jsx
修复 PLUM/assumption 地震烈度过滤以及 JMA 烈度详情相关的展示行为。
  • IntensityRule 的双阈值评估可选择跳过主/次阈值检查;对于 scale 模式,PLUM/assumption 元数据现在跳过震级过滤,并调整接受原因。
  • 地震 presenter 的 JMA 烈度地区和城镇尺度渲染在 detailed_jma_intensity=false 时只展示最大烈度带,使城镇细节与区域汇总语义保持一致。
core/rules/intensity_rule.py
core/message/presenters/earthquake_presenter.py
改进天气查询命令在结果集较大时的行为,以及天气文本/机构/颜色提取逻辑。
  • 天气查询服务增强机构提取能力,支持以 气象台/局/站/中心 结尾的模式,并使用 extract_final_weather_color 正确处理颜色升级/降级变更。
  • 天气 presenter 同样使用 extract_final_weather_color,并在标题前加上“中国气象局”,调整颜色 emoji 的位置。
  • 天气查询命令对多结果集优先使用批量 forward 消息发送,辅以文本块回退和特性追踪;forward 的最大节点数从 8 提升到 10。
core/services/query/weather_query_service.py
core/message/presenters/weather_constants.py
core/message/presenters/weather_presenter.py
plugin/commands/plugin_query_command_service.py
调整 EQSC HTTP 客户端超时及若干杂项行为,以减少超时和噪音。
  • EQSC HTTP 客户端会话超时放宽(connect/sock_connect 15s,sock_read 30s,总计 45s),并补充注释说明理由。
  • 原始消息过滤器现在将 OpenQuake 聚合数据源视为仅允许 earthquake 和 weather 类型。
  • Global source router 的 openquake 映射文档中提到 CMA 气象数据源。
  • 多处组件的小调整(例如 forward 节点上限、数据源标签、FSSN CMT 命名)使 UI 与内部行为更加一致。
core/network/http/eqsc_http_client.py
core/message/logging/filters/raw_message_filter.py
core/sources/source_router.py
plugin/commands/plugin_query_command_service.py
admin/js/utils/formatters.js
core/storage/source_compat.py

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • 触发一次新的审查: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论即可。
  • 从审查评论生成 GitHub issue: 在审查评论下回复,要求 Sourcery 从该评论创建一个 issue。也可以直接回复审查评论 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题中的任意位置写入 @sourcery-ai,即可随时生成标题。也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文中的任意位置写入 @sourcery-ai summary,即可在指定位置随时生成 PR 摘要。也可以在 pull request 中评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成审查指南: 在 pull request 中评论 @sourcery-ai guide 来随时(重新)生成审查者指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve 即可标记所有 Sourcery 评论为已解决。如果你已经处理完所有评论且不希望再看到它们,这会很有用。
  • 全部忽略 Sourcery 审查: 在 pull request 中评论 @sourcery-ai dismiss 即可忽略所有现有的 Sourcery 审查结果。如果你希望以一次全新的审查开始,这尤其有用——别忘了再评论 @sourcery-ai review 来触发新的审查!

Customizing Your Experience

访问你的 dashboard 可以:

  • 启用或停用审查功能,例如 Sourcery 生成的 pull request 摘要、审查者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查设置。

Getting Help

Original review guide in English

Reviewer's Guide

Integrates CMA weather alerts from OpenQuakeAPI, adds a per-session weather aggregation/forwarding push service, introduces fine-grained log level control by event stream, improves startup silence coordination and EQSC/S-Net polling behavior, and fixes several presentation, parser, query, and configuration issues.

Sequence diagram for CMA weather event aggregation and push

sequenceDiagram
    actor OpenQuakeAPI
    participant SourceMessageRouter
    participant WeatherAlarmParser
    participant EventPipeline
    participant WeatherAggregationService
    participant MessageManager
    participant MessageBuildService
    participant SessionSender

    OpenQuakeAPI->>SourceMessageRouter: WebSocket message (source=cma, type=weather)
    SourceMessageRouter->>WeatherAlarmParser: _parse_data(data)
    WeatherAlarmParser->>WeatherAlarmParser: _extract_realtime_payload(data)
    WeatherAlarmParser-->>SourceMessageRouter: EventEnvelope(weather_alarm)
    SourceMessageRouter->>EventPipeline: handle(event)

    loop per session
        EventPipeline->>WeatherAggregationService: should_aggregate(event, session, runtime_config)
        alt should_aggregate == False
            EventPipeline->>MessageManager: push_event(event, target_sessions)
        else should_aggregate == True
            EventPipeline->>WeatherAggregationService: buffer event
        end
    end

    rect rgb(230,230,250)
    note over WeatherAggregationService: later, when flush conditions met
    WeatherAggregationService->>EventPipeline: _flush_weather_buffer(session, entries, config, mode="forward")
    loop each WeatherBufferEntry
        EventPipeline->>MessageManager: evaluate_push_decision(entry.event, runtime_config)
        alt decision.accepted
            EventPipeline->>MessageBuildService: build_message_async(entry.event, runtime_config)
            MessageBuildService-->>EventPipeline: MessageChain
        else decision.rejected
            EventPipeline-->>EventPipeline: skip entry
        end
    end

    alt mode == "forward"
        EventPipeline->>SessionSender: send(session, MessageChain(Comp.Nodes))
    else mode == "single"
        loop each built message
            EventPipeline->>SessionSender: send(session, message)
        end
    end
    end
Loading

File-Level Changes

Change Details Files
Introduce per-session weather alert aggregation and forwarding push flow, plus graceful buffer flushing on shutdown.
  • EventPipeline now injects a WeatherAggregationService and branches weather events into aggregation vs normal push paths per session configuration.
  • WeatherAggregationService maintains per-session buffers, supports red-level immediate flush, forward-merge mode, rate-limited single-message fallback, and exposes a flush callback used by EventPipeline.
  • Disaster service lifecycle flushes any remaining aggregated weather buffer on shutdown to avoid losing alerts.
core/app/pipeline/event_pipeline.py
core/message/push/weather_aggregation_service.py
core/app/runtime/disaster_service_lifecycle.py
Add OpenQuakeAPI CMA weather source and adapt weather parsing, routing, and icon building to handle both Fan and CMA payloads.
  • Source catalog registers a new china_weather_openquake SourceEntry with OpenQuake dispatch family and CMA provider names.
  • WeatherAlarmParser gains RealtimeEvent payload unpacking for type=weather/action=new, per-source dedup queues, source_family propagation, and event_stream tags on logs.
  • Parser registry passes source_id into WeatherAlarmParser, base parser treats china_weather_openquake as non-heartbeat, and raw-message filter keeps only earthquake/weather RealtimeEvent types for OpenQuake.
  • Weather icon presenter now resolves CMA p-codes and titles into 11B icon codes via weather_alarm_code_map, builds Fan Studio URLs, handles pseudo-image failures as debug, and falls back to local icons with downgraded log levels.
  • Admin/status UI and storage compat mappings are updated to show and normalize the new CMA weather source from OpenQuake.
core/sources/source_catalog.py
core/parsers/weather_parser.py
core/parsers/parser_registry.py
core/message/logging/filters/raw_message_filter.py
core/message/push/message_build_service.py
core/message/presenters/weather_alarm_code_map.py
admin/js/utils/formatters.js
core/storage/source_compat.py
admin/js/components/status/ConnectionsGrid.jsx
docs/OpenQuakeAPI.md
Implement fine-grained log level control per event stream and tag logs consistently across earthquake, weather, typhoon, tsunami, and Global Quake flows.
  • PluginLogger now supports event_stream_log_level overrides (all or per-stream) in debug_config, alongside existing log_mode/downgrade behavior.
  • Many parser, fusion, deduplication, push-flow, router, and polling log calls were updated to pass event_stream tags such as weather_alarm, earthquake, global_quake, typhoon, and tsunami.
  • Event stream tags are derived from source_id or event_type in EventDeduplicationService, SourceMessageRouter, PushFlowHandler, and other components.
utils/plugin_logger.py
core/services/identity/event_deduplication_service.py
core/network/source_message_router.py
core/message/push/push_flow_handler.py
core/message/fusion/cwa_eew_fusion_service.py
core/message/fusion/cenc_fusion_service.py
core/parsers/global_sources_parser.py
core/parsers/japan_eew_parser.py
core/parsers/tsunami_parser.py
core/parsers/china_eew_parser.py
core/parsers/japan_earthquake_parser.py
core/parsers/taiwan_eew_parser.py
core/parsers/china_earthquake_parser.py
core/parsers/china_intensity_report_eqsc_parser.py
core/parsers/china_intensity_report_parser.py
core/parsers/fssn_cmt_parser.py
core/parsers/taiwan_earthquake_parser.py
core/parsers/typhoon_parser.py
core/parsers/snet_parser.py
Refine startup silence coordination and EQSC/S-Net polling to avoid premature un-silencing and to seed dedup caches during silence.
  • StartupSilenceCoordinator tracks per-gate fetch_started_at and fetching flags; poll gates now prime based on actual fetch-start times and avoid timeout while fetching.
  • EQSC typhoon, CENC intensity, and tsunami poll services notify the silence coordinator at fetch start/completion, encapsulate completion notification, and tag typhoon/earthquake logs with event_stream.
  • EQSC typhoon polling seeds dedup caches via _handle_disaster_event even during silence to avoid duplicate pushes when silence ends.
  • S-Net poll service also integrates silence coordinator notifications for first fetch failures and later failures.
core/app/runtime/startup_silence_coordinator.py
core/services/eqsc/eqsc_typhoon_poll_service.py
core/services/eqsc/eqsc_cenc_intensity_poll_service.py
core/services/eqsc/eqsc_tsunami_poll_service.py
core/services/snet/snet_poll_service.py
Enhance configuration validation and admin status reporting for data sources, weather aggregation, EQSC, S-Net, and OpenQuake.
  • ConfigValidationService validates weather_aggregation per-session options (enable flags, time window, batch size, rate limiting thresholds).
  • OpenQuake global_quake config gains a china_weather_alarm sub-source toggle; S-Net config uses enabled key; EQSC data source validation is simplified.
  • Admin disaster status command updates source names, uses connections view to distinguish disabled vs abnormal connections, removes hardcoded EQSC/S-Net sub-source injection, and improves grouping formatting.
  • Admin/ConnectionsGrid adds mappings for CMA weather via OpenQuake and differentiates Fan vs OQ weather sources.
core/services/config/config_validation_service.py
plugin/commands/plugin_admin_command_service.py
admin/js/components/status/ConnectionsGrid.jsx
Fix PLUM/assumption earthquake intensity filtering and related presentation behavior for JMA intensity details.
  • IntensityRule’s dual-threshold evaluation can skip primary/secondary checks; for scale mode, PLUM/assumption metadata now skips magnitude filtering and adjusts accept reasons.
  • Earthquake presenter’s JMA intensity region and town-scale rendering honors detailed_jma_intensity=false by only showing the max-intensity band, aligning town-detail and region-summary semantics.
core/rules/intensity_rule.py
core/message/presenters/earthquake_presenter.py
Improve weather query command behavior for large result sets and weather text/org/color extraction.
  • Weather query service enhances organization extraction with patterns ending in 气象台/局/站/中心, and uses extract_final_weather_color to correctly handle upgrade/downgrade color changes.
  • Weather presenter also uses extract_final_weather_color and prefixes titles with 中国气象局, adjusting color emoji placement.
  • Weather query command sends multi-result sets via batched forward messages first, with text-block fallback and feature tracking; max nodes per forward increased from 8 to 10.
core/services/query/weather_query_service.py
core/message/presenters/weather_constants.py
core/message/presenters/weather_presenter.py
plugin/commands/plugin_query_command_service.py
Adjust EQSC HTTP client timeouts and minor misc behaviors to reduce timeouts and noise.
  • EQSC HTTP client session timeouts are relaxed (connect/sock_connect 15s, sock_read 30s, total 45s) with comments explaining rationale.
  • Raw message filter now treats OpenQuake aggregate source as allowing earthquake and weather types only.
  • Global source router openquake mapping docs mention CMA weather source.
  • Small tweaks across various components (e.g., forward node limits, data source labels, FSSN CMT naming) align UI and internal behavior.
core/network/http/eqsc_http_client.py
core/message/logging/filters/raw_message_filter.py
core/sources/source_router.py
plugin/commands/plugin_query_command_service.py
admin/js/utils/formatters.js
core/storage/source_compat.py

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions

Copy link
Copy Markdown

😷 屎山代码检测报告 允许合并

🎯 本次检测概览

「代码开始散发气味,谨慎维护」

维度 数据
屎山指数 37.61 / 100
屎山等级 糟糕级
检测时间 2026/7/31 06:18:56

🎭 整体印象

有点臭味,但还不至于熏死人

🧭 下一步

这代码像个叛逆期的青少年,需要适当管教才能成才


📥 详细数据

fuck-u-code 自动检测 · 本评论不代表人类观点

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

本次变更接入 CMA OpenQuake 气象预警源,新增天气预警解析、图标与颜色展示、按会话聚合推送和合并转发;同时扩展事件流日志控制、启动静默状态追踪、配置兼容及相关后台状态展示。

Changes

CMA 气象预警接入

Layer / File(s) Summary
数据源注册与解析
core/sources/*, core/parsers/*, core/storage/*, docs/OpenQuakeAPI.md, admin/js/*
注册 china_weather_openquake,解析 OpenQuake RealtimeEvent,并补充配置、别名、展示名和文档。
天气预警展示
core/message/presenters/*, core/message/push/message_build_service.py, core/services/query/weather_query_service.py
解析最终颜色、官方图标编码和图标 URL,并支持官方图标失败后的本地回退。
聚合推送
core/message/push/weather_aggregation_service.py, core/app/pipeline/event_pipeline.py, core/app/disaster_service.py
按会话缓冲天气预警,支持定时刷新、红色预警即时刷新、合并转发和降级限流。
日志与运行时状态
utils/plugin_logger.py, core/runtime/*, core/parsers/*, core/services/*
新增事件流日志级别覆盖,并完善启动静默轮询抓取状态和事件流日志标记。
相关规则与后台状态
core/rules/intensity_rule.py, core/network/http/eqsc_http_client.py, plugin/commands/*
调整占位震级过滤、EQSC 超时、连接三态展示及天气查询分段发送。

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested labels: type/design, type/refactor, type/fix, type/docs

Suggested reviewers: aloys233

Poem

我是兔子,蹦进新风里,
CMA 预警排队成诗。
红色警报先到门前,
小图标闪亮,消息相连。
日志分流,静默守时,
胡萝卜庆祝这次发布!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 94.12% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了本次 PR 的核心:OpenQuakeAPI 气象预警接入、聚合推送、日志级别控制和修复项。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

⚠️ 代码质量检查报告

发现部分文件需要格式化或存在代码问题,建议在本地运行 ruff format .ruff check --fix .

🖌️ 格式化: 1 个文件被重新格式化 (未变更: 282)
🛡️ 代码检查: ✅ 通过

@codacy-production

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 9 medium

Alerts:
⚠ 9 issues (≤ 0 issues of at least minor severity)

Results:
9 new issues

Category Results
Security 5 medium
Complexity 4 medium

View in Codacy

🟢 Metrics 124 complexity · 9 duplication

Metric Results
Complexity 124
Duplication 9

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@DBJD-CR DBJD-CR linked an issue Jul 30, 2026 that may be closed by this pull request
2 tasks
@coderabbitai coderabbitai Bot added type/design 🎨 交互设计、UI/UX 方案讨论 type/docs 📚 文档变更 type/fix 🐛 Bug 修复 type/refactor ♻️ 代码重构 (无功能变化) labels Jul 30, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并留下了一些总体反馈:

  • WeatherAggregationService._flush_session/_flush_via_forward 中,聚合和限流设置始终取自 self._config,因此在刷新时会忽略 runtime_config.push_frequency_control.weather_aggregation 中的任意会话级覆盖配置;建议将“有效的会话配置”贯穿到 flush 逻辑中,以保持与 should_aggregate 的行为一致。
  • 新的事件流日志级别处理逻辑在多个位置被重复实现(例如 EventDeduplicationService._resolve_event_stream_by_source_idSourceMessageRouter._resolve_stream_by_source_id);考虑将其抽取到一个共享的辅助方法中,以减少逻辑偏移,并让未来新增事件流时更易维护。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `WeatherAggregationService._flush_session/_flush_via_forward` the aggregation and rate-limit settings are always taken from `self._config`, so any per-session overrides in `runtime_config.push_frequency_control.weather_aggregation` are ignored during flushing; consider threading the effective session config through to the flush path to keep behavior consistent with `should_aggregate`.
- The new event-stream log level handling logic is duplicated in several places (e.g. `EventDeduplicationService._resolve_event_stream_by_source_id` and `SourceMessageRouter._resolve_stream_by_source_id`); factoring this into a single shared helper would reduce drift and make future additions of new event streams easier to maintain.

## Individual Comments

### Comment 1
<location path="core/message/push/weather_aggregation_service.py" line_range="45-54" />
<code_context>
+_MAX_NODES_PER_FORWARD = 20
</code_context>
<issue_to_address>
**issue (bug_risk):** Aggregation batch size is hard-coded and ignores the validated `max_batch_size` config.

`weather_aggregation.max_batch_size` is validated but `_flush_via_forward` still uses the hard-coded `_MAX_NODES_PER_FORWARD = 20`, so config changes and docs around `max_batch_size` are ineffective. Please either read `max_batch_size` when computing `batches` (e.g. `agg_config.get("max_batch_size", _MAX_NODES_PER_FORWARD)`) or remove `max_batch_size` from validation and documentation if the limit is intended to be fixed.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • In WeatherAggregationService._flush_session/_flush_via_forward the aggregation and rate-limit settings are always taken from self._config, so any per-session overrides in runtime_config.push_frequency_control.weather_aggregation are ignored during flushing; consider threading the effective session config through to the flush path to keep behavior consistent with should_aggregate.
  • The new event-stream log level handling logic is duplicated in several places (e.g. EventDeduplicationService._resolve_event_stream_by_source_id and SourceMessageRouter._resolve_stream_by_source_id); factoring this into a single shared helper would reduce drift and make future additions of new event streams easier to maintain.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `WeatherAggregationService._flush_session/_flush_via_forward` the aggregation and rate-limit settings are always taken from `self._config`, so any per-session overrides in `runtime_config.push_frequency_control.weather_aggregation` are ignored during flushing; consider threading the effective session config through to the flush path to keep behavior consistent with `should_aggregate`.
- The new event-stream log level handling logic is duplicated in several places (e.g. `EventDeduplicationService._resolve_event_stream_by_source_id` and `SourceMessageRouter._resolve_stream_by_source_id`); factoring this into a single shared helper would reduce drift and make future additions of new event streams easier to maintain.

## Individual Comments

### Comment 1
<location path="core/message/push/weather_aggregation_service.py" line_range="45-54" />
<code_context>
+_MAX_NODES_PER_FORWARD = 20
</code_context>
<issue_to_address>
**issue (bug_risk):** Aggregation batch size is hard-coded and ignores the validated `max_batch_size` config.

`weather_aggregation.max_batch_size` is validated but `_flush_via_forward` still uses the hard-coded `_MAX_NODES_PER_FORWARD = 20`, so config changes and docs around `max_batch_size` are ineffective. Please either read `max_batch_size` when computing `batches` (e.g. `agg_config.get("max_batch_size", _MAX_NODES_PER_FORWARD)`) or remove `max_batch_size` from validation and documentation if the limit is intended to be fixed.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +45 to +54
_MAX_NODES_PER_FORWARD = 20


@dataclass
class WeatherBufferEntry:
"""缓冲区中的单条气象预警条目。"""

event: EventEnvelope
color_level: int = 0
color_name: str = "白色"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 聚合批大小被硬编码,忽略了已验证的 max_batch_size 配置。

weather_aggregation.max_batch_size 已经做了验证,但 _flush_via_forward 仍然使用硬编码的 _MAX_NODES_PER_FORWARD = 20,因此针对 max_batch_size 的配置变更和文档说明都不会生效。如果该限制是可配置的,请在计算 batches 时读取 max_batch_size(例如 agg_config.get("max_batch_size", _MAX_NODES_PER_FORWARD));如果该限制预期是固定值,则请从配置校验和文档中移除 max_batch_size

Original comment in English

issue (bug_risk): Aggregation batch size is hard-coded and ignores the validated max_batch_size config.

weather_aggregation.max_batch_size is validated but _flush_via_forward still uses the hard-coded _MAX_NODES_PER_FORWARD = 20, so config changes and docs around max_batch_size are ineffective. Please either read max_batch_size when computing batches (e.g. agg_config.get("max_batch_size", _MAX_NODES_PER_FORWARD)) or remove max_batch_size from validation and documentation if the limit is intended to be fixed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
core/app/pipeline/event_pipeline.py (1)

89-105: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

全部会话进入聚合时,统计会记入上一个事件的成功会话。

non_aggregated_sessions 为空(所有会话都启用聚合)时本次完全不调用 push_event,但第 102 行仍用 self.service.message_manager.last_success_sessions 记录统计。该字段保留的是上一次推送结果,会把无关会话记为本事件的推送目标;真正的聚合推送发生在 _flush_weather_buffer,也没有回写统计。建议在聚合分支显式传入空列表(或标记为“已缓冲待推送”),并在 flush 成功后补记统计。

🐛 建议修复方向
-        await self.service.statistics_manager.record_push(
-            event,
-            pushed_sessions=self.service.message_manager.last_success_sessions,
-        )
+        pushed_sessions = self.service.message_manager.last_success_sessions
+        if aggregated_only:
+            # 事件仅进入聚合缓冲区,本次未产生实际推送
+            pushed_sessions = []
+        await self.service.statistics_manager.record_push(
+            event,
+            pushed_sessions=pushed_sessions,
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/app/pipeline/event_pipeline.py` around lines 89 - 105, Update the
statistics call in the event-processing flow around _push_event_normal and
record_push so events whose all sessions enter aggregation do not reuse
message_manager.last_success_sessions from a prior event. Track whether the
event was actually pushed versus only buffered, pass an empty session list for
the fully aggregated case, and ensure _flush_weather_buffer records the sessions
after the deferred aggregated push succeeds.
core/network/source_message_router.py (1)

267-272: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

按实际子源标记缺失解析器日志。

FAN Studio 映射包含气象、台风和海啸子源;固定为 earthquake 会绕过对应事件流的日志策略。改为 _resolve_stream_by_source_id(source_id)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/network/source_message_router.py` around lines 267 - 272, Update the
missing-parser warning in the source message routing flow to set event_stream
from _resolve_stream_by_source_id(source_id) instead of the hardcoded
"earthquake" value. Preserve the existing warning message and is_event_linked
behavior.
core/services/eqsc/eqsc_typhoon_poll_service.py (1)

362-373: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

在静默播种完成后再报告首轮抓取成功。

当前先调用 note_poll_fetch_completed(success=True),再检查 is_silencing()。若该门闩使协调器进入 READY,首批台风会跳过播种并直接推送。应先按静默状态处理、播种首批指纹,再通知抓取完成。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/services/eqsc/eqsc_typhoon_poll_service.py` around lines 362 - 373, 调整
eqsc 轮询成功处理流程:在设置成功状态并调用 _notify_silence_fetch_completed(success=True)
前,先依据当前静默状态处理首批 active_items,并通过 _process_typhoon_updates
完成指纹播种;确保静默协调器仍处于播种阶段时不会因提前进入 READY 而直接推送首批台风,同时保留 emit_event 为 false 时的返回行为。
core/services/identity/event_deduplication_service.py (1)

375-407: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

plugin_logger.debug 不能接收事件流参数
core/services/identity/event_deduplication_service.py:375-407log_level="debug" 时,log_fn 会变成 plugin_logger.debug,但这里仍传了 is_event_linkedevent_stream。这会把未知关键字透传到底层 logger,触发 TypeError 并中断去重。仅在 info 分支传这些参数,debug 分支只记录消息即可。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/services/identity/event_deduplication_service.py` around lines 375 -
407, Update the duplicate-filter logging in the deduplication flow around the
priority checks so event metadata is passed only when log_fn is
plugin_logger.info. Keep debug logging limited to the message argument,
preventing is_event_linked and event_stream from reaching plugin_logger.debug,
while preserving the existing filtering behavior.
core/services/query/weather_query_service.py (1)

282-286: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

气象预警查询结果的 icon_url 未复用新的编码映射,OpenQuakeAPI(CMA) 来源的图标会失效。

两处 icon_url 都是直接用原始 weather_type_code 拼接 Fan Studio 图标接口 URL。若 weather_type_code 是 OpenQuakeAPI CMA 的 p 编码格式(如 p0002003),该接口无法识别,会返回 message_build_service.py 中专门识别处理的"伪图片"错误页——但查询命令这条路径并未接入 resolve_weather_icon_code/build_weather_icon_url,导致 CMA 来源预警的查询结果图标必现失效。

🐛 建议修复
+from ...message.presenters.weather_alarm_code_map import (
+    build_weather_icon_url,
+    resolve_weather_icon_code,
+)
         detected_type = detect_weather_type(title_text, weather_type_code)
         detected_color = detect_weather_color(level_text, title_text)
         color_emoji = COLOR_LEVEL_EMOJI.get(detected_color, "")
+        icon_code = resolve_weather_icon_code(
+            weather_type_code, title=title_text, headline=headline_text
+        )
@@
-                "icon_url": (
-                    f"https://api.fanstudio.tech/we/img/alarm_icon.php?type={weather_type_code}"
-                    if weather_type_code
-                    else None
-                ),
+                "icon_url": build_weather_icon_url(icon_code) if icon_code else None,

同样的替换需应用到搜索分支(约 400-427 行)。

Also applies to: 308-312, 400-404, 423-427

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/services/query/weather_query_service.py` around lines 282 - 286,
更新天气预警查询结果中两处查询分支的 icon_url 构建逻辑,先通过 resolve_weather_icon_code 将原始
weather_type_code(包括 CMA 的 p 编码)转换为 Fan Studio 可识别的图标编码,再复用
build_weather_icon_url 生成 URL。覆盖当前分支及搜索分支对应的全部 icon_url 拼接位置,保持其他天气类型检测逻辑不变。
🧹 Nitpick comments (3)
core/app/pipeline/event_pipeline.py (1)

139-146: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

回调签名与冗余 raise e

两处小问题:

  • set_flush_callback 的文档约定回调返回 bool,此处声明 -> None;调用方也未使用返回值,建议统一约定。
  • 第 241-242 行 except Exception as e: raise e 会重置回溯信息,直接去掉 try 或改为裸 raise 更合适。
♻️ 建议调整
-            chain = MessageChain([nodes])
-            try:
-                await message_manager.session_sender.send(session, chain)
-                plugin_logger.debug(
-                    f"[灾害预警] 气象预警合并转发已发送到 {session}, "
-                    f"含 {len(built_messages)} 条预警",
-                    event_stream="weather_alarm",
-                )
-            except Exception as e:
-                raise e
+            chain = MessageChain([nodes])
+            # 发送失败向上抛出,由聚合服务降级为逐条发送
+            await message_manager.session_sender.send(session, chain)
+            plugin_logger.debug(
+                f"[灾害预警] 气象预警合并转发已发送到 {session}, "
+                f"含 {len(built_messages)} 条预警",
+                event_stream="weather_alarm",
+            )

Also applies to: 241-242

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/app/pipeline/event_pipeline.py` around lines 139 - 146, Update
_flush_weather_buffer to match the set_flush_callback contract by declaring a
bool return type, and ensure its implementation returns the appropriate boolean
value. In the exception handling around this method, remove the redundant
`except Exception as e: raise e` pattern or replace it with a bare raise so the
original traceback is preserved.
core/rules/intensity_rule.py (1)

217-254: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

泛化占位震级文案
skip_magnitude 也会覆盖 isAssumption 的假定震源场景,accept_reason 直接写“PLUM法占位震级”会把这类报表述错。改成更通用的“假定震源占位震级,已跳过震级过滤”更稳妥。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/rules/intensity_rule.py` around lines 217 - 254, Update the
skip_magnitude accept_reason branch in _evaluate_dual_threshold_filter’s caller
to use the generic “假定震源占位震级,已跳过震级过滤” wording, covering both is_assumption and
magnitude_is_placeholder cases instead of naming only PLUM.
core/message/push/message_build_service.py (1)

661-704: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

_resolve_weather_color_keyweather_alarm_code_map.py 的 p 编码解析逻辑重复且校验不一致。

_P_FORMAT_COLOR_MAP(54-59 行)与 weather_alarm_code_map._P_COLOR_DIGIT_TO_SUFFIX 数值完全相同,属于跨文件重复维护;且此处 p 编码判定要求 len(code) >= 8(685 行),而 weather_alarm_code_map._is_p_code 只要求 len(code) >= 7——两者对"什么算合法 p 编码"的定义不一致。更关键的是,此处完全不感知 weather_alarm_code_map._P_CODE_SKIP_GENERIC 特殊短码列表,一旦该列表未来因颜色(而不仅是类型)误判而扩展,本地回退图标的颜色解析会静默地与官方图标解析逻辑产生分歧。

建议在 weather_alarm_code_map.py 中新增一个统一的 resolve_p_code_color(code) -> str | None 辅助函数(复用 _P_COLOR_DIGIT_TO_SUFFIX 并感知 _P_CODE_SKIP_GENERIC),让 _resolve_weather_color_key 直接调用它,而不是维护第二套独立实现。

♻️ 重构方向示意
# weather_alarm_code_map.py
+def resolve_p_code_color(code: str) -> str | None:
+    """仅解析 p 编码的颜色部分,供颜色回退场景复用。"""
+    if not (code and _is_p_code(code)) or code in _P_CODE_SKIP_GENERIC:
+        return None
+    digits = code[1:]
+    if len(digits) < 5:
+        return None
+    return _P_COLOR_DIGIT_TO_SUFFIX.get(digits[-1])
# message_build_service.py
-            if code.startswith("p") and len(code) >= 8:
-                return cls._P_FORMAT_COLOR_MAP.get(code[-1])
+            if code.startswith("p"):
+                return resolve_p_code_color(code)

同时建议核实 CMA 真实 p 编码长度是否恒为 8(若是,7 vs 8 的差异目前是休眠状态,但仍建议对齐避免未来隐患)。

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/app/runtime/disaster_service_lifecycle.py`:
- Around line 288-299: 将灾害服务停止流程中针对 _weather_aggregation_service.flush_all
的刷新移到后台任务及 ws_manager、http_fetcher 等资源回收之前,确保消息构建和发送依赖仍可用。为 flush_all 增加
asyncio.wait_for 超时保护,超时或其他异常继续记录并忽略,避免阻塞 stop()。

In `@core/message/logging/filters/raw_message_filter.py`:
- Around line 74-80: Update should_filter_message and the string-processing path
around _handle_string_message so OpenQuakeAPI JSON strings are parsed and
subjected to the same global_quake type check as dictionary payloads, allowing
only earthquake and weather. Reuse or extract a shared helper for the type
validation, then preserve the existing filtering result and _handle_dict_message
behavior for accepted messages.

In `@core/message/push/weather_aggregation_service.py`:
- Line 216: Update the background-task creation in the session flush flow around
_flush_session so each asyncio.create_task result is retained in a service-level
task collection or registered through the existing register_background_task
mechanism. Add completion cleanup and ensure exceptions from _flush_session are
observed and recorded, applying the same handling to both affected task-creation
sites.
- Around line 12-21: 实现注释中声明的缓冲条数触发条件:更新 should_aggregate,使单会话缓冲数量达到配置的
max_batch_size 时立即触发推送,并确保读取并使用该配置而非仅依赖
_MAX_NODES_PER_FORWARD。保持时间窗口和红色预警的现有触发逻辑不变,同时避免缓冲区在窗口内无限增长。
- Around line 262-265: Update the session flush flow around _flush_session and
_flush_via_forward so it uses the session’s effective runtime configuration,
matching the configuration used by should_aggregate, instead of
self._get_aggregation_config() and the global self._config. Resolve or preserve
the session-specific configuration through session_config_manager and pass it to
the flush callback so per-session time-window and rate-limit overrides remain
effective.

In `@core/network/source_message_router.py`:
- Around line 150-153: 在未找到解析器的告警分支中,将 `logger.warning` 替换为
`plugin_logger.warning`,并把 `event_stream` 改为调用
`_resolve_stream_by_source_id(source_id)`;保留现有告警消息及 `is_event_linked=True` 参数。

In `@core/parsers/base_parser.py`:
- Around line 172-176: Update BaseParser._parse_datetime() so the
time-parse-failure warning derives event_stream from source_entry.source_type or
the existing source-to-stream mapping instead of hardcoding “earthquake”; ensure
weather, tsunami, typhoon, and other parser sources receive their correct labels
while preserving the existing warning behavior.

In `@core/parsers/global_sources_parser.py`:
- Around line 745-749: 更新 UsgsEarthquakeParser 中地震解析成功日志的 event_stream 配置,将当前的
global_quake 改为 usgs_fanstudio,使 USGS 日志使用其实际来源对应的流配置;保留日志消息及 is_event_linked
行为不变。

In `@core/parsers/weather_parser.py`:
- Around line 34-41: Update _parse_data and _extract_realtime_payload so they
distinguish missing/non-RealtimeEvent wrappers from recognized RealtimeEvent
messages with unsupported type or action. Only fall back to _extract_data when
the input is not a RealtimeEvent; return None immediately for recognized
non-weather or non-new events, preserving the contract described by the
RealtimeEvent parsing logic around lines 203-237.

In `@core/services/config/config_validation_service.py`:
- Around line 1177-1221: Update the weather_aggregation validation in
ConfigValidator so max_batch_size uses the schema contract’s maximum of 20
instead of 50. Also verify the schema bounds for rate_limit_max_messages and
rate_limit_window_seconds, and align their _clamp_number ranges with those
declared limits.

In `@core/services/eqsc/eqsc_cenc_intensity_poll_service.py`:
- Around line 306-314: 修复 eqsc 轮询流程中将请求失败的空列表标记为成功的问题。更新 fetch_list 与调用方,使无
token、非 200 或请求异常可被区分;在 fetch_list 返回结果未知/失败时保持失败状态并调用
_notify_silence_fetch_completed(success=False),仅在请求确实成功时更新 _last_success_at、重置
_consecutive_failures 并标记 success=True。

In `@core/services/eqsc/eqsc_tsunami_poll_service.py`:
- Around line 219-236: Move silent-gate completion reporting out of reusable
fetch_once methods: in core/services/eqsc/eqsc_tsunami_poll_service.py lines
219-236, remove the success/failure notifications from fetch_once and have
_poll_loop report completion only after its first fetch_once call fully finishes
processing, including _handle_disaster_event; in
core/services/snet/snet_poll_service.py lines 199-204, remove the existing
fetch_once completion notifications as well so fetch_for_query cannot affect
startup silence state.

In `@docs/OpenQuakeAPI.md`:
- Around line 468-472: 更新 docs/OpenQuakeAPI.md 示例中的 timestampMs,使其对应 2026 年 7 月
29 日 12:45,并与同一示例的 id、effective 和说明文本保持一致;不要修改示例中的其他字段。

In `@plugin/commands/plugin_query_command_service.py`:
- Around line 489-529: 修复全国分支在 `_send_forward_batches` 和 `_send_text_blocks`
均失败后继续落入普通搜索分支的问题:在全国分支最终失败路径显式返回,避免重复投递。将全国与普通区域中重复的投递及
`_track_command_feature` 逻辑抽取为内部辅助函数,并通过 `is_nationwide`
等参数区分行为,保持现有成功与回退遥测结果不变。

---

Outside diff comments:
In `@core/app/pipeline/event_pipeline.py`:
- Around line 89-105: Update the statistics call in the event-processing flow
around _push_event_normal and record_push so events whose all sessions enter
aggregation do not reuse message_manager.last_success_sessions from a prior
event. Track whether the event was actually pushed versus only buffered, pass an
empty session list for the fully aggregated case, and ensure
_flush_weather_buffer records the sessions after the deferred aggregated push
succeeds.

In `@core/network/source_message_router.py`:
- Around line 267-272: Update the missing-parser warning in the source message
routing flow to set event_stream from _resolve_stream_by_source_id(source_id)
instead of the hardcoded "earthquake" value. Preserve the existing warning
message and is_event_linked behavior.

In `@core/services/eqsc/eqsc_typhoon_poll_service.py`:
- Around line 362-373: 调整 eqsc 轮询成功处理流程:在设置成功状态并调用
_notify_silence_fetch_completed(success=True) 前,先依据当前静默状态处理首批 active_items,并通过
_process_typhoon_updates 完成指纹播种;确保静默协调器仍处于播种阶段时不会因提前进入 READY 而直接推送首批台风,同时保留
emit_event 为 false 时的返回行为。

In `@core/services/identity/event_deduplication_service.py`:
- Around line 375-407: Update the duplicate-filter logging in the deduplication
flow around the priority checks so event metadata is passed only when log_fn is
plugin_logger.info. Keep debug logging limited to the message argument,
preventing is_event_linked and event_stream from reaching plugin_logger.debug,
while preserving the existing filtering behavior.

In `@core/services/query/weather_query_service.py`:
- Around line 282-286: 更新天气预警查询结果中两处查询分支的 icon_url 构建逻辑,先通过
resolve_weather_icon_code 将原始 weather_type_code(包括 CMA 的 p 编码)转换为 Fan Studio
可识别的图标编码,再复用 build_weather_icon_url 生成 URL。覆盖当前分支及搜索分支对应的全部 icon_url
拼接位置,保持其他天气类型检测逻辑不变。

---

Nitpick comments:
In `@core/app/pipeline/event_pipeline.py`:
- Around line 139-146: Update _flush_weather_buffer to match the
set_flush_callback contract by declaring a bool return type, and ensure its
implementation returns the appropriate boolean value. In the exception handling
around this method, remove the redundant `except Exception as e: raise e`
pattern or replace it with a bare raise so the original traceback is preserved.

In `@core/rules/intensity_rule.py`:
- Around line 217-254: Update the skip_magnitude accept_reason branch in
_evaluate_dual_threshold_filter’s caller to use the generic “假定震源占位震级,已跳过震级过滤”
wording, covering both is_assumption and magnitude_is_placeholder cases instead
of naming only PLUM.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c0affe2c-7b3c-4b43-b1fb-3aa69e8a703d

📥 Commits

Reviewing files that changed from the base of the PR and between b91995d and dad2b91.

📒 Files selected for processing (51)
  • _conf_schema.json
  • admin/js/components/status/ConnectionsGrid.jsx
  • admin/js/utils/formatters.js
  • core/app/disaster_service.py
  • core/app/pipeline/event_pipeline.py
  • core/app/runtime/disaster_service_lifecycle.py
  • core/app/runtime/startup_silence_coordinator.py
  • core/message/fusion/cenc_fusion_service.py
  • core/message/fusion/cwa_eew_fusion_service.py
  • core/message/logging/filters/raw_message_filter.py
  • core/message/presenters/earthquake_presenter.py
  • core/message/presenters/weather_alarm_code_map.py
  • core/message/presenters/weather_constants.py
  • core/message/presenters/weather_presenter.py
  • core/message/push/__init__.py
  • core/message/push/message_build_service.py
  • core/message/push/push_flow_handler.py
  • core/message/push/weather_aggregation_service.py
  • core/network/http/eqsc_http_client.py
  • core/network/source_message_router.py
  • core/parsers/base_parser.py
  • core/parsers/china_earthquake_parser.py
  • core/parsers/china_eew_parser.py
  • core/parsers/china_intensity_report_eqsc_parser.py
  • core/parsers/china_intensity_report_parser.py
  • core/parsers/fssn_cmt_parser.py
  • core/parsers/global_sources_parser.py
  • core/parsers/japan_earthquake_parser.py
  • core/parsers/japan_eew_parser.py
  • core/parsers/parser_registry.py
  • core/parsers/snet_parser.py
  • core/parsers/taiwan_earthquake_parser.py
  • core/parsers/taiwan_eew_parser.py
  • core/parsers/tsunami_parser.py
  • core/parsers/typhoon_parser.py
  • core/parsers/weather_parser.py
  • core/rules/intensity_rule.py
  • core/services/config/config_validation_service.py
  • core/services/eqsc/eqsc_cenc_intensity_poll_service.py
  • core/services/eqsc/eqsc_tsunami_poll_service.py
  • core/services/eqsc/eqsc_typhoon_poll_service.py
  • core/services/identity/event_deduplication_service.py
  • core/services/query/weather_query_service.py
  • core/services/snet/snet_poll_service.py
  • core/sources/source_catalog.py
  • core/sources/source_router.py
  • core/storage/source_compat.py
  • docs/OpenQuakeAPI.md
  • plugin/commands/plugin_admin_command_service.py
  • plugin/commands/plugin_query_command_service.py
  • utils/plugin_logger.py
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Sourcery review
  • GitHub Check: Codacy Static Code Analysis
⚠️ CI failures not shown inline (2)

GitHub Actions: 🐍 代码格式与质量检查 / 0_Ruff 代码检查.txt: feat: 新增 OpenQuakeAPI 气象预警接入、聚合推送服务、细粒度日志级别控制及多项修复

Conclusion: failure

View job details

##[group]Run actions/github-script@v9
 with:
   script: const fs = require('fs');
const formatLog = fs.readFileSync('format.log', 'utf8');
const checkLog = fs.readFileSync('check.log', 'utf8');
// --- 解析格式化日志 ---
// 示例: "1 file reformatted, 2 files left unchanged."
const formatMatch = formatLog.match(/(\d+) file(s)? reformatted/);
const reformattedCount = formatMatch ? parseInt(formatMatch[1]) : 0;
const unchangedMatch = formatLog.match(/(\d+) file(s)? left unchanged/);
const unchangedCount = unchangedMatch ? parseInt(unchangedMatch[1]) : 0;
// --- 解析检查日志 ---
// 示例: "Found 2 errors (1 fixed, 1 remaining)."
let fixedCount = 0;
let remainingCount = 0;
let totalErrors = 0;
const errorMatch = checkLog.match(/Found (\d+) error/);
if (errorMatch) {
    totalErrors = parseInt(errorMatch[1]);
    const fixedMatch = checkLog.match(/\((\d+) fixed/);
    if (fixedMatch) fixedCount = parseInt(fixedMatch[1]);
    const remainingMatch = checkLog.match(/, (\d+) remaining\)/);
    if (remainingMatch) {
        remainingCount = parseInt(remainingMatch[1]);
    } else {
        // 如果没有详细的 fixed/remaining 信息,通常意味着没有修复或全部剩余
        if (fixedCount === 0) remainingCount = totalErrors;
    }
}
// --- 构建报告内容 ---
let summary = '';
let isSuccess = (reformattedCount === 0 && remainingCount === 0);
if (isSuccess) {
    summary = '### ✅ All checked pass!';
} else {
    summary = '### ⚠️ 代码质量检查报告\n\n';
    summary += '> 发现部分文件需要格式化或存在代码问题,建议在本地运行 `ruff format .` 和 `ruff check --fix .`。\n\n';
    // 格式化部分
    if (reformattedCount > 0) {
        summary += `**🖌️ 格式化**: \`${reformattedCount}\` 个文件被重新格式化 (未变更: ${unchangedCount})\n`;
    } else {
        summary += `**🖌️ 格式化**: ✅ 无需变更\n`;
    }
    // 代码检查部分
    if (totalErrors > 0) {
        summary += `**🛡️ 代码检查**: 发现 \`${totalErrors}\` 个问题\n`;
        summary += `- 🛠️ 已自动修复: \`${fixedCount}\`\n`;
        summary += `- ❌ 剩余未修复: \`${remainingCount}\`\n`;
    } else {
        summary += `**🛡️ 代码检查**: ✅ 通过\n`;
    }
    // 错误详情 (如果有剩余错误)
    i...

GitHub Actions: 🐍 代码格式与质量检查 / Ruff 代码检查: feat: 新增 OpenQuakeAPI 气象预警接入、聚合推送服务、细粒度日志级别控制及多项修复

Conclusion: failure

View job details

##[group]Run actions/github-script@v9
 with:
   script: const fs = require('fs');
const formatLog = fs.readFileSync('format.log', 'utf8');
const checkLog = fs.readFileSync('check.log', 'utf8');
// --- 解析格式化日志 ---
// 示例: "1 file reformatted, 2 files left unchanged."
const formatMatch = formatLog.match(/(\d+) file(s)? reformatted/);
const reformattedCount = formatMatch ? parseInt(formatMatch[1]) : 0;
const unchangedMatch = formatLog.match(/(\d+) file(s)? left unchanged/);
const unchangedCount = unchangedMatch ? parseInt(unchangedMatch[1]) : 0;
// --- 解析检查日志 ---
// 示例: "Found 2 errors (1 fixed, 1 remaining)."
let fixedCount = 0;
let remainingCount = 0;
let totalErrors = 0;
const errorMatch = checkLog.match(/Found (\d+) error/);
if (errorMatch) {
    totalErrors = parseInt(errorMatch[1]);
    const fixedMatch = checkLog.match(/\((\d+) fixed/);
    if (fixedMatch) fixedCount = parseInt(fixedMatch[1]);
    const remainingMatch = checkLog.match(/, (\d+) remaining\)/);
    if (remainingMatch) {
        remainingCount = parseInt(remainingMatch[1]);
    } else {
        // 如果没有详细的 fixed/remaining 信息,通常意味着没有修复或全部剩余
        if (fixedCount === 0) remainingCount = totalErrors;
    }
}
// --- 构建报告内容 ---
let summary = '';
let isSuccess = (reformattedCount === 0 && remainingCount === 0);
if (isSuccess) {
    summary = '### ✅ All checked pass!';
} else {
    summary = '### ⚠️ 代码质量检查报告\n\n';
    summary += '> 发现部分文件需要格式化或存在代码问题,建议在本地运行 `ruff format .` 和 `ruff check --fix .`。\n\n';
    // 格式化部分
    if (reformattedCount > 0) {
        summary += `**🖌️ 格式化**: \`${reformattedCount}\` 个文件被重新格式化 (未变更: ${unchangedCount})\n`;
    } else {
        summary += `**🖌️ 格式化**: ✅ 无需变更\n`;
    }
    // 代码检查部分
    if (totalErrors > 0) {
        summary += `**🛡️ 代码检查**: 发现 \`${totalErrors}\` 个问题\n`;
        summary += `- 🛠️ 已自动修复: \`${fixedCount}\`\n`;
        summary += `- ❌ 剩余未修复: \`${remainingCount}\`\n`;
    } else {
        summary += `**🛡️ 代码检查**: ✅ 通过\n`;
    }
    // 错误详情 (如果有剩余错误)
    i...
🧰 Additional context used
🪛 ast-grep (0.45.0)
core/message/presenters/weather_constants.py

[warning] 141-141: XPath query is request-/variable-derived; use parameterized XPath to prevent injection.
Context: text.find(keyword)
Note: [CWE-643] Improper Neutralization of Data within XPath Expressions ('XPath Injection').

(xpath-injection-python)

core/message/push/message_build_service.py

[warning] 804-804: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(fallback_path, "rb")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🪛 GitHub Check: Codacy Static Code Analysis
core/services/snet/snet_poll_service.py

[warning] 164-164: core/services/snet/snet_poll_service.py#L164
Try, Except, Pass detected.

core/services/eqsc/eqsc_tsunami_poll_service.py

[warning] 174-174: core/services/eqsc/eqsc_tsunami_poll_service.py#L174
Try, Except, Pass detected.

core/services/eqsc/eqsc_cenc_intensity_poll_service.py

[warning] 189-189: core/services/eqsc/eqsc_cenc_intensity_poll_service.py#L189
Try, Except, Pass detected.

core/services/eqsc/eqsc_typhoon_poll_service.py

[warning] 195-195: core/services/eqsc/eqsc_typhoon_poll_service.py#L195
Try, Except, Pass detected.

core/app/pipeline/event_pipeline.py

[warning] 139-139: core/app/pipeline/event_pipeline.py#L139
EventPipeline._flush_weather_buffer is too complex (20) (MC0001)


[warning] 139-139: core/app/pipeline/event_pipeline.py#L139
Method _flush_weather_buffer has 88 lines of code (limit is 50)


[warning] 210-210: core/app/pipeline/event_pipeline.py#L210
Try, Except, Pass detected.

core/message/push/weather_aggregation_service.py

[warning] 135-135: core/message/push/weather_aggregation_service.py#L135
Method should_aggregate has 56 lines of code (limit is 50)


[warning] 267-267: core/message/push/weather_aggregation_service.py#L267
Method _flush_via_forward has 71 lines of code (limit is 50)

🔇 Additional comments (53)
core/app/runtime/startup_silence_coordinator.py (1)

42-46: LGTM!

Also applies to: 262-283, 296-297, 387-388, 540-552

core/services/eqsc/eqsc_tsunami_poll_service.py (1)

159-175: LGTM!

Also applies to: 209-233

core/message/push/__init__.py (1)

14-22: LGTM!

core/app/disaster_service.py (1)

204-212: LGTM!

plugin/commands/plugin_query_command_service.py (1)

298-298: LGTM!

core/parsers/typhoon_parser.py (1)

225-225: LGTM!

utils/plugin_logger.py (1)

7-24: LGTM!

Also applies to: 33-83, 105-121, 130-140

core/message/fusion/cenc_fusion_service.py (1)

137-137: LGTM!

Also applies to: 148-148, 185-185, 196-196, 282-282

core/message/fusion/cwa_eew_fusion_service.py (1)

112-112: LGTM!

Also applies to: 123-123, 157-157, 168-168, 268-274

core/message/push/push_flow_handler.py (1)

263-298: LGTM!

Also applies to: 337-343

core/network/source_message_router.py (2)

125-138: LGTM!


445-445: LGTM!

Also applies to: 519-519

core/parsers/global_sources_parser.py (1)

230-230: LGTM!

Also applies to: 257-257, 414-414, 574-574, 924-924

core/parsers/tsunami_parser.py (1)

238-238: LGTM!

Also applies to: 409-409, 678-678

core/services/identity/event_deduplication_service.py (1)

112-130: LGTM!

Also applies to: 211-211, 238-238, 455-455, 736-744, 862-872

core/services/eqsc/eqsc_cenc_intensity_poll_service.py (1)

174-190: LGTM!

Also applies to: 361-361

core/services/eqsc/eqsc_typhoon_poll_service.py (1)

180-196: LGTM!

Also applies to: 295-310, 378-378

core/parsers/china_earthquake_parser.py (1)

126-126: LGTM!

core/parsers/china_eew_parser.py (1)

144-144: LGTM!

Also applies to: 252-252

core/parsers/china_intensity_report_eqsc_parser.py (1)

307-307: LGTM!

core/parsers/china_intensity_report_parser.py (1)

351-351: LGTM!

core/parsers/fssn_cmt_parser.py (1)

131-131: LGTM!

core/parsers/japan_earthquake_parser.py (1)

206-206: LGTM!

Also applies to: 357-357

core/parsers/japan_eew_parser.py (1)

126-126: LGTM!

Also applies to: 150-150, 273-274, 281-281, 433-433, 671-671

core/parsers/snet_parser.py (1)

631-633: LGTM!

core/parsers/taiwan_earthquake_parser.py (1)

238-238: LGTM!

core/parsers/taiwan_eew_parser.py (1)

144-147: LGTM!

Also applies to: 303-306

core/message/presenters/earthquake_presenter.py (1)

1043-1051: LGTM!

Also applies to: 1099-1101

core/rules/intensity_rule.py (1)

86-134: LGTM!

core/services/config/config_validation_service.py (1)

1282-1293: LGTM!

plugin/commands/plugin_admin_command_service.py (5)

157-157: LGTM!


259-260: LGTM!


351-365: LGTM!


313-335: 🎯 Functional Correctness

这处映射不需要改动。 status["connections"]group_key 和这里的 connection_label_map 使用的是同一套稳定标识,不会把已启用连接误判成“⚪ 未启用”。

			> Likely an incorrect or invalid review comment.

176-187: 🎯 Functional Correctness

NIED S-Net 子源键就是 enabled

			> Likely an incorrect or invalid review comment.
core/network/http/eqsc_http_client.py (1)

79-97: 🩺 Stability & Availability

无需修改

_conf_schema.json (1)

228-233: LGTM!

Also applies to: 769-837, 1438-1486

admin/js/utils/formatters.js (1)

248-250: LGTM!

Also applies to: 369-370

core/message/presenters/weather_alarm_code_map.py (1)

1-203: LGTM!

core/message/presenters/weather_constants.py (1)

119-156: LGTM!

core/message/presenters/weather_presenter.py (1)

74-81: LGTM!

core/message/push/message_build_service.py (2)

269-337: LGTM!

Also applies to: 785-822


744-754: 🗄️ Data Integrity & Integration

这处 type 回退可保留metadata["type"]weather_parser.py 里写入的是 weather_code,不是事件类型。

			> Likely an incorrect or invalid review comment.
core/services/query/weather_query_service.py (1)

97-149: LGTM!

admin/js/components/status/ConnectionsGrid.jsx (1)

353-357: 📐 Maintainability & Code Quality

这条映射不是死代码 scopedSourceMapconnectionName 分组,同一个 china_weather_openquakeFAN StudioOpenQuakeAPI 下都可单独保留。

			> Likely an incorrect or invalid review comment.
core/sources/source_catalog.py (1)

793-822: LGTM!

core/sources/source_router.py (1)

176-178: LGTM!

core/storage/source_compat.py (1)

62-64: LGTM!

Also applies to: 118-119

docs/OpenQuakeAPI.md (2)

122-122: LGTM!


445-466: LGTM!

Also applies to: 482-493

core/parsers/parser_registry.py (1)

77-81: LGTM!

Also applies to: 94-94, 104-104, 114-114

core/parsers/weather_parser.py (1)

21-32: LGTM!

Also applies to: 57-57, 134-136, 190-194

core/parsers/base_parser.py (1)

119-126: LGTM!

Comment on lines +288 to +299
# 停机前推送气象预警聚合缓冲区中尚未发送的事件,避免丢失。
weather_agg = getattr(
self.service, "_weather_aggregation_service", None
)
if weather_agg is not None:
try:
await weather_agg.flush_all()
except Exception as flush_err:
logger.debug(
f"[灾害预警] 停机时推送气象预警聚合缓冲区失败(已忽略): {flush_err}"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

停机刷新位置偏后且无超时保护。

此处刷新发生在后台任务已取消、ws_manager / http_fetcher 已关闭之后,而 flush_all() 内部会走 message_build_service.build_message_async(图标/地图渲染可能依赖已关闭的 HTTP 资源)并逐条发送消息;同时没有超时,缓冲较多时会明显拖长停机,甚至卡住 stop()。建议把该刷新提前到任务/资源回收之前,并加 asyncio.wait_for 兜底。

🛡️ 建议修复
                 if weather_agg is not None:
                     try:
-                        await weather_agg.flush_all()
+                        await asyncio.wait_for(weather_agg.flush_all(), timeout=15)
+                    except TimeoutError:
+                        logger.warning(
+                            "[灾害预警] 停机时推送气象预警聚合缓冲区超时,已放弃"
+                        )
                     except Exception as flush_err:
                         logger.debug(
                             f"[灾害预警] 停机时推送气象预警聚合缓冲区失败(已忽略): {flush_err}"
                         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/app/runtime/disaster_service_lifecycle.py` around lines 288 - 299,
将灾害服务停止流程中针对 _weather_aggregation_service.flush_all 的刷新移到后台任务及
ws_manager、http_fetcher 等资源回收之前,确保消息构建和发送依赖仍可用。为 flush_all 增加 asyncio.wait_for
超时保护,超时或其他异常继续记录并忽略,避免阻塞 stop()。

Comment on lines +74 to 80
# 针对 JSON 结构的字典数据,如果是 OpenQuakeAPI 聚合连接,
# 仅保留 earthquake(GQ)和 weather(CMA 气象)业务类型,其余过滤。
if "global_quake" in source_id.lower():
inner_type = str(payload_data.get("type") or "").lower()
if inner_type not in ("earthquake", "weather"):
return f"OpenQuakeAPI 非地震/气象业务JSON消息过滤: {inner_type}"
return self._handle_dict_message(payload_data, source_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

字符串 JSON 会绕过 OpenQuake 类型过滤。

RawMessageLoggingService 传入的仍可能是原始 JSON 字符串,而 should_filter_message() 对字符串会先走 _handle_string_message();因此 Line 74-79 仅对已经解析成 dict 的消息生效。实际字符串形式的 type=tsunami/status/station 不会被过滤,和“仅保留 earthquake/weather”的规则不一致,并可能增加日志存储和处理压力。

请在字符串解析后复用同一套 OpenQuake 类型检查,或将该检查提取为公共 helper,避免字符串与字典入口产生两套行为。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/message/logging/filters/raw_message_filter.py` around lines 74 - 80,
Update should_filter_message and the string-processing path around
_handle_string_message so OpenQuakeAPI JSON strings are parsed and subjected to
the same global_quake type check as dictionary payloads, allowing only
earthquake and weather. Reuse or extract a shared helper for the type
validation, then preserve the existing filtering result and _handle_dict_message
behavior for accepted messages.

Comment on lines +12 to +21
设计要点:
1. 按会话维度独立缓冲,避免跨群混淆;
2. 缓冲期间对同一预警 ID 去重,只保留最新;
3. 触发推送条件:时间窗口到期 / 缓冲条数达上限 / 收到红色预警立即推送;
红色预警立即推送时仍需通过规则链复核,不绕过已有过滤配置;
若红色预警触发推送时缓冲区仅有这一条事件,则直接走常规推送流,
避免为单条事件构建合并转发节点的额外开销;
4. 合并转发失败时降级为逐条文本发送,此时才启用限流;
5. 限流平台按颜色级别排序,超出配额的低级别预警被丢弃并记录 debug 日志。
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

文档描述的触发条件与实现不一致:缓冲条数上限未实现。

模块注释(第 15 行)称"缓冲条数达上限"是推送触发条件之一,但 should_aggregate 只依赖时间窗口与红色预警,max_batch_size 配置也从未被读取(分批只使用常量 _MAX_NODES_PER_FORWARD)。结果是单会话缓冲区在窗口内无条数上限,极端情况下(如全国范围批量预警)内存与单次转发体积不可控。建议要么按 max_batch_size 实现提前触发/裁剪,要么修正注释以免误导。

Also applies to: 219-223

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/message/push/weather_aggregation_service.py` around lines 12 - 21,
实现注释中声明的缓冲条数触发条件:更新 should_aggregate,使单会话缓冲数量达到配置的 max_batch_size
时立即触发推送,并确保读取并使用该配置而非仅依赖
_MAX_NODES_PER_FORWARD。保持时间窗口和红色预警的现有触发逻辑不变,同时避免缓冲区在窗口内无限增长。

is_event_linked=True,
event_stream="weather_alarm",
)
asyncio.create_task(self._flush_session(session))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

后台任务未持引用,可能被 GC 回收导致聚合推送丢失。

asyncio.create_task(...) 的返回值未保存,事件循环只持弱引用,任务在完成前可能被垃圾回收;同时 _flush_session 抛出的异常也无人观察。建议持有任务集合(或复用主服务的 register_background_task),并加上完成回调清理与异常记录。

🛡️ 建议修复
     def __init__(self, config: dict[str, Any]):
         self._config = config
+        # 持有正在执行的 flush 任务引用,避免被 GC 回收
+        self._flush_tasks: set[asyncio.Task] = set()
+    def _spawn_flush(self, session: str) -> None:
+        task = asyncio.create_task(self._flush_session(session))
+        self._flush_tasks.add(task)
+        task.add_done_callback(self._flush_tasks.discard)
+
-            asyncio.create_task(self._flush_session(session))
+            self._spawn_flush(session)
         timer = loop.call_later(
             delay,
-            lambda: asyncio.create_task(self._flush_session(session)),
+            lambda: self._spawn_flush(session),
         )

Also applies to: 241-245

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/message/push/weather_aggregation_service.py` at line 216, Update the
background-task creation in the session flush flow around _flush_session so each
asyncio.create_task result is retained in a service-level task collection or
registered through the existing register_background_task mechanism. Add
completion cleanup and ensure exceptions from _flush_session are observed and
recorded, applying the same handling to both affected task-creation sites.

Comment on lines +262 to +265
agg_config = self._get_aggregation_config()

# 统一走合并转发,平台是否支持由框架层处理
await self._flush_via_forward(session, entries, agg_config)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

flush 阶段回退到全局配置,会话级差异配置失效。

should_aggregate 使用会话级 runtime_config 读取 time_window_seconds 等参数,而 _flush_session/_flush_via_forward 只用 self._get_aggregation_config()(全局),同时 _flush_callback(session, batch, self._config, ...) 也传全局 config。若某会话覆写了限流参数,推送阶段不会生效。建议在缓冲时按会话记录一次生效配置,或在 flush 时通过 session_config_manager 重新解析。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/message/push/weather_aggregation_service.py` around lines 262 - 265,
Update the session flush flow around _flush_session and _flush_via_forward so it
uses the session’s effective runtime configuration, matching the configuration
used by should_aggregate, instead of self._get_aggregation_config() and the
global self._config. Resolve or preserve the session-specific configuration
through session_config_manager and pass it to the flush callback so per-session
time-window and rate-limit overrides remain effective.

Comment on lines +1177 to +1221
# 气象预警聚合推送配置校验
agg = cfg.get("weather_aggregation")
if isinstance(agg, dict):
ConfigValidator._ensure_bool(agg, "enabled", True)
ConfigValidator._ensure_bool(agg, "flush_on_red", True)
ConfigValidator._ensure_bool(agg, "rate_limit_enabled", True)

agg["time_window_seconds"] = int(
ConfigValidator._clamp_number(
agg.get("time_window_seconds", 900),
minimum=60,
maximum=3600,
default=900,
field_name="气象预警聚合时间窗口",
)
)
agg["max_batch_size"] = int(
ConfigValidator._clamp_number(
agg.get("max_batch_size", 20),
minimum=1,
maximum=50,
default=20,
field_name="气象预警聚合单批最大条数",
)
)
agg["rate_limit_max_messages"] = int(
ConfigValidator._clamp_number(
agg.get("rate_limit_max_messages", 3),
minimum=1,
maximum=20,
default=3,
field_name="气象预警限流最大消息数",
)
)
agg["rate_limit_window_seconds"] = int(
ConfigValidator._clamp_number(
agg.get("rate_limit_window_seconds", 900),
minimum=60,
maximum=3600,
default=900,
field_name="气象预警限流时间窗口",
)
)
cfg["weather_aggregation"] = agg

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

max_batch_size 校验范围与配置契约不一致。

_conf_schema.jsonweather_aggregation.max_batch_size 的 slider 声明为 min=1, max=20,但此处 _clamp_number 使用 maximum=50。若某种途径写入 21-50 之间的值,校验器会将其当作合法值保留,而不是按契约钳制到 20,导致运行时行为与管理面板/schema 描述不一致。

另外 rate_limit_max_messagesrate_limit_window_seconds 在提供的 schema 片段中被截断,建议一并确认其边界与此处 clamp 范围是否一致。

🐛 建议修复
             agg["max_batch_size"] = int(
                 ConfigValidator._clamp_number(
                     agg.get("max_batch_size", 20),
                     minimum=1,
-                    maximum=50,
+                    maximum=20,
                     default=20,
                     field_name="气象预警聚合单批最大条数",
                 )
             )
🔍 验证脚本:确认 rate_limit 相关字段的 schema 边界
#!/bin/bash
fd _conf_schema.json --exec cat -n {} \; | rg -n -A 30 '"rate_limit_max_messages"|"rate_limit_window_seconds"'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 气象预警聚合推送配置校验
agg = cfg.get("weather_aggregation")
if isinstance(agg, dict):
ConfigValidator._ensure_bool(agg, "enabled", True)
ConfigValidator._ensure_bool(agg, "flush_on_red", True)
ConfigValidator._ensure_bool(agg, "rate_limit_enabled", True)
agg["time_window_seconds"] = int(
ConfigValidator._clamp_number(
agg.get("time_window_seconds", 900),
minimum=60,
maximum=3600,
default=900,
field_name="气象预警聚合时间窗口",
)
)
agg["max_batch_size"] = int(
ConfigValidator._clamp_number(
agg.get("max_batch_size", 20),
minimum=1,
maximum=50,
default=20,
field_name="气象预警聚合单批最大条数",
)
)
agg["rate_limit_max_messages"] = int(
ConfigValidator._clamp_number(
agg.get("rate_limit_max_messages", 3),
minimum=1,
maximum=20,
default=3,
field_name="气象预警限流最大消息数",
)
)
agg["rate_limit_window_seconds"] = int(
ConfigValidator._clamp_number(
agg.get("rate_limit_window_seconds", 900),
minimum=60,
maximum=3600,
default=900,
field_name="气象预警限流时间窗口",
)
)
cfg["weather_aggregation"] = agg
# 气象预警聚合推送配置校验
agg = cfg.get("weather_aggregation")
if isinstance(agg, dict):
ConfigValidator._ensure_bool(agg, "enabled", True)
ConfigValidator._ensure_bool(agg, "flush_on_red", True)
ConfigValidator._ensure_bool(agg, "rate_limit_enabled", True)
agg["time_window_seconds"] = int(
ConfigValidator._clamp_number(
agg.get("time_window_seconds", 900),
minimum=60,
maximum=3600,
default=900,
field_name="气象预警聚合时间窗口",
)
)
agg["max_batch_size"] = int(
ConfigValidator._clamp_number(
agg.get("max_batch_size", 20),
minimum=1,
maximum=20,
default=20,
field_name="气象预警聚合单批最大条数",
)
)
agg["rate_limit_max_messages"] = int(
ConfigValidator._clamp_number(
agg.get("rate_limit_max_messages", 3),
minimum=1,
maximum=20,
default=3,
field_name="气象预警限流最大消息数",
)
)
agg["rate_limit_window_seconds"] = int(
ConfigValidator._clamp_number(
agg.get("rate_limit_window_seconds", 900),
minimum=60,
maximum=3600,
default=900,
field_name="气象预警限流时间窗口",
)
)
cfg["weather_aggregation"] = agg
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/services/config/config_validation_service.py` around lines 1177 - 1221,
Update the weather_aggregation validation in ConfigValidator so max_batch_size
uses the schema contract’s maximum of 20 instead of 50. Also verify the schema
bounds for rate_limit_max_messages and rate_limit_window_seconds, and align
their _clamp_number ranges with those declared limits.

Comment on lines 306 to +314
items = await client.fetch_list(limit=limit, use_cache=False)
if not isinstance(items, list):
self._consecutive_failures += 1
self._notify_silence_fetch_completed(success=False)
return []

self._consecutive_failures = 0
self._last_success_at = time.time()
coordinator = getattr(self.service, "startup_silence", None)
if coordinator is not None:
try:
coordinator.note_poll_fetch_completed("eqsc_cenc_ir", success=True)
except Exception as exc:
logger.debug(
f"[灾害预警] EQSC CENC 烈度速报轮询通知静默协调器失败: {exc}"
)
self._notify_silence_fetch_completed(success=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

不要将失败返回的空列表标记为首轮成功。

fetch_list() 在无 token、非 200 和请求异常时都会返回 [],但这里仍调用 success=True。静默协调器会因此将轮询门闩置为 ready,后续恢复时可能把首批历史数据当作新事件处理。请让客户端返回可区分的请求状态,或在此保留“结果未知/失败”状态。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/services/eqsc/eqsc_cenc_intensity_poll_service.py` around lines 306 -
314, 修复 eqsc 轮询流程中将请求失败的空列表标记为成功的问题。更新 fetch_list 与调用方,使无 token、非 200
或请求异常可被区分;在 fetch_list 返回结果未知/失败时保持失败状态并调用
_notify_silence_fetch_completed(success=False),仅在请求确实成功时更新 _last_success_at、重置
_consecutive_failures 并标记 success=True。

Comment on lines 219 to +236
async def fetch_once(self, *, emit_event: bool = True) -> dict[str, Any] | None:
"""抓取一轮 EQSC 海啸快照,可选投递事件。"""
client = self._ensure_client()
if client is None:
self._notify_silence_fetch_completed(success=False)
return None

# 轮询侧强制绕过短缓存,确保按间隔拿到最新快照;
# 客户端缓存仍可用于同间隔内的并发查询复用。
raw = await client.fetch_latest_tsunami(use_cache=False)
if not isinstance(raw, dict) or not raw:
self._consecutive_failures += 1
self._notify_silence_fetch_completed(success=False)
return None

self._consecutive_failures = 0
self._last_success_at = time.time()
coordinator = getattr(self.service, "startup_silence", None)
if coordinator is not None:
try:
coordinator.note_poll_fetch_completed("eqsc_tsunami", success=True)
except Exception as exc:
logger.debug(f"[灾害预警] EQSC 海啸轮询通知静默协调器失败: {exc}")
self._notify_silence_fetch_completed(success=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

将静默门闩完成通知移到首轮轮询结束后。

Line 236 在解析和 _handle_disaster_event 前就将门闩标记为成功;超过 settle 时间后,启动快照仍在处理时可能已恢复正常推送。S-Net 的 fetch_once 还会被 fetch_for_query 调用,因此查询请求也能改变启动门闩状态。仅由 _poll_loop 在首轮 fetch_once 完整返回后报告完成,并从可复用的 fetch_once 中移除这些通知。

  • core/services/eqsc/eqsc_tsunami_poll_service.py#L219-L236: 将成功/失败完成通知移至首轮 _poll_loop 调用之后。
  • core/services/snet/snet_poll_service.py#L199-L204: 移除 fetch_once 内的完成通知(包括现有成功分支);避免 fetch_for_query 影响启动静默状态。
📍 Affects 2 files
  • core/services/eqsc/eqsc_tsunami_poll_service.py#L219-L236 (this comment)
  • core/services/snet/snet_poll_service.py#L199-L204
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/services/eqsc/eqsc_tsunami_poll_service.py` around lines 219 - 236, Move
silent-gate completion reporting out of reusable fetch_once methods: in
core/services/eqsc/eqsc_tsunami_poll_service.py lines 219-236, remove the
success/failure notifications from fetch_once and have _poll_loop report
completion only after its first fetch_once call fully finishes processing,
including _handle_disaster_event; in core/services/snet/snet_poll_service.py
lines 199-204, remove the existing fetch_once completion notifications as well
so fetch_for_query cannot affect startup silence state.

Comment thread docs/OpenQuakeAPI.md
Comment on lines +468 to +472
"timestampMs": 1752401400000,
"payload": {
"id": "32031241600000_20260729124523",
"headline": "铜山区气象台发布强对流黄色预警[Ⅲ级/较重]",
"effective": "2026/07/29 12:45",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

修正示例中的 timestampMs

timestampMs=1752401400000 对应 2025 年的时间,而同一示例的 ideffective 和说明文本都指向 2026 年 7 月 29 日 12:45。请替换为与该示例日期一致的毫秒时间戳,避免接入方据此编写错误测试。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/OpenQuakeAPI.md` around lines 468 - 472, 更新 docs/OpenQuakeAPI.md 示例中的
timestampMs,使其对应 2026 年 7 月 29 日 12:45,并与同一示例的 id、effective
和说明文本保持一致;不要修改示例中的其他字段。

Comment on lines +489 to +529
# 正常区域搜索:结果较多时也走合并转发分批发送,避免单条消息过长
if text_blocks and len(text_blocks) > 1:
try:
ok = await _send_forward_batches(text_blocks)
if ok:
await self._track_command_feature(
"command_weather_query",
{
"success": True,
"query_mode": str(result.get("query_mode") or "search"),
"is_nationwide": is_nationwide,
"result_count": int(total or 0),
"has_optional_type": bool(optional_a),
"has_optional_level": bool(optional_b),
"delivery_mode": "forward_batches",
},
)
return
except Exception as forward_error:
logger.warning(
f"[灾害预警] 合并转发送失败,回退文本: {forward_error}"
)
try:
await _send_text_blocks(text_blocks, total)
await self._track_command_feature(
"command_weather_query",
{
"success": True,
"query_mode": str(result.get("query_mode") or "search"),
"is_nationwide": is_nationwide,
"result_count": int(total or 0),
"has_optional_type": bool(optional_a),
"has_optional_level": bool(optional_b),
"delivery_mode": "text_blocks",
},
)
return
except Exception as text_error:
logger.warning(f"[灾害预警] 文本回退发送失败: {text_error}")

# 结果较少时,组装文字概要

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

全国分支失败后会二次进入该分支,可能重复发送。

全国分支(第 449-487 行)在 _send_forward_batches 抛异常且文本回退也失败时不会 return,控制流会落到这里并再次尝试 _send_forward_batches。由于 _send_forward_batches 是逐批发送、可能在中途失败,用户可能已收到前几批内容,此时重复发送会造成重复消息。建议在全国分支的最终失败路径显式 return,或用 is_nationwide 排除本分支。

另外这段与全国分支的投递+遥测逻辑几乎完全一致,建议抽成一个内部辅助函数(参数化 is_nationwide)以消除重复。

🐛 建议修复
-            # 正常区域搜索:结果较多时也走合并转发分批发送,避免单条消息过长
-            if text_blocks and len(text_blocks) > 1:
+            # 正常区域搜索:结果较多时也走合并转发分批发送,避免单条消息过长
+            # 全国分支已尝试过投递(含回退),此处不再重复发送
+            if not is_nationwide and text_blocks and len(text_blocks) > 1:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 正常区域搜索:结果较多时也走合并转发分批发送,避免单条消息过长
if text_blocks and len(text_blocks) > 1:
try:
ok = await _send_forward_batches(text_blocks)
if ok:
await self._track_command_feature(
"command_weather_query",
{
"success": True,
"query_mode": str(result.get("query_mode") or "search"),
"is_nationwide": is_nationwide,
"result_count": int(total or 0),
"has_optional_type": bool(optional_a),
"has_optional_level": bool(optional_b),
"delivery_mode": "forward_batches",
},
)
return
except Exception as forward_error:
logger.warning(
f"[灾害预警] 合并转发送失败,回退文本: {forward_error}"
)
try:
await _send_text_blocks(text_blocks, total)
await self._track_command_feature(
"command_weather_query",
{
"success": True,
"query_mode": str(result.get("query_mode") or "search"),
"is_nationwide": is_nationwide,
"result_count": int(total or 0),
"has_optional_type": bool(optional_a),
"has_optional_level": bool(optional_b),
"delivery_mode": "text_blocks",
},
)
return
except Exception as text_error:
logger.warning(f"[灾害预警] 文本回退发送失败: {text_error}")
# 结果较少时,组装文字概要
# 正常区域搜索:结果较多时也走合并转发分批发送,避免单条消息过长
# 全国分支已尝试过投递(含回退),此处不再重复发送
if not is_nationwide and text_blocks and len(text_blocks) > 1:
try:
ok = await _send_forward_batches(text_blocks)
if ok:
await self._track_command_feature(
"command_weather_query",
{
"success": True,
"query_mode": str(result.get("query_mode") or "search"),
"is_nationwide": is_nationwide,
"result_count": int(total or 0),
"has_optional_type": bool(optional_a),
"has_optional_level": bool(optional_b),
"delivery_mode": "forward_batches",
},
)
return
except Exception as forward_error:
logger.warning(
f"[灾害预警] 合并转发送失败,回退文本: {forward_error}"
)
try:
await _send_text_blocks(text_blocks, total)
await self._track_command_feature(
"command_weather_query",
{
"success": True,
"query_mode": str(result.get("query_mode") or "search"),
"is_nationwide": is_nationwide,
"result_count": int(total or 0),
"has_optional_type": bool(optional_a),
"has_optional_level": bool(optional_b),
"delivery_mode": "text_blocks",
},
)
return
except Exception as text_error:
logger.warning(f"[灾害预警] 文本回退发送失败: {text_error}")
# 结果较少时,组装文字概要
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/commands/plugin_query_command_service.py` around lines 489 - 529,
修复全国分支在 `_send_forward_batches` 和 `_send_text_blocks`
均失败后继续落入普通搜索分支的问题:在全国分支最终失败路径显式返回,避免重复投递。将全国与普通区域中重复的投递及
`_track_command_feature` 逻辑抽取为内部辅助函数,并通过 `is_nationwide`
等参数区分行为,保持现有成功与回退遥测结果不变。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 修改了 1000+ 行代码 (忽略生成文件) type/design 🎨 交互设计、UI/UX 方案讨论 type/docs 📚 文档变更 type/feat ✨ 新功能 / New Feature type/fix 🐛 Bug 修复 type/refactor ♻️ 代码重构 (无功能变化)

Projects

None yet

1 participant