Fix financial taxonomy snapshot normalization

This commit is contained in:
2026-03-13 19:01:56 -04:00
parent b1c9c0ef08
commit 30977dc15f
16 changed files with 1273 additions and 156 deletions

View File

@@ -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({