mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
feat(intelligence): GetCountryRisk RPC + MCP tool for per-country risk scores (#2502)
* feat(intelligence): GetCountryRisk RPC for per-country risk intelligence Adds a new fast Redis-read RPC that consolidates CII score, travel advisory level, and OFAC sanctions exposure into a single per-country response. Replaces the need to call GetRiskScores (all-countries) and filter client-side. Wired to MCP as get_country_risk tool (no LLM, ~200ms, good for agent screening). - proto/intelligence/v1/get_country_risk.proto (new) - server/intelligence/v1/get-country-risk.ts (reads 3 pre-seeded Redis keys) - gateway.ts: slow cache tier - api/mcp.ts: RpcToolDef with 8s timeout - tests/mcp.test.mjs: update tool count 27→28 * fix(intelligence): upstream-unavailable signal, fetchedAt from CII, drop redundant catch P1: return upstreamUnavailable:true when all Redis reads are null — prevents CDN from caching false-negative sanctions/risk responses during Redis outages. P2: fetchedAt now uses cii.computedAt (actual data age) instead of request time. P2: removed redundant .catch(() => null) — getCachedJson already swallows errors. * fix(intelligence): accurate OFAC counts and country names for GetCountryRisk P1: sanctions:pressure:v1.countries is a top-12 slice — switch to a new sanctions:country-counts:v1 key (ISO2→count across ALL 40K+ OFAC entries). Written by seed-sanctions-pressure.mjs in afterPublish alongside entity index. P1: trigger upstreamUnavailable:true when sanctions key alone is missing, preventing false-negative sanctionsActive:false from being cached by CDN. P2: advisory seeder now writes byCountryName (ISO2→display name) derived from country-names.json reverse map. Handler uses it as fallback so countries outside TIER1_COUNTRIES (TH, CO, BD, IT...) get proper names.
This commit is contained in:
@@ -60,6 +60,13 @@ function parseLevel(title, parser) {
|
||||
|
||||
const COUNTRY_NAMES = loadSharedConfig('country-names.json');
|
||||
const SORTED_COUNTRY_ENTRIES = Object.entries(COUNTRY_NAMES).sort((a, b) => b[0].length - a[0].length);
|
||||
// Reverse map: ISO2 → display name (title-cased from the config keys).
|
||||
const BY_COUNTRY_NAME = Object.fromEntries(
|
||||
Object.entries(COUNTRY_NAMES).map(([name, code]) => [
|
||||
code,
|
||||
name.replace(/\b\w/g, (c) => c.toUpperCase()),
|
||||
]),
|
||||
);
|
||||
|
||||
function extractCountry(title, feed) {
|
||||
if (feed.targetCountry) return feed.targetCountry;
|
||||
@@ -195,7 +202,7 @@ async function fetchAll() {
|
||||
deduped.sort((a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime());
|
||||
|
||||
const byCountry = buildByCountryMap(deduped);
|
||||
const report = { byCountry, advisories: deduped, fetchedAt: new Date().toISOString() };
|
||||
const report = { byCountry, byCountryName: BY_COUNTRY_NAME, advisories: deduped, fetchedAt: new Date().toISOString() };
|
||||
|
||||
console.log(` ${deduped.length} advisories, ${Object.keys(byCountry).length} countries with levels`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user