"""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'' for value, label in items ) def flash_html(messages: list[str]) -> str: """Handle flash html.""" if not messages: return "" return '
' + "
".join(html(message) for message in messages) + "
" 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'{html(label)}' 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"""

Welcome

Set up your election assistant

Choose your country and voting area to tailor deadlines, registration guidance, ballot research, and polling details.

""" 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'' result = f""" Election Process Assistant - {html(page.title() if page != 'setup' else 'Setup')} {nav_html}
{flash_html(messages) if page != "dashboard" else ""} {content}
""" return add_csrf_fields(result, state.get("csrf_token", "")) def add_csrf_fields(page: str, token: str) -> str: """Add csrf fields.""" field = f'' return re.sub(r'(]*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"""
""" 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"""

{html(tr(state, "kicker"))}

{html(tr(state, "workspace"))}

{flash_html(messages)}

Personal profile

Readiness snapshot

{stats['percent']}%process complete
{html(remaining)}days to next deadline
{len(state.get('offline_queue', []))}queued offline actions

{html(jurisdiction['name'])}, {html(jurisdiction.get('country_name', ''))} deadlines are shown in {html(jurisdiction['timezone_label'])}. {html(tr(state, "mock"))}

Generate guide
Map-style illustration of polling places, deadlines, and voter guide checkpoints
Election tasks, deadlines, and voting options in one focused workspace.

Ask the AI Assistant

Powered by Google Gemini{' - API configured' if GeminiService.configured() else ' (offline FAQ mode)'}.

""" 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"""
{html(status)}Step {index}{html(step['estimated_time'])}

{html(step['title'])}

{html(step['instructions'])}

Required documents and contextual help

Documents

{list_html(step['documents'])}

Common issues

{list_html(step['help'])}
""") return f"""

Interactive guide

Step-by-step election process

{''.join(cards)}
""" 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"""
{html(deadline['date'])}
{remaining if remaining >= 0 else abs(remaining)} days {'remaining' if remaining >= 0 else 'past'}

{html(deadline['title'])}

{html(deadline['description'])}

{html(deadline['category'])}{html(deadline['source'])}
""") prefs = state["notification_preferences"] return f"""

Deadlines and reminders

Personalized timeline

{''.join(items)}

Notification preferences

Registered users receive reminders at configured intervals. Critical deadlines also get a final 48-hour reminder notice.

{checkboxes("channels", ["email", "sms", "push"], prefs.get("channels", []))}
{checkboxes("intervals", ["30", "7", "1"], [str(item) for item in prefs.get("intervals", [])])}
{checkboxes("categories", ["deadlines", "ballot", "system"], prefs.get("categories", []))}

Local data timestamp: {html(META['last_updated'])}. Valid official source changes refresh through the Python integration adapter.

""" def checkboxes(name: str, values: list[str], selected: list[str]) -> str: """Handle checkboxes.""" return "".join( f'' 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"""

Registration tracker

Registration guidance and status

Status and methods

{html(registration_status(state['profile'], jurisdiction))}

Registration methods

{list_html(reg['methods'])}

Required information

{list_html(reg['required_documents'])}

Processing: {html(reg['processing_time'])}

Official registration portal

{html(reg['fallback'])}

Eligibility validation

Calculated age: {html(result['age'])}

{'Eligibility criteria met' if result['eligible'] else 'Needs resolution'}

{list_html(result['issues']) if result['issues'] else ''}

{html(result['rule'])}

Problem resolution{list_html(resolution_steps(jurisdiction))}
""" def render_voting(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str: """Render voting.""" selected = selected_method(state, jurisdiction) rows = "".join( f"""
{html(method['deadline'])} {html(method['requirements'])} {html(method['procedure'])} {html(method['accessibility'])} """ for method in jurisdiction["voting_methods"] ) tracking = selected.get("tracking", "") return f"""

Voting methods

Compare ways to vote

{rows}
Voting method comparison for {html(jurisdiction['name'])}
MethodDeadlineRequirementsProcedureAccessibility

{html(selected['name'])}

{html(selected['procedure'])}

{html(selected['requirements'])}

{html(selected['accessibility'])}

{f'

{html(tracking)}

' if tracking else ''}
""" 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"""

{html(candidate['name'])}

{html(candidate['race'])} - {html(candidate['party'])}

{html(candidate['statement'])}

{html(candidate.get('link_label', 'Candidate website'))}

{html(candidate['source'])}

""" for candidate in ballot_view["candidates"] ) comparison = comparison_rows(ballot_view) rows = "".join( "{}{}".format( html(row["issue"]), "".join(f"{html(position['position'])}" for position in row["positions"]), ) for row in comparison ) measures = "".join( f"""
{html(measure['title'])}

{html(measure['summary'])}

Fiscal impact: {html(measure['fiscal_impact'])}

For: {html(measure['arguments_for'])}

Against: {html(measure['arguments_against'])}

{html(measure['source'])}

""" for measure in ballot["measures"] ) note = state.get("research_notes", {}).get(ballot["district"], "") comparison_headers = "".join(f"{html(candidate['name'])}" for candidate in ballot_view["candidates"]) comparison_table = ( f'
{comparison_headers}{rows}
Candidate position comparison
Issue
' if len(ballot_view["candidates"]) > 1 and comparison else '

Candidate position comparison: Add two or more verified candidates to compare notes side by side.

' ) return f"""

Ballot research

Candidates and measures

{html(jurisdiction.get('district_label', 'District'))}: {html(ballot['district'])}

Coverage: {html(ballot.get('coverage_note') or _coverage_summary(jurisdiction))}

Verification: Use {html(jurisdiction['authority'])} or the official sample ballot source before making voting decisions.

{candidate_cards}
{comparison_table}

Measures and research notes

{measures}

Official sample ballot source

""" 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'' if location_confirmed and google_maps_embed_configured() else '

Use the official election authority to confirm the assigned polling location.

' ) alt_cards = "".join( ( f'

{html(location["name"])}

{html(location["distance_miles"])} miles away - {html(location["hours"])}

{html(location["wait_minutes"])} minute wait
' if location_confirmed else f'

{html(location["name"])}

{html(location["hours"])}

{html(location.get("source", jurisdiction["authority"]))}
' ) for location in alternatives ) location_details = ( f'
{html(assigned["hours"])}hours
{html(assigned["wait_minutes"])} minwait estimate
' if location_confirmed else f'

Status: Official lookup required

{html(assigned.get("lookup_instructions", "Check the official election authority for your assigned polling location."))}

' ) location_action = "Open Google Maps directions" if location_confirmed else "Check official authority" return f"""

Polling finder

Locations, wait times, and access

{map_visual}

Assigned location

Coverage: {html(_coverage_summary(jurisdiction))}

{location_details}

Parking: {html(assigned['parking'])}

Transit: {html(assigned['transit'])}

Accessibility features

{tag_html(assigned['accessibility'], 'success')}

Language assistance

{tag_html(assigned['languages'], 'neutral')}
{alt_cards}
""" 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"""

Eligibility and privacy

Verify eligibility and protect data

Eligibility result

{'Eligible based on current profile' if result['eligible'] else 'Eligibility issue found'}

{list_html(result['issues']) if result['issues'] else ''}
Resolution guidance{list_html(resolution_steps(jurisdiction))}

Security and privacy controls

Requires at least 12 characters with uppercase, lowercase, number, and symbol. Failed attempts are rate-limited.

{html(vault.get('algorithm', 'Vault encryption requires optional cryptography package') if not cryptography_available() else vault.get('algorithm', 'No local vault stored'))}

""" def render_guide(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str: """Render guide.""" return f"""

Document generator

Personalized voter guide

Export options

The Python document generator includes registration status, deadlines, polling location, ballot summaries, checklist state, and saved notes.

Official links

Official election resources

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.

Guide preview

Registration: {html(registration_status(state['profile'], jurisdiction))}

Polling place: {html(jurisdiction['polling']['assigned']['name'])}

{html(jurisdiction.get('district_label', 'District'))}: {html(jurisdiction['ballot']['district'])}

Last generated: {html(state.get('generated_guide_at') or 'Not generated yet')}

Translate guide content

{'Google Translate API connected.' if TranslateService.configured() else 'Google Translate offline - original text returned.'}

""" 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"""

Feedback and incident support

Tell us what needs attention

Feedback collector

Election day incident report

Feedback trend

{analysis['total']} submissions captured. {analysis['flagged']} flagged for review within 24 hours.

{tag_html([f"{key}: {value}" for key, value in analysis['category_counts'].items()], 'neutral')}

Incident escalation

{analysis['incident_count']} reports. Critical reports show hotline guidance and 15-minute escalation messaging.

{html(jurisdiction['contacts']['hotline'])}

Pattern detection

{html(analysis['patterns'] or 'No repeated-location pattern yet.')}

""" def render_learning(state: dict[str, Any], jurisdiction: dict[str, Any]) -> str: """Render learning.""" model = learning_model(state) modules = "".join( f"""

{html(module['title'])}

{html(module['summary'])}

{html(module['audience'])} {html(module['duration'])}

Caption transcript

{html(module['transcript'])}

""" for module in model["modules"] ) innovations = "".join( f'

{html(item["title"])}

Benefits: {html(item["benefits"])}

Challenges: {html(item["challenges"])}

Example: {html(item["example"])}

{html(item["advocacy"])}

' for item in model["innovations"] ) stats = model["organizer_stats"] return f"""

Education and organizing

Civic learning, reform, and drive tools

Educational modules

{modules}

Voting method innovation

{innovations}

Process improvement proposals

{list_html(model['process_improvements'])}

Registration drive support

Organizer toolkit

{list_html(model['organizer_toolkit']['best_practices'])}{tag_html(model['organizer_toolkit']['materials'], 'neutral')}Download printable toolkit

Batch tracking

Aggregate stats

{stats['registrations']} registrations tracked across {stats['batches']} batches.

""" 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""" {html(name.replace('_', ' ').title())} {'Active' if info['configured'] else 'Offline'} {html(info['description'])} """ for name, info in services.items() ) active_count = sum(1 for info in services.values() if info["configured"]) total_count = len(services) return f"""

Platform integrations

Google Services Dashboard

Integration status

{active_count} of {total_count} Google services are active.

{rows}
Google service integration status
ServiceStatusDescription

AI Assistant (Gemini)

{'Google Gemini API key is configured.' if GeminiService.configured() else 'Set GOOGLE_GEMINI_API_KEY for live AI Q&A. Using curated FAQ fallback.'}

Content Translation

{'Google Translate API key is configured.' if TranslateService.configured() else 'Set GOOGLE_TRANSLATE_API_KEY for live translation. Returning original text.'}

URL Safety Check (Safe Browsing)

Google OAuth

{'OAuth client configured.' if OAuthService.configured() else 'Set GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET for Google Sign-In.'}

Google Sign-In provides secure, passwordless authentication for persistent voter profiles.

Start Google Sign-In
"""