Automate issuer overlay creation from ticker searches
This commit is contained in:
@@ -14,7 +14,8 @@ import type {
|
||||
ResearchMemoConviction,
|
||||
ResearchMemoRating,
|
||||
ResearchMemoSection,
|
||||
TaskStatus
|
||||
TaskStatus,
|
||||
TickerAutomationSource
|
||||
} from '@/lib/types';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { requireAuthenticatedSession } from '@/lib/server/auth-session';
|
||||
@@ -71,6 +72,8 @@ import {
|
||||
upsertWatchlistItemRecord
|
||||
} from '@/lib/server/repos/watchlist';
|
||||
import { answerSearchQuery, searchKnowledgeBase } from '@/lib/server/search';
|
||||
import { shouldQueueTickerAutomation } from '@/lib/server/issuer-overlays';
|
||||
import { ensureIssuerOverlayRow } from '@/lib/server/repos/issuer-overlays';
|
||||
import {
|
||||
enqueueTask,
|
||||
findOrEnqueueTask,
|
||||
@@ -91,6 +94,7 @@ const FINANCIAL_STATEMENT_KINDS: FinancialStatementKind[] = [
|
||||
'income',
|
||||
'balance',
|
||||
'cash_flow',
|
||||
'disclosure',
|
||||
'equity',
|
||||
'comprehensive_income'
|
||||
];
|
||||
@@ -99,6 +103,8 @@ const FINANCIAL_SURFACES: FinancialSurfaceKind[] = [
|
||||
'income_statement',
|
||||
'balance_sheet',
|
||||
'cash_flow_statement',
|
||||
'equity_statement',
|
||||
'disclosures',
|
||||
'ratios',
|
||||
'segments_kpis',
|
||||
'adjusted',
|
||||
@@ -112,6 +118,7 @@ const RESEARCH_ARTIFACT_KINDS: ResearchArtifactKind[] = ['filing', 'ai_report',
|
||||
const RESEARCH_ARTIFACT_SOURCES: ResearchArtifactSource[] = ['system', 'user'];
|
||||
const RESEARCH_MEMO_RATINGS: ResearchMemoRating[] = ['strong_buy', 'buy', 'hold', 'sell'];
|
||||
const RESEARCH_MEMO_CONVICTIONS: ResearchMemoConviction[] = ['low', 'medium', 'high'];
|
||||
const TICKER_AUTOMATION_SOURCES: TickerAutomationSource[] = ['analysis', 'financials', 'search', 'graphing', 'research'];
|
||||
const RESEARCH_MEMO_SECTIONS: ResearchMemoSection[] = [
|
||||
'thesis',
|
||||
'variant_view',
|
||||
@@ -212,6 +219,10 @@ function surfaceFromLegacyStatement(statement: FinancialStatementKind): Financia
|
||||
return 'balance_sheet';
|
||||
case 'cash_flow':
|
||||
return 'cash_flow_statement';
|
||||
case 'disclosure':
|
||||
return 'disclosures';
|
||||
case 'equity':
|
||||
return 'equity_statement';
|
||||
default:
|
||||
return 'income_statement';
|
||||
}
|
||||
@@ -296,6 +307,12 @@ function asResearchMemoSection(value: unknown) {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function asTickerAutomationSource(value: unknown) {
|
||||
return TICKER_AUTOMATION_SOURCES.includes(value as TickerAutomationSource)
|
||||
? value as TickerAutomationSource
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function formatLabel(value: string) {
|
||||
return value
|
||||
.split('_')
|
||||
@@ -361,6 +378,42 @@ async function queueAutoFilingSync(
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureTickerAutomationTask(input: {
|
||||
userId: string;
|
||||
ticker: string;
|
||||
source: TickerAutomationSource;
|
||||
}) {
|
||||
void input.source;
|
||||
const ticker = input.ticker.trim().toUpperCase();
|
||||
await ensureIssuerOverlayRow(ticker);
|
||||
|
||||
if (!(await shouldQueueTickerAutomation(ticker))) {
|
||||
return {
|
||||
queued: false,
|
||||
task: null
|
||||
};
|
||||
}
|
||||
|
||||
const watchlistItem = await getWatchlistItemByTicker(input.userId, ticker);
|
||||
const task = await findOrEnqueueTask({
|
||||
userId: input.userId,
|
||||
taskType: 'sync_filings',
|
||||
payload: buildSyncFilingsPayload({
|
||||
ticker,
|
||||
limit: defaultFinancialSyncLimit(),
|
||||
category: watchlistItem?.category,
|
||||
tags: watchlistItem?.tags
|
||||
}),
|
||||
priority: 89,
|
||||
resourceKey: `sync_filings:${ticker}`
|
||||
});
|
||||
|
||||
return {
|
||||
queued: true,
|
||||
task
|
||||
};
|
||||
}
|
||||
|
||||
const authHandler = ({ request }: { request: Request }) => auth.handler(request);
|
||||
|
||||
async function checkWorkflowBackend() {
|
||||
@@ -821,6 +874,45 @@ export const app = new Elysia({ prefix: '/api' })
|
||||
|
||||
return Response.json({ insight });
|
||||
})
|
||||
.post('/tickers/ensure', async ({ body }) => {
|
||||
const { session, response } = await requireAuthenticatedSession();
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const payload = asRecord(body);
|
||||
const ticker = typeof payload.ticker === 'string' ? payload.ticker.trim().toUpperCase() : '';
|
||||
const source = asTickerAutomationSource(payload.source);
|
||||
|
||||
if (!ticker) {
|
||||
return jsonError('ticker is required');
|
||||
}
|
||||
|
||||
if (!source) {
|
||||
return jsonError('source is required');
|
||||
}
|
||||
|
||||
try {
|
||||
return Response.json(await ensureTickerAutomationTask({
|
||||
userId: session.user.id,
|
||||
ticker,
|
||||
source
|
||||
}));
|
||||
} catch (error) {
|
||||
return jsonError(asErrorMessage(error, `Failed to ensure ticker automation for ${ticker}`));
|
||||
}
|
||||
}, {
|
||||
body: t.Object({
|
||||
ticker: t.String({ minLength: 1 }),
|
||||
source: t.Union([
|
||||
t.Literal('analysis'),
|
||||
t.Literal('financials'),
|
||||
t.Literal('search'),
|
||||
t.Literal('graphing'),
|
||||
t.Literal('research')
|
||||
])
|
||||
})
|
||||
})
|
||||
.get('/research/workspace', async ({ query }) => {
|
||||
const { session, response } = await requireAuthenticatedSession();
|
||||
if (response) {
|
||||
@@ -1492,6 +1584,8 @@ export const app = new Elysia({ prefix: '/api' })
|
||||
t.Literal('income_statement'),
|
||||
t.Literal('balance_sheet'),
|
||||
t.Literal('cash_flow_statement'),
|
||||
t.Literal('equity_statement'),
|
||||
t.Literal('disclosures'),
|
||||
t.Literal('ratios'),
|
||||
t.Literal('segments_kpis'),
|
||||
t.Literal('adjusted'),
|
||||
@@ -1506,6 +1600,7 @@ export const app = new Elysia({ prefix: '/api' })
|
||||
t.Literal('income'),
|
||||
t.Literal('balance'),
|
||||
t.Literal('cash_flow'),
|
||||
t.Literal('disclosure'),
|
||||
t.Literal('equity'),
|
||||
t.Literal('comprehensive_income')
|
||||
])),
|
||||
|
||||
Reference in New Issue
Block a user