diff --git a/dashboard/src/api/meetKevin.ts b/dashboard/src/api/meetKevin.ts new file mode 100644 index 0000000..004a221 --- /dev/null +++ b/dashboard/src/api/meetKevin.ts @@ -0,0 +1,100 @@ +import client from './client'; +import type { + DashboardData, + PipelineHealth, + StockMention, + StockSummary, + Transcript, + VideoDetail, + VideoSummary, +} from '../types/meetKevin'; + +interface ListVideosParams { + status?: string; + q?: string; + page?: number; + per_page?: number; +} + +interface ListVideosResponse { + videos: VideoSummary[]; + total: number; + page: number; + per_page: number; +} + +interface ListStocksResponse { + stocks: StockSummary[]; +} + +interface GetStockResponse { + symbol: string; + mentions: StockMention[]; +} + +const meetKevinApi = { + async health(): Promise { + const res = await client.get('/meet-kevin/health'); + return res.data; + }, + + async dashboard(): Promise { + const res = await client.get('/meet-kevin/dashboard'); + return res.data; + }, + + async listVideos(params: ListVideosParams): Promise { + const res = await client.get('/meet-kevin/videos', { + params, + }); + return res.data; + }, + + async getVideo(id: number): Promise { + const res = await client.get(`/meet-kevin/videos/${id}`); + return res.data; + }, + + async getTranscript(id: number): Promise { + const res = await client.get( + `/meet-kevin/videos/${id}/transcript` + ); + return res.data; + }, + + async reprocess( + id: number, + stage: 'captions' | 'analysis' | 'auto' + ): Promise> { + const res = await client.post>( + `/meet-kevin/videos/${id}/reprocess`, + { stage } + ); + return res.data; + }, + + async listStocks(): Promise { + const res = await client.get('/meet-kevin/stocks'); + return res.data; + }, + + async getStock(symbol: string): Promise { + const res = await client.get( + `/meet-kevin/stocks/${symbol}` + ); + return res.data; + }, + + async getStockTimeline( + symbol: string, + bucket: 'day' | 'week' + ): Promise> { + const res = await client.get>( + `/meet-kevin/stocks/${symbol}/timeline`, + { params: { bucket } } + ); + return res.data; + }, +}; + +export default meetKevinApi; diff --git a/dashboard/src/types/meetKevin.ts b/dashboard/src/types/meetKevin.ts new file mode 100644 index 0000000..fd656ca --- /dev/null +++ b/dashboard/src/types/meetKevin.ts @@ -0,0 +1,114 @@ +export type TickerAction = 'buy' | 'sell' | 'hold' | 'watch' | 'avoid'; +export type TimeHorizon = + | 'intraday' + | 'days' + | 'weeks' + | 'months' + | 'long_term' + | 'unspecified'; +export type MarketOutlook = 'bullish' | 'neutral' | 'bearish' | 'mixed'; +export type VideoStatus = + | 'discovered' + | 'captioned' + | 'analyzed' + | 'failed' + | 'skipped'; + +export interface VideoSummary { + id: number; + youtube_video_id: string; + title: string; + published_at: string; + duration_seconds: number | null; + thumbnail_url: string | null; + status: VideoStatus; + failure_reason: string | null; + top_tickers: string[]; + outlook: MarketOutlook | null; + one_line_summary: string | null; +} + +export interface TickerMention { + symbol: string; + action: TickerAction; + conviction: number; + time_horizon: TimeHorizon; + rationale_quote: string; + video_timestamp_seconds: number | null; +} + +export interface VideoAnalysis { + id: number; + model: string; + prompt_version: string; + market_outlook_direction: MarketOutlook; + market_outlook_reasoning: string; + macro_themes: string[]; + key_risks: string[]; + summary: string; + tickers: TickerMention[]; +} + +export interface VideoDetail { + video: VideoSummary; + analysis: VideoAnalysis | null; + transcript_available: boolean; +} + +export interface TranscriptSegment { + start: number; + end: number; + text: string; +} + +export interface Transcript { + video_id: number; + source: string; + language: string; + segments: TranscriptSegment[]; + word_count: number; +} + +export interface StockSummary { + symbol: string; + mention_count: number; + last_seen_at: string; + latest_action: TickerAction; + latest_conviction: number; + avg_conviction: number; +} + +export interface StockMention { + video_id: number; + youtube_video_id: string; + video_title: string; + published_at: string; + action: TickerAction; + conviction: number; + time_horizon: TimeHorizon; + rationale_quote: string; + video_timestamp_seconds: number | null; +} + +export interface PipelineHealth { + last_poll_at: string | null; + last_poll_age_seconds: number | null; + counts_by_status: Record; + daily_cost_usd: number; + daily_cost_cap_usd: number; +} + +export interface DashboardData { + latest_video: VideoSummary | null; + latest_analysis: VideoAnalysis | null; + top_conviction_week: { + symbol: string; + max_conviction: number; + mention_count: number; + }[]; + outlook_trend_14d: { + day: string; + direction: MarketOutlook; + count: number; + }[]; +} diff --git a/dashboard/tsconfig.app.json b/dashboard/tsconfig.app.json index ab4554d..da3e985 100644 --- a/dashboard/tsconfig.app.json +++ b/dashboard/tsconfig.app.json @@ -1,11 +1,9 @@ { "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "ES2022", "useDefineForClassFields": true, "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", - "types": ["vite/client"], "skipLibCheck": true, /* Bundler mode */ @@ -20,9 +18,7 @@ "strict": true, "noUnusedLocals": false, "noUnusedParameters": false, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noFallthroughCasesInSwitch": true }, "include": ["src"] } diff --git a/dashboard/tsconfig.node.json b/dashboard/tsconfig.node.json index 8a67f62..7be5155 100644 --- a/dashboard/tsconfig.node.json +++ b/dashboard/tsconfig.node.json @@ -1,8 +1,7 @@ { "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2023", - "lib": ["ES2023"], + "target": "ES2022", + "lib": ["ES2022"], "module": "ESNext", "types": ["node"], "skipLibCheck": true, @@ -18,9 +17,7 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noFallthroughCasesInSwitch": true }, "include": ["vite.config.ts"] }