diff --git a/DEV_LOG.md b/DEV_LOG.md index bf349c7..89f84fa 100644 --- a/DEV_LOG.md +++ b/DEV_LOG.md @@ -43,3 +43,17 @@ This document tracks all modifications, npm packages, active compilation states, * **Active Bugs**: None. * **Type Checker Status**: Verified clean compilation (`npx tsc --noEmit` returns exit code 0). +--- + +## [2026-06-12] - Offline-First Architectural Shield (`DEV_MODE`) (#ISSUE-009) + +### Added +* **API Outbound Interception Layer**: Configured [/api/scanner/route.ts](file:///c:/Users/jannr/.gemini/antigravity/scratch/investment-sandbox/app/api/scanner/route.ts) and [/api/tech/ai/route.ts](file:///c:/Users/jannr/.gemini/antigravity/scratch/investment-sandbox/app/api/tech/ai/route.ts) to intercept fetches if `process.env.DEV_MODE === 'true'`, completely bypassing outbound network calls and short-circuiting to high-fidelity mock/fallback data with `isShieldActive: true`. +* **Visual Status Badges**: Mounted highly polished, glassmorphic "API Layer Status" badges inside [ScannerDemo.tsx](file:///c:/Users/jannr/.gemini/antigravity/scratch/investment-sandbox/components/modules/scanner/ScannerDemo.tsx) and [AiSpecialSilo.tsx](file:///c:/Users/jannr/.gemini/antigravity/scratch/investment-sandbox/components/modules/tech/AiSpecialSilo.tsx) upper control bars, displaying "🟡 DEV-ARCHIV AKTIV (0 CALLS)" if the shield is active, and "🟢 LIVE-API ENDPUNKT (FMP CORPO)" if inactive. +* **Environment Perimeter Lock**: Appended `DEV_MODE=true` inside [.env](file:///c:/Users/jannr/.gemini/antigravity/scratch/investment-sandbox/.env) to lock the local sandbox securely into a zero-cost offline state. + +### Active Bugs / Compile Status +* **Active Bugs**: None. +* **Type Checker Status**: Verified clean compilation (`npx tsc --noEmit` returns exit code 0). + + diff --git a/app/api/scanner/route.ts b/app/api/scanner/route.ts index b60082c..3afac62 100644 --- a/app/api/scanner/route.ts +++ b/app/api/scanner/route.ts @@ -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; } diff --git a/app/api/tech/ai/route.ts b/app/api/tech/ai/route.ts index 377c08f..9c09c5f 100644 --- a/app/api/tech/ai/route.ts +++ b/app/api/tech/ai/route.ts @@ -134,9 +134,10 @@ async function fetchFmpData(ticker: string, apiKey: string): Promise { export async function GET() { const apiKey = process.env.FMP_API_KEY; const now = Date.now(); + const isDevMode = process.env.DEV_MODE === 'true'; - // Return cached result if valid - if (cache && (now - cache.timestamp < CACHE_TTL)) { + // Return cached result if valid (unless in DEV_MODE) + if (!isDevMode && cache && (now - cache.timestamp < CACHE_TTL)) { return NextResponse.json(cache.data, { status: 200, headers: { 'Cache-Control': 'public, max-age=3600' } @@ -147,7 +148,9 @@ export async function GET() { // Deep clone fallback data const companyData: CompanyData[] = JSON.parse(JSON.stringify(MOCK_TECH_AI_DATA)); - if (apiKey) { + if (isDevMode) { + // Short-circuit: completely skip outbound FMP fetches + } else if (apiKey) { try { // Test the API key first with a quick check const testRes = await fetchWithTimeout(`https://financialmodelingprep.com/api/v3/income-statement/NVDA?period=quarter&limit=1&apikey=${apiKey}`); @@ -422,6 +425,7 @@ export async function GET() { const payload = { dates, liveDataAvailable, + isShieldActive: isDevMode, timestamp: now, metrics: { monetizationGap: { @@ -436,13 +440,22 @@ export async function GET() { } }; - cache = { - timestamp: now, - data: payload + if (!isDevMode) { + cache = { + timestamp: now, + data: payload + }; + } + + const responseHeaders: Record = { + 'Cache-Control': isDevMode ? 'no-store, max-age=0, must-revalidate' : 'public, max-age=3600' }; + if (isDevMode) { + responseHeaders['X-Shield-Active'] = 'true'; + } return NextResponse.json(payload, { status: 200, - headers: { 'Cache-Control': 'public, max-age=3600' } + headers: responseHeaders }); } diff --git a/components/modules/scanner/ScannerDemo.tsx b/components/modules/scanner/ScannerDemo.tsx index 5e541b0..bcd6a26 100644 --- a/components/modules/scanner/ScannerDemo.tsx +++ b/components/modules/scanner/ScannerDemo.tsx @@ -46,6 +46,7 @@ export default function ScannerDemo() { const [showMathAccordion, setShowMathAccordion] = useState(false); const [isMathModalOpen, setIsMathModalOpen] = useState(false); + const [isShieldActive, setIsShieldActive] = useState(false); // Cache for metadata and prices retrieved dynamically const [alertsMetadata, setAlertsMetadata] = useState>({}); @@ -94,6 +95,7 @@ export default function ScannerDemo() { throw new Error('Failed to fetch scanner tickers'); } const data = await response.json(); + setIsShieldActive(!!data.isShieldActive); const results = data.results || []; setScanProgress('Berechne GJR-GARCH Volatilitäten...'); @@ -551,8 +553,20 @@ export default function ScannerDemo() {
Market Scanner Engine -

- Anomalien-Scanner & Marktverzerrungen +

+ + Anomalien-Scanner & Marktverzerrungen + {isShieldActive ? ( + + + DEV-ARCHIV AKTIV (0 CALLS) + + ) : ( + + + LIVE-API ENDPUNKT (FMP CORPO) + + )}

Isoliert Kursstürze > 5% bei relativem Gesamtmarkt-Stopp (S&P 500 driftet seitwärts oder steigt). Misst die Asymmetrie mittels GJR-GARCH, um Panik von strukturellen Risiken zu separieren. diff --git a/components/modules/tech/AiSpecialSilo.tsx b/components/modules/tech/AiSpecialSilo.tsx index e43dd36..df5a64a 100644 --- a/components/modules/tech/AiSpecialSilo.tsx +++ b/components/modules/tech/AiSpecialSilo.tsx @@ -77,6 +77,7 @@ export default function AiSpecialSilo() { const [error, setError] = useState(null); const [payload, setPayload] = useState(null); const [isMathModalOpen, setIsMathModalOpen] = useState(false); + const [isShieldActive, setIsShieldActive] = useState(false); useEffect(() => { const fetchData = async () => { @@ -87,6 +88,7 @@ export default function AiSpecialSilo() { if (response.ok) { const data = await response.json(); setPayload(data); + setIsShieldActive(!!data.isShieldActive); } else { setError('Error fetching AI Tech Hyper-Leverage metrics.'); } @@ -186,8 +188,20 @@ export default function AiSpecialSilo() {

AI & Tech Silo -

- AI Hyper-Leverage & CapEx Matrix +

+ + AI Hyper-Leverage & CapEx Matrix + {isShieldActive ? ( + + + DEV-ARCHIV AKTIV (0 CALLS) + + ) : ( + + + LIVE-API ENDPUNKT (FMP CORPO) + + )}

Monitors Big Tech capital expenditures, segment revenues, and inventory velocities to diagnose infrastructure bubbles.