All files / src/browser/cia/loaders election.ts

90.47% Statements 38/42
80% Branches 36/45
100% Functions 6/6
94.59% Lines 35/37

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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123                                    4x 376x 6x       6x     4x 30x 30x 30x 30x 9x   1x                     10x         10x 53x 53x 53x 53x 53x   53x 1x     52x 52x   52x                         10x 30x 30x 30x 30x 30x 30x   107x     30x               3x     27x                   10x                          
/**
 * @module CIA/Loaders/Election
 * @category Intelligence Platform - Data Acquisition & Pipeline Management
 *
 * @description
 * Builds the election analysis payload from CIA CSV exports.
 * Parses seat forecasts and coalition scenarios with strict numeric/boolean
 * validation; rows missing required fields are dropped.
 *
 * @author Hack23 AB - Data Pipeline Engineering
 * @license Apache-2.0
 * @since 2026
 */
 
import type { ElectionAnalysis } from '../types.js';
import type { LoadCSV } from '../csv-utils.js';
import { CSV_SOURCES } from '../sources.js';
 
const toFiniteNumber = (value: unknown): number | undefined => {
  if (typeof value === 'number' && Number.isFinite(value)) return value;
  Iif (typeof value === 'string' && value.trim() !== '') {
    const num = Number(value);
    if (Number.isFinite(num)) return num;
  }
  return undefined;
};
 
const toBoolean = (value: unknown): boolean | undefined => {
  Iif (typeof value === 'boolean') return value;
  Eif (typeof value === 'string') {
    const normalized = value.trim().toLowerCase();
    if (normalized === 'true') return true;
    if (normalized === 'false') return false;
  }
  return undefined;
};
 
/**
 * Build `ElectionAnalysis` from CSV sources.
 * Replaces the legacy `election-analysis.json` static export.
 *
 * @param loadCSV - CSV loader closure
 * @returns Election forecast with seat predictions, coalition scenarios and key factors
 */
export async function loadElectionAnalysis(loadCSV: LoadCSV): Promise<ElectionAnalysis> {
  const [forecastRows, scenarioRows] = await Promise.all([
    loadCSV(CSV_SOURCES.electionForecast.local),
    loadCSV(CSV_SOURCES.coalitionScenarios.local)
  ]);
 
  const parties = forecastRows.flatMap(r => {
    const name = String(r.name ?? '').trim();
    const currentSeats = toFiniteNumber(r.currentSeats);
    const predictedSeats = toFiniteNumber(r.predictedSeats);
    const change = toFiniteNumber(r.change);
    const voteShare = toFiniteNumber(r.voteShare);
 
    if (!name || currentSeats === undefined || predictedSeats === undefined || change === undefined || voteShare === undefined) {
      return [];
    }
 
    const confidenceMin = toFiniteNumber(r.confidenceMin);
    const confidenceMax = toFiniteNumber(r.confidenceMax);
 
    return [{
      name,
      currentSeats,
      predictedSeats,
      change,
      voteShare,
      confidenceInterval:
        confidenceMin !== undefined && confidenceMax !== undefined
          ? { min: confidenceMin, max: confidenceMax }
          : undefined
    }];
  });
 
  const coalitionScenarios = scenarioRows.flatMap(r => {
    const name = String(r.name ?? '').trim();
    const probability = toFiniteNumber(r.probability);
    const totalSeats = toFiniteNumber(r.totalSeats);
    const majority = toBoolean(r.majority);
    const riskLevel = String(r.riskLevel ?? '').trim();
    const composition = String(r.composition ?? '')
      .split(',')
      .map(s => s.trim())
      .filter(Boolean);
 
    if (
      !name ||
      probability === undefined ||
      totalSeats === undefined ||
      majority === undefined ||
      !riskLevel ||
      composition.length === 0
    ) {
      return [];
    }
 
    return [{
      name,
      probability,
      composition,
      totalSeats,
      majority,
      riskLevel
    }];
  });
 
  return {
    forecast: { parties },
    coalitionScenarios,
    keyFactors: [
      'Economic conditions',
      'Immigration policy',
      'Climate change priorities',
      'Healthcare reform',
      'NATO membership impact'
    ],
    electionDate: '2026-09-13'
  };
}