feat(dashboard): Meet Kevin TypeScript types + API client

This commit is contained in:
Viktor Barzin 2026-05-21 19:56:13 +00:00
parent bfa7a503da
commit cafcaad502
4 changed files with 218 additions and 11 deletions

View file

@ -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<PipelineHealth> {
const res = await client.get<PipelineHealth>('/meet-kevin/health');
return res.data;
},
async dashboard(): Promise<DashboardData> {
const res = await client.get<DashboardData>('/meet-kevin/dashboard');
return res.data;
},
async listVideos(params: ListVideosParams): Promise<ListVideosResponse> {
const res = await client.get<ListVideosResponse>('/meet-kevin/videos', {
params,
});
return res.data;
},
async getVideo(id: number): Promise<VideoDetail> {
const res = await client.get<VideoDetail>(`/meet-kevin/videos/${id}`);
return res.data;
},
async getTranscript(id: number): Promise<Transcript> {
const res = await client.get<Transcript>(
`/meet-kevin/videos/${id}/transcript`
);
return res.data;
},
async reprocess(
id: number,
stage: 'captions' | 'analysis' | 'auto'
): Promise<Record<string, unknown>> {
const res = await client.post<Record<string, unknown>>(
`/meet-kevin/videos/${id}/reprocess`,
{ stage }
);
return res.data;
},
async listStocks(): Promise<ListStocksResponse> {
const res = await client.get<ListStocksResponse>('/meet-kevin/stocks');
return res.data;
},
async getStock(symbol: string): Promise<GetStockResponse> {
const res = await client.get<GetStockResponse>(
`/meet-kevin/stocks/${symbol}`
);
return res.data;
},
async getStockTimeline(
symbol: string,
bucket: 'day' | 'week'
): Promise<Record<string, unknown>> {
const res = await client.get<Record<string, unknown>>(
`/meet-kevin/stocks/${symbol}/timeline`,
{ params: { bucket } }
);
return res.data;
},
};
export default meetKevinApi;

View file

@ -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<string, number>;
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;
}[];
}

View file

@ -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"]
}

View file

@ -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"]
}