feat(dashboard): Meet Kevin TypeScript types + API client
This commit is contained in:
parent
bfa7a503da
commit
cafcaad502
4 changed files with 218 additions and 11 deletions
100
dashboard/src/api/meetKevin.ts
Normal file
100
dashboard/src/api/meetKevin.ts
Normal 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;
|
||||
114
dashboard/src/types/meetKevin.ts
Normal file
114
dashboard/src/types/meetKevin.ts
Normal 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;
|
||||
}[];
|
||||
}
|
||||
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue