Fix financial taxonomy snapshot normalization
This commit is contained in:
@@ -78,6 +78,49 @@ type FilingDocumentRef = {
|
||||
primaryDocument: string | null;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function hasRequiredDerivedRowArrays(row: unknown) {
|
||||
return isRecord(row)
|
||||
&& Array.isArray(row.sourceConcepts)
|
||||
&& Array.isArray(row.sourceRowKeys)
|
||||
&& Array.isArray(row.sourceFactIds);
|
||||
}
|
||||
|
||||
function hasRequiredDetailRowArrays(row: unknown) {
|
||||
return isRecord(row)
|
||||
&& Array.isArray(row.sourceFactIds)
|
||||
&& Array.isArray(row.dimensionsSummary);
|
||||
}
|
||||
|
||||
function isValidStatementBundlePayload(value: unknown): value is StandardizedStatementBundlePayload {
|
||||
if (!isRecord(value) || !Array.isArray(value.rows) || !isRecord(value.detailRows)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!value.rows.every((row) => hasRequiredDerivedRowArrays(row))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.values(value.detailRows).every((rows) => (
|
||||
Array.isArray(rows) && rows.every((row) => hasRequiredDetailRowArrays(row))
|
||||
));
|
||||
}
|
||||
|
||||
function isValidRatioBundlePayload(value: unknown): value is Pick<CompanyFinancialStatementsResponse, 'ratioRows' | 'trendSeries' | 'categories'> {
|
||||
return isRecord(value)
|
||||
&& Array.isArray(value.ratioRows)
|
||||
&& value.ratioRows.every((row) => hasRequiredDerivedRowArrays(row));
|
||||
}
|
||||
|
||||
function isValidKpiBundlePayload(value: unknown): value is Pick<CompanyFinancialStatementsResponse, 'kpiRows' | 'trendSeries' | 'categories'> {
|
||||
return isRecord(value)
|
||||
&& Array.isArray(value.kpiRows)
|
||||
&& value.kpiRows.every((row) => isRecord(row) && Array.isArray(row.sourceConcepts) && Array.isArray(row.sourceFactIds));
|
||||
}
|
||||
|
||||
function safeTicker(input: string) {
|
||||
return input.trim().toUpperCase();
|
||||
}
|
||||
@@ -350,7 +393,7 @@ function detailConceptIdentity(row: DetailFinancialRow) {
|
||||
}
|
||||
|
||||
function detailMergeKey(row: DetailFinancialRow) {
|
||||
const dimensionsKey = [...row.dimensionsSummary]
|
||||
const dimensionsKey = [...(row.dimensionsSummary ?? [])]
|
||||
.map((value) => value.trim().toLowerCase())
|
||||
.filter((value) => value.length > 0)
|
||||
.sort((left, right) => left.localeCompare(right))
|
||||
@@ -713,10 +756,9 @@ async function buildStatementSurfaceBundle(input: {
|
||||
|
||||
if (
|
||||
cached
|
||||
&& Array.isArray((cached as Partial<StandardizedStatementBundlePayload>).rows)
|
||||
&& typeof (cached as Partial<StandardizedStatementBundlePayload>).detailRows === 'object'
|
||||
&& isValidStatementBundlePayload(cached)
|
||||
) {
|
||||
return cached as StandardizedStatementBundlePayload;
|
||||
return cached;
|
||||
}
|
||||
|
||||
const statement = surfaceToStatementKind(input.surfaceKind);
|
||||
@@ -794,8 +836,8 @@ async function buildRatioSurfaceBundle(input: {
|
||||
snapshots: input.snapshots
|
||||
});
|
||||
|
||||
if (cached) {
|
||||
return cached as Pick<CompanyFinancialStatementsResponse, 'ratioRows' | 'trendSeries' | 'categories'>;
|
||||
if (cached && isValidRatioBundlePayload(cached)) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const pricesByDate = await getHistoricalClosingPrices(input.ticker, input.periods.map((period) => latestPeriodDate(period)));
|
||||
@@ -846,8 +888,8 @@ async function buildKpiSurfaceBundle(input: {
|
||||
snapshots: input.snapshots
|
||||
});
|
||||
|
||||
if (cached) {
|
||||
return cached as Pick<CompanyFinancialStatementsResponse, 'kpiRows' | 'trendSeries' | 'categories'>;
|
||||
if (cached && isValidKpiBundlePayload(cached)) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const persistedRows = aggregatePersistedKpiRows({
|
||||
|
||||
Reference in New Issue
Block a user