import type { FinancialStatementPeriod, StructuredKpiRow, TaxonomyFactRow } from '@/lib/types'; import type { KpiDefinition } from '@/lib/server/financials/kpi-registry'; import { factMatchesPeriod } from '@/lib/server/financials/standardize'; function normalizeSegmentToken(value: string) { return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, ''); } function humanizeMember(value: string) { const source = value.split(':').pop() ?? value; return source .replace(/Member$/i, '') .replace(/([a-z])([A-Z])/g, '$1 $2') .replace(/_/g, ' ') .trim(); } function factMatchesDefinition(fact: TaxonomyFactRow, definition: KpiDefinition) { if (definition.preferredConceptNames && !definition.preferredConceptNames.includes(fact.localName)) { return false; } if (!definition.preferredAxisIncludes || definition.preferredAxisIncludes.length === 0) { return fact.dimensions.length > 0; } return fact.dimensions.some((dimension) => { const axisMatch = definition.preferredAxisIncludes?.some((token) => dimension.axis.toLowerCase().includes(token.toLowerCase())) ?? false; const memberMatch = definition.preferredMemberIncludes && definition.preferredMemberIncludes.length > 0 ? definition.preferredMemberIncludes.some((token) => dimension.member.toLowerCase().includes(token.toLowerCase())) : true; return axisMatch && memberMatch; }); } function categoryForDefinition(definition: KpiDefinition, axis: string) { if (definition.key === 'segment_revenue' && /geo|country|region|area/i.test(axis)) { return 'geographic_mix'; } return definition.category; } export function extractStructuredKpisFromDimensions(input: { facts: TaxonomyFactRow[]; periods: FinancialStatementPeriod[]; definitions: KpiDefinition[]; }) { const rowMap = new Map(); const orderByKey = new Map(); input.definitions.forEach((definition, index) => { orderByKey.set(definition.key, (index + 1) * 10); }); for (const definition of input.definitions) { for (const fact of input.facts) { if (fact.dimensions.length === 0 || !factMatchesDefinition(fact, definition)) { continue; } const matchedPeriod = input.periods.find((period) => period.filingId === fact.filingId && factMatchesPeriod(fact, period)); if (!matchedPeriod) { continue; } for (const dimension of fact.dimensions) { const axis = dimension.axis; const member = dimension.member; const normalizedAxis = normalizeSegmentToken(axis); const normalizedMember = normalizeSegmentToken(member); const key = `${definition.key}__${normalizedAxis}__${normalizedMember}`; const labelSuffix = humanizeMember(member); const existing = rowMap.get(key); if (existing) { existing.values[matchedPeriod.id] = fact.value; if (!existing.sourceConcepts.includes(fact.qname)) { existing.sourceConcepts.push(fact.qname); } if (!existing.sourceFactIds.includes(fact.id)) { existing.sourceFactIds.push(fact.id); } continue; } rowMap.set(key, { key, label: `${definition.label} - ${labelSuffix}`, category: categoryForDefinition(definition, axis), unit: definition.unit, order: orderByKey.get(definition.key) ?? 999, segment: labelSuffix || null, axis, member, values: { [matchedPeriod.id]: fact.value }, sourceConcepts: [fact.qname], sourceFactIds: [fact.id], provenanceType: 'taxonomy', hasDimensions: true }); } } } const rows = [...rowMap.values()].sort((left, right) => { if (left.order !== right.order) { return left.order - right.order; } return left.label.localeCompare(right.label); }); const marginRows = new Map(); for (const row of rows.filter((entry) => entry.category === 'segment_profit')) { const revenueKey = row.key.replace(/^segment_profit__/, 'segment_revenue__'); const revenueRow = rowMap.get(revenueKey); if (!revenueRow) { continue; } const values: Record = {}; for (const period of input.periods) { const revenue = revenueRow.values[period.id] ?? null; const profit = row.values[period.id] ?? null; values[period.id] = revenue === null || profit === null || revenue === 0 ? null : profit / revenue; } marginRows.set(row.key.replace(/^segment_profit__/, 'segment_margin__'), { key: row.key.replace(/^segment_profit__/, 'segment_margin__'), label: row.label.replace(/^Segment Profit/, 'Segment Margin'), category: 'segment_margin', unit: 'percent', order: 25, segment: row.segment, axis: row.axis, member: row.member, values, sourceConcepts: [...new Set([...row.sourceConcepts, ...revenueRow.sourceConcepts])], sourceFactIds: [...new Set([...row.sourceFactIds, ...revenueRow.sourceFactIds])], provenanceType: 'taxonomy', hasDimensions: true }); } return [...rows, ...marginRows.values()].sort((left, right) => { if (left.order !== right.order) { return left.order - right.order; } return left.label.localeCompare(right.label); }); }