All files / scripts/statskontoret/extractors rows-to-records.ts

92.1% Statements 35/38
86.66% Branches 26/30
84.61% Functions 11/13
96.55% Lines 28/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61                                    8x 8x 7x 20x   7x 7x 16x 16x 16x 56x 56x 56x   16x   7x       6x 5x 5x 10x 13x 16x 7x   5x 2x 4x     2x   4x 4x   2x        
/**
 * @module scripts/statskontoret/extractors/rows-to-records
 * @description Convert raw 2-D sheet rows into header-keyed records.
 *
 * Used by every Statskontoret domain workflow (headcount, budget outturn,
 * budget time-series) — the headers vary across workbooks but the parsing
 * approach is identical, so it lives in its own module.
 *
 * @author Hack23 AB
 * @license Apache-2.0
 */
 
import { normalizeKey } from '../internal/text.js';
 
export function rowsToRecords(
  rows: readonly (readonly string[])[],
  headerRowIndex?: number,
): Record<string, string>[] {
  const resolvedHeaderIndex = headerRowIndex ?? findLikelyHeaderRow(rows);
  if (resolvedHeaderIndex < 0) return [];
  const headers = rows[resolvedHeaderIndex].map(
    (header, index) => header.trim() || `column_${index + 1}`,
  );
  const records: Record<string, string>[] = [];
  for (const row of rows.slice(resolvedHeaderIndex + 1)) {
    const record: Record<string, string> = {};
    let hasValue = false;
    for (let i = 0; i < headers.length; i++) {
      const value = row[i]?.trim() ?? '';
      Eif (value) hasValue = true;
      record[headers[i]] = value;
    }
    Eif (hasValue) records.push(record);
  }
  return records;
}
 
export function findLikelyHeaderRow(rows: readonly (readonly string[])[]): number {
  for (let i = 0; i < rows.length; i++) {
    const normalized = rows[i].map(normalizeKey);
    const headcountScore = [
      normalized.some((cell) => cell.includes('myndighet')),
      normalized.some((cell) => cell.includes('departement')),
      normalized.some((cell) => cell.includes('arsarbetskrafter') || cell === 'aa'),
      normalized.some((cell) => cell === 'ar' || cell === 'year'),
    ].filter(Boolean).length;
    if (headcountScore >= 2) return i;
    const budgetScore = [
      normalized.some((cell) => cell.includes('utfall') || cell.includes('outturn')),
      normalized.some(
        (cell) =>
          cell.includes('inkomst') || cell.includes('utgift') || cell.includes('anslag'),
      ),
      normalized.some((cell) => cell === 'ar' || cell.includes('kalenderar') || cell === 'year'),
      normalized.some((cell) => cell.includes('budget') || cell.includes('belopp')),
    ].filter(Boolean).length;
    Eif (budgetScore >= 2) return i;
  }
  return rows.findIndex((row) => row.filter((cell) => cell.trim()).length >= 2);
}