GOOD CAT
Add expanded Google services and submission polish
0692b87
"""Server-side HTML rendering."""
from __future__ import annotations
from copy import deepcopy
import re
from typing import Any
from .analytics import completion_stats
from .ballot import comparison_rows
from .data_sources import COUNTRIES, get_jurisdiction, list_countries, list_regions
from .education import learning_model
from .feedback import analyze_feedback
from .google_services import (
CalendarService,
GeminiService,
OAuthService,
TranslateService,
google_services_status,
)
from .integrations import google_maps_embed_configured, google_maps_embed_url, google_maps_directions_url
from .polling import alternatives_within_10_miles, assigned_location
from .registration import eligibility, registration_status, resolution_steps
from .security import cryptography_available
from .seed_data import INCIDENT_TYPES, LANGUAGES, META, TRANSLATIONS
from .timeline import days_until, deadlines_within_24_months, is_critical, next_deadline
from .utils import current_year, html, list_html, tag_html
from .voting import selected_method
def tr(state: dict[str, Any], key: str) -> str:
"""Handle tr."""
language = state.get("language", "en")
return TRANSLATIONS.get(language, TRANSLATIONS["en"]).get(key, TRANSLATIONS["en"].get(key, key))
def _override_key(state: dict[str, Any]) -> str:
return f"{state.get('country_code', 'US')}:{state.get('jurisdiction_id', '')}"
def _merge_nested(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
result = deepcopy(base)
for key, value in override.items():
if isinstance(result.get(key), dict) and isinstance(value, dict):
result[key] = _merge_nested(result[key], value)
else:
result[key] = deepcopy(value)
return result
def current_jurisdiction(state: dict[str, Any]) -> dict[str, Any]:
"""Return jurisdiction."""
jurisdiction = deepcopy(get_jurisdiction(state.get("country_code", "US"), state.get("jurisdiction_id", "")))
override = state.get("jurisdiction_overrides", {}).get(_override_key(state), {})
if override:
jurisdiction = _merge_nested(jurisdiction, override)
return jurisdiction
def current_language(state: dict[str, Any]) -> dict[str, Any]:
"""Return language."""
return next((item for item in LANGUAGES if item["code"] == state.get("language")), LANGUAGES[0])
def options(items: list[tuple[str, str]], selected: str) -> str:
"""Handle options."""
return "".join(
f'<option value="{html(value)}"{" selected" if value == selected else ""}>{html(label)}</option>'
for value, label in items
)
def flash_html(messages: list[str]) -> str:
"""Handle flash html."""
if not messages:
return ""
return '<div class="status-alert" role="status" aria-live="polite">' + "<br>".join(html(message) for message in messages) + "</div>"
def _nav_link(label: str, page_id: str, current: str) -> str:
"""Render a single navigation link with active state."""
active = ' class="nav-active" aria-current="page"' if page_id == current else ""
return f'<a href="/{page_id}"{active}>{html(label)}</a>'
def _is_placeholder_candidate(candidate: dict[str, Any]) -> bool:
return candidate.get("id") == "manual-candidate-notes"
def _coverage_summary(jurisdiction: dict[str, Any]) -> str:
if jurisdiction.get("country") == "IN":
assembly = jurisdiction.get("contacts", {}).get("assembly_seats")
lok_sabha = jurisdiction.get("contacts", {}).get("lok_sabha_seats")
if assembly and assembly != "0":
return (
f"{jurisdiction['name']} includes {assembly} assembly constituencies and "
f"{lok_sabha} Lok Sabha seats. Local ward coverage still requires official sources."
)
return (
f"{jurisdiction['name']} includes {lok_sabha} Lok Sabha seat(s). "
"State assembly or local ward coverage still requires official sources."
)
return f"{jurisdiction['country_name']} coverage is tailored to {jurisdiction['name']} using the configured {jurisdiction.get('district_label', 'district').lower()} label."
def render_onboarding(state: dict[str, Any]) -> str:
"""Render onboarding."""
birth_year_max = current_year()
country_code = state.get("country_code", "US")
country = COUNTRIES.get(country_code, COUNTRIES["US"])
jurisdiction_options = [(key, value) for key, value in list_regions(country_code).items()]
return f"""
<section class="section-band dashboard-band" aria-labelledby="setupTitle">
<div class="section-inner setup-inner">
<div class="section-heading">
<p class="eyebrow">Welcome</p>
<h2 id="setupTitle">Set up your election assistant</h2>
</div>
<p class="setup-copy">Choose your country and voting area to tailor deadlines, registration guidance, ballot research, and polling details.</p>
<form method="post" action="/action/onboard" class="panel field-group">
<label>Country
<select name="country_code">{options(list_countries(), country_code)}</select>
</label>
<label>{html(country['region_label'])}
<select name="jurisdiction_id">{options(jurisdiction_options, state.get("jurisdiction_id", "california"))}</select>
</label>
<label>Street address <input name="address" type="text" autocomplete="street-address" value="{html(state['profile']['address'])}" required></label>
<label>Birth year <input name="birth_year" type="number" min="1900" max="{birth_year_max}" value="{html(state['profile']['birth_year'])}" required></label>
<label class="switch-row"><input name="citizen" type="checkbox" checked> I am a citizen</label>
<label class="switch-row"><input name="resident" type="checkbox" checked> I am a resident of this voting area</label>
<label class="switch-row"><input name="registered" type="checkbox"> I am already registered to vote</label>
<button type="submit">Get started</button>
</form>
</div>
</section>
"""
PAGES = {
"dashboard": ("Dashboard", lambda s, j, m: render_dashboard(s, j, m)),
"process": ("Process", lambda s, j, m: render_process(s, j)),
"timeline": ("Timeline", lambda s, j, m: render_timeline(s, j)),
"registration": ("Registration", lambda s, j, m: render_registration(s, j)),
"voting": ("Voting", lambda s, j, m: render_voting(s, j)),
"ballot": ("Ballot", lambda s, j, m: render_ballot(s, j)),
"polling": ("Polling", lambda s, j, m: render_polling(s, j)),
"eligibility": ("Security", lambda s, j, m: render_eligibility_security(s, j)),
"guide": ("Guide", lambda s, j, m: render_guide(s, j)),
"support": ("Support", lambda s, j, m: render_support(s, j)),
"learning": ("Learning", lambda s, j, m: render_learning(s, j)),
"services": ("Services", lambda s, j, m: render_services(s, j)),
}
def render_app(state: dict[str, Any], messages: list[str], page: str = "dashboard") -> str:
"""Render app."""
jurisdiction = current_jurisdiction(state)
language = current_language(state)
scale = int(state.get("text_scale", 100))
body_class = "screen-reader-mode" if state.get("screen_reader") else ""
if page == "setup":
content = render_onboarding(state)
nav_html = ""
else:
if page not in PAGES:
page = "dashboard"
_, renderer = PAGES[page]
content = renderer(state, jurisdiction, messages)
nav_items = " ".join(_nav_link(label, pid, page) for pid, (label, _) in PAGES.items())
nav_html = f'<nav class="section-nav" aria-label="Primary sections">{nav_items}</nav>'
result = f"""<!doctype html>
<html lang="{html(language['code'])}" dir="{html(language['dir'])}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#12343b">
<meta name="description" content="Personalized election process assistant with Google service integration for voter guidance, registration, ballot research, and polling.">
<title>Election Process Assistant - {html(page.title() if page != 'setup' else 'Setup')}</title>
<link rel="manifest" href="/static/manifest.webmanifest">
<link rel="icon" href="/static/assets/icon.svg" type="image/svg+xml">
<link rel="stylesheet" href="/static/styles.css">
<style>:root {{ --font-scale: {scale / 100:.2f}; }}</style>
<script src="/static/pwa-register.js" defer></script>
</head>
<body class="{body_class}">
<a class="skip-link" href="#main">Skip to main content</a>
<header class="app-header" role="banner">
<div class="brand">
<img src="/static/assets/icon.svg" alt="" width="48" height="48">
<div>
<p class="eyebrow">{html(tr(state, "kicker"))}</p>
<h1>Election Process Assistant</h1>
</div>
</div>
{render_settings_form(state) if page != "setup" else ""}
</header>
{nav_html}
<main id="main" tabindex="-1">
{flash_html(messages) if page != "dashboard" else ""}
{content}
</main>
<footer class="app-footer">
<p>{html(META['data_boundary'])}</p>
<p>Local civic guidance workspace. Offline cache target: {html(META['cache_limit_mb'])} MB</p>
</footer>
</body>
</html>"""
return add_csrf_fields(result, state.get("csrf_token", ""))
def add_csrf_fields(page: str, token: str) -> str:
"""Add csrf fields."""
field = f'<input type="hidden" name="_csrf_token" value="{html(token)}">'
return re.sub(r'(<form\b[^>]*method="post"[^>]*>)', rf'\1{field}', page)
def render_settings_form(state: dict[str, Any]) -> str:
"""Render settings form."""
country_code = state.get("country_code", "US")
country = COUNTRIES.get(country_code, COUNTRIES["US"])
jurisdiction_options = [(key, value) for key, value in list_regions(country_code).items()]
language_options = [(item["code"], item["label"]) for item in LANGUAGES]
return f"""
<form method="post" action="/action/settings" class="control-panel" aria-label="Personalization controls">
<label>Country
<select name="country_code">{options(list_countries(), country_code)}</select>
</label>
<label>{html(country['region_label'])}
<select name="jurisdiction_id">{options(jurisdiction_options, state.get("jurisdiction_id", "california"))}</select>
</label>
<label>Language
<select name="language">{options(language_options, state.get("language", "en"))}</select>
</label>
<label>Text size
<input name="text_scale" type="range" min="100" max="200" step="10" value="{html(state.get('text_scale', 100))}">
</label>
<label class="switch-row">
<input name="screen_reader" type="checkbox"{" checked" if state.get("screen_reader") else ""}>
Screen reader mode
</label>
<button type="submit" aria-label="Apply personalization settings">Apply</button>
</form>
"""
def render_dashboard(state: dict[str, Any], jurisdiction: dict[str, Any], messages: list[str]) -> str:
"""Render dashboard."""
birth_year_max = current_year()
stats = completion_stats(state, jurisdiction)
deadline = next_deadline(jurisdiction)
remaining = days_until(deadline["date"]) if deadline else "N/A"
return f"""
<section id="dashboard" class="section-band dashboard-band" data-requirement="1,2,3,4,7,13,15,17" aria-labelledby="dashboardTitle">
<div class="section-inner">
<div class="section-heading">
<p class="eyebrow">{html(tr(state, "kicker"))}</p>
<h2 id="dashboardTitle">{html(tr(state, "workspace"))}</h2>
</div>
{flash_html(messages)}
<div class="dashboard-grid">
<div class="panel">
<h3>Personal profile</h3>
<form method="post" action="/action/profile#dashboard" class="field-group">
<label>Street address <input name="address" type="text" autocomplete="street-address" value="{html(state['profile']['address'])}"></label>
<div class="field-grid">
<label>Birth year <input name="birth_year" type="number" min="1900" max="{birth_year_max}" value="{html(state['profile']['birth_year'])}"></label>
<label>User type
<select name="user_type">
{options([("anonymous", "Anonymous user"), ("registered", "Registered account"), ("organizer", "Registration drive organizer"), ("researcher", "Policy researcher")], state['profile'].get('user_type', 'anonymous'))}
</select>
</label>
</div>
<label class="switch-row"><input name="citizen" type="checkbox"{" checked" if state['profile'].get('citizen') else ""}> Citizen</label>
<label class="switch-row"><input name="resident" type="checkbox"{" checked" if state['profile'].get('resident') else ""}> Resident of this {html(jurisdiction.get('district_label', 'voting area')).lower()}</label>
<label class="switch-row"><input name="registered" type="checkbox"{" checked" if state['profile'].get('registered') else ""}> Registration already confirmed</label>
<button type="submit">Save profile</button>
</form>
</div>
<div class="panel">
<h3>Readiness snapshot</h3>
<div class="metric-row">
<div class="metric"><strong>{stats['percent']}%</strong>process complete</div>
<div class="metric"><strong>{html(remaining)}</strong>days to next deadline</div>
<div class="metric"><strong>{len(state.get('offline_queue', []))}</strong>queued offline actions</div>
</div>
<div class="progress-track" aria-label="Election process progress">
<div class="progress-fill" style="width: {stats['percent']}%"></div>
</div>
<p>{html(jurisdiction['name'])}, {html(jurisdiction.get('country_name', ''))} deadlines are shown in {html(jurisdiction['timezone_label'])}. {html(tr(state, "mock"))}</p>
<form method="post" action="/action/lookup-registration#dashboard" class="action-row">
<button type="submit">Check registration status</button>
<a class="button secondary" href="#guide">Generate guide</a>
</form>
</div>
<figure class="visual-panel" aria-labelledby="visualTitle">
<img src="/static/assets/civic-map.svg" alt="Map-style illustration of polling places, deadlines, and voter guide checkpoints">
<figcaption id="visualTitle">Election tasks, deadlines, and voting options in one focused workspace.</figcaption>
</figure>
</div>
<div class="panel" style="margin-top:1rem">
<h3>Ask the AI Assistant</h3>
<p>Powered by Google Gemini{' - API configured' if GeminiService.configured() else ' (offline FAQ mode)'}.</p>
<form method="post" action="/action/ask-gemini#dashboard" class="field-group">
<label>Your civic question <input name="question" type="text" placeholder="How do I register to vote?" required></label>
<button type="submit" aria-label="Ask Gemini a civic question">Ask Gemini</button>
</form>
</div>
</div>
</section>
"""
def render_process(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str:
"""Render process."""
steps = jurisdiction["process_steps"]
first_open = next((step["id"] for step in steps if not state.get("completed_steps", {}).get(step["id"])), "")
cards = []
for index, step in enumerate(steps, start=1):
complete = bool(state.get("completed_steps", {}).get(step["id"]))
current = step["id"] == first_open
status = "Complete" if complete else "Current" if current else "Upcoming"
cards.append(f"""
<article class="panel step-card {'complete' if complete else ''} {'current' if current else ''}">
<div class="tag-row"><span class="tag">{html(status)}</span><span class="tag">Step {index}</span><span class="tag">{html(step['estimated_time'])}</span></div>
<h3>{html(step['title'])}</h3>
<p>{html(step['instructions'])}</p>
<details><summary>Required documents and contextual help</summary>
<h4>Documents</h4>{list_html(step['documents'])}
<h4>Common issues</h4>{list_html(step['help'])}
</details>
<form method="post" action="/action/step#process" class="action-row">
<input type="hidden" name="step_id" value="{html(step['id'])}">
<button type="submit">{'Mark incomplete' if complete else 'Mark complete'}</button>
</form>
</article>
""")
return f"""
<section id="process" class="section-band" data-requirement="1" aria-labelledby="processTitle">
<div class="section-inner">
<div class="section-heading"><p class="eyebrow">Interactive guide</p><h2 id="processTitle">Step-by-step election process</h2></div>
<div class="step-list">{''.join(cards)}</div>
</div>
</section>
"""
def render_timeline(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str:
"""Render timeline."""
items = []
for deadline in deadlines_within_24_months(jurisdiction):
remaining = days_until(deadline["date"])
critical = is_critical(deadline)
items.append(f"""
<article class="timeline-item {'deadline-critical' if critical else ''}">
<div><div class="timeline-date">{html(deadline['date'])}</div><span class="tag {'critical' if critical else ''}">{remaining if remaining >= 0 else abs(remaining)} days {'remaining' if remaining >= 0 else 'past'}</span></div>
<div>
<h3>{html(deadline['title'])}</h3>
<p>{html(deadline['description'])}</p>
<div class="tag-row"><span class="tag">{html(deadline['category'])}</span><span class="tag neutral">{html(deadline['source'])}</span></div>
<div class="action-row">
<a class="button secondary" href="/download/ics?id={html(deadline['id'])}">Add to calendar</a>
<a class="button secondary" href="{html(CalendarService.event_url(deadline['title'], deadline['date'], deadline['description']))}" target="_blank" rel="noreferrer">Add to Google Calendar</a>
</div>
</div>
</article>
""")
prefs = state["notification_preferences"]
return f"""
<section id="timeline" class="section-band muted-band" data-requirement="2,7" aria-labelledby="timelineTitle">
<div class="section-inner">
<div class="section-heading"><p class="eyebrow">Deadlines and reminders</p><h2 id="timelineTitle">Personalized timeline</h2></div>
<div class="two-column">
<div class="timeline-list">{''.join(items)}</div>
<div class="panel">
<h3>Notification preferences</h3>
<p>Registered users receive reminders at configured intervals. Critical deadlines also get a final 48-hour reminder notice.</p>
<form method="post" action="/action/notifications#timeline" class="field-group">
<label class="switch-row"><input name="enabled" type="checkbox"{" checked" if prefs.get("enabled") else ""}> Enable reminders</label>
<div class="field-grid">
{checkboxes("channels", ["email", "sms", "push"], prefs.get("channels", []))}
</div>
<div class="field-grid">
{checkboxes("intervals", ["30", "7", "1"], [str(item) for item in prefs.get("intervals", [])])}
</div>
<div class="field-grid">
{checkboxes("categories", ["deadlines", "ballot", "system"], prefs.get("categories", []))}
</div>
<button type="submit">Save notification preferences</button>
</form>
<p>Local data timestamp: {html(META['last_updated'])}. Valid official source changes refresh through the Python integration adapter.</p>
</div>
</div>
</div>
</section>
"""
def checkboxes(name: str, values: list[str], selected: list[str]) -> str:
"""Handle checkboxes."""
return "".join(
f'<label class="switch-row"><input name="{html(name)}" value="{html(value)}" type="checkbox"{" checked" if value in selected else ""}> {html(value.title())}</label>'
for value in values
)
def render_registration(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str:
"""Render registration."""
result = eligibility(state["profile"], jurisdiction)
reg = jurisdiction["registration"]
return f"""
<section id="registration" class="section-band" data-requirement="3,9" aria-labelledby="registrationTitle">
<div class="section-inner">
<div class="section-heading"><p class="eyebrow">Registration tracker</p><h2 id="registrationTitle">Registration guidance and status</h2></div>
<div class="two-column">
<div class="panel">
<h3>Status and methods</h3>
<p><span class="tag {'success' if state['profile'].get('registered') else 'critical'}">{html(registration_status(state['profile'], jurisdiction))}</span></p>
<h4>Registration methods</h4>{list_html(reg['methods'])}
<h4>Required information</h4>{list_html(reg['required_documents'])}
<p><strong>Processing:</strong> {html(reg['processing_time'])}</p>
<a class="button" href="{html(jurisdiction['official']['registration_url'])}" target="_blank" rel="noreferrer">Official registration portal</a>
<p>{html(reg['fallback'])}</p>
</div>
<div class="panel">
<h3>Eligibility validation</h3>
<p>Calculated age: <strong>{html(result['age'])}</strong></p>
<p><span class="tag {'success' if result['eligible'] else 'critical'}">{'Eligibility criteria met' if result['eligible'] else 'Needs resolution'}</span></p>
{list_html(result['issues']) if result['issues'] else ''}
<p>{html(result['rule'])}</p>
<details open><summary>Problem resolution</summary>{list_html(resolution_steps(jurisdiction))}</details>
</div>
</div>
</div>
</section>
"""
def render_voting(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str:
"""Render voting."""
selected = selected_method(state, jurisdiction)
rows = "".join(
f"""
<tr>
<td>
<form method="post" action="/action/method#voting">
<input type="hidden" name="method_id" value="{html(method['id'])}">
<button type="submit" class="secondary">{html(method['name'])}</button>
</form>
</td>
<td>{html(method['deadline'])}</td>
<td>{html(method['requirements'])}</td>
<td>{html(method['procedure'])}</td>
<td>{html(method['accessibility'])}</td>
</tr>
"""
for method in jurisdiction["voting_methods"]
)
tracking = selected.get("tracking", "")
return f"""
<section id="voting" class="section-band muted-band" data-requirement="5" aria-labelledby="votingTitle">
<div class="section-inner">
<div class="section-heading"><p class="eyebrow">Voting methods</p><h2 id="votingTitle">Compare ways to vote</h2></div>
<div class="table-wrap"><table><caption>Voting method comparison for {html(jurisdiction['name'])}</caption>
<thead><tr><th>Method</th><th>Deadline</th><th>Requirements</th><th>Procedure</th><th>Accessibility</th></tr></thead>
<tbody>{rows}</tbody>
</table></div>
<article class="method-card">
<h3>{html(selected['name'])}</h3>
<p>{html(selected['procedure'])}</p>
<p>{html(selected['requirements'])}</p>
<p>{html(selected['accessibility'])}</p>
{f'<p><a href="{html(jurisdiction["official"]["mail_tracking_url"])}" target="_blank" rel="noreferrer">{html(tracking)}</a></p>' if tracking else ''}
</article>
</div>
</section>
"""
def render_ballot(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str:
"""Render ballot."""
ballot = jurisdiction["ballot"]
ballot_view = dict(ballot)
manual_candidates = state.get("manual_candidates", {}).get(ballot["district"], [])
base_candidates = ballot["candidates"]
if manual_candidates and all(_is_placeholder_candidate(candidate) for candidate in base_candidates):
ballot_view["candidates"] = manual_candidates
else:
ballot_view["candidates"] = base_candidates + manual_candidates
candidate_cards = "".join(
f"""
<article class="card">
<h3>{html(candidate['name'])}</h3>
<p><strong>{html(candidate['race'])}</strong> - {html(candidate['party'])}</p>
<p>{html(candidate['statement'])}</p>
<a href="{html(candidate['website'])}" target="_blank" rel="noreferrer">{html(candidate.get('link_label', 'Candidate website'))}</a>
<p><span class="tag neutral">{html(candidate['source'])}</span></p>
</article>
"""
for candidate in ballot_view["candidates"]
)
comparison = comparison_rows(ballot_view)
rows = "".join(
"<tr><th>{}</th>{}</tr>".format(
html(row["issue"]),
"".join(f"<td>{html(position['position'])}</td>" for position in row["positions"]),
)
for row in comparison
)
measures = "".join(
f"""
<details open><summary>{html(measure['title'])}</summary>
<p>{html(measure['summary'])}</p>
<p><strong>Fiscal impact:</strong> {html(measure['fiscal_impact'])}</p>
<p><strong>For:</strong> {html(measure['arguments_for'])}</p>
<p><strong>Against:</strong> {html(measure['arguments_against'])}</p>
<p><span class="tag neutral">{html(measure['source'])}</span></p>
</details>
"""
for measure in ballot["measures"]
)
note = state.get("research_notes", {}).get(ballot["district"], "")
comparison_headers = "".join(f"<th>{html(candidate['name'])}</th>" for candidate in ballot_view["candidates"])
comparison_table = (
f'<div class="table-wrap"><table><caption>Candidate position comparison</caption><thead><tr><th>Issue</th>{comparison_headers}</tr></thead><tbody>{rows}</tbody></table></div>'
if len(ballot_view["candidates"]) > 1 and comparison
else '<p><strong>Candidate position comparison:</strong> Add two or more verified candidates to compare notes side by side.</p>'
)
return f"""
<section id="ballot" class="section-band" data-requirement="6" aria-labelledby="ballotTitle">
<div class="section-inner">
<div class="section-heading"><p class="eyebrow">Ballot research</p><h2 id="ballotTitle">Candidates and measures</h2></div>
<div class="two-column">
<div>
<div class="panel">
<p><strong>{html(jurisdiction.get('district_label', 'District'))}:</strong> {html(ballot['district'])}</p>
<p><strong>Coverage:</strong> {html(ballot.get('coverage_note') or _coverage_summary(jurisdiction))}</p>
<p><strong>Verification:</strong> Use {html(jurisdiction['authority'])} or the official sample ballot source before making voting decisions.</p>
</div>
<div class="card-grid">{candidate_cards}</div>
{comparison_table}
</div>
<div class="panel">
<h3>Measures and research notes</h3>
{measures}
<form method="post" action="/action/notes#ballot" class="field-group">
<input type="hidden" name="district" value="{html(ballot['district'])}">
<label>Private research notes <textarea name="notes" rows="7">{html(note)}</textarea></label>
<button type="submit">Save notes locally</button>
</form>
<form method="post" action="/action/add-candidate#ballot" class="field-group candidate-form">
<input type="hidden" name="district" value="{html(ballot['district'])}">
<div class="field-grid">
<label>Candidate name <input name="name" type="text" required></label>
<label>Party or affiliation <input name="party" type="text" value="Independent or local listing"></label>
</div>
<label>Race or office <input name="race" type="text" value="{html(ballot['district'])}"></label>
<label>Website <input name="website" type="text" value="{html(ballot['sample_ballot_url'])}"></label>
<label>Research note <textarea name="statement" rows="4" required></textarea></label>
<button type="submit" class="secondary">Add candidate note</button>
</form>
<p><a href="{html(ballot['sample_ballot_url'])}" target="_blank" rel="noreferrer">Official sample ballot source</a></p>
</div>
</div>
</div>
</section>
"""
def render_polling(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str:
"""Render polling."""
assigned = assigned_location(jurisdiction)
alternatives = alternatives_within_10_miles(jurisdiction)
location_confirmed = assigned.get("status", "confirmed") == "confirmed"
directions_url = google_maps_directions_url(assigned["address"]) if location_confirmed else jurisdiction["official"]["elections_url"]
embed_url = google_maps_embed_url(assigned["address"]) if location_confirmed else ""
map_visual = (
f'<iframe title="Google map for {html(assigned["name"])}" src="{html(embed_url)}" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>'
if location_confirmed and google_maps_embed_configured()
else '<div class="map-canvas"><span class="map-pin" aria-hidden="true"></span><p>Use the official election authority to confirm the assigned polling location.</p></div>'
)
alt_cards = "".join(
(
f'<article class="card"><h3>{html(location["name"])}</h3><p>{html(location["distance_miles"])} miles away - {html(location["hours"])}</p><span class="tag">{html(location["wait_minutes"])} minute wait</span></article>'
if location_confirmed
else f'<article class="card"><h3>{html(location["name"])}</h3><p>{html(location["hours"])}</p><span class="tag">{html(location.get("source", jurisdiction["authority"]))}</span></article>'
)
for location in alternatives
)
location_details = (
f'<div class="metric-row"><div class="metric"><strong>{html(assigned["hours"])}</strong>hours</div><div class="metric"><strong>{html(assigned["wait_minutes"])} min</strong>wait estimate</div></div>'
if location_confirmed
else f'<p><strong>Status:</strong> Official lookup required</p><p>{html(assigned.get("lookup_instructions", "Check the official election authority for your assigned polling location."))}</p>'
)
location_action = "Open Google Maps directions" if location_confirmed else "Check official authority"
return f"""
<section id="polling" class="section-band muted-band" data-requirement="8" aria-labelledby="pollingTitle">
<div class="section-inner">
<div class="section-heading"><p class="eyebrow">Polling finder</p><h2 id="pollingTitle">Locations, wait times, and access</h2></div>
<div class="two-column">
<div class="map-card" aria-label="Polling location map panel">{map_visual}<footer><strong>{html(assigned['name'])}</strong><p>{html(assigned['address'])}</p><a href="{html(directions_url)}" target="_blank" rel="noreferrer">{location_action}</a></footer></div>
<div class="panel">
<h3>Assigned location</h3>
<p><strong>Coverage:</strong> {html(_coverage_summary(jurisdiction))}</p>
{location_details}
<p><strong>Parking:</strong> {html(assigned['parking'])}</p>
<p><strong>Transit:</strong> {html(assigned['transit'])}</p>
<h4>Accessibility features</h4>{tag_html(assigned['accessibility'], 'success')}
<h4>Language assistance</h4>{tag_html(assigned['languages'], 'neutral')}
</div>
</div>
<div class="card-grid">{alt_cards}</div>
</div>
</section>
"""
def render_eligibility_security(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str:
"""Render eligibility security."""
result = eligibility(state["profile"], jurisdiction)
vault = state.get("vault", {})
return f"""
<section id="eligibility" class="section-band" data-requirement="9,14,15" aria-labelledby="eligibilityTitle">
<div class="section-inner">
<div class="section-heading"><p class="eyebrow">Eligibility and privacy</p><h2 id="eligibilityTitle">Verify eligibility and protect data</h2></div>
<div class="two-column">
<div class="panel">
<h3>Eligibility result</h3>
<p><span class="tag {'success' if result['eligible'] else 'critical'}">{'Eligible based on current profile' if result['eligible'] else 'Eligibility issue found'}</span></p>
{list_html(result['issues']) if result['issues'] else ''}
<details open><summary>Resolution guidance</summary>{list_html(resolution_steps(jurisdiction))}</details>
</div>
<div class="panel">
<h3>Security and privacy controls</h3>
<form method="post" action="/action/password#eligibility" class="field-group">
<label>Password simulation <input name="password" type="password" autocomplete="new-password"></label>
<p>Requires at least 12 characters with uppercase, lowercase, number, and symbol. Failed attempts are rate-limited.</p>
<button type="submit">Validate password policy</button>
</form>
<form method="post" action="/action/seal-vault#eligibility" class="field-group">
<label>Vault passphrase <input name="passphrase" type="password" autocomplete="current-password"></label>
<button type="submit"{" disabled" if not cryptography_available() else ""}>Seal private data with cryptography/Fernet</button>
</form>
<form method="post" action="/action/open-vault#eligibility" class="field-group">
<label>Open vault passphrase <input name="passphrase" type="password" autocomplete="current-password"></label>
<button type="submit" class="secondary"{" disabled" if not vault else ""}>Open vault</button>
</form>
<p><span class="tag neutral">{html(vault.get('algorithm', 'Vault encryption requires optional cryptography package') if not cryptography_available() else vault.get('algorithm', 'No local vault stored'))}</span></p>
<form method="post" action="/action/delete-account#dashboard"><button type="submit" class="warning">Delete local account data</button></form>
</div>
</div>
</div>
</section>
"""
def render_guide(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str:
"""Render guide."""
return f"""
<section id="guide" class="section-band muted-band" data-requirement="10,13" aria-labelledby="guideTitle">
<div class="section-inner">
<div class="section-heading"><p class="eyebrow">Document generator</p><h2 id="guideTitle">Personalized voter guide</h2></div>
<div class="two-column">
<div class="panel">
<h3>Export options</h3>
<p>The Python document generator includes registration status, deadlines, polling location, ballot summaries, checklist state, and saved notes.</p>
<div class="action-row">
<a class="button" href="/download/guide.html">Download HTML</a>
<a class="button secondary" href="/download/guide.txt">Download text</a>
</div>
<div class="qr-tile" aria-label="QR-style official resource marker"><span>Official links</span></div>
<p><a href="{html(jurisdiction['official']['elections_url'])}" target="_blank" rel="noreferrer">Official election resources</a></p>
<p>Use the browser print command for the print-optimized guide layout. PWA offline access caches the app shell after first visit and keeps cached content within {html(META['cache_limit_mb'])} MB.</p>
</div>
<div class="guide-preview">
<h3>Guide preview</h3>
<p><strong>Registration:</strong> {html(registration_status(state['profile'], jurisdiction))}</p>
<p><strong>Polling place:</strong> {html(jurisdiction['polling']['assigned']['name'])}</p>
<p><strong>{html(jurisdiction.get('district_label', 'District'))}:</strong> {html(jurisdiction['ballot']['district'])}</p>
<p><strong>Last generated:</strong> {html(state.get('generated_guide_at') or 'Not generated yet')}</p>
</div>
<div class="panel" style="margin-top:1rem">
<h3>Translate guide content</h3>
<p>{'Google Translate API connected.' if TranslateService.configured() else 'Google Translate offline - original text returned.'}</p>
<form method="post" action="/action/translate#guide" class="field-group">
<label>Text to translate <textarea name="text" rows="3">{html(jurisdiction['name'])} voter guide content</textarea></label>
<label>Target language
<select name="target_language">
<option value="es">Spanish</option>
<option value="zh">Chinese</option>
<option value="hi">Hindi</option>
<option value="ta">Tamil</option>
<option value="fr">French</option>
<option value="ar">Arabic</option>
<option value="ko">Korean</option>
<option value="vi">Vietnamese</option>
<option value="ru">Russian</option>
<option value="pt">Portuguese</option>
</select>
</label>
<button type="submit" aria-label="Translate voter guide content with Google Translate">Translate with Google</button>
</form>
</div>
</div>
</div>
</section>
"""
def render_support(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str:
"""Render support."""
analysis = analyze_feedback(state)
incident_options = [(item["id"], item["label"]) for item in INCIDENT_TYPES]
return f"""
<section id="support" class="section-band" data-requirement="11,12,20" aria-labelledby="supportTitle">
<div class="section-inner">
<div class="section-heading"><p class="eyebrow">Feedback and incident support</p><h2 id="supportTitle">Tell us what needs attention</h2></div>
<div class="two-column">
<form method="post" action="/action/feedback#support" class="panel field-group">
<h3>Feedback collector</h3>
<div class="field-grid"><label>Page <input name="page" type="text" value="General"></label><label>Category <select name="category">{options([("usability", "Usability issue"), ("content-error", "Content error"), ("feature-request", "Feature request"), ("process-pain-point", "Process pain point")], "usability")}</select></label></div>
<label>Feedback <textarea name="message" rows="5" required></textarea></label>
<label>Optional contact <input name="contact" type="email" autocomplete="email"></label>
<button type="submit">Submit feedback</button>
</form>
<form method="post" action="/action/incident#support" class="panel field-group">
<h3>Election day incident report</h3>
<label>Issue type <select name="type">{options(incident_options, "long-wait")}</select></label>
<label>Location <input name="location" type="text" value="{html(jurisdiction['polling']['assigned']['name'])}"></label>
<label>Details <textarea name="details" rows="5" required></textarea></label>
<button type="submit">Report incident</button>
</form>
</div>
<div class="card-grid">
<article class="card"><h3>Feedback trend</h3><p>{analysis['total']} submissions captured. {analysis['flagged']} flagged for review within 24 hours.</p>{tag_html([f"{key}: {value}" for key, value in analysis['category_counts'].items()], 'neutral')}</article>
<article class="card"><h3>Incident escalation</h3><p>{analysis['incident_count']} reports. Critical reports show hotline guidance and 15-minute escalation messaging.</p><p>{html(jurisdiction['contacts']['hotline'])}</p></article>
<article class="card"><h3>Pattern detection</h3><p>{html(analysis['patterns'] or 'No repeated-location pattern yet.')}</p></article>
</div>
</div>
</section>
"""
def render_learning(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str:
"""Render learning."""
model = learning_model(state)
modules = "".join(
f"""
<article class="card">
<h4>{html(module['title'])}</h4>
<p>{html(module['summary'])}</p>
<p><span class="tag">{html(module['audience'])}</span> <span class="tag neutral">{html(module['duration'])}</span></p>
<details><summary>Caption transcript</summary><p>{html(module['transcript'])}</p></details>
<form method="post" action="/action/complete-module#learning"><input type="hidden" name="module_id" value="{html(module['id'])}"><button type="submit" class="secondary">{'Completed' if module['id'] in model['completed'] else 'Mark complete'}</button></form>
</article>
"""
for module in model["modules"]
)
innovations = "".join(
f'<article class="card"><h4>{html(item["title"])}</h4><p><strong>Benefits:</strong> {html(item["benefits"])}</p><p><strong>Challenges:</strong> {html(item["challenges"])}</p><p><strong>Example:</strong> {html(item["example"])}</p><p>{html(item["advocacy"])}</p></article>'
for item in model["innovations"]
)
stats = model["organizer_stats"]
return f"""
<section id="learning" class="section-band muted-band" data-requirement="16,18,19" aria-labelledby="learningTitle">
<div class="section-inner">
<div class="section-heading"><p class="eyebrow">Education and organizing</p><h2 id="learningTitle">Civic learning, reform, and drive tools</h2></div>
<div class="three-column">
<div><h3>Educational modules</h3>{modules}</div>
<div><h3>Voting method innovation</h3>{innovations}<article class="card"><h4>Process improvement proposals</h4>{list_html(model['process_improvements'])}</article></div>
<div>
<h3>Registration drive support</h3>
<article class="card"><h4>Organizer toolkit</h4>{list_html(model['organizer_toolkit']['best_practices'])}{tag_html(model['organizer_toolkit']['materials'], 'neutral')}<a class="button secondary" href="/download/organizer-toolkit.txt">Download printable toolkit</a></article>
<form method="post" action="/action/batch#learning" class="card field-group"><h4>Batch tracking</h4><label>Batch label <input name="label" type="text" required></label><label>Forms submitted <input name="count" type="number" min="1" value="1" required></label><button type="submit">Add batch</button></form>
<article class="card"><h4>Aggregate stats</h4><p>{stats['registrations']} registrations tracked across {stats['batches']} batches.</p></article>
<form method="post" action="/action/export-sheets#learning" class="action-row">
<button type="submit" class="secondary" aria-label="Export registration drive batches to Google Sheets or CSV">Export to Google Sheets / CSV</button>
</form>
</div>
</div>
</div>
</section>
"""
def render_services(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str:
"""Render the Google Services status and integration dashboard."""
services = google_services_status()
rows = "".join(
f"""
<tr>
<td><strong>{html(name.replace('_', ' ').title())}</strong></td>
<td><span class="tag {'success' if info['configured'] else 'neutral'}">{'Active' if info['configured'] else 'Offline'}</span></td>
<td>{html(info['description'])}</td>
</tr>
"""
for name, info in services.items()
)
active_count = sum(1 for info in services.values() if info["configured"])
total_count = len(services)
return f"""
<section id="services" class="section-band" data-requirement="4,15" aria-labelledby="servicesTitle">
<div class="section-inner">
<div class="section-heading"><p class="eyebrow">Platform integrations</p><h2 id="servicesTitle">Google Services Dashboard</h2></div>
<div class="panel">
<h3>Integration status</h3>
<p><strong>{active_count}</strong> of <strong>{total_count}</strong> Google services are active.</p>
<div class="progress-track" aria-label="Google service adoption"><div class="progress-fill" style="width: {round(active_count / total_count * 100)}%"></div></div>
<div class="table-wrap"><table><caption>Google service integration status</caption>
<thead><tr><th>Service</th><th>Status</th><th>Description</th></tr></thead>
<tbody>{rows}</tbody>
</table></div>
</div>
<div class="two-column" style="margin-top:1rem">
<div class="panel">
<h3>AI Assistant (Gemini)</h3>
<p>{'Google Gemini API key is configured.' if GeminiService.configured() else 'Set GOOGLE_GEMINI_API_KEY for live AI Q&A. Using curated FAQ fallback.'}</p>
<form method="post" action="/action/ask-gemini#services" class="field-group">
<label>Ask a civic question <input name="question" type="text" placeholder="What documents do I need to vote?" required></label>
<button type="submit" aria-label="Ask Gemini AI a civic question">Ask Gemini AI</button>
</form>
</div>
<div class="panel">
<h3>Content Translation</h3>
<p>{'Google Translate API key is configured.' if TranslateService.configured() else 'Set GOOGLE_TRANSLATE_API_KEY for live translation. Returning original text.'}</p>
<form method="post" action="/action/translate#services" class="field-group">
<label>Text <textarea name="text" rows="2">Election registration deadline reminder</textarea></label>
<label>Target language
<select name="target_language">
<option value="es">Spanish</option><option value="hi">Hindi</option><option value="zh">Chinese</option>
<option value="ta">Tamil</option><option value="fr">French</option><option value="ar">Arabic</option>
</select>
</label>
<button type="submit" aria-label="Translate sample election text">Translate</button>
</form>
</div>
</div>
<div class="two-column" style="margin-top:1rem">
<div class="panel">
<h3>URL Safety Check (Safe Browsing)</h3>
<form method="post" action="/action/check-url#services" class="field-group">
<label>URL to validate <input name="url" type="url" placeholder="https://example.org" required></label>
<button type="submit" class="secondary" aria-label="Check URL with Google Safe Browsing">Check with Google Safe Browsing</button>
</form>
</div>
<div class="panel">
<h3>Google OAuth</h3>
<p>{'OAuth client configured.' if OAuthService.configured() else 'Set GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET for Google Sign-In.'}</p>
<p>Google Sign-In provides secure, passwordless authentication for persistent voter profiles.</p>
<a class="button secondary" href="/auth/google">Start Google Sign-In</a>
</div>
</div>
</div>
</section>
"""