Files
intelligence-terminal/apis/sources/comtrade.mjs
calesthio ef2c6470fb Initial release — Crucix Intelligence Engine v2.0.0
26-source OSINT intelligence engine with live Jarvis dashboard,
auto-refresh via SSE, optional LLM layer (4 providers), delta/memory
system, and Telegram breaking news alerts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:45:46 -07:00

202 lines
6.1 KiB
JavaScript

// UN Comtrade — Global Trade Data
// Public preview endpoint requires no key. Full API needs free registration.
// Tracks commodity trade flows between nations: crude oil, gas, gold, semiconductors, arms.
// Reporter codes: 842 (US), 156 (China), 276 (Germany), 392 (Japan), 826 (UK), 643 (Russia), 356 (India)
import { safeFetch, daysAgo, today } from '../utils/fetch.mjs';
const BASE = 'https://comtradeapi.un.org/public/v1';
// Strategic commodity codes (HS classification)
const STRATEGIC_COMMODITIES = {
'2709': 'Crude Petroleum',
'2711': 'Natural Gas (LNG & Pipeline)',
'7108': 'Gold (unwrought/semi-manufactured)',
'8542': 'Semiconductors (Electronic Integrated Circuits)',
'93': 'Arms & Ammunition',
'2844': 'Radioactive Elements (Nuclear)',
'8471': 'Computers & Processing Units',
'2701': 'Coal',
'7601': 'Aluminium (unwrought)',
'2612': 'Uranium & Thorium Ores',
};
// Key reporter/partner country codes
const COUNTRIES = {
842: 'United States',
156: 'China',
276: 'Germany',
392: 'Japan',
826: 'United Kingdom',
643: 'Russia',
356: 'India',
410: 'South Korea',
158: 'Taiwan',
380: 'Italy',
};
// Get trade data for a specific reporter, commodity, and period
export async function getTradeData(opts = {}) {
const {
reporterCode = 842, // default: US
period = new Date().getFullYear(),
cmdCode = '2709', // default: crude oil
flowCode = 'M', // M = imports, X = exports
partnerCode = null, // null = all partners
} = opts;
const params = new URLSearchParams({
reporterCode: String(reporterCode),
period: String(period),
cmdCode,
flowCode,
});
if (partnerCode) params.set('partnerCode', String(partnerCode));
return safeFetch(`${BASE}/preview/C/A/HS?${params}`, { timeout: 20000 });
}
// Get bilateral trade between two countries for a commodity
export async function getBilateralTrade(reporter, partner, cmdCode, period) {
return getTradeData({
reporterCode: reporter,
partnerCode: partner,
cmdCode,
period: period || new Date().getFullYear(),
});
}
// Check multiple commodities for a given reporter
async function checkReporterCommodities(reporterCode, commodityCodes, period) {
const results = [];
for (const cmdCode of commodityCodes) {
const data = await getTradeData({
reporterCode,
cmdCode,
period,
flowCode: 'M', // imports
});
results.push({
commodity: STRATEGIC_COMMODITIES[cmdCode] || cmdCode,
cmdCode,
data,
});
}
return results;
}
// Compact a trade record for briefing output
function compactRecord(rec) {
return {
reporter: rec.reporterDesc || rec.reporterCode,
partner: rec.partnerDesc || rec.partnerCode,
commodity: rec.cmdDesc || rec.cmdCode,
flow: rec.flowDesc || rec.flowCode,
value: rec.primaryValue || rec.cifvalue || rec.fobvalue || null,
quantity: rec.qty || rec.netWgt || null,
unit: rec.qtyUnitAbbr || rec.qtyUnitDesc || null,
period: rec.period,
};
}
// Detect anomalies in trade data (unusually large flows, new partners, etc.)
function detectAnomalies(tradeRecords) {
const signals = [];
if (!Array.isArray(tradeRecords) || tradeRecords.length === 0) return signals;
const values = tradeRecords
.map(r => r.value)
.filter(v => typeof v === 'number' && v > 0);
if (values.length > 2) {
const avg = values.reduce((a, b) => a + b, 0) / values.length;
const stdDev = Math.sqrt(values.reduce((a, v) => a + (v - avg) ** 2, 0) / values.length);
tradeRecords.forEach(r => {
if (typeof r.value === 'number' && r.value > avg + 2 * stdDev) {
signals.push(
`OUTLIER: ${r.commodity} trade with ${r.partner} = $${(r.value / 1e9).toFixed(2)}B ` +
`(mean: $${(avg / 1e9).toFixed(2)}B)`
);
}
});
}
return signals;
}
// Briefing — check recent trade data for key commodities, detect anomalies
export async function briefing() {
const currentYear = new Date().getFullYear();
const prevYear = currentYear - 1;
// Key combinations to check: US imports of strategic commodities
const keyCommodities = ['2709', '2711', '7108', '8542', '93'];
const keyReporters = [842, 156]; // US, China
const tradeFlows = [];
const signals = [];
for (const reporter of keyReporters) {
for (const cmdCode of keyCommodities) {
// Try current year first, fall back to previous year
let data = await getTradeData({
reporterCode: reporter,
cmdCode,
period: currentYear,
flowCode: 'M',
});
// Comtrade returns data in different structures; normalize
let records = data?.data || data?.dataset || [];
if (!Array.isArray(records)) records = [];
// If no current year data, try previous year
if (records.length === 0) {
data = await getTradeData({
reporterCode: reporter,
cmdCode,
period: prevYear,
flowCode: 'M',
});
records = data?.data || data?.dataset || [];
if (!Array.isArray(records)) records = [];
}
const compact = records.slice(0, 10).map(compactRecord);
if (compact.length > 0) {
tradeFlows.push({
reporter: COUNTRIES[reporter] || reporter,
commodity: STRATEGIC_COMMODITIES[cmdCode] || cmdCode,
cmdCode,
topPartners: compact,
totalRecords: records.length,
});
// Run anomaly detection
const anomalies = detectAnomalies(compact);
signals.push(...anomalies);
}
}
}
return {
source: 'UN Comtrade',
timestamp: new Date().toISOString(),
tradeFlows,
signals: signals.length > 0
? signals
: ['No significant trade anomalies detected in sampled commodities'],
status: tradeFlows.length > 0 ? 'ok' : 'no_data',
note: 'Comtrade data often lags 1-2 months. Recent periods may be incomplete.',
coveredCommodities: STRATEGIC_COMMODITIES,
coveredCountries: COUNTRIES,
};
}
// Run standalone
if (process.argv[1]?.endsWith('comtrade.mjs')) {
const data = await briefing();
console.log(JSON.stringify(data, null, 2));
}