Fix alignment_rate scale bugs across coalition and party dashboards#738
Fix alignment_rate scale bugs across coalition and party dashboards#738
Conversation
…:image dimensions - coalition-dashboard.ts: Remove erroneous /100 division on alignment_rate (already 0-1) - coalition-dashboard.ts: Fix influence calculation to use *10 instead of /10 - coalition-dashboard.ts: Add meaningful mock anomaly and annual votes fallback data - party-dashboard.ts: Fix Math.round(rate) to Math.round(rate * 100) for percentage display - party-dashboard.ts: Fix momentum filter to accept zero values - Fix og:image dimensions (140x140) in all 28 index*.html and dashboard/index*.html files - Add targeted tests for alignment rate processing and mock data quality Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
There was a problem hiding this comment.
Pull request overview
Fixes incorrect handling of alignment_rate values (0–1 scale) across coalition/party dashboards so displayed percentages, network strengths, and node sizing match CSV semantics; also improves mock-data fallbacks and corrects OG image metadata.
Changes:
- Correct
alignment_ratescaling and related derived calculations in coalition/party dashboards (remove/100, fix percentage rounding, fix influence formula, fix momentum filtering for0). - Add/expand tests around alignment rate scaling and momentum filtering.
- Update Open Graph
og:imagedimensions across index HTML pages to matchcia-icon-140.webp.
Reviewed changes
Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/party-dashboard.test.js | Adds tests for alignment rate percentage display and momentum filtering behavior |
| tests/coalition-dashboard.test.js | Adds tests for alignment rate usage, influence sizing, and mock data non-emptiness |
| src/browser/dashboards/party-dashboard.ts | Fixes displayed coalition strength percentage and momentum filtering |
| src/browser/dashboards/coalition-dashboard.ts | Fixes alignment scaling, node influence computation, and adds mock anomaly/vote fallback data |
| index.html | Updates og:image width/height metadata to 140x140 |
| index_ar.html | Updates og:image width/height metadata to 140x140 |
| index_da.html | Updates og:image width/height metadata to 140x140 |
| index_de.html | Updates og:image width/height metadata to 140x140 |
| index_es.html | Updates og:image width/height metadata to 140x140 |
| index_fi.html | Updates og:image width/height metadata to 140x140 |
| index_fr.html | Updates og:image width/height metadata to 140x140 |
| index_he.html | Updates og:image width/height metadata to 140x140 |
| index_ja.html | Updates og:image width/height metadata to 140x140 |
| index_ko.html | Updates og:image width/height metadata to 140x140 |
| index_nl.html | Updates og:image width/height metadata to 140x140 |
| index_no.html | Updates og:image width/height metadata to 140x140 |
| index_sv.html | Updates og:image width/height metadata to 140x140 |
| index_zh.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_ar.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_da.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_de.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_es.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_fi.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_fr.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_he.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_ja.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_ko.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_nl.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_no.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_sv.html | Updates og:image width/height metadata to 140x140 |
| dashboard/index_zh.html | Updates og:image width/height metadata to 140x140 |
Comments suppressed due to low confidence (2)
tests/coalition-dashboard.test.js:1
- These tests don’t exercise the production mock generators (
generateMockAnomalyData/generateMockAnnualVotesData); they only validate locally-constructed data. To cover the new fallback behavior, add tests that call the actual functions (or the public rendering paths that use them when CSV is unavailable) and assert the returned/consumed data is non-empty and shape-correct.
tests/party-dashboard.test.js:1 - These assertions re-encode the arithmetic directly instead of validating
party-dashboard.tsbehavior. Consider adding a test that feeds a minimal CSVRow into the code path that builds coalition entries and asserts the producedstrengthis84(and similarly, that momentum filtering includesmomentum: 0rows). That way the test will fail if the dashboard logic regresses even if the math in the test stays correct.
| nodes.forEach((target, j) => { | ||
| if (i < j) { | ||
| const strength = alignment && alignment[source.id] && alignment[source.id][target.id] ? alignment[source.id][target.id] / 100 : 0.5; | ||
| const strength = alignment && alignment[source.id] && alignment[source.id][target.id] ? alignment[source.id][target.id] : 0.5; |
There was a problem hiding this comment.
This truthy-check will treat a valid alignment value of 0 as missing and fall back to 0.5. Use an explicit numeric check (e.g., typeof value === 'number') or nullish coalescing so 0 remains a valid strength.
| const strength = alignment && alignment[source.id] && alignment[source.id][target.id] ? alignment[source.id][target.id] : 0.5; | |
| const rawStrength = alignment?.[source.id]?.[target.id]; | |
| const strength = typeof rawStrength === 'number' ? rawStrength : 0.5; |
There was a problem hiding this comment.
Fixed in 1025144. Replaced the truthy check with typeof rawStrength === 'number' so alignment value 0 is treated as valid. Added a test verifying 0 is not replaced by the 0.5 fallback.
| partyIds.forEach(party1 => { | ||
| partyIds.forEach(party2 => { | ||
| const alignmentVal = party1 === party2 ? 1.0 : ((dataCache.coalitionAlignment?.[party1]?.[party2]) ? dataCache.coalitionAlignment[party1][party2] / 100 : 0.5); | ||
| const alignmentVal = party1 === party2 ? 1.0 : ((dataCache.coalitionAlignment?.[party1]?.[party2]) ? dataCache.coalitionAlignment[party1][party2] : 0.5); |
There was a problem hiding this comment.
Same issue as the network strength: the conditional uses truthiness, so an alignment of 0 will be treated as absent and replaced with 0.5. Prefer an explicit typeof === 'number' check or ?? with a properly typed value retrieval.
| const alignmentVal = party1 === party2 ? 1.0 : ((dataCache.coalitionAlignment?.[party1]?.[party2]) ? dataCache.coalitionAlignment[party1][party2] : 0.5); | |
| const rawAlignment = dataCache.coalitionAlignment?.[party1]?.[party2]; | |
| const alignmentVal = party1 === party2 ? 1.0 : (typeof rawAlignment === 'number' ? rawAlignment : 0.5); |
There was a problem hiding this comment.
Fixed in 1025144. Same typeof rawAlignment === 'number' check applied to the heat map lookup, matching the suggested pattern.
| function generateMockAnomalyData(): AnomalyEntry[] { | ||
| // Provide realistic fallback data when CIA anomaly data is unavailable | ||
| const parties = Object.keys(PARTIES); | ||
| const anomalies: AnomalyEntry[] = []; | ||
| parties.forEach(party => { | ||
| const deviation = 0.5 + Math.random() * 3; | ||
| if (deviation > 1.0) { | ||
| anomalies.push({ | ||
| party, | ||
| date: '2024-06-15', | ||
| deviation: parseFloat(deviation.toFixed(2)), | ||
| severity: deviation > 3 ? 'critical' : deviation > 2 ? 'major' : 'minor' | ||
| }); | ||
| } | ||
| }); | ||
| return anomalies; | ||
| } | ||
|
|
||
| function generateMockAnnualVotesData(): Record<string, AnnualVoteEntry[]> { | ||
| // Provide realistic fallback data for annual vote trends | ||
| const data: Record<string, AnnualVoteEntry[]> = {}; | ||
| const partyBaselines: Record<string, number> = { | ||
| 'S': 50000, 'M': 35000, 'SD': 25000, 'V': 12000, | ||
| 'MP': 10000, 'C': 12000, 'L': 10000, 'KD': 10000 | ||
| }; | ||
| Object.keys(PARTIES).forEach(party => { | ||
| data[party] = []; | ||
| const baseline = partyBaselines[party] || 15000; | ||
| for (let year = 2002; year <= 2025; year++) { | ||
| const variation = 0.8 + Math.random() * 0.4; | ||
| data[party].push({ year, votes: Math.round(baseline * variation) }); | ||
| } | ||
| }); | ||
| return data; | ||
| } |
There was a problem hiding this comment.
These mock-data generators depend on Math.random(), making the UI (and any snapshots/visual regression tests) non-deterministic. Also, generateMockAnomalyData() can still return an empty array (low probability, but possible), reintroducing the blank-chart failure mode this change is trying to avoid. Consider generating deterministic (seeded or fixed) values and enforcing at least one anomaly entry when used as a fallback.
There was a problem hiding this comment.
Fixed in 1025144. Both mock generators are now fully deterministic — generateMockAnomalyData() uses a fixed deviation map and always returns exactly 8 entries (one per party), and generateMockAnnualVotesData() uses year-parity-based ±10% variation instead of Math.random().
|
@copilot apply changes based on the comments in this thread |
…ta deterministic - Use typeof === 'number' for alignment lookups so value 0 is not treated as missing - Replace Math.random() in mock data generators with deterministic fixed values - generateMockAnomalyData() now always returns exactly 8 entries (one per party) - generateMockAnnualVotesData() uses year-parity-based ±10% variation - Add tests for zero-alignment edge case and deterministic mock data Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
All three review comments addressed in
|
|
@copilot apply changes based on the comments in this thread |
…ixtures - Store both alignment[party1][party2] and alignment[party2][party1] when loading CSV (CSV has KD,M but not M,KD — symmetric matrix ensures all lookups succeed) - Add defensive reverse-pair fallback in network and heat map lookups - Update coalition test fixtures to match CSV pair direction (KD,M not M,KD) - Add test for reverse-pair lookup resolution - Update momentum test to use string values matching CSV parsing runtime types Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
All 5 review comments addressed in
|
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
| const party1 = row['party1']; const party2 = row['party2']; const alignmentRate = parseFloat(row['alignment_rate']); | ||
| if (!alignment[party1]) alignment[party1] = {}; | ||
| alignment[party1][party2] = alignmentRate; | ||
| if (!alignment[party2]) alignment[party2] = {}; | ||
| alignment[party2][party1] = alignmentRate; |
There was a problem hiding this comment.
When loading coalition alignment CSV, the new symmetric insert will also create entries like alignment['SD']['-'] for rows where party1 is '-' (these exist in cia-data/party/distribution_coalition_alignment.csv). Those '-' values are then included in renderCoalitionNetwork() node influence averaging via Object.values(alignment[id]), skewing influence/size even though '-' is not a real party node. Filter out rows where party1/party2 are not real parties (e.g. '-' or not in PARTIES) before storing (both directions).
There was a problem hiding this comment.
Fixed in 214f2f6. Added if (!PARTIES[party1] || !PARTIES[party2]) return; guard at the top of the CSV forEach loop, consistent with the party === '-' filter already used in fetchBehavioralData() and fetchDecisionTrendsData(). This prevents - and any other non-party values from entering the symmetric matrix and skewing influence averages.
|
@copilot apply changes based on the comments in this thread |
CSV rows where party1 or party2 is not in PARTIES (e.g., '-' for aggregate/independent) are now skipped before storing in the symmetric alignment matrix. This prevents polluting node influence averages with non-party alignment rates. Added test verifying '-' entries are excluded. Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Applied in |
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
tests/coalition-dashboard.test.js
Outdated
| it('should generate deterministic non-empty mock annual votes data', () => { | ||
| const parties = ['S', 'M', 'SD', 'V', 'MP', 'C', 'L', 'KD']; | ||
| const baseline = 15000; | ||
| const data = {}; | ||
| parties.forEach(party => { | ||
| data[party] = []; | ||
| for (let year = 2002; year <= 2025; year++) { | ||
| const variation = year % 2 === 0 ? 0.9 : 1.1; | ||
| data[party].push({ year, votes: Math.round(baseline * variation) }); | ||
| } |
There was a problem hiding this comment.
This test creates annual-votes data locally with a single baseline = 15000, but the production fallback generateMockAnnualVotesData() now uses per-party baselines (e.g., S: 50000) and is not invoked here. As written, the test won’t fail if the real generator changes (or regresses to random/empty output). Consider asserting against the real generator’s output (or shared exported helper) so the test actually protects the behavior being changed.
There was a problem hiding this comment.
Fixed in a5c42e4. Exported generateMockAnnualVotesData() from coalition-dashboard.ts and updated the test to import and call the real generator. The test now verifies determinism, non-emptiness, and per-party baselines from the production function (e.g., S baseline 50000 not generic 15000, M baseline 35000).
tests/party-dashboard.test.js
Outdated
| it('should convert 0-1 alignment_rate to percentage for display', () => { | ||
| // Real CSV: alignment_rate is 0.84 (meaning 84%) | ||
| const rate = 0.84; | ||
| const displayStrength = Math.round(rate * 100); | ||
| expect(displayStrength).toBe(84); |
There was a problem hiding this comment.
These new assertions validate standalone math (e.g., Math.round(rate * 100)) rather than exercising the dashboard implementation that was changed in src/browser/dashboards/party-dashboard.ts. As a result, the test would still pass if the production code regressed back to Math.round(rate) or if the DOM rendering used a different conversion. Consider testing the actual createCoalitionNetwork() output (e.g., by calling it with fixture rows and asserting rendered % text/width), or extracting/exporting a small helper used by both code and tests so the invariant is enforced against the real implementation.
There was a problem hiding this comment.
Fixed in a5c42e4. Extracted alignmentRateToPercent() as an exported helper from party-dashboard.ts, used it in createCoalitionNetwork(), and updated tests to import and call the real function. A regression back to Math.round(rate) in the production code would now break the test.
tests/coalition-dashboard.test.js
Outdated
| it('should generate deterministic non-empty mock anomaly data for all parties', () => { | ||
| // Deterministic mock deviations (no Math.random) | ||
| const deviations = { 'S': 1.85, 'M': 2.10, 'SD': 3.25, 'V': 1.45, 'MP': 2.70, 'C': 1.30, 'L': 1.95, 'KD': 2.50 }; | ||
| const parties = Object.keys(deviations); | ||
| const anomalies = parties.map(party => ({ | ||
| party, date: '2024-06-15', deviation: deviations[party], | ||
| severity: deviations[party] > 3 ? 'critical' : deviations[party] > 2 ? 'major' : 'minor' | ||
| })); |
There was a problem hiding this comment.
This mock-data test builds its own anomalies array instead of calling the production fallback (generateMockAnomalyData() in src/browser/dashboards/coalition-dashboard.ts). That means it won’t catch regressions where the real generator becomes non-deterministic again or returns an empty array. Consider invoking the real generator (or an exported helper) and asserting its determinism/non-emptiness directly.
There was a problem hiding this comment.
Fixed in a5c42e4. Exported generateMockAnomalyData() from coalition-dashboard.ts and updated the test to import and call the real generator. The test now asserts determinism, non-emptiness, and known deviation values (e.g., SD.deviation === 3.25, SD.severity === 'critical') directly from the production function.
|
@copilot apply changes based on the comments in this thread |
…nctions - Export generateMockAnomalyData() and generateMockAnnualVotesData() from coalition-dashboard.ts so tests can invoke the real generators - Export alignmentRateToPercent() from party-dashboard.ts and use it in createCoalitionNetwork() — tests now exercise the real conversion logic - Coalition mock data tests import and call the real generators, asserting determinism, non-emptiness, and known per-party baselines - Party alignment rate tests import and call alignmentRateToPercent(), ensuring the real implementation matches expected percentages Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
All 3 review comments addressed in
|
🔍 Lighthouse Performance Audit
📥 Download full Lighthouse report Budget Compliance: Performance budgets enforced via |
CIA platform CSV
alignment_ratevalues are 0–1 (0.84 = 84%), but multiple dashboards applied an additional/100, rendering all alignment data ~100× too small.Data processing fixes
/100on alignment_rate in network link strength and heat map rendering — values like 0.84 were becoming 0.0084avg/10 + 3→avg*10 + 3so 0–1 inputs produce meaningful size variation (was collapsing all nodes to ~3.06)KD,Mbut notM,KD), so both directions are now populated to prevent silent fallback to 0.5 on reverse lookupsparty1 = '-'for aggregate/independent data) before storing in the symmetric alignment matrix, preventing non-party entries from polluting node influence averagesMath.round(rate)→Math.round(rate * 100)—Math.round(0.84)= 1, so all coalitions displayed as "1%"alignmentRateToPercent()as an exported helper used by both production code and tests, ensuring the conversion logic is tested against the real implementationrow.momentum(rejects 0 as falsy) →row.momentum !== undefined && row.momentum !== ''Mock data fallbacks
generateMockAnomalyData()andgenerateMockAnnualVotesData()now use deterministic fixed values (noMath.random()) and always return non-empty results, preventing blank charts and ensuring visual regression test stabilityHTML metadata
og:image:width/heightfrom 1200×630 to 140×140 across all 28 index HTML files to match actualcia-icon-140.webpdimensionsTests
generateMockAnomalyData()andgenerateMockAnnualVotesData()generators, asserting determinism, non-emptiness, and known per-party baselines — regressions to random/empty output will now fail testsalignmentRateToPercent()helper — a regression back toMath.round(rate)will now fail testsKD,MnotM,KD)🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.