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": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"types": ["vite/client"],
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
|
|
@ -20,9 +18,7 @@
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"erasableSyntaxOnly": true,
|
"noFallthroughCasesInSwitch": true
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedSideEffectImports": true
|
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
"target": "ES2022",
|
||||||
"target": "ES2023",
|
"lib": ["ES2022"],
|
||||||
"lib": ["ES2023"],
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
@ -18,9 +17,7 @@
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"erasableSyntaxOnly": true,
|
"noFallthroughCasesInSwitch": true
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedSideEffectImports": true
|
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue