A House Divided A House DividedDocumentation
Changelog
Game Design/Elections & Campaigns

Demographic & Election Implementation Audit

Last updated 2026-08-21
Source files

Date: 2025-02-23
Scope: 12 archetype system, group-level competitive allocation, poll, elections, admin

Historical implementation audit. The live vote path now uses the granular Layer-1 electorate and swing-flow general-election engine. Use Granular electorate as shipped and Election engine for current behavior. The checks below describe the retired archetype-era implementation and should not be used as current operational guidance.


✅ What's Working#

Election Engine#

Poll#

NPP Election Behavior#

State Page Demographics Tab#

Admin Demographics Manager#

Debug & Utils#


⚠️ Issues & Gaps#

1. CRITICAL: demographicDefaults not updated by reseed/seed-demographics ✅ FIXED#

Problem: Game reset copies from demographicDefaultsstateDemographics. The reseed API and seed-demographics script only updated stateDemographics and demographicCategories. They did not update demographicDefaults.

Fix applied: Reseed API and seed-demographics.ts now also upsert demographicDefaults with the same state demographics.

2. Actions page still says "Full Demographic Poll"#

Location: lines 114-122

Current: label: "Full Demographic Poll", flavor: "A comprehensive breakdown across every demographic category...", effect: "Full demographic breakdown"

Fix: Align with poll page: "Full Poll", "voter group" terminology.

3. Dashboard "demographic group" copy#

Location: line 685

Current: "Commission a poll to see your support breakdown by demographic group."

Fix: Change to "voter group".

4. Poll API success message#

Location: line 538

Current: "Commissioned a full demographic poll for $..."

Fix: "Commissioned a full poll for $..."

5. pollHelpers.ts - legacy CATEGORY_ICONS#

Location: lines 58-60

Current: race: "👥", gender: "⚧", education: "🎓", wealth: "💰", age: "📅", ideology: "🧭", voterGroups: "🗳️"

Impact: Low. Old keys are unused with 12-archetype schema. voterGroups is used. No functional bug, but dead code.

Optional: Remove old keys or leave for backward compatibility if old polls exist.

6. DemographicCategoryId type includes legacy IDs#

Location:

Current: "race" | "gender" | "education" | "wealth" | "age" | "ideology" | "voterGroups"

Impact: None. Type allows both; schema mismatch detection in poll uses string arrays. Fine to keep for migration/backward compatibility.

7. StatePageTabsDemographics - "demographic" wording#

Location: src/components/state/StatePageTabsDemographics.tsx lines 40-42

Current: "No demographic data available", "Demographic information has not been configured"

Impact: Low. Could change to "voter group" for consistency, but "demographic" is still technically correct (demographics = population characteristics).


🔗 Election ↔ Poll Integration#

Flow Status
Poll computePollData uses same categories/demographics as election engine
Poll in-race share uses group-level competitive allocation (matches distributeVotesByGroupLevelAllocation)
Election accumulateVoteTurn uses distributeVotesByGroupLevelAllocation
Both use calcAppeal, approvalScalar, partyOrgScalar from demographicAppeal
ElectionComparisonPanel uses inRaceVoteShare when available

Verdict: Poll and elections are correctly integrated.


  1. High: Update reseed API and seed-demographics to also upsert demographicDefaults
  2. Medium: Actions page - "Full Poll", voter group wording
  3. Low: Dashboard, poll API message, StatePageTabsDemographics - terminology tweaks
  4. Optional: Clean up CATEGORY_ICONS legacy keys

Partisan Lean Display#

Yes - already uses the new system. calculateStateLean(demographics, categories) is category-agnostic: it iterates over whatever categories exist in the DB. With 12 archetypes (voterGroups + 12 groups), it computes economic/social lean from those groups. Used by:


Migration Checklist (for existing DBs)#

Connected pages