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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | 3x 3x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 3x 7x 7x 6x 4x 2x 1x 4x 2x | /**
* @module scripts/statskontoret/domain/headcount
* @description Headcount/authority-count aggregation derived from
* Statskontoret myndighetsförteckning workbooks.
*
* Consumes the records produced by `rowsToRecords` and groups by
* `(year, department)` returning a sorted, deterministic time series.
*
* @author Hack23 AB
* @license Apache-2.0
*/
import { rowsToRecords } from '../extractors/rows-to-records.js';
import {
buildRecordLookup,
findField,
parseStatskontoretOptionalInt,
parseStatskontoretSwedishNumber,
roundOneDecimal,
} from '../internal/text.js';
import type {
StatskontoretHeadcountOptions,
StatskontoretHeadcountRow,
StatskontoretWorkbook,
} from '../types.js';
export function aggregateHeadcountByDepartment(
records: readonly Record<string, string>[],
fallbackYear?: number,
): StatskontoretHeadcountRow[] {
const aggregate = new Map<string, { headcount: number; authorities: Set<string> }>();
for (const record of records) {
const lookup = buildRecordLookup(record);
const year =
parseStatskontoretOptionalInt(findField(lookup, ['år', 'ar', 'year']) ?? '') ?? fallbackYear;
const department = findField(lookup, [
'departement',
'departementstillhörighet',
'departementstillhorighet',
])?.trim();
const headcountValue = parseStatskontoretSwedishNumber(
findField(lookup, ['årsarbetskrafter', 'arsarbetskrafter', 'åa', 'aa']) ?? '',
);
Iif (!year || !department || headcountValue === undefined) continue;
const authority = findField(lookup, ['myndighet', 'myndighetsnamn', 'namn'])?.trim() ?? '';
const key = `${year}::${department}`;
const current = aggregate.get(key) ?? { headcount: 0, authorities: new Set<string>() };
current.headcount += headcountValue;
Eif (authority) current.authorities.add(authority);
aggregate.set(key, current);
}
return [...aggregate.entries()]
.map(([key, value]) => {
const [yearRaw, department] = key.split('::');
return {
year: Number.parseInt(yearRaw, 10),
department,
headcount: roundOneDecimal(value.headcount),
authorityCount: value.authorities.size,
};
})
.sort((a, b) => a.year - b.year || a.department.localeCompare(b.department, 'sv'));
}
export function buildHeadcountTimeSeries(
workbook: StatskontoretWorkbook,
options: StatskontoretHeadcountOptions = {},
): StatskontoretHeadcountRow[] {
const sheet = options.sheetNamePattern
? workbook.sheets.find((candidate) => options.sheetNamePattern?.test(candidate.name))
: workbook.sheets.find((candidate) => /förteckning|forteckning/i.test(candidate.name)) ??
workbook.sheets[0];
if (!sheet) return [];
return aggregateHeadcountByDepartment(rowsToRecords(sheet.rows), options.fallbackYear);
}
|