import type { Filing } from '@/lib/types'; import type { TaxonomyFact } from '@/lib/server/taxonomy/types'; const METRIC_LOCAL_NAME_PRIORITY = { revenue: [ 'Revenues', 'SalesRevenueNet', 'RevenueFromContractWithCustomerExcludingAssessedTax', 'TotalRevenuesAndOtherIncome' ], netIncome: ['NetIncomeLoss', 'ProfitLoss'], totalAssets: ['Assets'], cash: [ 'CashAndCashEquivalentsAtCarryingValue', 'CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents' ], debtDirect: [ 'DebtAndFinanceLeaseLiabilities', 'Debt', 'LongTermDebtAndCapitalLeaseObligations' ], debtCurrent: [ 'DebtCurrent', 'ShortTermBorrowings', 'LongTermDebtCurrent' ], debtNonCurrent: [ 'LongTermDebtNoncurrent', 'LongTermDebt', 'DebtNoncurrent' ] } as const; function normalizeDateToEpoch(value: string | null) { if (!value) { return Number.NaN; } return Date.parse(value); } function sameLocalName(left: string, right: string) { return left.toLowerCase() === right.toLowerCase(); } function pickPreferredFact(facts: TaxonomyFact[]) { const ordered = [...facts].sort((left, right) => { const leftDimensionScore = left.isDimensionless ? 1 : 0; const rightDimensionScore = right.isDimensionless ? 1 : 0; if (leftDimensionScore !== rightDimensionScore) { return rightDimensionScore - leftDimensionScore; } const leftDate = normalizeDateToEpoch(left.periodEnd ?? left.periodInstant); const rightDate = normalizeDateToEpoch(right.periodEnd ?? right.periodInstant); if (Number.isFinite(leftDate) && Number.isFinite(rightDate) && leftDate !== rightDate) { return rightDate - leftDate; } return Math.abs(right.value) - Math.abs(left.value); }); return ordered[0] ?? null; } function pickBestFact(facts: TaxonomyFact[], localNames: readonly string[]) { for (const localName of localNames) { const matches = facts.filter((fact) => sameLocalName(fact.localName, localName)); if (matches.length === 0) { continue; } return pickPreferredFact(matches); } return null; } function sumIfBoth(left: number | null, right: number | null) { if (left === null || right === null) { return null; } return left + right; } export function deriveTaxonomyMetrics(facts: TaxonomyFact[]): NonNullable { const revenue = pickBestFact(facts, METRIC_LOCAL_NAME_PRIORITY.revenue)?.value ?? null; const netIncome = pickBestFact(facts, METRIC_LOCAL_NAME_PRIORITY.netIncome)?.value ?? null; const totalAssets = pickBestFact(facts, METRIC_LOCAL_NAME_PRIORITY.totalAssets)?.value ?? null; const cash = pickBestFact(facts, METRIC_LOCAL_NAME_PRIORITY.cash)?.value ?? null; const directDebt = pickBestFact(facts, METRIC_LOCAL_NAME_PRIORITY.debtDirect)?.value ?? null; const debt = directDebt ?? sumIfBoth( pickBestFact(facts, METRIC_LOCAL_NAME_PRIORITY.debtCurrent)?.value ?? null, pickBestFact(facts, METRIC_LOCAL_NAME_PRIORITY.debtNonCurrent)?.value ?? null ); return { revenue, netIncome, totalAssets, cash, debt }; }