Files
intelligence-terminal/apis/sources/gscpi.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

167 lines
5.2 KiB
JavaScript

// GSCPI — NY Fed Global Supply Chain Pressure Index
// Measures global supply chain stress (standard deviations from historical average).
// Values above 0 = above average pressure. Above 1.0 = elevated. Below -1.0 = unusually loose.
// Data fetched directly from NY Fed — no API key required.
const GSCPI_CSV_URL = 'https://www.newyorkfed.org/medialibrary/research/interactives/data/gscpi/gscpi_interactive_data.csv';
// Fetch and parse the GSCPI CSV from the NY Fed
// The CSV is wide-format: each column is a revision vintage, last column is latest estimate.
// Uses raw fetch instead of safeFetch because safeFetch truncates non-JSON to 500 chars.
export async function getGSCPI(months = 12) {
try {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 20000);
const res = await fetch(GSCPI_CSV_URL, {
signal: controller.signal,
headers: { 'User-Agent': 'Crucix/1.0' },
});
clearTimeout(timer);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const text = await res.text();
return { data: parseCSV(text, months) };
} catch (e) {
return { error: e.message || 'Failed to fetch GSCPI data', data: [] };
}
}
// Parse the wide-format CSV, extracting the latest vintage value for each date
function parseCSV(text, months) {
const lines = text.trim().split('\n').filter(l => l.trim() && !l.startsWith(','));
if (lines.length < 2) return [];
// Header row tells us column count; we want the last non-empty column for each row
const results = [];
for (let i = 1; i < lines.length; i++) {
const cols = lines[i].split(',');
const dateStr = cols[0]?.trim();
if (!dateStr) continue;
// Find the last non-empty, non-#N/A value (latest vintage estimate)
let value = null;
for (let j = cols.length - 1; j >= 1; j--) {
const v = cols[j]?.trim();
if (v && v !== '#N/A' && v !== '') {
const num = parseFloat(v);
if (!isNaN(num)) {
value = num;
break;
}
}
}
if (value === null) continue;
// Parse date from "31-Jan-2026" format to "2026-01"
const date = parseNYFedDate(dateStr);
if (date) {
results.push({ date, value });
}
}
// Sort newest first
results.sort((a, b) => b.date.localeCompare(a.date));
return results.slice(0, months);
}
// Parse "31-Jan-2026" -> "2026-01"
function parseNYFedDate(str) {
const months = {
Jan: '01', Feb: '02', Mar: '03', Apr: '04', May: '05', Jun: '06',
Jul: '07', Aug: '08', Sep: '09', Oct: '10', Nov: '11', Dec: '12',
};
const parts = str.split('-');
if (parts.length !== 3) return null;
const mon = months[parts[1]];
const year = parts[2];
if (!mon || !year) return null;
return `${year}-${mon}`;
}
// Detect trend from an array of {date, value} sorted newest-first
function detectTrend(history) {
if (history.length < 3) return 'insufficient data';
// Compare recent 3 months direction
const recent = history.slice(0, 3);
let rising = 0;
let falling = 0;
for (let i = 0; i < recent.length - 1; i++) {
// history is newest-first, so recent[0] is latest
if (recent[i].value > recent[i + 1].value) rising++;
else if (recent[i].value < recent[i + 1].value) falling++;
}
if (rising > falling) return 'rising';
if (falling > rising) return 'falling';
return 'stable';
}
// Briefing — latest GSCPI, trend, and signals
export async function briefing() {
const result = await getGSCPI(12);
if (result.error) {
return {
source: 'NY Fed GSCPI',
error: result.error,
timestamp: new Date().toISOString(),
};
}
const history = result.data;
const trend = detectTrend(history);
const signals = [];
const latest = history.length > 0 ? history[0] : null;
if (latest) {
if (latest.value > 2.0) {
signals.push(`GSCPI extremely elevated at ${latest.value.toFixed(2)} — severe supply chain stress`);
} else if (latest.value > 1.0) {
signals.push(`GSCPI elevated at ${latest.value.toFixed(2)} — above-normal supply chain pressure`);
} else if (latest.value < -1.0) {
signals.push(`GSCPI at ${latest.value.toFixed(2)} — unusually loose supply chains`);
}
if (trend === 'rising' && latest.value > 0) {
signals.push('Supply chain pressure trending higher');
}
if (trend === 'falling' && latest.value > 1.0) {
signals.push('Supply chain pressure elevated but improving');
}
}
// Check month-over-month change
if (history.length >= 2) {
const mom = history[0].value - history[1].value;
if (Math.abs(mom) > 0.5) {
const dir = mom > 0 ? 'surged' : 'dropped';
signals.push(`GSCPI ${dir} ${Math.abs(mom).toFixed(2)} points month-over-month`);
}
}
return {
source: 'NY Fed GSCPI',
timestamp: new Date().toISOString(),
latest: latest ? {
value: latest.value,
date: latest.date,
interpretation: latest.value > 1.0 ? 'elevated' :
latest.value > 0 ? 'above average' :
latest.value > -1.0 ? 'below average' : 'unusually loose',
} : null,
trend,
history,
signals,
};
}
if (process.argv[1]?.endsWith('gscpi.mjs')) {
const data = await briefing();
console.log(JSON.stringify(data, null, 2));
}