PoliticoAI_GUI / static /mnt_politicoai_gui.html
ee-in's picture
Upload 6 files
f33c318 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PoliticoAI - Campaign Strategy Platform</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<!-- Plotly -->
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
<style>
:root {
--primary-blue: #1e40af;
--primary-red: #dc2626;
--sidebar-width: 260px;
--header-height: 60px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: #f8fafc;
overflow-x: hidden;
}
/* Header */
.app-header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--header-height);
background: linear-gradient(135deg, var(--primary-blue) 0%, #3b82f6 100%);
color: white;
display: flex;
align-items: center;
padding: 0 24px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
z-index: 1000;
}
.app-header h1 {
font-size: 24px;
font-weight: 700;
margin: 0;
}
.app-header .badge {
margin-left: 12px;
font-size: 11px;
font-weight: 600;
}
/* Sidebar */
.sidebar {
position: fixed;
top: var(--header-height);
left: 0;
width: var(--sidebar-width);
height: calc(100vh - var(--header-height));
background: white;
border-right: 1px solid #e2e8f0;
overflow-y: auto;
z-index: 999;
}
.nav-section {
padding: 24px 0;
border-bottom: 1px solid #e2e8f0;
}
.nav-section-title {
padding: 0 20px 8px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
color: #64748b;
letter-spacing: 0.5px;
}
.nav-item {
padding: 10px 20px;
cursor: pointer;
display: flex;
align-items: center;
color: #475569;
transition: all 0.2s;
}
.nav-item:hover {
background: #f1f5f9;
color: var(--primary-blue);
}
.nav-item.active {
background: #eff6ff;
color: var(--primary-blue);
border-left: 3px solid var(--primary-blue);
}
.nav-item i {
width: 20px;
margin-right: 12px;
font-size: 18px;
}
/* Main Content */
.main-content {
margin-left: var(--sidebar-width);
margin-top: var(--header-height);
padding: 32px;
min-height: calc(100vh - var(--header-height));
}
.content-section {
display: none;
}
.content-section.active {
display: block;
}
/* Cards */
.card {
border: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
border-radius: 8px;
margin-bottom: 24px;
}
.card-header {
background: white;
border-bottom: 1px solid #e2e8f0;
padding: 20px 24px;
font-weight: 600;
color: #1e293b;
}
.card-body {
padding: 24px;
}
/* Metrics Grid */
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
margin-bottom: 32px;
}
.metric-card {
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
border-left: 4px solid;
}
.metric-card.blue {
border-left-color: var(--primary-blue);
}
.metric-card.red {
border-left-color: var(--primary-red);
}
.metric-card.purple {
border-left-color: #9333ea;
}
.metric-card.green {
border-left-color: #16a34a;
}
.metric-label {
font-size: 13px;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.metric-value {
font-size: 32px;
font-weight: 700;
color: #1e293b;
margin-top: 8px;
}
.metric-change {
font-size: 13px;
margin-top: 8px;
}
.metric-change.positive {
color: #16a34a;
}
.metric-change.negative {
color: #dc2626;
}
/* Forms */
.form-label {
font-weight: 600;
font-size: 14px;
color: #334155;
margin-bottom: 8px;
}
.form-control, .form-select {
border: 1px solid #cbd5e1;
border-radius: 6px;
padding: 10px 14px;
}
.form-control:focus, .form-select:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
}
/* Buttons */
.btn {
padding: 10px 20px;
border-radius: 6px;
font-weight: 600;
transition: all 0.2s;
}
.btn-primary {
background: var(--primary-blue);
border: none;
}
.btn-primary:hover {
background: #1e3a8a;
transform: translateY(-1px);
}
.btn-success {
background: #16a34a;
border: none;
}
.btn-danger {
background: var(--primary-red);
border: none;
}
/* Tables */
.table {
font-size: 14px;
}
.table thead th {
background: #f8fafc;
color: #475569;
font-weight: 600;
border-bottom: 2px solid #e2e8f0;
text-transform: uppercase;
font-size: 12px;
letter-spacing: 0.5px;
}
.table tbody tr:hover {
background: #f8fafc;
}
/* Visualization Container */
.viz-container {
background: white;
border-radius: 8px;
padding: 24px;
margin-top: 24px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* Alerts */
.alert {
border: none;
border-radius: 6px;
border-left: 4px solid;
}
.alert-info {
border-left-color: #3b82f6;
background: #eff6ff;
color: #1e40af;
}
.alert-success {
border-left-color: #16a34a;
background: #f0fdf4;
color: #15803d;
}
.alert-warning {
border-left-color: #f59e0b;
background: #fffbeb;
color: #b45309;
}
/* Loading Spinner */
.spinner {
display: none;
text-align: center;
padding: 40px;
}
.spinner.active {
display: block;
}
/* Range Slider Custom Style */
input[type="range"] {
width: 100%;
height: 6px;
border-radius: 3px;
background: #e2e8f0;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-blue);
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-blue);
cursor: pointer;
}
.slider-value {
display: inline-block;
min-width: 50px;
text-align: center;
font-weight: 600;
color: var(--primary-blue);
}
/* File Upload */
.file-upload {
border: 2px dashed #cbd5e1;
border-radius: 8px;
padding: 40px;
text-align: center;
background: #f8fafc;
cursor: pointer;
transition: all 0.2s;
}
.file-upload:hover {
border-color: var(--primary-blue);
background: #eff6ff;
}
.file-upload i {
font-size: 48px;
color: #94a3b8;
margin-bottom: 16px;
}
/* Progress Bar */
.progress {
height: 8px;
border-radius: 4px;
background: #e2e8f0;
}
.progress-bar {
background: var(--primary-blue);
border-radius: 4px;
}
/* Tabs */
.nav-tabs {
border-bottom: 2px solid #e2e8f0;
}
.nav-tabs .nav-link {
border: none;
color: #64748b;
font-weight: 600;
padding: 12px 24px;
}
.nav-tabs .nav-link.active {
color: var(--primary-blue);
border-bottom: 2px solid var(--primary-blue);
margin-bottom: -2px;
}
</style>
</head>
<body>
<!-- Header -->
<div class="app-header">
<h1><i class="bi bi-graph-up-arrow"></i> PoliticoAI</h1>
<span class="badge bg-light text-primary">Campaign Strategy Platform</span>
</div>
<!-- Sidebar Navigation -->
<div class="sidebar">
<div class="nav-section">
<div class="nav-section-title">Analytics</div>
<div class="nav-item active" data-section="dashboard">
<i class="bi bi-speedometer2"></i>
<span>Dashboard</span>
</div>
<div class="nav-item" data-section="demographics">
<i class="bi bi-people"></i>
<span>Demographics</span>
</div>
<div class="nav-item" data-section="precincts">
<i class="bi bi-map"></i>
<span>Precinct Analysis</span>
</div>
</div>
<div class="nav-section">
<div class="nav-section-title">Modeling</div>
<div class="nav-item" data-section="simulation">
<i class="bi bi-cpu"></i>
<span>Monte Carlo</span>
</div>
<div class="nav-item" data-section="scenarios">
<i class="bi bi-sliders"></i>
<span>Scenarios</span>
</div>
<div class="nav-item" data-section="forecasting">
<i class="bi bi-graph-up"></i>
<span>Forecasting</span>
</div>
</div>
<div class="nav-section">
<div class="nav-section-title">Strategy</div>
<div class="nav-item" data-section="targeting">
<i class="bi bi-bullseye"></i>
<span>Voter Targeting</span>
</div>
<div class="nav-item" data-section="gotv">
<i class="bi bi-megaphone"></i>
<span>GOTV Planning</span>
</div>
<div class="nav-item" data-section="messaging">
<i class="bi bi-chat-square-text"></i>
<span>Messaging</span>
</div>
</div>
<div class="nav-section">
<div class="nav-section-title">Data</div>
<div class="nav-item" data-section="upload">
<i class="bi bi-upload"></i>
<span>Data Upload</span>
</div>
<div class="nav-item" data-section="export">
<i class="bi bi-download"></i>
<span>Export</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Dashboard Section -->
<div id="dashboard" class="content-section active">
<h2>Campaign Dashboard</h2>
<p class="text-muted mb-4">Real-time overview of district performance and strategic metrics</p>
<div class="metrics-grid">
<div class="metric-card blue">
<div class="metric-label">Current Win Probability</div>
<div class="metric-value" id="win-prob">--</div>
<div class="metric-change positive" id="win-prob-change">
<i class="bi bi-arrow-up"></i> +5.2% vs. baseline
</div>
</div>
<div class="metric-card purple">
<div class="metric-label">Expected Margin</div>
<div class="metric-value" id="expected-margin">--</div>
<div class="metric-change" id="margin-change">
±2.1% (95% CI)
</div>
</div>
<div class="metric-card green">
<div class="metric-label">High-Value Precincts</div>
<div class="metric-value" id="high-value-precincts">--</div>
<div class="metric-change">
<i class="bi bi-geo-alt"></i> Top quartile by net value
</div>
</div>
<div class="metric-card red">
<div class="metric-label">Voter Contact Target</div>
<div class="metric-value" id="contact-target">--</div>
<div class="metric-change">
<i class="bi bi-person-check"></i> Doors + phones
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<i class="bi bi-map-fill"></i> District Map - Partisan Lean (PVI)
</div>
<div class="card-body">
<div id="district-map" style="height: 400px;"></div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<i class="bi bi-pie-chart-fill"></i> Demographic Breakdown
</div>
<div class="card-body">
<div id="demo-pie" style="height: 400px;"></div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<i class="bi bi-graph-up"></i> Win Probability Over Time
</div>
<div class="card-body">
<div id="prob-timeline" style="height: 300px;"></div>
</div>
</div>
</div>
<!-- Demographics Section -->
<div id="demographics" class="content-section">
<h2>Demographic Segmentation</h2>
<p class="text-muted mb-4">Analyze voter universe by age, education, race, income, and geography</p>
<div class="card">
<div class="card-header">
<i class="bi bi-sliders"></i> Segmentation Controls
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<label class="form-label">Primary Dimension</label>
<select class="form-select" id="demo-primary">
<option value="age">Age Cohort</option>
<option value="education">Education Level</option>
<option value="race">Race/Ethnicity</option>
<option value="income">Income Quintile</option>
<option value="geography">Geography (Urban/Suburban/Rural)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Secondary Dimension</label>
<select class="form-select" id="demo-secondary">
<option value="none">None</option>
<option value="age">Age Cohort</option>
<option value="education">Education Level</option>
<option value="race">Race/Ethnicity</option>
<option value="income">Income Quintile</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Metric to Display</label>
<select class="form-select" id="demo-metric">
<option value="size">Segment Size</option>
<option value="turnout">Turnout Rate</option>
<option value="lean">Partisan Lean</option>
<option value="persuadability">Persuadability</option>
</select>
</div>
</div>
<div class="mt-3">
<button class="btn btn-primary" onclick="generateDemoSegmentation()">
<i class="bi bi-play-fill"></i> Generate Segmentation
</button>
</div>
</div>
</div>
<div class="viz-container">
<div id="demo-treemap" style="height: 500px;"></div>
</div>
<div class="card">
<div class="card-header">
<i class="bi bi-table"></i> Targeting Matrix
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover" id="targeting-matrix">
<thead>
<tr>
<th>Segment</th>
<th>Size</th>
<th>Turnout Rate</th>
<th>Partisan Lean</th>
<th>Persuadability</th>
<th>Strategy</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="6" class="text-center text-muted">
Run segmentation to populate table
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Precinct Analysis Section -->
<div id="precincts" class="content-section">
<h2>Precinct-Level Analysis</h2>
<p class="text-muted mb-4">Deep dive into precinct metrics, clustering, and strategic prioritization</p>
<div class="alert alert-info">
<i class="bi bi-info-circle"></i>
<strong>Analysis Level:</strong> This module computes PVI, swing scores, elasticity, turnout metrics, and strategic value for every precinct.
</div>
<div class="card">
<div class="card-header">
<i class="bi bi-gear"></i> Analysis Parameters
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<label class="form-label">Clustering Method</label>
<select class="form-select" id="cluster-method">
<option value="kmeans">K-Means</option>
<option value="hierarchical">Hierarchical (Ward)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Number of Clusters</label>
<select class="form-select" id="num-clusters">
<option value="4">4 Tiers</option>
<option value="5">5 Tiers</option>
<option value="6">6 Tiers</option>
<option value="8">8 Tiers</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Priority Metric</label>
<select class="form-select" id="priority-metric">
<option value="net">Net Mobilization Score</option>
<option value="persuasion">Persuasion Value</option>
<option value="mobilization">Mobilization Value</option>
<option value="competitiveness">Competitiveness</option>
</select>
</div>
</div>
<div class="mt-3">
<button class="btn btn-primary" onclick="analyzePrecincts()">
<i class="bi bi-play-fill"></i> Run Precinct Analysis
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="viz-container">
<h5>PVI vs. Swing Score (by Cluster)</h5>
<div id="precinct-scatter" style="height: 400px;"></div>
</div>
</div>
<div class="col-lg-6">
<div class="viz-container">
<h5>Precinct Priority Ranking</h5>
<div id="precinct-ranking" style="height: 400px;"></div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<i class="bi bi-list-ol"></i> Top 20 Precincts by Strategic Value
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm" id="precinct-table">
<thead>
<tr>
<th>Rank</th>
<th>Precinct</th>
<th>PVI</th>
<th>Swing</th>
<th>TO Delta</th>
<th>Net Value</th>
<th>Tier</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="7" class="text-center text-muted">
Run analysis to populate table
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Monte Carlo Simulation Section -->
<div id="simulation" class="content-section">
<h2>Monte Carlo Simulation</h2>
<p class="text-muted mb-4">Run thousands of election simulations to estimate win probability and margin distribution</p>
<div class="card">
<div class="card-header">
<i class="bi bi-sliders"></i> Simulation Parameters
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<label class="form-label">Number of Iterations</label>
<select class="form-select" id="mc-iterations">
<option value="1000">1,000 (Quick)</option>
<option value="5000">5,000 (Standard)</option>
<option value="10000" selected>10,000 (Recommended)</option>
<option value="50000">50,000 (Tight Race)</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Baseline Environment</label>
<select class="form-select" id="mc-environment">
<option value="neutral">Neutral / Even</option>
<option value="d3">D+3 Environment</option>
<option value="r3">R+3 Environment</option>
<option value="d5">D+5 Wave</option>
<option value="r5">R+5 Wave</option>
</select>
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<label class="form-label">
Polling Error (σ): <span class="slider-value" id="polling-error-val">3.5</span>%
</label>
<input type="range" id="polling-error" min="1" max="6" step="0.5" value="3.5"
oninput="document.getElementById('polling-error-val').textContent = this.value">
</div>
<div class="col-md-6">
<label class="form-label">
Turnout Variability: <span class="slider-value" id="turnout-var-val">5</span>%
</label>
<input type="range" id="turnout-var" min="2" max="10" step="1" value="5"
oninput="document.getElementById('turnout-var-val').textContent = this.value">
</div>
</div>
<div class="mt-4">
<button class="btn btn-primary btn-lg" onclick="runMonteCarloSimulation()">
<i class="bi bi-play-circle-fill"></i> Run Simulation
</button>
<button class="btn btn-secondary" onclick="resetSimulation()">
<i class="bi bi-arrow-clockwise"></i> Reset
</button>
</div>
<div id="mc-spinner" class="spinner">
<div class="spinner-border text-primary" role="status"></div>
<p class="mt-3">Running simulation...</p>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-4">
<div class="metric-card blue">
<div class="metric-label">Win Probability</div>
<div class="metric-value" id="mc-win-prob">--</div>
</div>
</div>
<div class="col-lg-4">
<div class="metric-card purple">
<div class="metric-label">Expected Margin</div>
<div class="metric-value" id="mc-margin">--</div>
</div>
</div>
<div class="col-lg-4">
<div class="metric-card green">
<div class="metric-label">Recount Probability</div>
<div class="metric-value" id="mc-recount">--</div>
</div>
</div>
</div>
<div class="viz-container">
<h5>Margin Distribution (10,000 Simulations)</h5>
<div id="mc-histogram" style="height: 400px;"></div>
</div>
</div>
<!-- Scenarios Section -->
<div id="scenarios" class="content-section">
<h2>Scenario Builder</h2>
<p class="text-muted mb-4">Model "what if" scenarios with demographic shifts, turnout changes, and third-party effects</p>
<div class="card">
<div class="card-header">
<i class="bi bi-plus-circle"></i> Build Scenario
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<label class="form-label">Scenario Name</label>
<input type="text" class="form-control" id="scenario-name" placeholder="e.g., High Youth Turnout">
</div>
<div class="col-md-6">
<label class="form-label">Compare Against</label>
<select class="form-select" id="scenario-baseline">
<option value="2024">2024 Baseline</option>
<option value="2022">2022 Midterm</option>
<option value="2020">2020 Presidential</option>
</select>
</div>
</div>
<hr class="my-4">
<h6 class="mb-3">Turnout Adjustments (by demographic)</h6>
<div class="row">
<div class="col-md-6">
<label class="form-label">
18-29 Turnout: <span class="slider-value" id="turnout-18-29-val">0</span>%
</label>
<input type="range" id="turnout-18-29" min="-30" max="30" step="5" value="0"
oninput="document.getElementById('turnout-18-29-val').textContent = (this.value > 0 ? '+' : '') + this.value">
</div>
<div class="col-md-6">
<label class="form-label">
College+ Turnout: <span class="slider-value" id="turnout-college-val">0</span>%
</label>
<input type="range" id="turnout-college" min="-30" max="30" step="5" value="0"
oninput="document.getElementById('turnout-college-val').textContent = (this.value > 0 ? '+' : '') + this.value">
</div>
</div>
<h6 class="mb-3 mt-4">Partisan Shifts (by demographic)</h6>
<div class="row">
<div class="col-md-6">
<label class="form-label">
Hispanic D Margin: <span class="slider-value" id="hispanic-shift-val">0</span> pts
</label>
<input type="range" id="hispanic-shift" min="-15" max="15" step="1" value="0"
oninput="document.getElementById('hispanic-shift-val').textContent = (this.value > 0 ? '+' : '') + this.value">
</div>
<div class="col-md-6">
<label class="form-label">
Suburban Women D Margin: <span class="slider-value" id="subwomen-shift-val">0</span> pts
</label>
<input type="range" id="subwomen-shift" min="-15" max="15" step="1" value="0"
oninput="document.getElementById('subwomen-shift-val').textContent = (this.value > 0 ? '+' : '') + this.value">
</div>
</div>
<div class="mt-4">
<button class="btn btn-success" onclick="runScenario()">
<i class="bi bi-calculator"></i> Calculate Scenario
</button>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<i class="bi bi-bar-chart-line"></i> Scenario Comparison
</div>
<div class="card-body">
<div id="scenario-comparison" style="height: 400px;"></div>
</div>
</div>
</div>
<!-- Voter Targeting Section -->
<div id="targeting" class="content-section">
<h2>Voter Targeting</h2>
<p class="text-muted mb-4">Build persuasion and mobilization universes with data-driven segment prioritization</p>
<div class="card">
<div class="card-header">
<i class="bi bi-funnel"></i> Build Universe
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<label class="form-label">Universe Type</label>
<select class="form-select" id="universe-type">
<option value="persuasion">Persuasion</option>
<option value="mobilization">Mobilization</option>
<option value="combined">Combined</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Competitiveness Threshold</label>
<select class="form-select" id="comp-threshold">
<option value="0.7">High (>70% competitive)</option>
<option value="0.5" selected>Medium (>50%)</option>
<option value="0.3">Low (>30%)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Min Support Score</label>
<select class="form-select" id="support-score">
<option value="3">Lean Support (3+)</option>
<option value="4" selected>Likely Support (4+)</option>
<option value="5">Strong Support (5)</option>
</select>
</div>
</div>
<div class="mt-3">
<button class="btn btn-primary" onclick="buildUniverse()">
<i class="bi bi-play-fill"></i> Generate Universe
</button>
</div>
</div>
</div>
<div class="metrics-grid">
<div class="metric-card blue">
<div class="metric-label">Universe Size</div>
<div class="metric-value" id="universe-size">--</div>
</div>
<div class="metric-card purple">
<div class="metric-label">Contact Rate Goal</div>
<div class="metric-value" id="contact-rate">--</div>
</div>
<div class="metric-card green">
<div class="metric-label">Estimated Vote Yield</div>
<div class="metric-value" id="vote-yield">--</div>
</div>
</div>
<div class="card">
<div class="card-header">
<i class="bi bi-people-fill"></i> Segment Performance
</div>
<div class="card-body">
<div id="segment-performance" style="height: 400px;"></div>
</div>
</div>
</div>
<!-- GOTV Section -->
<div id="gotv" class="content-section">
<h2>GOTV Planning</h2>
<p class="text-muted mb-4">Optimize canvass turf, phone banks, and volunteer deployment</p>
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle"></i>
<strong>GOTV Window:</strong> Most effective in final 10-14 days. Focus on identified supporters only.
</div>
<div class="card">
<div class="card-header">
<i class="bi bi-calendar-check"></i> Resource Allocation
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<label class="form-label">Volunteer Hours Available</label>
<input type="number" class="form-control" id="volunteer-hours" value="500">
</div>
<div class="col-md-4">
<label class="form-label">Phone Bank Capacity</label>
<input type="number" class="form-control" id="phone-capacity" value="10000">
</div>
<div class="col-md-4">
<label class="form-label">Days Until Election</label>
<input type="number" class="form-control" id="days-to-election" value="14">
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<label class="form-label">Contact Mix</label>
<select class="form-select" id="contact-mix">
<option value="balanced">Balanced (50/50 doors/phones)</option>
<option value="door-heavy">Door-Heavy (70/30)</option>
<option value="phone-heavy">Phone-Heavy (30/70)</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Priority Mode</label>
<select class="form-select" id="priority-mode">
<option value="net">Net Value (Balanced)</option>
<option value="persuasion">Persuasion Focus</option>
<option value="mobilization">Turnout Focus</option>
</select>
</div>
</div>
<div class="mt-4">
<button class="btn btn-success btn-lg" onclick="optimizeTurf()">
<i class="bi bi-geo-alt-fill"></i> Optimize Turf Cutting
</button>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<i class="bi bi-map"></i> Recommended Precinct Allocation
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table" id="turf-allocation">
<thead>
<tr>
<th>Priority</th>
<th>Precinct</th>
<th>Doors</th>
<th>Phone Attempts</th>
<th>Vol. Hours</th>
<th>Est. Votes</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="6" class="text-center text-muted">
Run optimization to generate allocation
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Messaging Section -->
<div id="messaging" class="content-section">
<h2>Messaging Strategy</h2>
<p class="text-muted mb-4">Issue salience mapping and message-to-market fit analysis</p>
<div class="card">
<div class="card-header">
<i class="bi bi-list-check"></i> Issue Priorities by Segment
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Issue</th>
<th>Young Urban</th>
<th>Suburban Swing</th>
<th>Rural Senior</th>
<th>Overall</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Economy/Jobs</strong></td>
<td><span class="badge bg-warning">Medium</span></td>
<td><span class="badge bg-danger">Very High</span></td>
<td><span class="badge bg-danger">Very High</span></td>
<td><span class="badge bg-danger">Very High</span></td>
</tr>
<tr>
<td><strong>Healthcare</strong></td>
<td><span class="badge bg-success">High</span></td>
<td><span class="badge bg-success">High</span></td>
<td><span class="badge bg-danger">Very High</span></td>
<td><span class="badge bg-success">High</span></td>
</tr>
<tr>
<td><strong>Abortion/Reproductive Rights</strong></td>
<td><span class="badge bg-danger">Very High</span></td>
<td><span class="badge bg-success">High</span></td>
<td><span class="badge bg-secondary">Low</span></td>
<td><span class="badge bg-success">High</span></td>
</tr>
<tr>
<td><strong>Immigration</strong></td>
<td><span class="badge bg-secondary">Low</span></td>
<td><span class="badge bg-warning">Medium</span></td>
<td><span class="badge bg-danger">Very High</span></td>
<td><span class="badge bg-success">High</span></td>
</tr>
<tr>
<td><strong>Climate/Environment</strong></td>
<td><span class="badge bg-danger">Very High</span></td>
<td><span class="badge bg-warning">Medium</span></td>
<td><span class="badge bg-secondary">Low</span></td>
<td><span class="badge bg-warning">Medium</span></td>
</tr>
<tr>
<td><strong>Education</strong></td>
<td><span class="badge bg-warning">Medium</span></td>
<td><span class="badge bg-danger">Very High</span></td>
<td><span class="badge bg-warning">Medium</span></td>
<td><span class="badge bg-success">High</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="alert alert-info">
<i class="bi bi-lightbulb"></i>
<strong>Strategy Recommendation:</strong> Lead with economy/jobs for suburban swing voters. Layer in healthcare for seniors. Use abortion rights to mobilize young urban base. Avoid immigration emphasis with young voters.
</div>
</div>
<!-- Forecasting Section -->
<div id="forecasting" class="content-section">
<h2>Election Forecasting</h2>
<p class="text-muted mb-4">Bayesian model with polling integration and early vote tracking</p>
<div class="card">
<div class="card-header">
<i class="bi bi-graph-up"></i> Model Inputs
</div>
<div class="card-body">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#fundamentals-tab">Fundamentals</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#polling-tab">Polling</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#early-vote-tab">Early Vote</a>
</li>
</ul>
<div class="tab-content mt-3">
<div id="fundamentals-tab" class="tab-pane fade show active">
<div class="row">
<div class="col-md-6">
<label class="form-label">Incumbent Party</label>
<select class="form-select">
<option>Democrat</option>
<option>Republican</option>
<option>Open Seat</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Generic Ballot (D+/-)</label>
<input type="number" class="form-control" value="0" step="0.5">
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<label class="form-label">Presidential Approval</label>
<input type="number" class="form-control" value="45" step="1">
</div>
<div class="col-md-6">
<label class="form-label">Right Track/Wrong Track</label>
<input type="number" class="form-control" value="35" step="1">
</div>
</div>
</div>
<div id="polling-tab" class="tab-pane fade">
<p class="text-muted">Add recent polls to update the forecast</p>
<div class="row">
<div class="col-md-4">
<label class="form-label">D Margin</label>
<input type="number" class="form-control" step="0.5">
</div>
<div class="col-md-4">
<label class="form-label">Sample Size</label>
<input type="number" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">Pollster Rating</label>
<select class="form-select">
<option>A+</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</div>
<button class="btn btn-sm btn-primary mt-2">
<i class="bi bi-plus"></i> Add Poll
</button>
</div>
<div id="early-vote-tab" class="tab-pane fade">
<p class="text-muted">Track early/mail ballots returned</p>
<div class="row">
<div class="col-md-6">
<label class="form-label">D Early Vote Share (%)</label>
<input type="number" class="form-control" step="0.5">
</div>
<div class="col-md-6">
<label class="form-label">% of Expected Turnout</label>
<input type="number" class="form-control" step="1">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<i class="bi bi-speedometer"></i> Current Forecast
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-md-4">
<h1 class="text-primary">52.3%</h1>
<p class="text-muted">Win Probability</p>
</div>
<div class="col-md-4">
<h1>D+1.8</h1>
<p class="text-muted">Expected Margin</p>
</div>
<div class="col-md-4">
<h1 class="text-success">85%</h1>
<p class="text-muted">Model Confidence</p>
</div>
</div>
<div class="progress mt-3" style="height: 30px;">
<div class="progress-bar bg-primary" style="width: 52.3%">Democrat 52.3%</div>
<div class="progress-bar bg-danger" style="width: 47.7%">Republican 47.7%</div>
</div>
</div>
</div>
</div>
<!-- Data Upload Section -->
<div id="upload" class="content-section">
<h2>Data Upload & Management</h2>
<p class="text-muted mb-4">Import voter files, election results, demographics, and precinct shapefiles</p>
<div class="row">
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<i class="bi bi-file-earmark-arrow-up"></i> Upload Election Results
</div>
<div class="card-body">
<div class="file-upload">
<i class="bi bi-cloud-upload"></i>
<h5>Drag & Drop or Click to Upload</h5>
<p class="text-muted">CSV, Excel, TXT (precinct-level results)</p>
<input type="file" class="d-none" id="results-file" accept=".csv,.xlsx,.txt">
<button class="btn btn-primary mt-2" onclick="document.getElementById('results-file').click()">
Select File
</button>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<i class="bi bi-people"></i> Upload Voter File
</div>
<div class="card-body">
<div class="file-upload">
<i class="bi bi-cloud-upload"></i>
<h5>Drag & Drop or Click to Upload</h5>
<p class="text-muted">CSV, Excel (L2, TargetSmart, VAN export)</p>
<input type="file" class="d-none" id="voter-file" accept=".csv,.xlsx">
<button class="btn btn-primary mt-2" onclick="document.getElementById('voter-file').click()">
Select File
</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<i class="bi bi-bar-chart"></i> Upload Demographics
</div>
<div class="card-body">
<div class="file-upload">
<i class="bi bi-cloud-upload"></i>
<h5>Drag & Drop or Click to Upload</h5>
<p class="text-muted">CSV, Excel (Census ACS tables)</p>
<input type="file" class="d-none" id="demo-file" accept=".csv,.xlsx">
<button class="btn btn-primary mt-2" onclick="document.getElementById('demo-file').click()">
Select File
</button>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<i class="bi bi-map"></i> Upload Precinct Boundaries
</div>
<div class="card-body">
<div class="file-upload">
<i class="bi bi-cloud-upload"></i>
<h5>Drag & Drop or Click to Upload</h5>
<p class="text-muted">GeoJSON, Shapefile (VEST, TIGER/Line)</p>
<input type="file" class="d-none" id="geo-file" accept=".geojson,.json,.zip">
<button class="btn btn-primary mt-2" onclick="document.getElementById('geo-file').click()">
Select File
</button>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<i class="bi bi-database"></i> Loaded Datasets
</div>
<div class="card-body">
<div class="alert alert-success">
<i class="bi bi-check-circle"></i> <strong>Sample District Data</strong> - 10 precincts, 2020-2024 results
</div>
<div class="alert alert-secondary">
<i class="bi bi-x-circle"></i> No voter file loaded
</div>
<div class="alert alert-secondary">
<i class="bi bi-x-circle"></i> No demographic data loaded
</div>
</div>
</div>
</div>
<!-- Export Section -->
<div id="export" class="content-section">
<h2>Export & Reports</h2>
<p class="text-muted mb-4">Download analysis results, visualizations, and campaign reports</p>
<div class="card">
<div class="card-header">
<i class="bi bi-file-earmark-text"></i> Available Exports
</div>
<div class="card-body">
<div class="list-group">
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-1">Precinct-Level Analysis (CSV)</h6>
<small class="text-muted">All computed metrics, PVI, swing, turnout, strategic values</small>
</div>
<button class="btn btn-sm btn-primary">
<i class="bi bi-download"></i> Download
</button>
</div>
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-1">Voter Targeting Matrix (Excel)</h6>
<small class="text-muted">Segment-by-segment breakdown with strategies</small>
</div>
<button class="btn btn-sm btn-primary">
<i class="bi bi-download"></i> Download
</button>
</div>
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-1">Monte Carlo Results (JSON)</h6>
<small class="text-muted">Full simulation output with margin distribution</small>
</div>
<button class="btn btn-sm btn-primary">
<i class="bi bi-download"></i> Download
</button>
</div>
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-1">Interactive Maps (HTML)</h6>
<small class="text-muted">Standalone HTML files with all visualizations</small>
</div>
<button class="btn btn-sm btn-primary">
<i class="bi bi-download"></i> Download
</button>
</div>
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-1">Campaign Strategy Report (PDF)</h6>
<small class="text-muted">Executive summary with recommendations</small>
</div>
<button class="btn btn-sm btn-success">
<i class="bi bi-file-pdf"></i> Generate PDF
</button>
</div>
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-1">GOTV Turf Packets (Printable)</h6>
<small class="text-muted">Walk lists by precinct with maps</small>
</div>
<button class="btn btn-sm btn-success">
<i class="bi bi-printer"></i> Generate
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Navigation
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', function() {
// Update nav active state
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
this.classList.add('active');
// Show corresponding section
const sectionId = this.dataset.section;
document.querySelectorAll('.content-section').forEach(section => {
section.classList.remove('active');
});
document.getElementById(sectionId).classList.add('active');
});
});
// Initialize Dashboard with Sample Data
function initializeDashboard() {
// Update metrics
document.getElementById('win-prob').textContent = '58.3%';
document.getElementById('expected-margin').textContent = 'D+2.4';
document.getElementById('high-value-precincts').textContent = '8';
document.getElementById('contact-target').textContent = '24.5K';
// Sample scatter plot
const sampleData = [
{x: -5, y: 3, name: 'P01', size: 200, color: 'Red Lean'},
{x: 2, y: 7, name: 'P02', size: 150, color: 'Swing'},
{x: 8, y: 2, name: 'P03', size: 180, color: 'Blue Base'},
{x: -2, y: 5, name: 'P04', size: 220, color: 'Swing'},
{x: 12, y: 4, name: 'P05', size: 160, color: 'Blue Base'},
];
const trace = {
x: sampleData.map(d => d.x),
y: sampleData.map(d => d.y),
text: sampleData.map(d => d.name),
mode: 'markers',
marker: {
size: sampleData.map(d => d.size / 10),
color: sampleData.map(d => d.color === 'Blue Base' ? '#1e40af' : d.color === 'Red Lean' ? '#dc2626' : '#9333ea')
},
type: 'scatter'
};
Plotly.newPlot('district-map', [trace], {
title: 'Precinct Positioning',
xaxis: {title: 'PVI (Partisan Lean)'},
yaxis: {title: 'Swing Score'},
hovermode: 'closest'
}, {responsive: true});
// Demographic pie
Plotly.newPlot('demo-pie', [{
values: [35, 28, 22, 15],
labels: ['White Non-Hispanic', 'Hispanic/Latino', 'Black/AA', 'Asian/Other'],
type: 'pie',
marker: {
colors: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6']
}
}], {
title: 'Racial/Ethnic Composition'
}, {responsive: true});
// Win probability timeline
const dates = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct'];
const probs = [45, 48, 50, 52, 54, 56, 57, 58, 58, 58.3];
Plotly.newPlot('prob-timeline', [{
x: dates,
y: probs,
type: 'scatter',
mode: 'lines+markers',
line: {color: '#1e40af', width: 3},
fill: 'tozeroy'
}], {
yaxis: {title: 'Win Probability (%)', range: [0, 100]},
xaxis: {title: '2024'}
}, {responsive: true});
}
// Placeholder functions (would connect to backend/data processing)
function generateDemoSegmentation() {
alert('Generating demographic segmentation...
This would analyze uploaded data and compute segment-level metrics.');
}
function analyzePrecincts() {
alert('Running precinct-level analysis...
This would compute PVI, swing, elasticity, and clustering for all precincts.');
}
function runMonteCarloSimulation() {
document.getElementById('mc-spinner').classList.add('active');
setTimeout(() => {
document.getElementById('mc-spinner').classList.remove('active');
document.getElementById('mc-win-prob').textContent = '58.3%';
document.getElementById('mc-margin').textContent = 'D+2.4';
document.getElementById('mc-recount').textContent = '3.2%';
// Generate histogram
const margins = Array.from({length: 1000}, () =>
Math.random() * 10 - 3
);
Plotly.newPlot('mc-histogram', [{
x: margins,
type: 'histogram',
nbinsx: 50,
marker: {color: '#1e40af'}
}], {
title: '10,000 Simulated Election Outcomes',
xaxis: {title: 'D Margin (%)'},
yaxis: {title: 'Frequency'}
}, {responsive: true});
}, 2000);
}
function resetSimulation() {
document.getElementById('mc-win-prob').textContent = '--';
document.getElementById('mc-margin').textContent = '--';
document.getElementById('mc-recount').textContent = '--';
Plotly.purge('mc-histogram');
}
function runScenario() {
alert('Calculating scenario...
This would re-run the model with adjusted demographic/turnout parameters.');
}
function buildUniverse() {
document.getElementById('universe-size').textContent = '24,518';
document.getElementById('contact-rate').textContent = '65%';
document.getElementById('vote-yield').textContent = '+1,842';
alert('Universe generated!
24,518 voters identified across persuasion and mobilization targets.');
}
function optimizeTurf() {
alert('Optimizing turf allocation...
This would rank precincts and allocate volunteer resources to maximize vote yield.');
}
// Initialize on load
window.addEventListener('load', initializeDashboard);
</script>
</body>
</html>