Implement fiscal-style research MVP flows
Some checks failed
PR Checks / typecheck-and-build (push) Has been cancelled

This commit is contained in:
2026-03-07 09:51:18 -05:00
parent f69e5b671b
commit 52136271d3
26 changed files with 2719 additions and 243 deletions

View File

@@ -4,12 +4,16 @@ import type {
CompanyAiReportDetail,
CompanyAnalysis,
CompanyFinancialStatementsResponse,
CoveragePriority,
CoverageStatus,
Filing,
Holding,
FinancialHistoryWindow,
FinancialStatementKind,
PortfolioInsight,
PortfolioSummary,
ResearchJournalEntry,
ResearchJournalEntryType,
Task,
TaskStatus,
TaskTimeline,
@@ -99,6 +103,36 @@ async function unwrapData<T>(result: TreatyResult, fallback: string) {
return payload as T;
}
async function requestJson<T>(input: {
path: string;
method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';
body?: unknown;
}, fallback: string) {
const response = await fetch(`${API_BASE}${input.path}`, {
method: input.method ?? 'GET',
credentials: 'include',
cache: 'no-store',
headers: input.body === undefined ? undefined : {
'content-type': 'application/json'
},
body: input.body === undefined ? undefined : JSON.stringify(input.body)
});
const payload = await response.json().catch(() => null);
if (!response.ok) {
throw new ApiError(
extractErrorMessage({ value: payload }, fallback),
response.status
);
}
if (payload === null || payload === undefined) {
throw new ApiError(fallback, response.status);
}
return payload as T;
}
export async function getMe() {
const result = await client.api.me.get();
return await unwrapData<{ user: User }>(result, 'Unable to fetch session');
@@ -115,16 +149,80 @@ export async function upsertWatchlistItem(input: {
sector?: string;
category?: string;
tags?: string[];
status?: CoverageStatus;
priority?: CoveragePriority;
lastReviewedAt?: string;
}) {
const result = await client.api.watchlist.post(input);
return await unwrapData<{ item: WatchlistItem }>(result, 'Unable to save watchlist item');
}
export async function updateWatchlistItem(id: number, input: {
companyName?: string;
sector?: string;
category?: string;
tags?: string[];
status?: CoverageStatus;
priority?: CoveragePriority;
lastReviewedAt?: string;
}) {
return await requestJson<{ item: WatchlistItem; statusChangeJournalCreated: boolean }>({
path: `/api/watchlist/${id}`,
method: 'PATCH',
body: input
}, 'Unable to update watchlist item');
}
export async function deleteWatchlistItem(id: number) {
const result = await client.api.watchlist[id].delete();
return await unwrapData<{ success: boolean }>(result, 'Unable to delete watchlist item');
}
export async function listResearchJournal(ticker: string) {
const result = await client.api.research.journal.get({
$query: {
ticker: ticker.trim().toUpperCase()
}
});
return await unwrapData<{ entries: ResearchJournalEntry[] }>(result, 'Unable to fetch research journal');
}
export async function createResearchJournalEntry(input: {
ticker: string;
accessionNumber?: string;
entryType: ResearchJournalEntryType;
title?: string;
bodyMarkdown: string;
metadata?: Record<string, unknown>;
}) {
return await requestJson<{ entry: ResearchJournalEntry }>({
path: '/api/research/journal',
method: 'POST',
body: {
...input,
ticker: input.ticker.trim().toUpperCase()
}
}, 'Unable to create journal entry');
}
export async function updateResearchJournalEntry(id: number, input: {
title?: string;
bodyMarkdown?: string;
metadata?: Record<string, unknown>;
}) {
return await requestJson<{ entry: ResearchJournalEntry }>({
path: `/api/research/journal/${id}`,
method: 'PATCH',
body: input
}, 'Unable to update journal entry');
}
export async function deleteResearchJournalEntry(id: number) {
const result = await client.api.research.journal[id].delete();
return await unwrapData<{ success: boolean }>(result, 'Unable to delete journal entry');
}
export async function listHoldings() {
const result = await client.api.portfolio.holdings.get();
return await unwrapData<{ holdings: Holding[] }>(result, 'Unable to fetch holdings');
@@ -140,11 +238,22 @@ export async function upsertHolding(input: {
shares: number;
avgCost: number;
currentPrice?: number;
companyName?: string;
}) {
const result = await client.api.portfolio.holdings.post(input);
return await unwrapData<{ holding: Holding }>(result, 'Unable to save holding');
}
export async function updateHolding(id: number, input: {
shares?: number;
avgCost?: number;
currentPrice?: number;
companyName?: string;
}) {
const result = await client.api.portfolio.holdings[id].patch(input);
return await unwrapData<{ holding: Holding }>(result, 'Unable to update holding');
}
export async function deleteHolding(id: number) {
const result = await client.api.portfolio.holdings[id].delete();
return await unwrapData<{ success: boolean }>(result, 'Unable to delete holding');