Closes #ISSUE-009 - Implement DEV_MODE Offline-First Protection Shield

This commit is contained in:
Antigravity Agent
2026-06-12 20:50:10 +02:00
parent ef4edd97a6
commit 6e4dd29d1d
5 changed files with 112 additions and 38 deletions

View File

@@ -347,6 +347,7 @@ export async function GET(request: Request) {
const mode = searchParams.get('mode') || 'day_crash';
const region = searchParams.get('region') || 'us';
const fmpApiKey = process.env.FMP_API_KEY || 'U6lOXaOFPye7oc1D235kyAqJeQaiTAWc';
const isDevMode = process.env.DEV_MODE === 'true';
let tickersPool: string[] = [];
@@ -358,38 +359,49 @@ export async function GET(request: Request) {
// US region: Large/Mid pool + Small Cap pool
tickersPool = [...US_MEGA_MID];
// Try to load FMP Small Caps or use static Small-Caps fallback list
let fmpSmallCaps: string[] = [];
try {
fmpSmallCaps = await fetchFmpScreener(fmpApiKey);
} catch (_) {}
if (fmpSmallCaps.length > 0) {
tickersPool.push(...fmpSmallCaps);
} else {
if (isDevMode) {
tickersPool.push(...US_SMALL_CAPS);
} else {
// Try to load FMP Small Caps or use static Small-Caps fallback list
let fmpSmallCaps: string[] = [];
try {
fmpSmallCaps = await fetchFmpScreener(fmpApiKey);
} catch (_) {}
if (fmpSmallCaps.length > 0) {
tickersPool.push(...fmpSmallCaps);
} else {
tickersPool.push(...US_SMALL_CAPS);
}
}
}
// De-duplicate tickers pool
tickersPool = Array.from(new Set(tickersPool));
// Fetch chart details for all tickers in parallel
const rawCharts = await Promise.allSettled(
tickersPool.map(t => fetchYahooChart(t))
);
const parsedResults: TickerDetails[] = [];
tickersPool.forEach((ticker, idx) => {
const res = rawCharts[idx];
if (res.status === 'fulfilled' && res.value) {
parsedResults.push(res.value);
} else {
// Fetch failed, use high-fidelity simulation
if (isDevMode) {
// Bypass Yahoo fetches completely, generate simulated charts directly!
tickersPool.forEach((ticker) => {
parsedResults.push(generateSimulatedChart(ticker, mode));
}
});
});
} else {
// Fetch chart details for all tickers in parallel
const rawCharts = await Promise.allSettled(
tickersPool.map(t => fetchYahooChart(t))
);
tickersPool.forEach((ticker, idx) => {
const res = rawCharts[idx];
if (res.status === 'fulfilled' && res.value) {
parsedResults.push(res.value);
} else {
// Fetch failed, use high-fidelity simulation
parsedResults.push(generateSimulatedChart(ticker, mode));
}
});
}
// Fetch fundamentals for top 15 candidates to reduce payload weight
// Sort candidates first based on mode to isolate top 15
@@ -409,19 +421,26 @@ export async function GET(request: Request) {
// Overlay fundamentals
const finalResults = await Promise.all(
sortedCandidates.map(async (item) => {
const fund = top15Symbols.has(item.ticker)
? await fetchFMPFundamentalData(item.ticker, fmpApiKey)
: getMockFundamentals(item.ticker);
const useMock = isDevMode || !top15Symbols.has(item.ticker);
const fund = useMock
? getMockFundamentals(item.ticker)
: await fetchFMPFundamentalData(item.ticker, fmpApiKey);
return {
...item,
...fund,
dividendYield: fund.dividendYield ? Number((fund.dividendYield * (top15Symbols.has(item.ticker) ? 1 : 100)).toFixed(2)) : 0
dividendYield: fund.dividendYield ? Number((fund.dividendYield * (useMock ? 100 : 1)).toFixed(2)) : 0
};
})
);
const response = NextResponse.json({ results: finalResults });
const response = NextResponse.json({
results: finalResults,
isShieldActive: isDevMode
});
response.headers.set('Cache-Control', 'no-store, max-age=0, must-revalidate');
if (isDevMode) {
response.headers.set('X-Shield-Active', 'true');
}
return response;
}