mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-09-27 11:11:50 +02:00
feat: enhance East Asia coverage with adversary bases, news sources, ICAO ranges, and PLAN vessel DB
- Add 68 military bases (PLA, Russia, DPRK, ROC, Philippines, Australia) with data-driven color coding (red/blue/green) on the map - Add 6 news RSS feeds (Yonhap, Nikkei Asia, Taipei Times, Asia Times, Defense News, Japan Times) and 15 geocoding keywords for islands, straits, and disputed areas - Extend ICAO country ranges for Russia, Australia, Philippines, Singapore, DPRK and add Russian aircraft classification (fighters, bombers, cargo, recon) - Create PLAN/CCG vessel enrichment module (90+ ships) following yacht_alert pattern for automatic MMSI-based identification - Update frontend types and popup styling for adversary/allied/ROC color distinction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
95da3015d9
commit
910d1fd633
@@ -39,6 +39,11 @@ def fetch_ships():
|
||||
for ship in ships:
|
||||
enrich_with_yacht_alert(ship)
|
||||
|
||||
# Enrich ships with PLAN/CCG vessel data
|
||||
from services.fetchers.plan_vessel_alert import enrich_with_plan_vessel
|
||||
for ship in ships:
|
||||
enrich_with_plan_vessel(ship)
|
||||
|
||||
logger.info(f"Ships: {len(carriers)} carriers + {len(ais_vessels)} AIS vessels")
|
||||
with _data_lock:
|
||||
latest_data['ships'] = ships
|
||||
|
||||
@@ -38,6 +38,11 @@ _ICAO_COUNTRY_RANGES = [
|
||||
(0x840000, 0x87FFFF, "Japan", "JSDF"),
|
||||
(0x700000, 0x71FFFF, "South Korea", "ROK"),
|
||||
(0xE80000, 0xE80FFF, "Taiwan", "ROC"),
|
||||
(0x150000, 0x157FFF, "Russia", "VKS"),
|
||||
(0x7C0000, 0x7FFFFF, "Australia", "RAAF"),
|
||||
(0x758000, 0x75FFFF, "Philippines", "PAF"),
|
||||
(0x768000, 0x76FFFF, "Singapore", "RSAF"),
|
||||
(0x720000, 0x727FFF, "North Korea", "KPAF"),
|
||||
]
|
||||
|
||||
|
||||
@@ -66,18 +71,24 @@ def _classify_military_type(raw_model: str) -> str:
|
||||
if any(k in model for k in [
|
||||
"F16", "F35", "F22", "F15", "F18", "T38", "T6", "A10",
|
||||
"J10", "J11", "J15", "J16", "J20", "JF17",
|
||||
"SU27", "SU30", "SU35",
|
||||
"SU27", "SU30", "SU35", "SU57", "MIG29", "MIG31",
|
||||
"F15J", "F2", "IDF", "FA50", "KF21",
|
||||
]):
|
||||
return "fighter"
|
||||
if any(k in model for k in [
|
||||
"TU95", "TU160", "TU22",
|
||||
]):
|
||||
return "bomber"
|
||||
if any(k in model for k in [
|
||||
"C17", "C5", "C130", "C30", "A400", "V22",
|
||||
"Y20", "Y9", "Y8", "C2",
|
||||
"IL76", "AN124", "AN12",
|
||||
]):
|
||||
return "cargo"
|
||||
if any(k in model for k in [
|
||||
"P8", "E3", "E8", "U2",
|
||||
"KJ500", "KJ200", "GX11", "P1", "E767", "E2K", "E2C",
|
||||
"A50", "TU214R", "IL20",
|
||||
]):
|
||||
return "recon"
|
||||
return "default"
|
||||
|
||||
@@ -111,6 +111,22 @@ _KEYWORD_COORDS = {
|
||||
"singapore": (1.352, 103.819),
|
||||
"bangkok": (13.756, 100.501),
|
||||
"jakarta": (-6.208, 106.845),
|
||||
# East Asia — islands, straits, and disputed areas
|
||||
"pratas": (20.71, 116.72),
|
||||
"dongsha": (20.71, 116.72),
|
||||
"kinmen": (24.45, 118.38),
|
||||
"matsu": (26.16, 119.94),
|
||||
"scarborough": (15.14, 117.77),
|
||||
"paracel": (16.50, 112.00),
|
||||
"spratly": (10.00, 114.00),
|
||||
"miyako strait": (24.78, 125.30),
|
||||
"bashi channel": (21.00, 121.50),
|
||||
"luzon strait": (20.50, 121.50),
|
||||
" dmz ": (38.00, 127.00),
|
||||
"yalu": (40.00, 124.40),
|
||||
"yongbyon": (39.80, 125.76),
|
||||
"wonsan": (39.18, 127.48),
|
||||
"busan": (35.18, 129.07),
|
||||
}
|
||||
|
||||
# Immutable after module load — sort by descending keyword length so
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
"""PLAN/CCG Vessel Alert DB — load and enrich AIS vessels with Chinese navy/coast guard metadata."""
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("services.data_fetcher")
|
||||
|
||||
_PLAN_CCG_DB: dict = {}
|
||||
|
||||
|
||||
def _load_plan_ccg_db():
|
||||
"""Load plan_ccg_vessels.json into memory at import time."""
|
||||
global _PLAN_CCG_DB
|
||||
json_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
||||
"data", "plan_ccg_vessels.json"
|
||||
)
|
||||
if not os.path.exists(json_path):
|
||||
logger.warning(f"PLAN/CCG vessel DB not found at {json_path}")
|
||||
return
|
||||
try:
|
||||
with open(json_path, "r", encoding="utf-8") as fh:
|
||||
_PLAN_CCG_DB.update(json.load(fh))
|
||||
logger.info(f"PLAN/CCG vessel DB loaded: {len(_PLAN_CCG_DB)} vessels")
|
||||
except (IOError, OSError, json.JSONDecodeError, ValueError, KeyError) as e:
|
||||
logger.error(f"Failed to load PLAN/CCG vessel DB: {e}")
|
||||
|
||||
|
||||
_load_plan_ccg_db()
|
||||
|
||||
|
||||
def enrich_with_plan_vessel(ship: dict) -> dict:
|
||||
"""If ship's MMSI is in the PLAN/CCG DB, attach enrichment metadata."""
|
||||
mmsi = str(ship.get("mmsi", "")).strip()
|
||||
if mmsi and mmsi in _PLAN_CCG_DB:
|
||||
info = _PLAN_CCG_DB[mmsi]
|
||||
ship["plan_name"] = info.get("name", "")
|
||||
ship["plan_class"] = info.get("class", "")
|
||||
ship["plan_force"] = info.get("force", "")
|
||||
ship["plan_hull"] = info.get("hull_number", "")
|
||||
ship["plan_wiki"] = info.get("wiki", "")
|
||||
return ship
|
||||
@@ -9,7 +9,7 @@ from pathlib import Path
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_PATH = Path(__file__).parent.parent / "config" / "news_feeds.json"
|
||||
MAX_FEEDS = 20
|
||||
MAX_FEEDS = 25
|
||||
|
||||
DEFAULT_FEEDS = [
|
||||
{"name": "NPR", "url": "https://feeds.npr.org/1004/rss.xml", "weight": 4},
|
||||
@@ -25,6 +25,12 @@ DEFAULT_FEEDS = [
|
||||
{"name": "SCMP", "url": "https://www.scmp.com/rss/91/feed", "weight": 4},
|
||||
{"name": "The Diplomat", "url": "https://thediplomat.com/feed/", "weight": 4},
|
||||
{"name": "Stars and Stripes", "url": "https://www.stripes.com/feeds/pacific.rss", "weight": 4},
|
||||
{"name": "Yonhap", "url": "https://en.yna.co.kr/RSS/news.xml", "weight": 4},
|
||||
{"name": "Nikkei Asia", "url": "https://asia.nikkei.com/rss", "weight": 3},
|
||||
{"name": "Taipei Times", "url": "https://www.taipeitimes.com/xml/pda.rss", "weight": 4},
|
||||
{"name": "Asia Times", "url": "https://asiatimes.com/feed/", "weight": 3},
|
||||
{"name": "Defense News", "url": "https://www.defensenews.com/arc/outboundfeeds/rss/", "weight": 3},
|
||||
{"name": "Japan Times", "url": "https://www.japantimes.co.jp/feed/", "weight": 3},
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user