Expand financials surfaces with ratios, KPIs, and cadence support
- Add bundled financial modeling pipeline (ratios, KPI dimensions/notes, trend series, standardization) - Introduce company financial bundles storage (Drizzle migration + repo wiring) - Refactor financials page/API/query flow to use surfaceKind + cadence and new response shapes
This commit is contained in:
@@ -43,6 +43,102 @@ export async function getQuote(ticker: string): Promise<number> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getQuoteOrNull(ticker: string): Promise<number | null> {
|
||||
const normalizedTicker = ticker.trim().toUpperCase();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${YAHOO_BASE}/${normalizedTicker}?interval=1d&range=1d`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; FiscalClone/3.0)'
|
||||
},
|
||||
cache: 'no-store'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const payload = await response.json() as {
|
||||
chart?: {
|
||||
result?: Array<{ meta?: { regularMarketPrice?: number } }>;
|
||||
};
|
||||
};
|
||||
|
||||
const price = payload.chart?.result?.[0]?.meta?.regularMarketPrice;
|
||||
return typeof price === 'number' && Number.isFinite(price) ? price : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getHistoricalClosingPrices(ticker: string, dates: string[]) {
|
||||
const normalizedTicker = ticker.trim().toUpperCase();
|
||||
const normalizedDates = dates
|
||||
.map((value) => {
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isFinite(parsed)
|
||||
? { raw: value, iso: new Date(parsed).toISOString().slice(0, 10), epoch: parsed }
|
||||
: null;
|
||||
})
|
||||
.filter((entry): entry is { raw: string; iso: string; epoch: number } => entry !== null);
|
||||
|
||||
if (normalizedDates.length === 0) {
|
||||
return {} as Record<string, number | null>;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${YAHOO_BASE}/${normalizedTicker}?interval=1d&range=10y`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; FiscalClone/3.0)'
|
||||
},
|
||||
cache: 'no-store'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return Object.fromEntries(normalizedDates.map((entry) => [entry.raw, null]));
|
||||
}
|
||||
|
||||
const payload = await response.json() as {
|
||||
chart?: {
|
||||
result?: Array<{
|
||||
timestamp?: number[];
|
||||
indicators?: {
|
||||
quote?: Array<{
|
||||
close?: Array<number | null>;
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
const result = payload.chart?.result?.[0];
|
||||
const timestamps = result?.timestamp ?? [];
|
||||
const closes = result?.indicators?.quote?.[0]?.close ?? [];
|
||||
const points = timestamps
|
||||
.map((timestamp, index) => {
|
||||
const close = closes[index];
|
||||
if (typeof close !== 'number' || !Number.isFinite(close)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
epoch: timestamp * 1000,
|
||||
close
|
||||
};
|
||||
})
|
||||
.filter((entry): entry is { epoch: number; close: number } => entry !== null);
|
||||
|
||||
return Object.fromEntries(normalizedDates.map((entry) => {
|
||||
const point = [...points]
|
||||
.reverse()
|
||||
.find((candidate) => candidate.epoch <= entry.epoch) ?? null;
|
||||
return [entry.raw, point?.close ?? null];
|
||||
}));
|
||||
} catch {
|
||||
return Object.fromEntries(normalizedDates.map((entry) => [entry.raw, null]));
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPriceHistory(ticker: string): Promise<Array<{ date: string; close: number }>> {
|
||||
const normalizedTicker = ticker.trim().toUpperCase();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user