From dd9d4baa0dfae6c2384e7bacf9b880590f78183f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 08:31:55 +0000 Subject: [PATCH 1/4] Initial plan From f53d0f7ac0b95fe40fa36e38f27fb13f817fe057 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 09:01:52 +0000 Subject: [PATCH 2/4] fix: resolve all 226 ESLint no-explicit-any warnings across scripts/ and src/browser/ Replace all `any` types with proper typed alternatives: - D3 force simulation callbacks use proper node/link interfaces - Chart.js tooltip contexts use structural types - Global library declarations use typed imports - Record replaced with Record or Record - Error catches use `unknown` type - (globalThis as any).X replaced with typed casts Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/78aa95e5-f033-4691-91f3-a8f29e9bcd60 Co-authored-by: pethers <1726836+pethers@users.noreply.github.com> --- scripts/coalition-dashboard/charts.ts | 8 +- scripts/coalition-dashboard/data.ts | 2 +- scripts/coalition-dashboard/scenarios.ts | 16 +-- scripts/committees-dashboard.ts | 54 +++++----- scripts/committees-dashboard/charts.ts | 18 ++-- scripts/committees-dashboard/data.ts | 18 ++-- scripts/committees-dashboard/init.ts | 6 +- scripts/committees-dashboard/table.ts | 12 +-- scripts/committees-dashboard/types.ts | 16 +-- scripts/generate-e2e-results-html.cjs | 2 +- src/browser/cia/visualizations.ts | 2 +- src/browser/dashboards/anomaly-detection.ts | 32 +++--- src/browser/dashboards/coalition-dashboard.ts | 66 ++++++------ .../dashboards/committees-dashboard.ts | 102 +++++++++--------- src/browser/dashboards/election-cycle.ts | 20 ++-- src/browser/dashboards/ministry-dashboard.ts | 10 +- src/browser/dashboards/party-dashboard.ts | 16 +-- .../dashboards/politician-dashboard.ts | 8 +- src/browser/dashboards/pre-election.ts | 12 +-- src/browser/dashboards/risk-dashboard.ts | 4 +- src/browser/shared/global-libs.ts | 42 ++++++++ src/browser/shared/index.ts | 1 + src/browser/shared/register-globals.ts | 6 +- 23 files changed, 258 insertions(+), 215 deletions(-) create mode 100644 src/browser/shared/global-libs.ts diff --git a/scripts/coalition-dashboard/charts.ts b/scripts/coalition-dashboard/charts.ts index 6cc4201a7e..35ac2c7a09 100644 --- a/scripts/coalition-dashboard/charts.ts +++ b/scripts/coalition-dashboard/charts.ts @@ -10,7 +10,7 @@ * @license Apache-2.0 */ -declare const d3: any; +declare const d3: typeof import('d3'); import type { PartyNode, CoalitionLink, HeatMapDatum, PartyConfig, CoalitionAlignment, DataCache } from './types.js'; // These symbols are provided by the enclosing IIFE in coalition-dashboard.ts at runtime @@ -32,7 +32,7 @@ function renderCoalitionNetwork(): void { const height: number = 600; // Create SVG - const svg: d3.Selection = d3.select('#coalitionNetwork') + const svg: d3.Selection = d3.select('#coalitionNetwork') .append('svg') .attr('width', width) .attr('height', height) @@ -230,14 +230,14 @@ function renderAlignmentHeatMap(): void { const innerWidth: number = width - margin.left - margin.right; const innerHeight: number = height - margin.top - margin.bottom; - const svg: d3.Selection = d3.select('#alignmentHeatMap') + const svg: d3.Selection = d3.select('#alignmentHeatMap') .append('svg') .attr('width', width) .attr('height', height) .attr('viewBox', `0 0 ${width} ${height}`) .attr('style', 'max-width: 100%; height: auto;'); - const g: d3.Selection = svg.append('g') + const g: d3.Selection = svg.append('g') .attr('transform', `translate(${margin.left},${margin.top})`); const partyIds: string[] = Object.keys(PARTIES); diff --git a/scripts/coalition-dashboard/data.ts b/scripts/coalition-dashboard/data.ts index b79a572c9d..620163b18a 100644 --- a/scripts/coalition-dashboard/data.ts +++ b/scripts/coalition-dashboard/data.ts @@ -10,7 +10,7 @@ * @license Apache-2.0 */ -declare const d3: any; +declare const d3: typeof import('d3'); import type { PartyConfig, PartyNode, CoalitionLink, VotingAnomaly, AnnualVoteEntry, DataCache, DataFiles, DataConfig, diff --git a/scripts/coalition-dashboard/scenarios.ts b/scripts/coalition-dashboard/scenarios.ts index 95f94b3cf7..157eb0ca0d 100644 --- a/scripts/coalition-dashboard/scenarios.ts +++ b/scripts/coalition-dashboard/scenarios.ts @@ -14,8 +14,8 @@ * @license Apache-2.0 */ -declare const Chart: any; -declare const d3: any; +declare const Chart: { new(ctx: CanvasRenderingContext2D | null, config: Record): unknown }; +declare const d3: typeof import('d3'); import type { PartyNode, CoalitionLink, VotingAnomaly, PartyConfig, DataCache, BehavioralPatterns, AnnualVotes, AnnualVoteEntry, CoalitionAlignment } from './types.js'; // These symbols are provided by the enclosing IIFE in coalition-dashboard.ts at runtime @@ -33,7 +33,7 @@ function renderVotingAnomalyChart(): void { const anomalies: VotingAnomaly[] = dataCache.votingAnomalies || []; // Prepare data - const datasets: any[] = Object.keys(PARTIES).map((partyId: string) => { + const datasets: Record[] = Object.keys(PARTIES).map((partyId: string) => { const partyData: VotingAnomaly[] = anomalies.filter((a: VotingAnomaly) => a.party === partyId); return { @@ -63,7 +63,7 @@ function renderVotingAnomalyChart(): void { }, tooltip: { callbacks: { - label: function(context: any): string { + label: function(context: { parsed: { x: number; y: number }; dataset: { label?: string }; label: string }): string { const date: Date = new Date(context.parsed.x); return `${context.dataset.label}: Deviation ${context.parsed.y.toFixed(2)} on ${date.toLocaleDateString()}`; } @@ -115,7 +115,7 @@ function renderBehavioralPatternsChart(): void { const behavioral: BehavioralPatterns = dataCache.behavioralPatterns || {}; const partyIds: string[] = Object.keys(PARTIES); - const data: any = { + const data: Record = { labels: partyIds.map((id: string) => PARTIES[id].name), datasets: [{ label: 'Party Consistency Score (%)', @@ -144,7 +144,7 @@ function renderBehavioralPatternsChart(): void { }, tooltip: { callbacks: { - label: function(context: any): string { + label: function(context: { parsed: { x: number; y: number }; dataset: { label?: string }; label: string }): string { return `Consistency: ${context.parsed.x.toFixed(1)}%`; } } @@ -199,7 +199,7 @@ function renderDecisionTrendsChart(): void { console.log('📊 Using generated data for decision trends'); } - const datasets: any[] = Object.keys(PARTIES).map((partyId: string) => { + const datasets: Record[] = Object.keys(PARTIES).map((partyId: string) => { let data: number[]; if (useRealData && annualVotes[partyId]) { @@ -249,7 +249,7 @@ function renderDecisionTrendsChart(): void { mode: 'index', intersect: false, callbacks: { - label: function(context: any): string { + label: function(context: { parsed: { x: number; y: number }; dataset: { label?: string }; label: string }): string { return context.dataset.label + ': ' + context.parsed.y.toLocaleString() + ' votes'; } } diff --git a/scripts/committees-dashboard.ts b/scripts/committees-dashboard.ts index 0163963447..0623e16abd 100644 --- a/scripts/committees-dashboard.ts +++ b/scripts/committees-dashboard.ts @@ -43,9 +43,9 @@ // always read from globalThis at the point of use, not at IIFE load time. // This ensures late-loaded libraries (defer/async/dynamic ordering) are // always picked up correctly. - let d3: any; - let Chart: any; - let Papa: any; + let d3: typeof import('d3'); + let Chart: { new(ctx: CanvasRenderingContext2D | null, config: Record): unknown }; + let Papa: { parse(input: string, config?: Record): { data: string[][] } }; // ============================================== // CONFIGURATION @@ -100,7 +100,7 @@ // ============================================== class DataManager { - private cache: Map[]>; + private cache: Map[]>; constructor() { this.cache = new Map(); @@ -110,9 +110,9 @@ * Fetch CSV data with caching support * @param {string} key - Cache key identifier * @param {string|string[]} url - URL(s) to fetch data from (tries in order if array) - * @returns {Promise[]>} Parsed CSV data + * @returns {Promise[]>} Parsed CSV data */ - async fetchData(key: string, url: string | string[]): Promise[]> { + async fetchData(key: string, url: string | string[]): Promise[]> { // Check cache first if (CONFIG.cache.enabled) { const cached = this.getCached(key); @@ -154,7 +154,7 @@ console.warn(`[DataManager] CSV parsing warnings for ${key}:`, parsed.errors); } - const data: Record[] = parsed.data; + const data: Record[] = parsed.data; // Cache the result if (CONFIG.cache.enabled) { @@ -178,9 +178,9 @@ /** * Get cached data if valid * @param {string} key - Cache key - * @returns {Record[] | null} Cached data or null + * @returns {Record[] | null} Cached data or null */ - getCached(key: string): Record[] | null { + getCached(key: string): Record[] | null { const cacheKey: string = CONFIG.cache.prefix + key; try { const cached: string | null = localStorage.getItem(cacheKey); @@ -212,9 +212,9 @@ /** * Set cached data * @param {string} key - Cache key - * @param {Record[]} data - Data to cache + * @param {Record[]} data - Data to cache */ - setCached(key: string, data: Record[]): void { + setCached(key: string, data: Record[]): void { const cacheKey: string = CONFIG.cache.prefix + key; const cacheData: CacheEntry = { data: data, @@ -364,10 +364,10 @@ // Update positions on simulation tick this.simulation.on('tick', () => { link - .attr('x1', (d: any) => d.source.x) - .attr('y1', (d: any) => d.source.y) - .attr('x2', (d: any) => d.target.x) - .attr('y2', (d: any) => d.target.y); + .attr('x1', (d: { source: { x: number }; target: { x: number } }) => d.source.x) + .attr('y1', (d: { source: { y: number }; target: { y: number } }) => d.source.y) + .attr('x2', (d: { source: { x: number }; target: { x: number } }) => d.target.x) + .attr('y2', (d: { source: { y: number }; target: { y: number } }) => d.target.y); node .attr('transform', (d: NetworkNode) => `translate(${d.x},${d.y})`); @@ -433,8 +433,8 @@ const prodDiff: number = Math.abs(nodes[i].productivity - nodes[j].productivity); if (prodDiff < 20) { links.push({ - source: nodes[i].id as any, - target: nodes[j].id as any, + source: nodes[i].id as string, + target: nodes[j].id as string, value: 10 - prodDiff / 2 }); } @@ -484,8 +484,8 @@ nodes.forEach((node: NetworkNode) => { // Handle both string and object types for source/target const connections: number = links.filter((l: NetworkLink) => { - const sourceId: string = typeof l.source === 'string' ? l.source : (l.source as any)?.id ?? ''; - const targetId: string = typeof l.target === 'string' ? l.target : (l.target as any)?.id ?? ''; + const sourceId: string = typeof l.source === 'string' ? l.source : (l.source as { id: string })?.id ?? ''; + const targetId: string = typeof l.target === 'string' ? l.target : (l.target as { id: string })?.id ?? ''; return sourceId === node.id || targetId === node.id; }).length; html += ` @@ -777,7 +777,7 @@ // ============================================== class ChartJSVisualizations { - private charts: Record; + private charts: Record; constructor() { this.charts = {}; @@ -863,7 +863,7 @@ }, tooltip: { callbacks: { - label: function(context: any): string { + label: function(context: { parsed: { x: number; y: number }; dataset: { label?: string }; label: string }): string { return `Productivity: ${context.parsed.y.toFixed(1)}`; } } @@ -1004,7 +1004,7 @@ }, tooltip: { callbacks: { - label: function(context: any): string { + label: function(context: { parsed: { x: number; y: number }; dataset: { label?: string }; label: string }): string { return `${context.dataset.label}: ${context.parsed.y.toFixed(1)}%`; } } @@ -1080,7 +1080,7 @@ const availableYears: string[] = Object.keys(yearQuarterData).sort().slice(-3); const yearColors: string[] = ['#1e88e5', '#43a047', '#fb8c00']; - const datasets: any[] = availableYears.length > 0 + const datasets: Record[] = availableYears.length > 0 ? availableYears.map((year: string, idx: number) => ({ label: year, data: [1, 2, 3, 4].map((q: number) => yearQuarterData[year][q] || 0), @@ -1127,7 +1127,7 @@ }, tooltip: { callbacks: { - label: function(context: any): string { + label: function(context: { parsed: { x: number; y: number }; dataset: { label?: string }; label: string }): string { return `${context.dataset.label}: ${context.parsed.y} activity score`; } } @@ -1218,9 +1218,9 @@ try { // Resolve browser globals here so late-loaded libraries are picked up. - d3 = (globalThis as any).d3; - Chart = (globalThis as any).Chart; - Papa = (globalThis as any).Papa; + d3 = (globalThis as unknown as { d3: typeof import('d3') }).d3; + Chart = (globalThis as unknown as { Chart: { new(ctx: CanvasRenderingContext2D | null, config: Record): unknown } }).Chart; + Papa = (globalThis as unknown as { Papa: { parse(input: string, config?: Record): { data: string[][] } } }).Papa; // Check if required libraries are loaded if (typeof d3 === 'undefined') { diff --git a/scripts/committees-dashboard/charts.ts b/scripts/committees-dashboard/charts.ts index 1d89bc68e9..d81c267aa5 100644 --- a/scripts/committees-dashboard/charts.ts +++ b/scripts/committees-dashboard/charts.ts @@ -10,7 +10,7 @@ * @license Apache-2.0 */ -declare const d3: any; +declare const d3: typeof import('d3'); import type { CommitteeData, NetworkNode, NetworkLink, HeatMapCell, HeatMapData } from './types.js'; // ============================================== @@ -115,10 +115,10 @@ export class NetworkDiagram { // Update positions on simulation tick this.simulation.on('tick', () => { link - .attr('x1', (d: any) => d.source.x) - .attr('y1', (d: any) => d.source.y) - .attr('x2', (d: any) => d.target.x) - .attr('y2', (d: any) => d.target.y); + .attr('x1', (d: NetworkLink) => (d.source as NetworkNode).x ?? 0) + .attr('y1', (d: NetworkLink) => (d.source as NetworkNode).y ?? 0) + .attr('x2', (d: NetworkLink) => (d.target as NetworkNode).x ?? 0) + .attr('y2', (d: NetworkLink) => (d.target as NetworkNode).y ?? 0); node .attr('transform', (d: NetworkNode) => `translate(${d.x},${d.y})`); @@ -184,8 +184,8 @@ export class NetworkDiagram { const prodDiff: number = Math.abs(nodes[i].productivity - nodes[j].productivity); if (prodDiff < 20) { links.push({ - source: nodes[i].id as any, - target: nodes[j].id as any, + source: nodes[i].id as string, + target: nodes[j].id as string, value: 10 - prodDiff / 2 }); } @@ -235,8 +235,8 @@ export class NetworkDiagram { nodes.forEach((node: NetworkNode) => { // Handle both string and object types for source/target const connections: number = links.filter((l: NetworkLink) => { - const sourceId: string = typeof l.source === 'string' ? l.source : (l.source as any)?.id ?? ''; - const targetId: string = typeof l.target === 'string' ? l.target : (l.target as any)?.id ?? ''; + const sourceId: string = typeof l.source === 'string' ? l.source : (l.source as NetworkNode)?.id ?? ''; + const targetId: string = typeof l.target === 'string' ? l.target : (l.target as NetworkNode)?.id ?? ''; return sourceId === node.id || targetId === node.id; }).length; html += ` diff --git a/scripts/committees-dashboard/data.ts b/scripts/committees-dashboard/data.ts index 275c2659ca..d81d8d358d 100644 --- a/scripts/committees-dashboard/data.ts +++ b/scripts/committees-dashboard/data.ts @@ -8,7 +8,7 @@ * @license Apache-2.0 */ -declare const Papa: any; +declare const Papa: { parse(input: string, config?: Record): { data: string[][] } }; import type { AppConfig, CommitteeData, @@ -71,7 +71,7 @@ export const CONFIG: AppConfig = { // ============================================== export class DataManager { - private cache: Map[]>; + private cache: Map[]>; constructor() { this.cache = new Map(); @@ -81,9 +81,9 @@ export class DataManager { * Fetch CSV data with caching support * @param {string} key - Cache key identifier * @param {string|string[]} url - URL(s) to fetch data from (tries in order if array) - * @returns {Promise[]>} Parsed CSV data + * @returns {Promise[]>} Parsed CSV data */ - async fetchData(key: string, url: string | string[]): Promise[]> { + async fetchData(key: string, url: string | string[]): Promise[]> { // Check cache first if (CONFIG.cache.enabled) { const cached = this.getCached(key); @@ -125,7 +125,7 @@ export class DataManager { console.warn(`[DataManager] CSV parsing warnings for ${key}:`, parsed.errors); } - const data: Record[] = parsed.data; + const data: Record[] = parsed.data; // Cache the result if (CONFIG.cache.enabled) { @@ -149,9 +149,9 @@ export class DataManager { /** * Get cached data if valid * @param {string} key - Cache key - * @returns {Record[] | null} Cached data or null + * @returns {Record[] | null} Cached data or null */ - getCached(key: string): Record[] | null { + getCached(key: string): Record[] | null { const cacheKey: string = CONFIG.cache.prefix + key; try { const cached: string | null = localStorage.getItem(cacheKey); @@ -183,9 +183,9 @@ export class DataManager { /** * Set cached data * @param {string} key - Cache key - * @param {Record[]} data - Data to cache + * @param {Record[]} data - Data to cache */ - setCached(key: string, data: Record[]): void { + setCached(key: string, data: Record[]): void { const cacheKey: string = CONFIG.cache.prefix + key; const cacheData: CacheEntry = { data: data, diff --git a/scripts/committees-dashboard/init.ts b/scripts/committees-dashboard/init.ts index 2a81b94d4c..b1a09acc4e 100644 --- a/scripts/committees-dashboard/init.ts +++ b/scripts/committees-dashboard/init.ts @@ -12,9 +12,9 @@ * @license Apache-2.0 */ -declare const d3: any; -declare const Chart: any; -declare const Papa: any; +declare const d3: typeof import('d3'); +declare const Chart: { new(ctx: CanvasRenderingContext2D | null, config: Record): unknown }; +declare const Papa: { parse(input: string, config?: Record): { data: string[][] } }; import type { CommitteeData } from './types.js'; import { CONFIG, DataManager } from './data.js'; diff --git a/scripts/committees-dashboard/table.ts b/scripts/committees-dashboard/table.ts index 634e0676f2..b3e63d81f9 100644 --- a/scripts/committees-dashboard/table.ts +++ b/scripts/committees-dashboard/table.ts @@ -10,7 +10,7 @@ * @license Apache-2.0 */ -declare const Chart: any; +declare const Chart: { new(ctx: CanvasRenderingContext2D | null, config: Record): unknown }; import { CONFIG } from './data.js'; import type { CommitteeData, CommitteeDefinition, ProductivityMatrixRow, AnnualDocumentRow, SeasonalPatternRow } from './types.js'; @@ -19,7 +19,7 @@ import type { CommitteeData, CommitteeDefinition, ProductivityMatrixRow, AnnualD // ============================================== export class ChartJSVisualizations { - private charts: Record; + private charts: Record; constructor() { this.charts = {}; @@ -105,7 +105,7 @@ export class ChartJSVisualizations { }, tooltip: { callbacks: { - label: function(context: any): string { + label: function(context: { parsed: { x: number; y: number }; dataset: { label?: string }; label: string }): string { return `Productivity: ${context.parsed.y.toFixed(1)}`; } } @@ -246,7 +246,7 @@ export class ChartJSVisualizations { }, tooltip: { callbacks: { - label: function(context: any): string { + label: function(context: { parsed: { x: number; y: number }; dataset: { label?: string }; label: string }): string { return `${context.dataset.label}: ${context.parsed.y.toFixed(1)}%`; } } @@ -322,7 +322,7 @@ export class ChartJSVisualizations { const availableYears: string[] = Object.keys(yearQuarterData).sort().slice(-3); const yearColors: string[] = ['#1e88e5', '#43a047', '#fb8c00']; - const datasets: any[] = availableYears.length > 0 + const datasets: Record[] = availableYears.length > 0 ? availableYears.map((year: string, idx: number) => ({ label: year, data: [1, 2, 3, 4].map((q: number) => yearQuarterData[year][q] || 0), @@ -369,7 +369,7 @@ export class ChartJSVisualizations { }, tooltip: { callbacks: { - label: function(context: any): string { + label: function(context: { parsed: { x: number; y: number }; dataset: { label?: string }; label: string }): string { return `${context.dataset.label}: ${context.parsed.y} activity score`; } } diff --git a/scripts/committees-dashboard/types.ts b/scripts/committees-dashboard/types.ts index 7a92a6702f..5f5b5ea718 100644 --- a/scripts/committees-dashboard/types.ts +++ b/scripts/committees-dashboard/types.ts @@ -13,13 +13,13 @@ import type { SimulationNodeDatum, SimulationLinkDatum } from 'd3'; // Chart.js and Papa Parse are loaded as browser globals via script tags -declare const Chart: any; +declare const Chart: { new(ctx: CanvasRenderingContext2D | null, config: Record): unknown }; declare const Papa: { parse(input: string, config?: { header?: boolean; dynamicTyping?: boolean; skipEmptyLines?: boolean; - }): { data: Record[]; errors: { message: string }[] }; + }): { data: Record[]; errors: { message: string }[] }; }; // ============================================== @@ -75,14 +75,14 @@ export interface ProductivityMatrixRow { committee_code?: string; year?: string | number; productivity_level?: string; - [key: string]: any; + [key: string]: string; } export interface AnnualDocumentRow { committee?: string; year?: string | number; doc_count?: string | number; - [key: string]: any; + [key: string]: string; } export interface SeasonalPatternRow { @@ -91,14 +91,14 @@ export interface SeasonalPatternRow { median?: string | number; total_ballots?: string | number; value?: string | number; - [key: string]: any; + [key: string]: string; } export interface CommitteeData { productivityMatrix: ProductivityMatrixRow[]; - committeeDecisions: Record[]; + committeeDecisions: Record[]; annualDocuments: AnnualDocumentRow[]; - ballotSummary: Record[]; + ballotSummary: Record[]; seasonalPatterns: SeasonalPatternRow[]; } @@ -129,7 +129,7 @@ export interface HeatMapData { } export interface CacheEntry { - data: Record[]; + data: Record[]; timestamp: number; } diff --git a/scripts/generate-e2e-results-html.cjs b/scripts/generate-e2e-results-html.cjs index d882982205..9f908d6c06 100644 --- a/scripts/generate-e2e-results-html.cjs +++ b/scripts/generate-e2e-results-html.cjs @@ -71,7 +71,7 @@ try { /* ── Spec duration parsing ── */ const durationMatch = log.match(/(\d+:\d+)\s+(\d+)\s+(\d+)\s+(\d+|-)\s+(\d+|-)\s+(\d+|-)\s*$/m); - const totalDuration = durationMatch ? durationMatch[1] : '-'; + const _totalDuration = durationMatch ? durationMatch[1] : '-'; const specTableRows = specLines.map(s => { const icon = s.failing > 0 ? '❌' : '✅'; diff --git a/src/browser/cia/visualizations.ts b/src/browser/cia/visualizations.ts index 8c673a19ea..3b3295227a 100644 --- a/src/browser/cia/visualizations.ts +++ b/src/browser/cia/visualizations.ts @@ -41,7 +41,7 @@ import type { /* Global library reference (loaded via + @@ -8598,7 +8598,7 @@ · Built by Hack23 AB

- +