ashioyajotham/global-activity-monitor
GitHub: ashioyajotham/global-activity-monitor
基于GDELT和RSS数据源的自主地缘政治态势追踪系统,能自动发现、评分并在交互式地图上可视化全球事件。
Stars: 1 | Forks: 0
# 全球活动监视器
一个实时地缘政治态势跟踪器,能够自主发现、评分并在交互式世界地图上可视化活跃的全球事件。无需硬编码的监视列表,无需手动配置。该系统观察全球数据源,按地理位置对事件进行聚类,并呈现重要态势。
## 存在原因
大多数地缘政治仪表板要么是设有付费墙的情报平台,要么是贴着地图的 glorified RSS 阅读器。它们要么要求你告诉它们监视什么(关键词、区域、主题过滤器),要么倾倒原始源并称之为“监控”。
本项目采取不同的方法。它像分析师一样处理问题:摄取所有数据,按地理位置聚类,按严重程度评分,并呈现升级态势。系统对你应该关注什么不持立场。它发现正在发生的事情并进行排名。由你决定什么重要。
根本论点是一个有用的全球监视器需要三个属性:
1. **自主发现** —— 它应该发现你未预料到的情况。
2. **量化严重性** —— “情况很糟”不可操作;定义量表上的分数才可操作。
3. **时间感知** —— 升级比静态快照更重要。
## 架构
该系统是一个六文件 Node.js 应用程序,带有基于浏览器的前端。SQLite 在重启期间保持态势历史记录。无构建步骤,除 Express 外无框架。
```
global-activity-monitor/
server.js -- Express + WebSocket server, cron scheduling, pipeline orchestration, auth
discovery.js -- Situation discovery engine: clustering, scoring, summarisation, noise filter
countries-data.js -- ISO 3166-1 standard geodata: 150+ countries with coordinates, capitals, aliases
feeds.js -- RSS ingestion, geopolitical sentiment lexicon, deduplication
db.js -- SQLite persistence layer: situation snapshots, articles, escalation history
index.html -- Frontend: D3.js + TopoJSON world map, side panel, notifications, news river
package.json -- 7 dependencies
.env.example -- Environment variable reference
ARCHITECTURE_PLAN.md -- Hardcoding audit and improvement roadmap
```
### 数据流
```
graph LR
subgraph Data Sources
GEO["GDELT GEO 2.0 API
(satellite geo-events)"] DOC["GDELT DOC 2.0 API
(article metadata + locations)"] RSS["RSS Feeds
(9 sources)"] end subgraph Ingestion GF["fetchGeoTheme()"] DF["fetchDocTheme()"] NF["fetchAllNews()"] SA["Geopolitical Sentiment
(custom lexicon)"] end subgraph Discovery Engine NTE["newsToEvents()
(ISO gazetteer geocoding)"] NR["filterNoiseEvents()
(sports/entertainment removal)"] CL["clusterEvents()
(500km Haversine radius)"] CF["assessConfidence()
(high / normal / low / noise)"] SC["scoreSituation()
(volume + severity + tone)"] SUM["autoDescribeSituation()
(TF-IDF extractive summariser)"] end subgraph Persistence DB["SQLite
(better-sqlite3)"] TR["Trend queries"] ES["Escalation history"] end subgraph Output WS["WebSocket
(real-time push)"] ESC["Escalation Detector"] API["REST API
(/api/trends, /api/stats)"] UI["Browser UI
(D3 + TopoJSON map)"] end GEO --> GF --> NR DOC --> DF --> NR RSS --> NF --> SA --> NTE --> NR NR --> CL --> CF --> SC --> SUM SUM --> DB SUM --> WS --> UI SUM --> ESC --> WS DB --> TR --> API DB --> ES --> API ``` ### 管道时序 | 阶段 | 频率 | 详情 | |-------|-----------|--------| | GDELT GEO 扫描 | 每 10 分钟 | 5 个主题查询,每个 75 个事件,查询间隔 2.2s | | GDELT DOC 扫描 | 每 10 分钟 | 前 3 个主题,与 GEO 查询交错(速率预算) | | RSS 源刷新 | 每 5 分钟 | 顺次抓取 9 个源,每个源超时 10s | | 发现管道 | 每次 GDELT 扫描后 | 聚类、评分、持久化、通过 WebSocket 推送 | | DB 清理 | 每日 03:00 | 移除 30 天前的数据 | ## 评分机制 每个发现的态势都会获得一个从 1.0 到 10.0 的分数,该分数由三个独立信号计算得出。公式是确定性的且可审计。 ### 分数组成 ``` FINAL_SCORE = min(10, VOLUME + SEVERITY + TONE) ``` **1. 数量分数(0 到 8 分)** 该态势被报道的力度如何?RSS 和 DOC 文章的权重是原始 GDELT 地理事件(每个 0.5x)的 1.5 倍,反映了信号质量的差异。 | 有效文章数 | 数量分数 | |------------------------|-------------| | 4 | ~2.0 | | 8 | ~4.0 | | 16+ | 8.0(上限) | **2. 严重性关键词分数(0 到 3 分)** 扫描标题中的升级指示语言,分为三个层级: | 层级 | 分数 | 示例 | |------|-------|---------| | Critical(危急) | 3 | war, killed, airstrike, bombing, massacre, genocide, invasion, missile, casualties, death toll | | Elevated(升级) | 2 | conflict, fighting, attack, troops, military, clash, violence, crisis, hostage, artillery, drone, refugee | | Moderate(紧张) | 1 | tension, sanctions, protest, unrest, dispute, threat, escalation, riot, detain, arrest | 仅匹配到的最高层级计分。RSS/DOC 来源的标题优先于 GDELT 地理事件名称。 **3. 基调分数(0 到 2 分)** 源自情感分析。两个独立的管道为此提供支持: - **GDELT 事件**:GDELT 提供由其生产环境 NLP 管道计算的原生基调分数。 - **RSS 标题**:使用 **自定义地缘政治情感词典**(120+ 术语)在本地计算,该词典覆盖了基础 AFINN 字典。这修复了冲突语言的长期误判 —— “strike” 评分为 -4 而不是 AFINN 的 -1,“ceasefire” 评分为 +2 而不是 0,“casualties” 评分为 -5 而不是 -2。 转换公式:`tone_score = min(2, max(0, -average_tone / 5))`。 ### 置信度层级 v3 新增:态势根据来源多样性被分配置信度级别,这直接影响评分: | 置信度 | 条件 | 效果 | |------------|-----------|--------| | **high** | 3+ 篇 RSS 或 DOC 文章 | 完整评分 | | **normal** | 1+ 篇 RSS 或 DOC 文章 | 完整评分 | | **low** | 仅 GDELT 地理事件(8+) | 分数上限 3.9 | | **noise** | 事件太少,无文章 | 完全丢弃 | 低置信度态势在地图上显示为虚线、暗淡的点。在文章报道确认之前,它们无法达到升级或危急状态。这防止了 GDELT 卫星噪声产生误报。 ### 状态分类 | 状态 | 分数范围 | 颜色 | |--------|-----------|--------| | Critical(危急) | 6.5+ | 红色 | | Elevated(升级) | 4.0 - 6.4 | 橙色 | | Stable(稳定) | 低于 4.0 | 绿色 | ### 诚实评估 评分模型适用于数量、关键词严重程度和基调一致的高信号态势。置信度系统有效地减少了仅来自 GDELT 数据的误报。弱点依然存在:产生很少文章的慢热外交态势尽管后果严重,但得分很低。严重性关键词仍然是一个硬编码列表 —— `ARCHITECTURE_PLAN.md` 记录了用 GDELT CAMEO 事件代码替换它们的路径,这将是数据驱动而非人工维护的。 ## 噪声过滤 体育、娱乐和其他非地缘政治内容在聚类之前被过滤。过滤器使用两层方法: - **强术语**(>7 个字符):像“championship”或“tournament”这样的单个匹配会触发噪声分类,除非同时存在 2+ 个地缘政治严重性术语(处理如“Olympic boycott”的情况)。 - **弱术语**(<=7 个字符):需要 3+ 个同时匹配才能触发(避免对短歧义词的误报)。 `ARCHITECTURE_PLAN.md` 记录了一条改进路径,其中 GDELT CAMEO 代码完全取代关键词列表 —— 具有 CAMEO 代码的事件根据定义就是地缘政治的,无需过滤。 ## 态势摘要 侧面板中的态势字段由抽取式摘要引擎生成。这不是 LLM —— 它是一个确定性的 TF-IDF 句子排名器,从可用的新闻报道中选择信息量最大的句子。 ### 工作原理 ``` flowchart TD A["Collect RSS + DOC snippets
(article excerpts from cluster)"] --> B{"Snippets available?"} B -->|Yes| C["Combine into corpus, split sentences"] C --> D["Score each sentence via TF-IDF"] D --> E["Filter noise sentences (score * 0.1)"] E --> F["Select top 2-3 sentences"] F --> G["Re-order by original position"] G --> H["Return as coherent paragraph"] B -->|No| I{"Headlines available?"} I -->|Yes| J["Filter noise headlines"] J --> K["Return top 3 (dot-separated)"] I -->|No| L["Generate contextual fallback
(GDELT metadata inference)"] ``` TF-IDF 评分计算词频乘以逆文档频率。包含罕见的、特定主题词汇的句子得分高于通用语言。与噪声过滤器匹配的句子得分降低 90%。这完全在本地运行,零外部 API 调用。 ## 地缘政治情感词典 默认的 AFINN 情感字典是为产品评论和社交媒体构建的。它系统性低估了冲突语言。`feeds.js` 中的自定义 `GEO_LEXICON` 覆盖了 120+ 个术语: | 术语 | AFINN 默认值 | 地缘政治覆盖值 | 理由 | |------|--------------|----------------------|-----------| | strike | -1 | -4 | AFINN 将此视为劳资纠纷。在地缘政治中,它意味着空袭。 | | collapse | -2 | -4 | “Government collapse”不是轻微的负面。 | | casualties | -2 | -5 | 任何提到伤亡的标题都是严重的。 | | ceasefire | 0 | +2 | AFINN 没有条目。停火在冲突监控中是积极的。 | | invasion | 0 | -5 | AFINN 没有条目。 | 该词典还对降温术语给予正面评分(停火 +2,和平谈判 +3,和解 +3),以便基调组成部分正确反映局势改善,而不仅仅是恶化。 ## 数据源 ### GDELT GEO 2.0 返回匹配主题查询的地理位置事件。每 10 分钟查询五个主题: - Conflict(战争、战斗、战役) - Crisis(人道主义、难民、饥荒) - Military(空袭、军队、轰炸) - Unrest(抗议、暴乱、起义) - Tension(制裁、核、对峙) 每个查询返回最多 75 个带有坐标、文章引用和基调分数的地理位置事件。 ### GDELT DOC 2.0(v3 新增) 返回带有预提取元数据的实际文章。前 3 个主题也通过 DOC API 查询,该 API 提供: - 文章标题和 URL - 来源国家和语言 - 域名信息 - 预提取的国家提及(用于地理编码) DOC 结果与 GEO 查询交错进行,以保持在 GDELT 的速率预算内。 ### RSS 源 每 5 分钟轮询 9 个源: | 来源 | 类型 | 备注 | |--------|------|-------| | BBC World | 主流 | 可靠,覆盖面广 | | Al Jazeera | 主流 | 中东和全球南方覆盖强 | | Reuters | 通讯社 | 间歇性 DNS 错误 | | AP News | 通讯社 | 间歇性 403 错误 | | Counterpunch | 独立/左翼 | 美国外交政策批评,调查性 | | Declassified UK | 独立 | 英国外交和军事政策,基于 FOIA | | RT News | 国家资助(俄罗斯) | 纳入以保持观点多样性 | | Mint Press News | 独立 | 侧重中东 | | The Grayzone | 独立 | 调查性,反对建制派叙事 | 来源选择有意多样化。评分引擎不对来源进行加权。检测到某一态势在意识形态对立的媒体上被讨论,本身就是重要性的信号。 ## 持久化(v3 新增) SQLite(通过 `better-sqlite3`)存储态势历史、文章和升级事件。数据库支持: ### 持久化内容 | 表 | 内容 | 保留期 | |-------|----------|-----------| | `snapshots` | 每个发现周期的完整态势状态 | 30 天 | | `articles` | 与每个态势相关的热门文章 | 30 天 | | `escalations` | 带有时间戳的状态变更事件 | 30 天 | ### 基于持久化的 API | 端点 | 目的 | |----------|---------| | `GET /api/trends?hours=24` | 所有态势及其分数趋势(上升/下降/稳定)和变化量 | | `GET /api/trends/:name?days=7` | 特定态势的分数历史(用于趋势线) | | `GET /api/escalations?limit=50` | 按时间顺序排列的升级历史 | | `GET /api/stats` | 数据库统计信息(快照计数、文章计数、DB 大小) | | `GET /api/health` | 服务器正常运行时间、活动计数、DB 状态 | ### 状态恢复 重启时,服务器从最近的快照中恢复 `previousStates` 映射。这意味着升级检测在服务器重启后能正常工作 —— 无数据丢失,无错误重报。 ## 认证(v3 新增) 可选的 HTTP Basic Auth,由环境变量控制: ``` AUTH_PASSWORD=your_secret_here AUTH_USER=monitor # defaults to "monitor" ``` 当设置了 `AUTH_PASSWORD` 时: - 所有 HTTP 端点都需要 Basic Auth(`/api/health` 除外) - WebSocket 连接需要 Basic Auth 头或 `?token=` 查询参数 - 登录提示会在浏览器中自动出现 当 `AUTH_PASSWORD` 未设置(默认)时,认证完全禁用。 ## 聚类 事件使用带有 500km Haversine 半径的单遍贪婪算法进行分组: 1. 对于每个传入事件,检查它是否落在任何现有聚类中心的 500km 范围内。 2. 如果是,添加它并更新中心(滚动平均)。 3. 如果否,创建一个新聚类。 4. 丢弃少于 2 个事件的聚类。 ### 命名 态势通过从聚类标题中提取两个提及次数最多的国家并按字母顺序排序来自动命名。当只有 GDELT 地理数据可用时,通过 `findNearest()` 使用 `countries-data.js` ISO 地名录中的最近条目。 ### 限制 500km 半径对于密集区域(黎凡特)太宽,对于蔓延冲突(萨赫勒)太窄。`ARCHITECTURE_PLAN.md` 记录了通往密度自适应聚类和基于国家对的分组的路径。 ## 国家数据(`countries-data.js`) 用 ISO 3166-1 标准数据替换 v2 手工维护的地名录。150+ 个国家,包含: - ISO alpha-2 代码 - 代表性坐标(首都) - 主要城市(用于国家以下级别的匹配) - 官方缩写(UK, US, UAE 等) 在加载时结构化数据构建快速查找索引,而不是维护单独的查找表。`extractCountries()` 函数使用针对国家名称、首都、城市和缩写的词边界正则匹配。 ## 前端 前端是单个 `index.html` 文件。无框架,无构建工具。 ### D3.js + TopoJSON 地图(v3 新增) 用真实的世界地图替换 v2 手绘的 SVG 大陆块: - **Natural Earth 1 投影** —— 面积和形状失真的良好平衡 - **Natural Earth 110m TopoJSON** 从 CDN 加载(~100KB) - **国家悬停** —— 高亮国家边界并显示名称 - **ISO 3166-1 数字映射** 内置在前端用于标记特征 - **活动点** 根据置信度调整大小和样式(低置信度点更小更暗) - **连接线** 在附近非稳定态势之间 ### 关键 UI 组件 - **SVG 世界地图**:D3 渲染的国家路径,带有脉动活动点 - **置信度指示器**:低置信度(仅卫星)态势的虚线边框和暗淡点 - **活动条**:按严重程度排序的可滚动侧边栏列表,低置信度项目在视觉上有区分 - **侧面板**:分数细分、置信度徽章、态势摘要、带有基调分数的相关报道 - **新闻流**:带有来源标签的右侧源 - **通知铃铛**:升级警报,带有音频(Web Audio API)和浏览器通知 - **状态栏**:实时计数 + UTC 时钟 ## 运行 ### 要求 - Node.js 18+(在 20.x 和 22.x 上测试) - 互联网连接(GDELT API + RSS 源是远程的) - 无需 API 密钥 ### 设置 ``` git clone https://github.com/ashioyajotham/global-activity-monitor.git cd global-activity-monitor npm install node server.js ``` 打开 `http://localhost:4000`。第一个发现周期大约需要 60 秒(GDELT 速率限制)。之后数据会自动刷新。 ### 环境变量 | 变量 | 默认值 | 描述 | |----------|---------|-------------| | `PORT` | 4000 | 服务器端口 | | `AUTH_PASSWORD` | _(空)_ | 设置以启用 Basic Auth | | `AUTH_USER` | monitor | Basic Auth 用户名 | | `DB_PATH` | ./monitor.db | SQLite 数据库文件路径 | ### 数据库管理 ``` # 手动清理(保留最近 30 天) npm run cleanup # 检查健康状况 curl http://localhost:4000/api/health # 查看趋势 curl http://localhost:4000/api/trends?hours=24 # 查看特定情况历史 curl http://localhost:4000/api/trends/Iran%20-%20United%20States?days=7 ``` ## 已知限制 **基于关键词的分类。** 严重性评分和噪声过滤仍然使用硬编码的术语列表。`ARCHITECTURE_PLAN.md` 记录了通往 GDELT CAMEO 事件代码的路径,这将用结构化分类数据取代关键词猜测。 **固定聚类半径。** 500km 对于黎凡特来说太宽,对于俄罗斯来说太窄。密度自适应或国家结对聚类会更准确。 **静态分数阈值。** 6.5/4.0 不适应全球基线。在平静的日子,没有什么达到危急状态。在危机日子,一切都达到了。基于百分位数的阈值将自我校准。 **英语偏见。** 所有 9 个 RSS 源都是英语。GDELT DOC API 部分补偿(它以所有语言索引 150+ 个国家),但用于摘要的文章片段仅限英语。 **情感仍然是词袋模型。** 自定义词典改进了 AFINN,但无法处理否定(“no casualties reported”仍然评分为负面)或讽刺。GDELT 的生产基调分数更复杂,但仅适用于 GDELT 来源的事件。 **默认无认证。** 在未设置 `AUTH_PASSWORD` 的情况下暴露给互联网是不可取的。 **GDELT 速率限制。** 请求之间的 2.2s 延迟是保守的。交错 GEO 和 DOC 查询使有用数据加倍,但也使扫描时间加倍至每个周期约 50-70 秒。 ## 依赖项 | 包 | 用途 | 为什么选这个 | |---------|---------|-------------| | `express` | HTTP 服务器 | 标准,最小 | | `ws` | WebSocket 服务器 | 轻量级,无 Socket.io 开销 | | `better-sqlite3` | SQLite 持久化 | 同步 API(无异步复杂性),零配置,快速 | | `node-cron` | 计划任务 | 简单的 cron 语法 | | `rss-parser` | RSS 源解析 | 处理 RSS 2.0 和 Atom | | `sentiment` | NLP 基调查准 | 零依赖,通过自定义词典扩展 | | `cors` | 跨域头 | 本地开发支持 | 从 CDN 加载的前端依赖项(无 node_modules): - `d3` v7.9.0 —— 地图投影和渲染 - `topojson-client` v3.0.2 —— TopoJSON 特征提取 ## 逐文件分解 ### `server.js`(254 行) 编排层。Express 服务器、WebSocket 管理、GDELT GEO + DOC 获取器(带速率限制交错)、新闻到事件地理编码、发现管道执行、带状态恢复的升级检测、基本认证中间件、用于趋势/统计/健康的 REST API 端点、cron 调度、优雅关闭。 ### `discovery.js`(306 行) 分析核心。噪声分类和过滤、GDELT GEO/DOC 响应解析、haversine 聚类、置信度评估、自动命名(国家提取 + 字母顺序规范化)、带噪声句子降级的 TF-IDF 抽取式摘要、12 类分类、带置信度上限的三分量严重性评分、当事方提取、去重。 ### `countries-data.js`(347 行) ISO 3166-1 标准国家数据。150+ 个国家,包含坐标、首都、主要城市和官方缩写。在加载时构建快速查找索引。用于标题地理编码的 `extractCountries()`,用于反向地理编码 GDELT 坐标的 `findNearest()`。 ### `feeds.js`(259 行) 带有增强情感的 RSS 摄取。120+ 术语地缘政治词典覆盖 AFINN 默认值。抓取 9 个源,运行增强基调分析,按标题相似性去重。导出 `analyzeGeopoliticalTone()` 和 `GEO_LEXICON` 以保持透明度。 ### `db.js`(294 行) 通过 `better-sqlite3` 的 SQLite 持久化。模式:`snapshots`、`articles`、`escalations` 表。写操作:`storeSituations()`、`storeArticles()`、`storeEscalation()`。趋势查询:`getScoreTrend()`、`getAllTrends()`。状态恢复:`recoverPreviousStates()`。维护:带有可配置保留期的 `cleanup()`。 ### `index.html`(561 行) 完整前端。D3.js Natural Earth 投影、带有 ISO 3166-1 标记的 TopoJSON 国家渲染、置信度感知的点大小调整、邻近态势之间的连接线、带有置信度样式的活动条、带有分数 + 置信度细分的侧面板、新闻流、带有 Web Audio 警报的通知铃铛、响应式布局。 ### `ARCHITECTURE_PLAN.md`(345 行) 硬编码审计,记录了代码库中 12 类脆弱关键词列表,并为每一类提供了具体替代方案。提出了 3 波改进路径:Wave 1(地图 + DOC API,已完成),Wave 2(CAMEO 代码取代关键词列表),Wave 3(自适应聚类 + 百分位数阈值)。 ## 路线图 `ARCHITECTURE_PLAN.md` 包含完整的技术审计。已完成和剩余工作的摘要: | 波次 | 状态 | 描述 | |------|--------|-------------| | Wave 1 | 完成 | 真实 TopoJSON 地图、GDELT DOC API、ISO 国家数据、SQLite 持久化 | | Wave 2 | 计划中 | CAMEO 事件代码取代严重性关键词和类别模式 | | Wave 3 | 计划中 | 密度自适应聚类、基于百分位数的分数阈值、来源多样性评分 | ## 许可证 MIT
(satellite geo-events)"] DOC["GDELT DOC 2.0 API
(article metadata + locations)"] RSS["RSS Feeds
(9 sources)"] end subgraph Ingestion GF["fetchGeoTheme()"] DF["fetchDocTheme()"] NF["fetchAllNews()"] SA["Geopolitical Sentiment
(custom lexicon)"] end subgraph Discovery Engine NTE["newsToEvents()
(ISO gazetteer geocoding)"] NR["filterNoiseEvents()
(sports/entertainment removal)"] CL["clusterEvents()
(500km Haversine radius)"] CF["assessConfidence()
(high / normal / low / noise)"] SC["scoreSituation()
(volume + severity + tone)"] SUM["autoDescribeSituation()
(TF-IDF extractive summariser)"] end subgraph Persistence DB["SQLite
(better-sqlite3)"] TR["Trend queries"] ES["Escalation history"] end subgraph Output WS["WebSocket
(real-time push)"] ESC["Escalation Detector"] API["REST API
(/api/trends, /api/stats)"] UI["Browser UI
(D3 + TopoJSON map)"] end GEO --> GF --> NR DOC --> DF --> NR RSS --> NF --> SA --> NTE --> NR NR --> CL --> CF --> SC --> SUM SUM --> DB SUM --> WS --> UI SUM --> ESC --> WS DB --> TR --> API DB --> ES --> API ``` ### 管道时序 | 阶段 | 频率 | 详情 | |-------|-----------|--------| | GDELT GEO 扫描 | 每 10 分钟 | 5 个主题查询,每个 75 个事件,查询间隔 2.2s | | GDELT DOC 扫描 | 每 10 分钟 | 前 3 个主题,与 GEO 查询交错(速率预算) | | RSS 源刷新 | 每 5 分钟 | 顺次抓取 9 个源,每个源超时 10s | | 发现管道 | 每次 GDELT 扫描后 | 聚类、评分、持久化、通过 WebSocket 推送 | | DB 清理 | 每日 03:00 | 移除 30 天前的数据 | ## 评分机制 每个发现的态势都会获得一个从 1.0 到 10.0 的分数,该分数由三个独立信号计算得出。公式是确定性的且可审计。 ### 分数组成 ``` FINAL_SCORE = min(10, VOLUME + SEVERITY + TONE) ``` **1. 数量分数(0 到 8 分)** 该态势被报道的力度如何?RSS 和 DOC 文章的权重是原始 GDELT 地理事件(每个 0.5x)的 1.5 倍,反映了信号质量的差异。 | 有效文章数 | 数量分数 | |------------------------|-------------| | 4 | ~2.0 | | 8 | ~4.0 | | 16+ | 8.0(上限) | **2. 严重性关键词分数(0 到 3 分)** 扫描标题中的升级指示语言,分为三个层级: | 层级 | 分数 | 示例 | |------|-------|---------| | Critical(危急) | 3 | war, killed, airstrike, bombing, massacre, genocide, invasion, missile, casualties, death toll | | Elevated(升级) | 2 | conflict, fighting, attack, troops, military, clash, violence, crisis, hostage, artillery, drone, refugee | | Moderate(紧张) | 1 | tension, sanctions, protest, unrest, dispute, threat, escalation, riot, detain, arrest | 仅匹配到的最高层级计分。RSS/DOC 来源的标题优先于 GDELT 地理事件名称。 **3. 基调分数(0 到 2 分)** 源自情感分析。两个独立的管道为此提供支持: - **GDELT 事件**:GDELT 提供由其生产环境 NLP 管道计算的原生基调分数。 - **RSS 标题**:使用 **自定义地缘政治情感词典**(120+ 术语)在本地计算,该词典覆盖了基础 AFINN 字典。这修复了冲突语言的长期误判 —— “strike” 评分为 -4 而不是 AFINN 的 -1,“ceasefire” 评分为 +2 而不是 0,“casualties” 评分为 -5 而不是 -2。 转换公式:`tone_score = min(2, max(0, -average_tone / 5))`。 ### 置信度层级 v3 新增:态势根据来源多样性被分配置信度级别,这直接影响评分: | 置信度 | 条件 | 效果 | |------------|-----------|--------| | **high** | 3+ 篇 RSS 或 DOC 文章 | 完整评分 | | **normal** | 1+ 篇 RSS 或 DOC 文章 | 完整评分 | | **low** | 仅 GDELT 地理事件(8+) | 分数上限 3.9 | | **noise** | 事件太少,无文章 | 完全丢弃 | 低置信度态势在地图上显示为虚线、暗淡的点。在文章报道确认之前,它们无法达到升级或危急状态。这防止了 GDELT 卫星噪声产生误报。 ### 状态分类 | 状态 | 分数范围 | 颜色 | |--------|-----------|--------| | Critical(危急) | 6.5+ | 红色 | | Elevated(升级) | 4.0 - 6.4 | 橙色 | | Stable(稳定) | 低于 4.0 | 绿色 | ### 诚实评估 评分模型适用于数量、关键词严重程度和基调一致的高信号态势。置信度系统有效地减少了仅来自 GDELT 数据的误报。弱点依然存在:产生很少文章的慢热外交态势尽管后果严重,但得分很低。严重性关键词仍然是一个硬编码列表 —— `ARCHITECTURE_PLAN.md` 记录了用 GDELT CAMEO 事件代码替换它们的路径,这将是数据驱动而非人工维护的。 ## 噪声过滤 体育、娱乐和其他非地缘政治内容在聚类之前被过滤。过滤器使用两层方法: - **强术语**(>7 个字符):像“championship”或“tournament”这样的单个匹配会触发噪声分类,除非同时存在 2+ 个地缘政治严重性术语(处理如“Olympic boycott”的情况)。 - **弱术语**(<=7 个字符):需要 3+ 个同时匹配才能触发(避免对短歧义词的误报)。 `ARCHITECTURE_PLAN.md` 记录了一条改进路径,其中 GDELT CAMEO 代码完全取代关键词列表 —— 具有 CAMEO 代码的事件根据定义就是地缘政治的,无需过滤。 ## 态势摘要 侧面板中的态势字段由抽取式摘要引擎生成。这不是 LLM —— 它是一个确定性的 TF-IDF 句子排名器,从可用的新闻报道中选择信息量最大的句子。 ### 工作原理 ``` flowchart TD A["Collect RSS + DOC snippets
(article excerpts from cluster)"] --> B{"Snippets available?"} B -->|Yes| C["Combine into corpus, split sentences"] C --> D["Score each sentence via TF-IDF"] D --> E["Filter noise sentences (score * 0.1)"] E --> F["Select top 2-3 sentences"] F --> G["Re-order by original position"] G --> H["Return as coherent paragraph"] B -->|No| I{"Headlines available?"} I -->|Yes| J["Filter noise headlines"] J --> K["Return top 3 (dot-separated)"] I -->|No| L["Generate contextual fallback
(GDELT metadata inference)"] ``` TF-IDF 评分计算词频乘以逆文档频率。包含罕见的、特定主题词汇的句子得分高于通用语言。与噪声过滤器匹配的句子得分降低 90%。这完全在本地运行,零外部 API 调用。 ## 地缘政治情感词典 默认的 AFINN 情感字典是为产品评论和社交媒体构建的。它系统性低估了冲突语言。`feeds.js` 中的自定义 `GEO_LEXICON` 覆盖了 120+ 个术语: | 术语 | AFINN 默认值 | 地缘政治覆盖值 | 理由 | |------|--------------|----------------------|-----------| | strike | -1 | -4 | AFINN 将此视为劳资纠纷。在地缘政治中,它意味着空袭。 | | collapse | -2 | -4 | “Government collapse”不是轻微的负面。 | | casualties | -2 | -5 | 任何提到伤亡的标题都是严重的。 | | ceasefire | 0 | +2 | AFINN 没有条目。停火在冲突监控中是积极的。 | | invasion | 0 | -5 | AFINN 没有条目。 | 该词典还对降温术语给予正面评分(停火 +2,和平谈判 +3,和解 +3),以便基调组成部分正确反映局势改善,而不仅仅是恶化。 ## 数据源 ### GDELT GEO 2.0 返回匹配主题查询的地理位置事件。每 10 分钟查询五个主题: - Conflict(战争、战斗、战役) - Crisis(人道主义、难民、饥荒) - Military(空袭、军队、轰炸) - Unrest(抗议、暴乱、起义) - Tension(制裁、核、对峙) 每个查询返回最多 75 个带有坐标、文章引用和基调分数的地理位置事件。 ### GDELT DOC 2.0(v3 新增) 返回带有预提取元数据的实际文章。前 3 个主题也通过 DOC API 查询,该 API 提供: - 文章标题和 URL - 来源国家和语言 - 域名信息 - 预提取的国家提及(用于地理编码) DOC 结果与 GEO 查询交错进行,以保持在 GDELT 的速率预算内。 ### RSS 源 每 5 分钟轮询 9 个源: | 来源 | 类型 | 备注 | |--------|------|-------| | BBC World | 主流 | 可靠,覆盖面广 | | Al Jazeera | 主流 | 中东和全球南方覆盖强 | | Reuters | 通讯社 | 间歇性 DNS 错误 | | AP News | 通讯社 | 间歇性 403 错误 | | Counterpunch | 独立/左翼 | 美国外交政策批评,调查性 | | Declassified UK | 独立 | 英国外交和军事政策,基于 FOIA | | RT News | 国家资助(俄罗斯) | 纳入以保持观点多样性 | | Mint Press News | 独立 | 侧重中东 | | The Grayzone | 独立 | 调查性,反对建制派叙事 | 来源选择有意多样化。评分引擎不对来源进行加权。检测到某一态势在意识形态对立的媒体上被讨论,本身就是重要性的信号。 ## 持久化(v3 新增) SQLite(通过 `better-sqlite3`)存储态势历史、文章和升级事件。数据库支持: ### 持久化内容 | 表 | 内容 | 保留期 | |-------|----------|-----------| | `snapshots` | 每个发现周期的完整态势状态 | 30 天 | | `articles` | 与每个态势相关的热门文章 | 30 天 | | `escalations` | 带有时间戳的状态变更事件 | 30 天 | ### 基于持久化的 API | 端点 | 目的 | |----------|---------| | `GET /api/trends?hours=24` | 所有态势及其分数趋势(上升/下降/稳定)和变化量 | | `GET /api/trends/:name?days=7` | 特定态势的分数历史(用于趋势线) | | `GET /api/escalations?limit=50` | 按时间顺序排列的升级历史 | | `GET /api/stats` | 数据库统计信息(快照计数、文章计数、DB 大小) | | `GET /api/health` | 服务器正常运行时间、活动计数、DB 状态 | ### 状态恢复 重启时,服务器从最近的快照中恢复 `previousStates` 映射。这意味着升级检测在服务器重启后能正常工作 —— 无数据丢失,无错误重报。 ## 认证(v3 新增) 可选的 HTTP Basic Auth,由环境变量控制: ``` AUTH_PASSWORD=your_secret_here AUTH_USER=monitor # defaults to "monitor" ``` 当设置了 `AUTH_PASSWORD` 时: - 所有 HTTP 端点都需要 Basic Auth(`/api/health` 除外) - WebSocket 连接需要 Basic Auth 头或 `?token=` 查询参数 - 登录提示会在浏览器中自动出现 当 `AUTH_PASSWORD` 未设置(默认)时,认证完全禁用。 ## 聚类 事件使用带有 500km Haversine 半径的单遍贪婪算法进行分组: 1. 对于每个传入事件,检查它是否落在任何现有聚类中心的 500km 范围内。 2. 如果是,添加它并更新中心(滚动平均)。 3. 如果否,创建一个新聚类。 4. 丢弃少于 2 个事件的聚类。 ### 命名 态势通过从聚类标题中提取两个提及次数最多的国家并按字母顺序排序来自动命名。当只有 GDELT 地理数据可用时,通过 `findNearest()` 使用 `countries-data.js` ISO 地名录中的最近条目。 ### 限制 500km 半径对于密集区域(黎凡特)太宽,对于蔓延冲突(萨赫勒)太窄。`ARCHITECTURE_PLAN.md` 记录了通往密度自适应聚类和基于国家对的分组的路径。 ## 国家数据(`countries-data.js`) 用 ISO 3166-1 标准数据替换 v2 手工维护的地名录。150+ 个国家,包含: - ISO alpha-2 代码 - 代表性坐标(首都) - 主要城市(用于国家以下级别的匹配) - 官方缩写(UK, US, UAE 等) 在加载时结构化数据构建快速查找索引,而不是维护单独的查找表。`extractCountries()` 函数使用针对国家名称、首都、城市和缩写的词边界正则匹配。 ## 前端 前端是单个 `index.html` 文件。无框架,无构建工具。 ### D3.js + TopoJSON 地图(v3 新增) 用真实的世界地图替换 v2 手绘的 SVG 大陆块: - **Natural Earth 1 投影** —— 面积和形状失真的良好平衡 - **Natural Earth 110m TopoJSON** 从 CDN 加载(~100KB) - **国家悬停** —— 高亮国家边界并显示名称 - **ISO 3166-1 数字映射** 内置在前端用于标记特征 - **活动点** 根据置信度调整大小和样式(低置信度点更小更暗) - **连接线** 在附近非稳定态势之间 ### 关键 UI 组件 - **SVG 世界地图**:D3 渲染的国家路径,带有脉动活动点 - **置信度指示器**:低置信度(仅卫星)态势的虚线边框和暗淡点 - **活动条**:按严重程度排序的可滚动侧边栏列表,低置信度项目在视觉上有区分 - **侧面板**:分数细分、置信度徽章、态势摘要、带有基调分数的相关报道 - **新闻流**:带有来源标签的右侧源 - **通知铃铛**:升级警报,带有音频(Web Audio API)和浏览器通知 - **状态栏**:实时计数 + UTC 时钟 ## 运行 ### 要求 - Node.js 18+(在 20.x 和 22.x 上测试) - 互联网连接(GDELT API + RSS 源是远程的) - 无需 API 密钥 ### 设置 ``` git clone https://github.com/ashioyajotham/global-activity-monitor.git cd global-activity-monitor npm install node server.js ``` 打开 `http://localhost:4000`。第一个发现周期大约需要 60 秒(GDELT 速率限制)。之后数据会自动刷新。 ### 环境变量 | 变量 | 默认值 | 描述 | |----------|---------|-------------| | `PORT` | 4000 | 服务器端口 | | `AUTH_PASSWORD` | _(空)_ | 设置以启用 Basic Auth | | `AUTH_USER` | monitor | Basic Auth 用户名 | | `DB_PATH` | ./monitor.db | SQLite 数据库文件路径 | ### 数据库管理 ``` # 手动清理(保留最近 30 天) npm run cleanup # 检查健康状况 curl http://localhost:4000/api/health # 查看趋势 curl http://localhost:4000/api/trends?hours=24 # 查看特定情况历史 curl http://localhost:4000/api/trends/Iran%20-%20United%20States?days=7 ``` ## 已知限制 **基于关键词的分类。** 严重性评分和噪声过滤仍然使用硬编码的术语列表。`ARCHITECTURE_PLAN.md` 记录了通往 GDELT CAMEO 事件代码的路径,这将用结构化分类数据取代关键词猜测。 **固定聚类半径。** 500km 对于黎凡特来说太宽,对于俄罗斯来说太窄。密度自适应或国家结对聚类会更准确。 **静态分数阈值。** 6.5/4.0 不适应全球基线。在平静的日子,没有什么达到危急状态。在危机日子,一切都达到了。基于百分位数的阈值将自我校准。 **英语偏见。** 所有 9 个 RSS 源都是英语。GDELT DOC API 部分补偿(它以所有语言索引 150+ 个国家),但用于摘要的文章片段仅限英语。 **情感仍然是词袋模型。** 自定义词典改进了 AFINN,但无法处理否定(“no casualties reported”仍然评分为负面)或讽刺。GDELT 的生产基调分数更复杂,但仅适用于 GDELT 来源的事件。 **默认无认证。** 在未设置 `AUTH_PASSWORD` 的情况下暴露给互联网是不可取的。 **GDELT 速率限制。** 请求之间的 2.2s 延迟是保守的。交错 GEO 和 DOC 查询使有用数据加倍,但也使扫描时间加倍至每个周期约 50-70 秒。 ## 依赖项 | 包 | 用途 | 为什么选这个 | |---------|---------|-------------| | `express` | HTTP 服务器 | 标准,最小 | | `ws` | WebSocket 服务器 | 轻量级,无 Socket.io 开销 | | `better-sqlite3` | SQLite 持久化 | 同步 API(无异步复杂性),零配置,快速 | | `node-cron` | 计划任务 | 简单的 cron 语法 | | `rss-parser` | RSS 源解析 | 处理 RSS 2.0 和 Atom | | `sentiment` | NLP 基调查准 | 零依赖,通过自定义词典扩展 | | `cors` | 跨域头 | 本地开发支持 | 从 CDN 加载的前端依赖项(无 node_modules): - `d3` v7.9.0 —— 地图投影和渲染 - `topojson-client` v3.0.2 —— TopoJSON 特征提取 ## 逐文件分解 ### `server.js`(254 行) 编排层。Express 服务器、WebSocket 管理、GDELT GEO + DOC 获取器(带速率限制交错)、新闻到事件地理编码、发现管道执行、带状态恢复的升级检测、基本认证中间件、用于趋势/统计/健康的 REST API 端点、cron 调度、优雅关闭。 ### `discovery.js`(306 行) 分析核心。噪声分类和过滤、GDELT GEO/DOC 响应解析、haversine 聚类、置信度评估、自动命名(国家提取 + 字母顺序规范化)、带噪声句子降级的 TF-IDF 抽取式摘要、12 类分类、带置信度上限的三分量严重性评分、当事方提取、去重。 ### `countries-data.js`(347 行) ISO 3166-1 标准国家数据。150+ 个国家,包含坐标、首都、主要城市和官方缩写。在加载时构建快速查找索引。用于标题地理编码的 `extractCountries()`,用于反向地理编码 GDELT 坐标的 `findNearest()`。 ### `feeds.js`(259 行) 带有增强情感的 RSS 摄取。120+ 术语地缘政治词典覆盖 AFINN 默认值。抓取 9 个源,运行增强基调分析,按标题相似性去重。导出 `analyzeGeopoliticalTone()` 和 `GEO_LEXICON` 以保持透明度。 ### `db.js`(294 行) 通过 `better-sqlite3` 的 SQLite 持久化。模式:`snapshots`、`articles`、`escalations` 表。写操作:`storeSituations()`、`storeArticles()`、`storeEscalation()`。趋势查询:`getScoreTrend()`、`getAllTrends()`。状态恢复:`recoverPreviousStates()`。维护:带有可配置保留期的 `cleanup()`。 ### `index.html`(561 行) 完整前端。D3.js Natural Earth 投影、带有 ISO 3166-1 标记的 TopoJSON 国家渲染、置信度感知的点大小调整、邻近态势之间的连接线、带有置信度样式的活动条、带有分数 + 置信度细分的侧面板、新闻流、带有 Web Audio 警报的通知铃铛、响应式布局。 ### `ARCHITECTURE_PLAN.md`(345 行) 硬编码审计,记录了代码库中 12 类脆弱关键词列表,并为每一类提供了具体替代方案。提出了 3 波改进路径:Wave 1(地图 + DOC API,已完成),Wave 2(CAMEO 代码取代关键词列表),Wave 3(自适应聚类 + 百分位数阈值)。 ## 路线图 `ARCHITECTURE_PLAN.md` 包含完整的技术审计。已完成和剩余工作的摘要: | 波次 | 状态 | 描述 | |------|--------|-------------| | Wave 1 | 完成 | 真实 TopoJSON 地图、GDELT DOC API、ISO 国家数据、SQLite 持久化 | | Wave 2 | 计划中 | CAMEO 事件代码取代严重性关键词和类别模式 | | Wave 3 | 计划中 | 密度自适应聚类、基于百分位数的分数阈值、来源多样性评分 | ## 许可证 MIT
标签:Cron定时任务, ESC4, Express, GDELT, GNU通用公共许可证, HTTP/HTTPS抓包, MITM代理, Node.js, OSINT, SQLite, TF-IDF, WebSocket, 交互式地图, 依赖分析, 全球事件追踪器, 地缘政治, 态势感知, 情感分析, 情报分析, 数据持久化, 文本摘要, 新闻聚合, 网络诊断, 聚类分析, 自动发现, 自定义脚本, 自定义脚本, 自定义脚本