Closes #023-warn - Implement PEAD fallback transparency warning badge
This commit is contained in:
@@ -136,3 +136,33 @@ Indicators in the cockpit cards use HSL-tailored status lights (Emerald-Green, A
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### PEAD Drift Radar API Response Schema (`/api/scanner`)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"ticker": "NVDA",
|
||||||
|
"name": "NVIDIA Corporation",
|
||||||
|
"peadSector": "Technology",
|
||||||
|
"announcementDate": "2026-05-20",
|
||||||
|
"daysElapsed": 25,
|
||||||
|
"epsActual": 6.12,
|
||||||
|
"epsConsensus": 5.58,
|
||||||
|
"surprisePercent": 9.68,
|
||||||
|
"driftStatus": "Active Drift",
|
||||||
|
"isLiveApi": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isShieldActive": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Fallback Protection & Operational Transparency Levels
|
||||||
|
The workstation enforces zero silent caching or historical data ingestion:
|
||||||
|
* **PEAD Drift Radar**: Uses a dynamic state flag `isLivePeadApi` to toggle status rendering.
|
||||||
|
* *Green Badge (`🟢 LIVE EPS FEED`)*: FMP API endpoint responded with real-world quarterly reports (`isLiveApi: true`).
|
||||||
|
* *Amber Badge (`⚠️ ARCHIV-DATEN (API OFFLINE)`)*: Live API timeout, failure, or developer shield fallback active (`isLiveApi: false`).
|
||||||
|
|
||||||
|
|||||||
12
DEV_LOG.md
12
DEV_LOG.md
@@ -288,6 +288,18 @@ This document tracks all modifications, npm packages, active compilation states,
|
|||||||
* **Active Bugs**: None.
|
* **Active Bugs**: None.
|
||||||
* **Type Checker Status**: Verified 100% clean type verification (`npx tsc --noEmit` returns exit code 0).
|
* **Type Checker Status**: Verified 100% clean type verification (`npx tsc --noEmit` returns exit code 0).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-06-14] - PEAD Fallback Transparency Warning Graphic (#ISSUE-023-WARN)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* **Transparency Flag Ingestion**: Refactored `fetchFmpEarningsSurprise` and `getSimulatedPEAD` in [/api/scanner/route.ts](file:///c:/Users/jannr/.gemini/antigravity/scratch/investment-sandbox/app/api/scanner/route.ts) to return `isLiveApi: true` upon successful live API queries and `isLiveApi: false` on failure or simulated fallbacks.
|
||||||
|
* **Dynamic Warning Badges**: Declared a state hook `isLivePeadApi` in [ScannerDemo.tsx](file:///c:/Users/jannr/.gemini/antigravity/scratch/investment-sandbox/components/modules/scanner/ScannerDemo.tsx) and bound it to scan responses in `handleMarketScan`. Renders green `"🟢 LIVE EPS FEED"` and amber `"⚠️ ARCHIV-DATEN (API OFFLINE)"` status badges adjacent to the PEAD Drift Radar header title depending on the scan api status.
|
||||||
|
|
||||||
|
### Active Bugs / Compile Status
|
||||||
|
* **Active Bugs**: None.
|
||||||
|
* **Type Checker Status**: Verified 100% clean type verification (`npx tsc --noEmit` returns exit code 0).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ This document serves as the permanent, centralized system architecture design an
|
|||||||
* **Phase 7.0: Live Time-Lock Resolution, Ensemble Map Fix & Multi-Asset Logging**
|
* **Phase 7.0: Live Time-Lock Resolution, Ensemble Map Fix & Multi-Asset Logging**
|
||||||
* *Features*: Disabled developer mock test clocks inside verification loop hooks. Configured strict real-time calendar locks (T+1 resolves after 24h, T+5 after 5d, T+10 after 10d). Corrected the ensemble mapping anomaly inside `getPredictionProb` by passing the active asset's ticker context parameter. Embedded a dynamic asset selector filter tab-bar above the Active Learning Feedback Loop table component, enabling isolated tracking and filtering of multi-asset predictions (BTC, ETH, SOL).
|
* *Features*: Disabled developer mock test clocks inside verification loop hooks. Configured strict real-time calendar locks (T+1 resolves after 24h, T+5 after 5d, T+10 after 10d). Corrected the ensemble mapping anomaly inside `getPredictionProb` by passing the active asset's ticker context parameter. Embedded a dynamic asset selector filter tab-bar above the Active Learning Feedback Loop table component, enabling isolated tracking and filtering of multi-asset predictions (BTC, ETH, SOL).
|
||||||
* *Status*: **Fully Operational (Production Lock)**.
|
* *Status*: **Fully Operational (Production Lock)**.
|
||||||
|
* **Phase 8.0: Isolated PEAD Screener & Fallback Warning Graphic**
|
||||||
|
* *Features*: Integrated zero-coupling Post-Earnings Announcement Drift (PEAD) Screener parsing reported vs. consensus EPS surprise vectors. Implemented an `isLiveApi` flag to detect and handle offline API or rate-limit simulated fallbacks. Mounted glassmorphic warning status badges (`🟢 LIVE EPS FEED` vs `⚠️ ARCHIV-DATEN (API OFFLINE)`) in the frontend header to prevent trading on historical cached data without operational awareness.
|
||||||
|
* *Status*: **Fully Operational (Production Lock)**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ interface TickerDetails {
|
|||||||
epsConsensus?: number;
|
epsConsensus?: number;
|
||||||
surprisePercent?: number;
|
surprisePercent?: number;
|
||||||
driftStatus?: string;
|
driftStatus?: string;
|
||||||
|
isLiveApi?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 14-day Welles Wilder RSI solver
|
// 14-day Welles Wilder RSI solver
|
||||||
@@ -412,6 +413,7 @@ function getSimulatedPEAD(ticker: string): {
|
|||||||
epsConsensus: number;
|
epsConsensus: number;
|
||||||
surprisePercent: number;
|
surprisePercent: number;
|
||||||
driftStatus: 'Active Drift' | 'Consolidating';
|
driftStatus: 'Active Drift' | 'Consolidating';
|
||||||
|
isLiveApi: boolean;
|
||||||
} {
|
} {
|
||||||
const getSector = (t: string) => {
|
const getSector = (t: string) => {
|
||||||
const tech = ['AAPL', 'MSFT', 'NVDA', 'AMD', 'SMCI', 'ADBE', 'CRM', 'AVGO', 'QCOM', 'TXN', 'INTC', 'MU', 'AMAT', 'LRCX', 'PLTR'];
|
const tech = ['AAPL', 'MSFT', 'NVDA', 'AMD', 'SMCI', 'ADBE', 'CRM', 'AVGO', 'QCOM', 'TXN', 'INTC', 'MU', 'AMAT', 'LRCX', 'PLTR'];
|
||||||
@@ -438,7 +440,8 @@ function getSimulatedPEAD(ticker: string): {
|
|||||||
epsActual: 0,
|
epsActual: 0,
|
||||||
epsConsensus: 0,
|
epsConsensus: 0,
|
||||||
surprisePercent: 0,
|
surprisePercent: 0,
|
||||||
driftStatus: 'Consolidating'
|
driftStatus: 'Consolidating',
|
||||||
|
isLiveApi: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,7 +467,8 @@ function getSimulatedPEAD(ticker: string): {
|
|||||||
epsActual,
|
epsActual,
|
||||||
epsConsensus,
|
epsConsensus,
|
||||||
surprisePercent,
|
surprisePercent,
|
||||||
driftStatus
|
driftStatus,
|
||||||
|
isLiveApi: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,6 +480,7 @@ async function fetchFmpEarningsSurprise(ticker: string, apiKey: string): Promise
|
|||||||
epsConsensus: number;
|
epsConsensus: number;
|
||||||
surprisePercent: number;
|
surprisePercent: number;
|
||||||
driftStatus: 'Active Drift' | 'Consolidating';
|
driftStatus: 'Active Drift' | 'Consolidating';
|
||||||
|
isLiveApi: boolean;
|
||||||
}> {
|
}> {
|
||||||
const getSector = (t: string) => {
|
const getSector = (t: string) => {
|
||||||
const tech = ['AAPL', 'MSFT', 'NVDA', 'AMD', 'SMCI', 'ADBE', 'CRM', 'AVGO', 'QCOM', 'TXN', 'INTC', 'MU', 'AMAT', 'LRCX', 'PLTR'];
|
const tech = ['AAPL', 'MSFT', 'NVDA', 'AMD', 'SMCI', 'ADBE', 'CRM', 'AVGO', 'QCOM', 'TXN', 'INTC', 'MU', 'AMAT', 'LRCX', 'PLTR'];
|
||||||
@@ -502,7 +507,8 @@ async function fetchFmpEarningsSurprise(ticker: string, apiKey: string): Promise
|
|||||||
epsActual: 0,
|
epsActual: 0,
|
||||||
epsConsensus: 0,
|
epsConsensus: 0,
|
||||||
surprisePercent: 0,
|
surprisePercent: 0,
|
||||||
driftStatus: 'Consolidating'
|
driftStatus: 'Consolidating',
|
||||||
|
isLiveApi: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,7 +542,8 @@ async function fetchFmpEarningsSurprise(ticker: string, apiKey: string): Promise
|
|||||||
epsActual,
|
epsActual,
|
||||||
epsConsensus,
|
epsConsensus,
|
||||||
surprisePercent,
|
surprisePercent,
|
||||||
driftStatus
|
driftStatus,
|
||||||
|
isLiveApi: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -545,7 +552,10 @@ async function fetchFmpEarningsSurprise(ticker: string, apiKey: string): Promise
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to deterministic simulation
|
// Fallback to deterministic simulation
|
||||||
return getSimulatedPEAD(ticker);
|
return {
|
||||||
|
...getSimulatedPEAD(ticker),
|
||||||
|
isLiveApi: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ interface PEADData {
|
|||||||
epsConsensus: number;
|
epsConsensus: number;
|
||||||
surprisePercent: number;
|
surprisePercent: number;
|
||||||
driftStatus: 'Active Drift' | 'Consolidating';
|
driftStatus: 'Active Drift' | 'Consolidating';
|
||||||
|
isLiveApi?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ScannerDemo() {
|
export default function ScannerDemo() {
|
||||||
@@ -71,6 +72,7 @@ export default function ScannerDemo() {
|
|||||||
{ ticker: 'TSLA', name: 'Tesla Inc.', peadSector: 'Consumer Goods', announcementDate: '2026-04-23', daysElapsed: 52, epsActual: 0.45, epsConsensus: 0.51, surprisePercent: -11.76, driftStatus: 'Consolidating' },
|
{ ticker: 'TSLA', name: 'Tesla Inc.', peadSector: 'Consumer Goods', announcementDate: '2026-04-23', daysElapsed: 52, epsActual: 0.45, epsConsensus: 0.51, surprisePercent: -11.76, driftStatus: 'Consolidating' },
|
||||||
{ ticker: 'JPM', name: 'JPMorgan Chase & Co.', peadSector: 'Financial Services', announcementDate: '2026-04-12', daysElapsed: 63, epsActual: 4.44, epsConsensus: 4.15, surprisePercent: 6.99, driftStatus: 'Consolidating' }
|
{ ticker: 'JPM', name: 'JPMorgan Chase & Co.', peadSector: 'Financial Services', announcementDate: '2026-04-12', daysElapsed: 63, epsActual: 4.44, epsConsensus: 4.15, surprisePercent: 6.99, driftStatus: 'Consolidating' }
|
||||||
]);
|
]);
|
||||||
|
const [isLivePeadApi, setIsLivePeadApi] = useState(false);
|
||||||
|
|
||||||
// Cache for metadata and prices retrieved dynamically
|
// Cache for metadata and prices retrieved dynamically
|
||||||
const [alertsMetadata, setAlertsMetadata] = useState<Record<string, { name: string; whyDropped: string; sentiment: 'GREEN' | 'YELLOW' | 'RED' }>>({});
|
const [alertsMetadata, setAlertsMetadata] = useState<Record<string, { name: string; whyDropped: string; sentiment: 'GREEN' | 'YELLOW' | 'RED' }>>({});
|
||||||
@@ -193,12 +195,16 @@ export default function ScannerDemo() {
|
|||||||
epsActual: r.epsActual || 0,
|
epsActual: r.epsActual || 0,
|
||||||
epsConsensus: r.epsConsensus || 0,
|
epsConsensus: r.epsConsensus || 0,
|
||||||
surprisePercent: r.surprisePercent || 0,
|
surprisePercent: r.surprisePercent || 0,
|
||||||
driftStatus: r.driftStatus || 'Consolidating'
|
driftStatus: r.driftStatus || 'Consolidating',
|
||||||
|
isLiveApi: r.isLiveApi
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
peadList.sort((a, b) => Math.abs(b.surprisePercent) - Math.abs(a.surprisePercent));
|
peadList.sort((a, b) => Math.abs(b.surprisePercent) - Math.abs(a.surprisePercent));
|
||||||
setPeadData(peadList);
|
setPeadData(peadList);
|
||||||
|
|
||||||
|
const hasLivePead = peadList.some(item => item.isLiveApi === true);
|
||||||
|
setIsLivePeadApi(hasLivePead);
|
||||||
|
|
||||||
// Update global store alerts for Sandbox module use
|
// Update global store alerts for Sandbox module use
|
||||||
updateScannerAlerts(newAlerts);
|
updateScannerAlerts(newAlerts);
|
||||||
|
|
||||||
@@ -766,12 +772,27 @@ export default function ScannerDemo() {
|
|||||||
>
|
>
|
||||||
<Sparkles className="text-amber-400 w-4 h-4 animate-pulse" /> 3-Tier Screener Capacity Grid
|
<Sparkles className="text-amber-400 w-4 h-4 animate-pulse" /> 3-Tier Screener Capacity Grid
|
||||||
</button>
|
</button>
|
||||||
<button
|
<div className="flex items-center gap-2">
|
||||||
onClick={() => setLeftPanelTab('pead')}
|
<button
|
||||||
className={`text-sm font-bold flex items-center gap-2 pb-1 transition-all cursor-pointer ${leftPanelTab === 'pead' ? 'text-white border-b-2 border-amber-500' : 'text-slate-400 hover:text-slate-200'}`}
|
onClick={() => setLeftPanelTab('pead')}
|
||||||
>
|
className={`text-sm font-bold flex items-center gap-2 pb-1 transition-all cursor-pointer ${leftPanelTab === 'pead' ? 'text-white border-b-2 border-amber-500' : 'text-slate-400 hover:text-slate-200'}`}
|
||||||
<TrendingUp className="text-amber-400 w-4 h-4" /> 🚀 PEAD Drift Radar
|
>
|
||||||
</button>
|
<TrendingUp className="text-amber-400 w-4 h-4" /> 🚀 PEAD Drift Radar
|
||||||
|
</button>
|
||||||
|
{leftPanelTab === 'pead' && (
|
||||||
|
isLivePeadApi ? (
|
||||||
|
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[9px] font-bold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 shadow-[0_0_10px_rgba(16,185,129,0.15)]">
|
||||||
|
<span className="w-1 h-1 rounded-full bg-emerald-500 animate-ping" />
|
||||||
|
🟢 LIVE EPS FEED
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[9px] font-bold bg-amber-500/10 text-amber-400 border border-amber-500/20 shadow-[0_0_10px_rgba(245,158,11,0.15)]">
|
||||||
|
<span className="w-1 h-1 rounded-full bg-amber-500 animate-pulse" />
|
||||||
|
⚠️ ARCHIV-DATEN (API OFFLINE)
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] text-slate-400 font-mono">Modus: {scanMode.toUpperCase()} | Region: {marketRegion.toUpperCase()}</span>
|
<span className="text-[10px] text-slate-400 font-mono">Modus: {scanMode.toUpperCase()} | Region: {marketRegion.toUpperCase()}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user