Skip to content

Commit 99d0350

Browse files
koala73aa5064
andcommitted
feat(finance): integrate GCC investments into finance variant
Wire Gulf FDI data from PR #61 into the existing finance variant: - Add gulfInvestments map layer (ScatterplotLayer with SA/UAE colors) - Add GCC Business News feeds (Arabian Business, The National, Arab News, etc.) - Add gcc-investments and gccNews panels to finance panel config - Add Gulf FDI types to MapLayers interface - Add RSS proxy domains for Gulf news sources - Auto-wiring creates NewsPanel for gccNews feed category Co-authored-by: aa5064 <[email protected]>
1 parent 227777e commit 99d0350

12 files changed

Lines changed: 168 additions & 0 deletions

File tree

api/rss-proxy.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ const ALLOWED_DOMAINS = [
166166
'seekingalpha.com',
167167
'www.coindesk.com',
168168
'cointelegraph.com',
169+
// Gulf / GCC news domains
170+
'www.arabianbusiness.com',
171+
'www.thenationalnews.com',
169172
];
170173

171174
export default async function handler(req) {

src/App.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import {
7777
DisplacementPanel,
7878
ClimateAnomalyPanel,
7979
PopulationExposurePanel,
80+
InvestmentsPanel,
8081
} from '@/components';
8182
import type { SearchResult } from '@/components/SearchModal';
8283
import { collectStoryData } from '@/services/story-data';
@@ -2145,6 +2146,14 @@ export class App {
21452146
this.panels['population-exposure'] = populationExposurePanel;
21462147
}
21472148

2149+
// GCC Investments Panel (finance variant)
2150+
if (SITE_VARIANT === 'finance') {
2151+
const investmentsPanel = new InvestmentsPanel((inv) => {
2152+
this.map?.setCenter(inv.lat, inv.lon, 6);
2153+
});
2154+
this.panels['gcc-investments'] = investmentsPanel;
2155+
}
2156+
21482157
const liveNewsPanel = new LiveNewsPanel();
21492158
this.panels['live-news'] = liveNewsPanel;
21502159

src/components/DeckGLMap.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ import {
6767
FINANCIAL_CENTERS,
6868
CENTRAL_BANKS,
6969
COMMODITY_HUBS,
70+
GULF_INVESTMENTS,
7071
} from '@/config';
72+
import type { GulfInvestment } from '@/types';
7173
import { MapPopup, type PopupType } from './MapPopup';
7274
import {
7375
updateHotspotEscalation,
@@ -156,6 +158,7 @@ const LAYER_ZOOM_THRESHOLDS: Partial<Record<keyof MapLayers, { minZoom: number;
156158
datacenters: { minZoom: 5 },
157159
irradiators: { minZoom: 4 },
158160
spaceports: { minZoom: 3 },
161+
gulfInvestments: { minZoom: 2, showLabels: 5 },
159162
};
160163
// Export for external use
161164
export { LAYER_ZOOM_THRESHOLDS };
@@ -210,6 +213,8 @@ function getOverlayColors() {
210213
commodityHub: isLight
211214
? [190, 95, 40, 220] as [number, number, number, number]
212215
: [255, 150, 80, 200] as [number, number, number, number],
216+
gulfInvestmentSA: [0, 168, 107, 220] as [number, number, number, number],
217+
gulfInvestmentUAE: [255, 0, 100, 220] as [number, number, number, number],
213218
ucdpStateBased: [255, 50, 50, 200] as [number, number, number, number],
214219
ucdpNonState: [255, 165, 0, 200] as [number, number, number, number],
215220
ucdpOneSided: [255, 255, 0, 200] as [number, number, number, number],
@@ -1144,6 +1149,11 @@ export class DeckGLMap {
11441149
}
11451150
}
11461151

1152+
// Gulf FDI investments layer
1153+
if (mapLayers.gulfInvestments) {
1154+
layers.push(this.createGulfInvestmentsLayer());
1155+
}
1156+
11471157
// News geo-locations (always shown if data exists)
11481158
if (this.newsLocations.length > 0) {
11491159
layers.push(...this.createNewsLocationsLayer());
@@ -2143,6 +2153,28 @@ export class DeckGLMap {
21432153
return layers;
21442154
}
21452155

2156+
private createGulfInvestmentsLayer(): ScatterplotLayer {
2157+
return new ScatterplotLayer<GulfInvestment>({
2158+
id: 'gulf-investments-layer',
2159+
data: GULF_INVESTMENTS,
2160+
getPosition: (d: GulfInvestment) => [d.lon, d.lat],
2161+
getRadius: (d: GulfInvestment) => {
2162+
if (!d.investmentUSD) return 20000;
2163+
if (d.investmentUSD >= 50000) return 70000;
2164+
if (d.investmentUSD >= 10000) return 55000;
2165+
if (d.investmentUSD >= 1000) return 40000;
2166+
return 25000;
2167+
},
2168+
getFillColor: (d: GulfInvestment) =>
2169+
d.investingCountry === 'SA' ? COLORS.gulfInvestmentSA : COLORS.gulfInvestmentUAE,
2170+
getLineColor: [255, 255, 255, 80] as [number, number, number, number],
2171+
lineWidthMinPixels: 1,
2172+
radiusMinPixels: 5,
2173+
radiusMaxPixels: 28,
2174+
pickable: true,
2175+
});
2176+
}
2177+
21462178
private pulseTime = 0;
21472179

21482180
private canPulse(now = Date.now()): boolean {
@@ -2401,6 +2433,23 @@ export class DeckGLMap {
24012433
return { html: `<div class="deckgl-tooltip"><strong>Cyber Threat</strong><br/>${text(obj.severity || 'medium')} · ${text(obj.country || 'Unknown')}</div>` };
24022434
case 'news-locations-layer':
24032435
return { html: `<div class="deckgl-tooltip"><strong>📰 News</strong><br/>${text(obj.title?.slice(0, 80) || '')}</div>` };
2436+
case 'gulf-investments-layer': {
2437+
const inv = obj as GulfInvestment;
2438+
const flag = inv.investingCountry === 'SA' ? '🇸🇦' : '🇦🇪';
2439+
const usd = inv.investmentUSD != null
2440+
? (inv.investmentUSD >= 1000 ? `$${(inv.investmentUSD / 1000).toFixed(1)}B` : `$${inv.investmentUSD}M`)
2441+
: 'Undisclosed';
2442+
const stake = inv.stakePercent != null ? `<br/>${text(String(inv.stakePercent))}% stake` : '';
2443+
return {
2444+
html: `<div class="deckgl-tooltip">
2445+
<strong>${flag} ${text(inv.assetName)}</strong><br/>
2446+
<em>${text(inv.investingEntity)}</em><br/>
2447+
${text(inv.targetCountry)} · ${text(inv.sector)}<br/>
2448+
<strong>${usd}</strong>${stake}<br/>
2449+
<span style="text-transform:capitalize">${text(inv.status)}</span>
2450+
</div>`,
2451+
};
2452+
}
24042453
default:
24052454
return null;
24062455
}

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ export * from './UcdpEventsPanel';
3535
export * from './DisplacementPanel';
3636
export * from './ClimateAnomalyPanel';
3737
export * from './PopulationExposurePanel';
38+
export * from './InvestmentsPanel';

src/config/feeds.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,14 @@ const FINANCE_FEEDS: Record<string, Feed[]> = {
759759
{ name: 'Risk & Volatility', url: rss('https://news.google.com/rss/search?q=(VIX+OR+"market+volatility"+OR+"risk+off"+OR+"market+correction")+when:3d&hl=en-US&gl=US&ceid=US:en') },
760760
{ name: 'Bank Research', url: rss('https://news.google.com/rss/search?q=("Goldman+Sachs"+OR+"JPMorgan"+OR+"Morgan+Stanley")+forecast+OR+outlook+when:3d&hl=en-US&gl=US&ceid=US:en') },
761761
],
762+
gccNews: [
763+
{ name: 'Arabian Business', url: rss('https://www.arabianbusiness.com/rss') },
764+
{ name: 'The National', url: rss('https://www.thenationalnews.com/rss/') },
765+
{ name: 'Arab News', url: rss('https://www.arabnews.com/rss.xml') },
766+
{ name: 'Gulf FDI', url: rss('https://news.google.com/rss/search?q=(PIF+OR+"DP+World"+OR+Mubadala+OR+ADNOC+OR+Masdar+OR+"ACWA+Power")+infrastructure+when:7d&hl=en-US&gl=US&ceid=US:en') },
767+
{ name: 'Gulf Investments', url: rss('https://news.google.com/rss/search?q=("Saudi+Arabia"+OR+"UAE"+OR+"Abu+Dhabi")+investment+infrastructure+when:7d&hl=en-US&gl=US&ceid=US:en') },
768+
{ name: 'Vision 2030', url: rss('https://news.google.com/rss/search?q="Vision+2030"+(project+OR+investment+OR+announced)+when:14d&hl=en-US&gl=US&ceid=US:en') },
769+
],
762770
};
763771

764772
// Variant-aware exports

src/config/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,6 @@ export {
112112
type CentralBank,
113113
type CommodityHub,
114114
} from './finance-geo';
115+
116+
// Gulf FDI investment database
117+
export { GULF_INVESTMENTS } from './gulf-fdi';

src/config/panels.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const FULL_MAP_LAYERS: MapLayers = {
8383
financialCenters: false,
8484
centralBanks: false,
8585
commodityHubs: false,
86+
gulfInvestments: false,
8687
};
8788

8889
const FULL_MOBILE_MAP_LAYERS: MapLayers = {
@@ -123,6 +124,7 @@ const FULL_MOBILE_MAP_LAYERS: MapLayers = {
123124
financialCenters: false,
124125
centralBanks: false,
125126
commodityHubs: false,
127+
gulfInvestments: false,
126128
};
127129

128130
// ============================================
@@ -202,6 +204,7 @@ const TECH_MAP_LAYERS: MapLayers = {
202204
financialCenters: false,
203205
centralBanks: false,
204206
commodityHubs: false,
207+
gulfInvestments: false,
205208
};
206209

207210
const TECH_MOBILE_MAP_LAYERS: MapLayers = {
@@ -242,6 +245,7 @@ const TECH_MOBILE_MAP_LAYERS: MapLayers = {
242245
financialCenters: false,
243246
centralBanks: false,
244247
commodityHubs: false,
248+
gulfInvestments: false,
245249
};
246250

247251
// ============================================
@@ -272,6 +276,8 @@ const FINANCE_PANELS: Record<string, PanelConfig> = {
272276
analysis: { name: 'Market Analysis', enabled: true, priority: 2 },
273277
'etf-flows': { name: 'BTC ETF Tracker', enabled: true, priority: 2 },
274278
stablecoins: { name: 'Stablecoins', enabled: true, priority: 2 },
279+
'gcc-investments': { name: 'GCC Investments', enabled: true, priority: 2 },
280+
gccNews: { name: 'GCC Business News', enabled: true, priority: 2 },
275281
polymarket: { name: 'Predictions', enabled: true, priority: 2 },
276282
monitors: { name: 'My Monitors', enabled: true, priority: 2 },
277283
};
@@ -314,6 +320,7 @@ const FINANCE_MAP_LAYERS: MapLayers = {
314320
financialCenters: true,
315321
centralBanks: true,
316322
commodityHubs: false,
323+
gulfInvestments: false,
317324
};
318325

319326
const FINANCE_MOBILE_MAP_LAYERS: MapLayers = {
@@ -354,6 +361,7 @@ const FINANCE_MOBILE_MAP_LAYERS: MapLayers = {
354361
financialCenters: false,
355362
centralBanks: true,
356363
commodityHubs: false,
364+
gulfInvestments: false,
357365
};
358366

359367
// ============================================

src/config/variants/finance.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ export const FEEDS: Record<string, Feed[]> = {
119119
{ name: 'Sovereign Wealth', url: rss('https://news.google.com/rss/search?q=("sovereign+wealth+fund"+OR+"pension+fund"+OR+"institutional+investor")+when:7d&hl=en-US&gl=US&ceid=US:en') },
120120
],
121121

122+
// GCC Business & Investment News
123+
gccNews: [
124+
{ name: 'Arabian Business', url: rss('https://www.arabianbusiness.com/rss') },
125+
{ name: 'The National', url: rss('https://www.thenationalnews.com/rss/') },
126+
{ name: 'Arab News', url: rss('https://www.arabnews.com/rss.xml') },
127+
{ name: 'Gulf FDI', url: rss('https://news.google.com/rss/search?q=(PIF+OR+"DP+World"+OR+Mubadala+OR+ADNOC+OR+Masdar+OR+"ACWA+Power")+infrastructure+when:7d&hl=en-US&gl=US&ceid=US:en') },
128+
{ name: 'Gulf Investments', url: rss('https://news.google.com/rss/search?q=("Saudi+Arabia"+OR+"UAE"+OR+"Abu+Dhabi")+investment+infrastructure+when:7d&hl=en-US&gl=US&ceid=US:en') },
129+
{ name: 'Vision 2030', url: rss('https://news.google.com/rss/search?q="Vision+2030"+(project+OR+investment+OR+announced)+when:14d&hl=en-US&gl=US&ceid=US:en') },
130+
],
131+
122132
// Market Analysis & Outlook
123133
analysis: [
124134
{ name: 'Market Outlook', url: rss('https://news.google.com/rss/search?q=("market+outlook"+OR+"stock+market+forecast"+OR+"bull+market"+OR+"bear+market")+when:3d&hl=en-US&gl=US&ceid=US:en') },
@@ -153,6 +163,8 @@ export const DEFAULT_PANELS: Record<string, PanelConfig> = {
153163
analysis: { name: 'Market Analysis', enabled: true, priority: 2 },
154164
'etf-flows': { name: 'BTC ETF Tracker', enabled: true, priority: 2 },
155165
stablecoins: { name: 'Stablecoins', enabled: true, priority: 2 },
166+
'gcc-investments': { name: 'GCC Investments', enabled: true, priority: 2 },
167+
gccNews: { name: 'GCC Business News', enabled: true, priority: 2 },
156168
polymarket: { name: 'Predictions', enabled: true, priority: 2 },
157169
monitors: { name: 'My Monitors', enabled: true, priority: 2 },
158170
};
@@ -195,6 +207,7 @@ export const DEFAULT_MAP_LAYERS: MapLayers = {
195207
financialCenters: true,
196208
centralBanks: true,
197209
commodityHubs: false,
210+
gulfInvestments: false,
198211
};
199212

200213
// Mobile defaults for finance variant
@@ -235,6 +248,7 @@ export const MOBILE_DEFAULT_MAP_LAYERS: MapLayers = {
235248
financialCenters: false,
236249
centralBanks: true,
237250
commodityHubs: false,
251+
gulfInvestments: false,
238252
};
239253

240254
export const VARIANT_CONFIG: VariantConfig = {

src/config/variants/full.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export const DEFAULT_MAP_LAYERS: MapLayers = {
8686
financialCenters: false,
8787
centralBanks: false,
8888
commodityHubs: false,
89+
gulfInvestments: false,
8990
};
9091

9192
// Mobile-specific defaults for geopolitical
@@ -126,6 +127,7 @@ export const MOBILE_DEFAULT_MAP_LAYERS: MapLayers = {
126127
financialCenters: false,
127128
centralBanks: false,
128129
commodityHubs: false,
130+
gulfInvestments: false,
129131
};
130132

131133
export const VARIANT_CONFIG: VariantConfig = {

src/config/variants/tech.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ export const DEFAULT_MAP_LAYERS: MapLayers = {
237237
financialCenters: false,
238238
centralBanks: false,
239239
commodityHubs: false,
240+
gulfInvestments: false,
240241
};
241242

242243
// Mobile defaults for tech variant
@@ -277,6 +278,7 @@ export const MOBILE_DEFAULT_MAP_LAYERS: MapLayers = {
277278
financialCenters: false,
278279
centralBanks: false,
279280
commodityHubs: false,
281+
gulfInvestments: false,
280282
};
281283

282284
export const VARIANT_CONFIG: VariantConfig = {

0 commit comments

Comments
 (0)