import type { DimensionBreakdownRow, FinancialStatementKind, FinancialStatementPeriod, StandardizedFinancialRow, TaxonomyFactRow, TaxonomyStatementRow } from '@/lib/types'; function valueOrNull(values: Record, periodId: string) { return periodId in values ? values[periodId] : null; } function sumValues(values: Array, treatNullAsZero = false) { if (!treatNullAsZero && values.some((value) => value === null)) { return null; } return values.reduce((sum, value) => sum + (value ?? 0), 0); } export function factMatchesPeriod(fact: TaxonomyFactRow, period: FinancialStatementPeriod) { if (period.periodStart) { return fact.periodStart === period.periodStart && fact.periodEnd === period.periodEnd; } return (fact.periodInstant ?? fact.periodEnd) === period.periodEnd; } export function buildDimensionBreakdown( facts: TaxonomyFactRow[], periods: FinancialStatementPeriod[], faithfulRows: TaxonomyStatementRow[], standardizedRows: StandardizedFinancialRow[] ) { const periodByFilingId = new Map(); for (const period of periods) { periodByFilingId.set(period.filingId, period); } const faithfulRowByKey = new Map(faithfulRows.map((row) => [row.key, row])); const standardizedRowsBySource = new Map(); for (const row of standardizedRows) { for (const sourceRowKey of row.sourceRowKeys) { const existing = standardizedRowsBySource.get(sourceRowKey); if (existing) { existing.push(row); } else { standardizedRowsBySource.set(sourceRowKey, [row]); } } } const map = new Map(); const pushRow = (key: string, row: DimensionBreakdownRow) => { const existing = map.get(key); if (existing) { existing.push(row); } else { map.set(key, [row]); } }; for (const fact of facts) { if (fact.dimensions.length === 0) { continue; } const period = periodByFilingId.get(fact.filingId) ?? null; if (!period || !factMatchesPeriod(fact, period)) { continue; } const faithfulRow = faithfulRowByKey.get(fact.conceptKey) ?? null; const standardizedMatches = standardizedRowsBySource.get(fact.conceptKey) ?? []; for (const dimension of fact.dimensions) { const faithfulDimensionRow: DimensionBreakdownRow = { rowKey: fact.conceptKey, concept: fact.qname, sourceRowKey: fact.conceptKey, sourceLabel: faithfulRow?.label ?? null, periodId: period.id, axis: dimension.axis, member: dimension.member, value: fact.value, unit: fact.unit, provenanceType: 'taxonomy' }; pushRow(fact.conceptKey, faithfulDimensionRow); for (const standardizedRow of standardizedMatches) { pushRow(standardizedRow.key, { ...faithfulDimensionRow, rowKey: standardizedRow.key }); } } } return map.size > 0 ? Object.fromEntries(map.entries()) : null; } function cloneStandardizedRows(rows: StandardizedFinancialRow[]) { return rows.map((row) => ({ ...row, values: { ...row.values }, sourceConcepts: [...row.sourceConcepts], sourceRowKeys: [...row.sourceRowKeys], sourceFactIds: [...row.sourceFactIds], resolvedSourceRowKeys: { ...row.resolvedSourceRowKeys } })); } export function buildLtmStandardizedRows( quarterlyRows: StandardizedFinancialRow[], quarterlyPeriods: FinancialStatementPeriod[], ltmPeriods: FinancialStatementPeriod[], statement: Extract ) { const sortedQuarterlyPeriods = [...quarterlyPeriods].sort((left, right) => { return Date.parse(left.periodEnd ?? left.filingDate) - Date.parse(right.periodEnd ?? right.filingDate); }); const result = cloneStandardizedRows(quarterlyRows).map((row) => ({ ...row, values: {} as Record, resolvedSourceRowKeys: {} as Record })); for (const row of result) { const source = quarterlyRows.find((entry) => entry.key === row.key); if (!source) { continue; } for (const ltmPeriod of ltmPeriods) { const anchorIndex = sortedQuarterlyPeriods.findIndex((period) => `ltm:${period.id}` === ltmPeriod.id); if (anchorIndex < 3) { continue; } const slice = sortedQuarterlyPeriods.slice(anchorIndex - 3, anchorIndex + 1); const sourceValues = slice.map((period) => source.values[period.id] ?? null); row.values[ltmPeriod.id] = statement === 'balance' || statement === 'equity' ? sourceValues[sourceValues.length - 1] ?? null : sumValues(sourceValues); row.resolvedSourceRowKeys[ltmPeriod.id] = source.formulaKey ? null : source.resolvedSourceRowKeys[slice[slice.length - 1]?.id ?? ''] ?? null; } } return result; }