Run playwright UI tests
This commit is contained in:
106
lib/server/taxonomy/metrics.ts
Normal file
106
lib/server/taxonomy/metrics.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
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<Filing['metrics']> {
|
||||
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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user