# Project export: VoteSmart

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: CruzHacks 2025
- Tagline: A website to give a personalized feed of political policies, misinformation-free. Highly dependent on FastAPI and MongoDB.
- Devpost: https://devpost.com/software/votesmart-y7drwt
- GitHub: https://github.com/sathyarseelam/VoteSmartWeb
- Video: https://www.youtube.com/embed/ystTtOUBayM?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Justice Hacks)
- Team: 3 GitHub contributor(s) — mihirb-hub (6 commits), Sathya (5 commits), Avni Gandhi (1 commits)

## Devpost submission (written by the team)

### Inspiration

VoteSmart started after the 2024 elections, when many people around us were first-time voters, including us. As we went through the process, we noticed how hard it was to understand what we were actually voting on. The language in propositions was confusing, and it wasn’t always clear where candidates stood on the issues we cared about. That experience made us realize there was a real need for a tool that could make this information more approachable. So we built VoteSmart: a platform that breaks down complex political content into clear, unbiased, and personalized insights to help voters make confident and informed decisions.

### What it does

VoteSmart simplifies the voting process by breaking down complex propositions and candidate policies into clear, unbiased summaries tailored to each voter. We used Google Gemini's API to translate legal and political language into easy-to-understand insights. To ensure accuracy, we built a custom web-scraping pipeline, using Beautiful Soup that pulls information directly from primary sources like .gov websites and official presidential campaign platforms. Users select their policy interests during onboarding, which allows us to personalize their feed with propositions and policies that specifically affect them, whether positively or negatively, so they can focus on the issues that matter most and make informed decisions at the ballot box.

### How we built it

On Day 1, we started by nailing down what our app needed to do—after creating a comprehensive list of features we wanted, we settled on React for the front end, Python (FastAPI) for the back end, and MongoDB for storage. Two of us took on the React project: set up components, React Router, and a basic style guide. The other two started the FastAPI app: created a virtualenv, installed FastAPI and PyMongo, and made our first routes (/auth/login, /auth/signup) alongside a draft of our MongoDB collections (users, sessions). By Day 2, we focused more on the backend. We defined our user information in MongoDB (email, hashed password, timestamps), wrote helper functions to connect and query the database, and built out session management. Meanwhile, on the front end, we wired the login and signup forms to those FastAPI endpoints, and created a scraper using Gemini to gather data and articles. As all of the parts finished, we further integrated the site and submitted our code.

### Challenges we ran into

The night before the deadline, we spent two hours wrestling with Git: merge conflicts, accidental commits, and a wildly tangled branch structure. We ultimately scrapped the repo, reinitialized it, and overhauled our GitHub workflow with branch protections, PR templates, and strict feature branches. We’d picked React for the UI and FastAPI (with Python) for the backend, but integrating them proved more challenging than we’d anticipated. By midnight on Sunday, we were still wiring up core frontend and backend features, with no time left to finish personalization logic for our web‑scraped data or the candidate‑suggestion engine, despite having all the Gemini API prompts and scrapers ready. In the end, we’re proud of each component we built in isolation, but we learned that in a 48‑hour sprint, ambition must be balanced with the time and expertise available.

### Accomplishments we're proud of

We're proud that our login system actually works, users can securely sign up and log in with hashed passwords and real-time form checks. We built modular Python scrapers that pull data from reliable sources like .gov sites, and they’re easy to expand later. Even with just two days, we created a clean, responsive UI that looks good on both phones and desktops. Plus, we learned how to use the Gemini API and write better AI prompts along the way!

### What we learned

We learned how to connect programs from React to FastAPI to MongoDB, showing a clear front-to-back progression, and helped us understand how APIs can communicate. After the Git mistake that made our git commits more complicated, making this site has taught us more about better using Git and how branches should work. Wrapping the Gemini API and GPT helper also educated us on tokens and rate limits, preparing us more for work for AI. We’ve also learned more about using MongoDB and how to send data between FastAPI and a database. Overall, this experience educated us a lot on popular topics in the world and prepared us to work on similar projects in the future.

### What's next

We will continue to build VoteSmart by adding features like a personalized chatbot that can answer user-specific questions about voting and policy issues. We also plan to develop a real-time polling feature that shows how users are leaning on candidates and propositions, helping voters stay informed and engaged throughout the election cycle.

## README (from the GitHub repository)

# VoteSmartWeb

Group members: Sathya Seelam, Avni Gandhi, Amisha Kandi, Mihir Bhagatwala

VoteSmart is an AI-powered platform that simplifies complex political information into clear, personalized insights for voters. It helps users understand propositions, compare candidates, and make confident decisions at the ballot box. This idea originated after the 2024 election when I realized that, like me, many first-time voters found it difficult to navigate the legal jargon and dense language in official voting materials. With VoteSmart, we hope to increase civic engagement and reduce barriers to political participation—especially for first-time voters and underrepresented communities.

On the tech side, VoteSmart is built using React and Vite for the frontend, FastAPI and Python for the backend, and MongoDB for the database. We integrated Google Gemini's generative AI to simplify propositions and candidate policies, and used custom web scraping tools to pull information directly from trusted sources like .gov websites and official campaign platforms.


## Detected evidence (automated analysis)

Indexed codebase: 113 recognized source files, 284 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- Firebase (technology) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- MongoDB (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 126)

```
.DS_Store
.gitignore
backend/app.py
backend/gemini.py
backend/personalized_props.py
backend/policy_scraper.py
backend/props.json
backend/requirements.txt
backend/top_propositions.json
backend/trump_scraper.py
frontend/.env
frontend/bun.lockb
frontend/components.json
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/public/robots.txt
frontend/README.md
frontend/src/api/client.ts
frontend/src/App.css
frontend/src/App.tsx
frontend/src/components/CandidatePopup.tsx
frontend/src/components/dashboard/CandidateCard.tsx
frontend/src/components/dashboard/FeedCard.tsx
frontend/src/components/dashboard/FeedSection.tsx
frontend/src/components/dashboard/FloatingChatButton.tsx
frontend/src/components/dashboard/Header.tsx
frontend/src/components/dashboard/HeroSection.tsx
frontend/src/components/dashboard/LegislationCard.tsx
frontend/src/components/dashboard/PollCard.tsx
frontend/src/components/dashboard/QuickActionsSection.tsx
frontend/src/components/dashboard/TimelineCard.tsx
frontend/src/components/dashboard/TimelineSection.tsx
frontend/src/components/Header.tsx
frontend/src/components/LegislationPopup.tsx
frontend/src/components/Logo.tsx
frontend/src/components/mode-toggle.tsx
frontend/src/components/progress-bar.tsx
frontend/src/components/registration/NavigationButtons.tsx
frontend/src/components/registration/ProgressBar.tsx
frontend/src/components/registration/StepIndicator.tsx
frontend/src/components/registration/steps/BenefitsStep.tsx
frontend/src/components/registration/steps/BirthDateStep.tsx
frontend/src/components/registration/steps/CompletionStep.tsx
frontend/src/components/registration/steps/CountyStep.tsx
frontend/src/components/registration/steps/EducationStep.tsx
frontend/src/components/registration/steps/EmailStep.tsx
frontend/src/components/registration/steps/FamilySizeStep.tsx
frontend/src/components/registration/steps/FirstNameStep.tsx
frontend/src/components/registration/steps/GenderStep.tsx
frontend/src/components/registration/steps/IncomeStep.tsx
frontend/src/components/registration/steps/LastNameStep.tsx
frontend/src/components/registration/steps/PasswordStep.tsx
frontend/src/components/registration/steps/PolicyInterestsStep.tsx
frontend/src/components/registration/steps/RaceEthnicityStep.tsx
frontend/src/components/registration/steps/VotingPreferenceStep.tsx
frontend/src/components/ui/accordion.tsx
frontend/src/components/ui/alert-dialog.tsx
frontend/src/components/ui/alert.tsx
frontend/src/components/ui/aspect-ratio.tsx
frontend/src/components/ui/avatar.tsx
frontend/src/components/ui/badge.tsx
frontend/src/components/ui/breadcrumb.tsx
frontend/src/components/ui/button.tsx
frontend/src/components/ui/calendar.tsx
frontend/src/components/ui/card.tsx
frontend/src/components/ui/carousel.tsx
frontend/src/components/ui/chart.tsx
frontend/src/components/ui/checkbox.tsx
frontend/src/components/ui/collapsible.tsx
frontend/src/components/ui/command.tsx
frontend/src/components/ui/context-menu.tsx
frontend/src/components/ui/dialog.tsx
frontend/src/components/ui/drawer.tsx
frontend/src/components/ui/dropdown-menu.tsx
frontend/src/components/ui/form.tsx
frontend/src/components/ui/hover-card.tsx
frontend/src/components/ui/input-otp.tsx
frontend/src/components/ui/input.tsx
frontend/src/components/ui/label.tsx
frontend/src/components/ui/menubar.tsx
frontend/src/components/ui/navigation-menu.tsx
frontend/src/components/ui/pagination.tsx
frontend/src/components/ui/personalcards.tsx
frontend/src/components/ui/popover.tsx
frontend/src/components/ui/progress.tsx
frontend/src/components/ui/radio-group.tsx
frontend/src/components/ui/resizable.tsx
frontend/src/components/ui/scroll-area.tsx
frontend/src/components/ui/select.tsx
frontend/src/components/ui/separator.tsx
frontend/src/components/ui/sheet.tsx
frontend/src/components/ui/sidebar.tsx
frontend/src/components/ui/skeleton.tsx
frontend/src/components/ui/slider.tsx
frontend/src/components/ui/sonner.tsx
frontend/src/components/ui/switch.tsx
frontend/src/components/ui/table.tsx
frontend/src/components/ui/tabs.tsx
frontend/src/components/ui/textarea.tsx
frontend/src/components/ui/toast.tsx
frontend/src/components/ui/toaster.tsx
frontend/src/components/ui/toggle-group.tsx
frontend/src/components/ui/toggle.tsx
frontend/src/components/ui/tooltip.tsx
frontend/src/components/ui/use-toast.ts
frontend/src/contexts/RegistrationContext.tsx
frontend/src/hooks/use-mobile.tsx
frontend/src/hooks/use-toast.ts
frontend/src/index.css
frontend/src/lib/firebase.ts
frontend/src/lib/utils.ts
frontend/src/main.tsx
frontend/src/pages/Dashboard.tsx
frontend/src/pages/landing-page.tsx
frontend/src/pages/Login.tsx
frontend/src/pages/NotFound.tsx
frontend/src/pages/Registration.tsx
frontend/src/vite-env.d.ts
[6 more files omitted for size]
```

### Dependencies

- backend/requirements.txt: beautifulsoup4, certifi, dnspython, fastapi, Flask, google-generativeai, itsdangerous, passlib, pydantic, pymongo[srv]@==3.11, python-dotenv, python-multipart, requests, tiktoken, uvicorn[standard], Werkzeug
- frontend/package.json: @eslint/js@^9.9.0, @hookform/resolvers@^3.9.0, @radix-ui/react-accordion@^1.2.0, @radix-ui/react-alert-dialog@^1.1.1, @radix-ui/react-aspect-ratio@^1.1.0, @radix-ui/react-avatar@^1.1.0, @radix-ui/react-checkbox@^1.1.1, @radix-ui/react-collapsible@^1.1.0, @radix-ui/react-context-menu@^2.2.1, @radix-ui/react-dialog@^1.1.2, @radix-ui/react-dropdown-menu@^2.1.1, @radix-ui/react-hover-card@^1.1.1, @radix-ui/react-label@^2.1.0, @radix-ui/react-menubar@^1.1.1, @radix-ui/react-navigation-menu@^1.2.0, @radix-ui/react-popover@^1.1.1, @radix-ui/react-progress@^1.1.0, @radix-ui/react-radio-group@^1.2.0, @radix-ui/react-scroll-area@^1.1.0, @radix-ui/react-select@^2.1.1, @radix-ui/react-separator@^1.1.0, @radix-ui/react-slider@^1.2.0, @radix-ui/react-slot@^1.1.0, @radix-ui/react-switch@^1.1.0, @radix-ui/react-tabs@^1.1.0, @radix-ui/react-toast@^1.2.1, @radix-ui/react-toggle@^1.1.0, @radix-ui/react-toggle-group@^1.1.0, @radix-ui/react-tooltip@^1.1.4, @tailwindcss/typography@^0.5.15, @tanstack/react-query@^5.56.2, @types/node@^22.5.5, @types/react@^18.3.20, @types/react-dom@^18.3.0, @vitejs/plugin-react-swc@^3.5.0, autoprefixer@^10.4.20, axios@^1.8.4, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@^1.0.0, date-fns@^3.6.0, embla-carousel-react@^8.3.0, eslint@^9.9.0, eslint-plugin-react-hooks@^5.1.0-rc.0, eslint-plugin-react-refresh@^0.4.9, firebase@^11.6.0, globals@^15.9.0, input-otp@^1.2.4, lovable-tagger@^1.1.7, lucide-react@^0.462.0, next-themes@^0.3.0, postcss@^8.4.47, react@^18.3.1, react-day-picker@^8.10.1, react-dom@^18.3.1, react-hook-form@^7.53.0, react-resizable-panels@^2.1.3, react-router-dom@^6.26.2, recharts@^2.12.7, sonner@^1.5.0, tailwind-merge@^2.5.2, tailwindcss@^3.4.11, tailwindcss-animate@^1.0.7, typescript@^5.5.3, typescript-eslint@^8.0.1, vaul@^0.9.3, vite@^5.4.1, zod@^3.23.8

### Recent commits (newest first)

- Delete backend/.env
- Update README.md
- some changes
- comment for user data
- auth works, login works, fixed api stuff
- Merge pull request #8 from sathyarseelam/frontend-cards
- Merge pull request #7 from sathyarseelam/backend_database_change
- cleanup pt.3
- Merge pull request #6 from sathyarseelam/backend_database_change
- clean up pt.2
- clean up pt.2
- cleaning up backend
- cleaning up backend code
- Update README.md
- Update README.md
- finished last changes
- Merge pull request #5 from sathyarseelam/backend-cards
- Merge pull request #4 from sathyarseelam/frontend-cards
- last min changes
- added some more personalizable functionality

## Key source files (fetched from GitHub, selected and truncated for size)

### backend/requirements.txt

```
fastapi                # API framework
uvicorn[standard]      # ASGI server
beautifulsoup4         # HTML parsing
requests               # HTTP client
dnspython              # for mongodb+srv URIs
python-dotenv          # load .env
google-generativeai    # Gemini (Vertex AI) client
tiktoken               # tokenizer for chunking
pydantic               # settings & data validation
Flask                  # to make app
Werkzeug               # to hash user passwords
python-multipart
itsdangerous
pymongo[srv]==3.11
certifi
passlib
```

### frontend/package.json

```
{
  "name": "vite_react_shadcn_ts",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "build:dev": "vite build --mode development",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.9.0",
    "@radix-ui/react-accordion": "^1.2.0",
    "@radix-ui/react-alert-dialog": "^1.1.1",
    "@radix-ui/react-aspect-ratio": "^1.1.0",
    "@radix-ui/react-avatar": "^1.1.0",
    "@radix-ui/react-checkbox": "^1.1.1",
    "@radix-ui/react-collapsible": "^1.1.0",
    "@radix-ui/react-context-menu": "^2.2.1",
    "@radix-ui/react-dialog": "^1.1.2",
    "@radix-ui/react-dropdown-menu": "^2.1.1",
    "@radix-ui/react-hover-card": "^1.1.1",
    "@radix-ui/react-label": "^2.1.0",
    "@radix-ui/react-menubar": "^1.1.1",
    "@radix-ui/react-navigation-menu": "^1.2.0",
    "@radix-ui/react-popover": "^1.1.1",
    "@radix-ui/react-progress": "^1.1.0",
    "@radix-ui/react-radio-group": "^1.2.0",
    "@radix-ui/react-scroll-area": "^1.1.0",
    "@radix-ui/react-select": "^2.1.1",
    "@radix-ui/react-separator": "^1.1.0",
    "@radix-ui/react-slider": "^1.2.0",
    "@radix-ui/react-slot": "^1.1.0",
    "@radix-ui/react-switch": "^1.1.0",
    "@radix-ui/react-tabs": "^1.1.0",
    "@radix-ui/react-toast": "^1.2.1",
    "@radix-ui/react-toggle": "^1.1.0",
    "@radix-ui/react-toggle-group": "^1.1.0",
    "@radix-ui/react-tooltip": "^1.1.4",
    "@tanstack/react-query": "^5.56.2",
    "axios": "^1.8.4",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.0.0",
    "date-fns": "^3.6.0",
    "embla-carousel-react": "^8.3.0",
    "firebase": "^11.6.0",
    "input-otp": "^1.2.4",
    "lucide-react": "^0.462.0",
    "next-themes": "^0.3.0",
    "react": "^18.3.1",
    "react-day-picker": "^8.10.1",
    "react-dom": "^18.3.1",
    "react-hook-form": "^7.53.0",
    "react-resizable-panels": "^2.1.3",
    "react-router-dom": "^6.26.2",
    "recharts": "^2.12.7",
    "sonner": "^1.5.0",
    "tailwind-merge": "^2.5.2",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^0.9.3",
    "zod": "^3.23.8"
  },
  "devDependencies": {
    "@eslint/js": "^9.9.0",
    "@tailwindcss/typography": "^0.5.15",
    "@types/node": "^22.5.5",
    "@types/react": "^18.3.20",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react-swc": "^3.5.0",
    "autoprefixer": "^10.4.20",
    "eslint": "^9.9.0",
    "eslint-plugin-react-hooks": "^5.1.0-rc.0",
    "eslint-plugin-react-refresh": "^0.4.9",
    "globals": "^15.9.0",
    "lovable-tagger": "^1.1.7",
    "postcss": "^8.4.47",
    "tailwindcss": "^3.4.11",
    "typescript": "^5.5.3",
    "typescript-eslint": "^8.0.1",
    "vite": "^5.4.1"
  }
}

```

### backend/app.py

```python
from fastapi import FastAPI, Request, Form, HTTPException, status
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware
from pymongo import MongoClient, IndexModel
from pydantic import BaseModel, EmailStr, Field
from enum import Enum
from datetime import date
from typing import List, Dict, Optional
from policy_scraper import fetch_main_page, extract_prop_blocks, fetch_prop_details
from gemini import simplify_description, simplify_paragraph, people_affected, personalize_proposition
from dotenv import load_dotenv
import os, uuid
from passlib.hash import bcrypt
from fastapi.encoders import jsonable_encoder
import certifi                         # NEW



app = FastAPI()

# Set up CORS for your frontend
origins = ["http://localhost:5173"]
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.add_middleware(SessionMiddleware, secret_key=os.getenv("SESSION_SECRET"))


# Setup MongoDB connection using PyMongo
load_dotenv()
client = MongoClient(
    os.getenv("MONGO_URL"),            # e.g. mongodb+srv://user:pass@cluster0.mongodb.net/?retryWrites=true&w=majority
    tls=True,                          # ensure TLS
    tlsCAFile=certifi.where()          # use certifi’s CA bundle
)
db = client.voteSmart
users = db.users

# make sure email is unique
users.create_indexes([IndexModel("email", unique=True)])

# ----------- Base Models ---------------- #

class PolicyChoice(str, Enum):
    """Allowed stances for each policy area"""
    Left = "Left"
    Neutral = "Neutral"
    Right = "Right"

class UserProfile(BaseModel):
    first_name: str       = Field(alias="firstName")
    last_name: str        = Field(alias="lastName")
    date_of_birth: str    = Field(alias="dateOfBirth")  # ISO "YYYY-MM-DD"
    email: EmailStr       # validated email address

    # Truly optional fields (can be omitted or null)
    gender: Optional[str]          = None
    county: Optional[str]          = None
    income_bracket: Optional[str]  = Field(None, alias="incomeBracket")
    education_level: Optional[str] = Field(None, alias="educationLevel")
    occupation: Optional[str]      = None
    family_size: Optional[str]     = Field(None, alias="familySize")
    race_ethnicity: Optional[str]  = Field(None, alias="raceEthnicity")
    benefits: Optional[List[str]]  = None

    # Map of policy IDs (e.g. 'civil-rights') to stance enums
    policy_preferences: Optional[Dict[str, PolicyChoice]] = Field(
        None,
        alias="policyStances",
        description="Keys are policy IDs, values are one of 'Left', 'Neutral', 'Right'"
    )

class RegistrationRequest(BaseModel):
    profile: UserProfile
    password: str 
    uid: str


class Proposition(BaseModel):
    number: str
    title: str
    url: str
    details: Optional[str] = None
    simplified_description: Optional[str] = None
    simplified_paragraph: Optional[str] = None
    affected_people: Optional[str] = None
    personalization_summary: Optional[str] = None

# In-memory propositions cache (for scraped propositions)
propositions_cache: List[Dict] = []




# ----------- Routes -----------
@app.get("/", response_class=HTMLResponse)
async def root():
    return """
    <html>
        <head>
            <title>VoteSmart API</title>
        </head>
        <body>
            <h1>Welcome to the VoteSmart API</h1>
            <p>Use the endpoints to scrape and simplify propositions.</p>
        </body>
    </html>
    """

@app.post("/auth/register")
async def register(body: RegistrationRequest):
    # 1. make sure email isn’t taken

    doc = body.profile.model_dump(by_alias=False)
    doc["password_hash"] = bcrypt.hash(body.password)
    doc["uid"] = body.uid

    # 3. insert
    users.insert_one(doc)

    # 4. return lightweight success payload
    return {"uid": doc["uid"]}

# look here for user data
@app.get("/users/{uid}")
async def get_user(uid: str):
    # Look up the user document in MongoDB, omit the password_hash
    doc = users.find_one({"uid": uid}, {"password_hash": 0})
    if not doc:
        raise HTTPException(404, "user not found")
    # Convert MongoDB's special ObjectId to a string for JSON serialization
    doc["_id"] = str(doc["_id"])
    return jsonable_encoder(doc)



# ----------- Proposition API Endpoints -----------


@app.get("/scrape-propositions", response_model=List[Proposition])
def get_scraped_propositions():
    try:
        soup = fetch_main_page()  # Scraper function
        props = extract_prop_blocks(soup)
        for prop in props:
            prop["details"] = fetch_prop_details(prop["url"])
        global propositions_cache
        propositions_cache = props
        return props
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error scraping propositions: {str(e)}")


@app.get("/simplify-propositions", response_model=List[Proposition])
def get_simplified_propositions():
    if not propositions_cache:
        raise HTTPException(
            status_code=404,
            detail="No propositions have been scraped yet. Call /scrape-propositions first.",
        )
    simplified_props = []
    for prop in propositions_cache:
        details = prop.get("details", "")
        try:
            simple_desc = simplify_description(details)
            simple_para = simplify_paragraph(details)
            people_aff = people_affected(details)
        except Exception as e:
            simple_desc = f"Error in simplification: {str(e)}"
            simple_para = f"Error in simplification: {str(e)}"
            people_aff = f"Error formulating: {str(e)}"
        prop["simplified_description"] = simple_desc
        prop["simplified_paragraph"] = simple_para
        prop["affected_people"] = people_aff
        simplified_props.append(prop)
    return simplified_props


@app.get("/personali
[truncated — 1760 more characters]
```

### frontend/src/main.tsx

```typescript
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'

createRoot(document.getElementById("root")!).render(<App />);

```

### frontend/src/App.tsx

```typescript
import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/landing-page.tsx";
import NotFound from "./pages/NotFound.tsx";
import Registration from "./pages/Registration.tsx";
import Dashboard from "./pages/Dashboard.tsx";
import Login from "./pages/Login.tsx";


const queryClient = new QueryClient();

const App = () => (
  <QueryClientProvider client={queryClient}>
    <TooltipProvider>
      <Toaster />
      <Sonner />
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Index />} />
          <Route path="/registration" element={<Registration />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path='/login' element={<Login />}/>
          {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
          <Route path="*" element={<NotFound />} />
        </Routes>
      </BrowserRouter>
    </TooltipProvider>
  </QueryClientProvider>
);

export default App;
```

### frontend/postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

### frontend/eslint.config.js

```javascript
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";

export default tseslint.config(
  { ignores: ["dist"] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ["**/*.{ts,tsx}"],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    plugins: {
      "react-hooks": reactHooks,
      "react-refresh": reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      "react-refresh/only-export-components": [
        "warn",
        { allowConstantExport: true },
      ],
      "@typescript-eslint/no-unused-vars": "off",
    },
  }
);

```

### frontend/vite.config.ts

```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import path from "path";
import { componentTagger } from "lovable-tagger";

export default defineConfig(({ mode }) => ({
  server: {
    host: "::",
    port: 5173,               // run Vite on port 5173
    proxy: {                  // forward these paths to FastAPI on 8000
      "/auth":                "http://localhost:8000",
      "/users":               "http://localhost:8000",
      "/personalized-feed":   "http://localhost:8000",
      "/scrape-propositions": "http://localhost:8000",
      "/simplify-propositions":"http://localhost:8000",
    },
  },
  plugins: [
    react(),
    mode === "development" && componentTagger(),
  ].filter(Boolean),
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
}));
```

### frontend/index.html

```html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>VoteSmart - Make Voting Easier</title>
    <meta name="description" content="VoteSmart - Get personalized updates, track legislation, and compare candidates — all in one place." />
    <meta name="author" content="VoteSmart" />

    <meta property="og:title" content="VoteSmart - Make Voting Easier" />
    <meta property="og:description" content="VoteSmart - Get personalized updates, track legislation, and compare candidates — all in one place." />
    <meta property="og:type" content="website" />
    <meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />

    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:site" content="@lovable_dev" />
    <meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
  </head>

  <body>
    <div id="root"></div>
    <!-- IMPORTANT: DO NOT REMOVE THIS SCRIPT TAG OR THIS VERY COMMENT! -->
    <script src="https://cdn.gpteng.co/gptengineer.js" type="module"></script>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### backend/trump_scraper.py

```python
import os
import re
import requests
from bs4 import BeautifulSoup
from dotenv import load_dotenv
from typing import List, Dict
from bs4 import Tag

load_dotenv()

# 1) Load URL
TRUMP_URL = os.getenv("TRUMP_URL")
if not TRUMP_URL:
    raise RuntimeError("Set TRUMP_URL in .env")

def fetch_main_page() -> BeautifulSoup:
    resp = requests.get(TRUMP_URL, timeout=10)
    resp.raise_for_status()
    return BeautifulSoup(resp.text, "html.parser")

def parse_policies(soup: BeautifulSoup) -> List[Dict[str,str]]:
    policies = []
    # find each policy block
    for section in soup.select('section.section'):
        # 1) the name is the span under the <a class="bar…">
        name_tag = section.select_one('a.bar span')
        # 2) the description is the <p> inside <div class="pb-4 body">
        desc_tag = section.select_one('div.pb-4.body p')

        if name_tag and desc_tag:
            name = name_tag.get_text(strip=True)
            desc = desc_tag.get_text(strip=True)
            policies.append({'name': name, 'description': desc})

    return policies

# if __name__ == '__main__':
#     # 1) fetch and parse
#     soup     = fetch_main_page()
#     policies = parse_policies(soup)

#     # 2) print a quick summary
#     print(f"Found {len(policies)} policies:\n")
#     for i, pol in enumerate(policies, 1):
#         print(f"{i}. {pol['name']!r}")
#         print(f"   → {pol['description']!r}\n")

```

[103 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]