fix:优化pm2配置项

This commit is contained in:
Daniel
2026-03-05 19:53:05 +08:00
parent 004b03b374
commit 98d928f457
10 changed files with 125 additions and 10 deletions

View File

@@ -183,6 +183,23 @@ def merge(extracted: Dict[str, Any], db_path: Optional[str] = None) -> bool:
updated = True updated = True
except Exception: except Exception:
pass pass
# force_summary 增量:导弹消耗(看板「导弹消耗」「导弹库存」由 force_summary 提供)
if "force_summary_delta" in extracted:
for side, delta in extracted["force_summary_delta"].items():
if side not in ("us", "iran"):
continue
mc = delta.get("missile_consumed")
if mc is not None and isinstance(mc, (int, float)) and mc > 0:
mc = min(int(mc), 500)
try:
cur = conn.execute(
"UPDATE force_summary SET missile_consumed = missile_consumed + ?, missile_stock = max(0, missile_stock - ?) WHERE side = ?",
(mc, mc, side),
)
if cur.rowcount > 0:
updated = True
except Exception:
pass
# retaliation # retaliation
if "retaliation" in extracted: if "retaliation" in extracted:
r = extracted["retaliation"] r = extracted["retaliation"]

View File

@@ -4,7 +4,7 @@ import sqlite3
import hashlib import hashlib
import os import os
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Optional from typing import List, Optional
from config import DB_PATH from config import DB_PATH
@@ -87,7 +87,7 @@ def touch_situation_updated_at_path(db_path: Optional[str] = None) -> bool:
conn.close() conn.close()
def write_updates(updates: list[dict], db_path: Optional[str] = None) -> int: def write_updates(updates: List[dict], db_path: Optional[str] = None) -> int:
""" """
updates: [{"title","summary","url","published","category","severity"}, ...] updates: [{"title","summary","url","published","category","severity"}, ...]
db_path: 与 pipeline 一致,缺省用 config.DB_PATH db_path: 与 pipeline 一致,缺省用 config.DB_PATH

View File

@@ -51,6 +51,7 @@ def _call_ollama_extract(text: str, timeout: int = 15) -> Optional[Dict[str, Any
- retaliation_sentiment: 0-100仅当报道涉及伊朗报复/反击情绪时 - retaliation_sentiment: 0-100仅当报道涉及伊朗报复/反击情绪时
- wall_street_value: 0-100仅当报道涉及美股/市场时 - wall_street_value: 0-100仅当报道涉及美股/市场时
- key_location_updates: **双方攻击地点**。每项 {{ "name_keywords": "阿萨德|asad|al-asad", "side": "us或iran被打击方", "status": "attacked", "damage_level": 1-3 }}。美军基地例:阿萨德|asad、乌代德|udeid、埃尔比勒|erbil、因吉尔利克|incirlik。伊朗例德黑兰|tehran、布什尔|bushehr、伊斯法罕|isfahan、阿巴斯|abbas、纳坦兹|natanz - key_location_updates: **双方攻击地点**。每项 {{ "name_keywords": "阿萨德|asad|al-asad", "side": "us或iran被打击方", "status": "attacked", "damage_level": 1-3 }}。美军基地例:阿萨德|asad、乌代德|udeid、埃尔比勒|erbil、因吉尔利克|incirlik。伊朗例德黑兰|tehran、布什尔|bushehr、伊斯法罕|isfahan、阿巴斯|abbas、纳坦兹|natanz
- **导弹消耗增量**(仅当报道明确提到「发射/消耗 了 X 枚导弹」时填,用于看板导弹消耗累计): us_missile_consumed_delta, iran_missile_consumed_delta本则报道中该方新增消耗枚数整数
原文: 原文:
{raw} {raw}
@@ -132,4 +133,12 @@ def extract_from_news(text: str, timestamp: Optional[str] = None) -> Dict[str, A
}) })
if valid: if valid:
out["key_location_updates"] = valid out["key_location_updates"] = valid
# force_summary 增量:导弹消耗(看板「导弹消耗」由 force_summary.missile_consumed 提供)
fs_delta = {}
for side_key, side_val in [("us_missile_consumed_delta", "us"), ("iran_missile_consumed_delta", "iran")]:
v = parsed.get(side_key)
if isinstance(v, (int, float)) and v > 0:
fs_delta[side_val] = {"missile_consumed": min(500, int(v))}
if fs_delta:
out["force_summary_delta"] = fs_delta
return out return out

View File

@@ -42,6 +42,7 @@ def _call_dashscope_extract(text: str, timeout: int = 15) -> Optional[Dict[str,
- retaliation_sentiment: 0-100仅当报道涉及伊朗报复情绪时 - retaliation_sentiment: 0-100仅当报道涉及伊朗报复情绪时
- wall_street_value: 0-100仅当报道涉及美股/市场时) - wall_street_value: 0-100仅当报道涉及美股/市场时)
- key_location_updates: **双方攻击地点**。每项 {{"name_keywords":"阿萨德|asad","side":"us或iran被打击方","status":"attacked","damage_level":1-3}}。美军基地:阿萨德|asad、乌代德|udeid、埃尔比勒|erbil、因吉尔利克|incirlik。伊朗德黑兰|tehran、布什尔|bushehr、伊斯法罕|isfahan、阿巴斯|abbas、纳坦兹|natanz - key_location_updates: **双方攻击地点**。每项 {{"name_keywords":"阿萨德|asad","side":"us或iran被打击方","status":"attacked","damage_level":1-3}}。美军基地:阿萨德|asad、乌代德|udeid、埃尔比勒|erbil、因吉尔利克|incirlik。伊朗德黑兰|tehran、布什尔|bushehr、伊斯法罕|isfahan、阿巴斯|abbas、纳坦兹|natanz
- **导弹消耗增量**(仅当报道明确提到「发射/消耗 了 X 枚导弹」时填): us_missile_consumed_delta, iran_missile_consumed_delta本则该方新增消耗枚数整数
原文: 原文:
{raw} {raw}
@@ -110,6 +111,15 @@ def extract_from_news(text: str, timestamp: Optional[str] = None) -> Dict[str, A
if isinstance(v, (int, float)) and 0 <= v <= 100: if isinstance(v, (int, float)) and 0 <= v <= 100:
out["wall_street"] = {"time": ts, "value": int(v)} out["wall_street"] = {"time": ts, "value": int(v)}
# force_summary 增量:导弹消耗(看板「导弹消耗」)
fs_delta = {}
for key, side in [("us_missile_consumed_delta", "us"), ("iran_missile_consumed_delta", "iran")]:
v = parsed.get(key)
if isinstance(v, (int, float)) and v > 0:
fs_delta[side] = {"missile_consumed": min(500, int(v))}
if fs_delta:
out["force_summary_delta"] = fs_delta
if "key_location_updates" in parsed and isinstance(parsed["key_location_updates"], list): if "key_location_updates" in parsed and isinstance(parsed["key_location_updates"], list):
valid = [] valid = []
for u in parsed["key_location_updates"]: for u in parsed["key_location_updates"]:

View File

@@ -3,7 +3,7 @@
前端面板完整数据 schema与 DB / situationData / useReplaySituation 对齐 前端面板完整数据 schema与 DB / situationData / useReplaySituation 对齐
爬虫 + AI 清洗后的数据必须符合此 schema 才能正确更新前端 爬虫 + AI 清洗后的数据必须符合此 schema 才能正确更新前端
""" """
from typing import Any, Dict, List, Literal, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
# 事件脉络 # 事件脉络
SITUATION_UPDATE_CATEGORIES = ("deployment", "alert", "intel", "diplomatic", "other") SITUATION_UPDATE_CATEGORIES = ("deployment", "alert", "intel", "diplomatic", "other")

View File

@@ -1,7 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""新闻分类与严重度判定""" """新闻分类与严重度判定"""
import re import re
from typing import Literal from typing import List
try:
from typing import Literal # type: ignore
except ImportError:
try:
from typing_extensions import Literal # type: ignore
except ImportError:
from typing import Any
class _LiteralFallback:
def __getitem__(self, item):
return Any
Literal = _LiteralFallback()
Category = Literal["deployment", "alert", "intel", "diplomatic", "other"] Category = Literal["deployment", "alert", "intel", "diplomatic", "other"]
Severity = Literal["low", "medium", "high", "critical"] Severity = Literal["low", "medium", "high", "critical"]
@@ -13,7 +27,7 @@ CAT_INTEL = ["satellite", "intel", "image", "surveillance", "卫星", "情报"]
CAT_DIPLOMATIC = ["talk", "negotiation", "diplomat", "sanction", "谈判", "制裁"] CAT_DIPLOMATIC = ["talk", "negotiation", "diplomat", "sanction", "谈判", "制裁"]
def _match(text: str, words: list[str]) -> bool: def _match(text: str, words: List[str]) -> bool:
t = (text or "").lower() t = (text or "").lower()
for w in words: for w in words:
if w.lower() in t: if w.lower() in t:

View File

@@ -5,7 +5,19 @@ AI 新闻分类与严重度判定
设置 PARSER_AI_DISABLED=1 可只用规则(更快) 设置 PARSER_AI_DISABLED=1 可只用规则(更快)
""" """
import os import os
from typing import Literal, Optional, Tuple from typing import Any, Optional, Tuple
try:
from typing import Literal # type: ignore
except ImportError:
try:
from typing_extensions import Literal # type: ignore
except ImportError:
class _LiteralFallback:
def __getitem__(self, item):
return Any
Literal = _LiteralFallback()
Category = Literal["deployment", "alert", "intel", "diplomatic", "other"] Category = Literal["deployment", "alert", "intel", "diplomatic", "other"]
Severity = Literal["low", "medium", "high", "critical"] Severity = Literal["low", "medium", "high", "critical"]

9
crawler/run_uvicorn.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# PM2 用:在 crawler 目录下启动 uvicornGDELT/RSS 实时服务 :8000
set -e
cd "$(dirname "$0")"
[ -n "$LANG" ] || export LANG="${LANG:-en_US.UTF-8}"
[ -n "$LC_ALL" ] || export LC_ALL="${LC_ALL:-en_US.UTF-8}"
# 若项目根目录有 .env可在此加载PM2 一般已在 ecosystem 里配 env
if [ -f ../.env ]; then set -a; . ../.env; set +a; fi
exec python3 -m uvicorn realtime_conflict_service:app --host 0.0.0.0 --port 8000

View File

@@ -3,6 +3,7 @@
import re import re
import socket import socket
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import List, Set, Tuple
import feedparser import feedparser
@@ -33,7 +34,7 @@ def _matches_keywords(text: str) -> bool:
return False return False
def _fetch_one_feed(name: str, url: str, timeout: int) -> list[dict]: def _fetch_one_feed(name: str, url: str, timeout: int) -> List[dict]:
"""抓取单个 RSS 源,超时或异常返回空列表。不负责去重。""" """抓取单个 RSS 源,超时或异常返回空列表。不负责去重。"""
old_timeout = socket.getdefaulttimeout() old_timeout = socket.getdefaulttimeout()
socket.setdefaulttimeout(timeout) socket.setdefaulttimeout(timeout)
@@ -72,14 +73,14 @@ def _fetch_one_feed(name: str, url: str, timeout: int) -> list[dict]:
return out return out
def fetch_all() -> list[dict]: def fetch_all() -> List[dict]:
"""抓取所有配置的 RSS 源,按源超时与隔离错误,全局去重后返回。""" """抓取所有配置的 RSS 源,按源超时与隔离错误,全局去重后返回。"""
sources = get_feed_sources() sources = get_feed_sources()
if not sources: if not sources:
return [] return []
items: list[dict] = [] items: List[dict] = []
seen: set[tuple[str, str]] = set() seen: Set[Tuple[str, str]] = set()
for name, url in sources: for name, url in sources:
batch = _fetch_one_feed(name, url, FEED_TIMEOUT) batch = _fetch_one_feed(name, url, FEED_TIMEOUT)

43
ecosystem.config.cjs Normal file
View File

@@ -0,0 +1,43 @@
/**
* PM2 进程配置API + 爬虫GDELT/RSS uvicorn 服务)
* 用法:
* pm2 start ecosystem.config.cjs # 启动全部
* pm2 restart ecosystem.config.cjs # 重启全部
* pm2 stop ecosystem.config.cjs # 停止全部
* pm2 logs nsa_api / pm2 logs nsa_crawler
* 需 .env 时可在启动前 source .env或在应用内用 dotenv 加载。
*/
module.exports = {
apps: [
{
name: 'nsa_api',
script: 'server/index.js',
cwd: __dirname,
interpreter: 'node',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '300M',
env: {
NODE_ENV: 'production',
API_PORT: 3001,
},
},
{
name: 'nsa_crawler',
script: 'crawler/run_uvicorn.sh',
cwd: __dirname,
interpreter: 'bash',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '300M',
env: {
CLEANER_AI_DISABLED: '1',
PARSER_AI_DISABLED: '0',
GDELT_DISABLED: '1',
RSS_INTERVAL_SEC: '60',
},
},
],
};