# Project export: OpenPockets

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: because the financial interests of our politicians shouldn’t be a secret.
- Devpost: https://devpost.com/software/openpocket
- GitHub: https://github.com/riachx/open-pocket.git
- Video: https://www.youtube.com/embed/LZj0Asn8A3c?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — riachx (10 commits)

## Devpost submission (written by the team)

### Inspiration

Both of us recognized that the United States is currently in a politically complex and pivotal moment. We saw an opportunity to make a meaningful impact by helping people better understand the flow of money in politics. In the U.S., Super PACs and PACs wield significant influence over the political landscape, often shaping outcomes behind the scenes.

### What it does

Our website OpenPockets sifts through massive bulk data downloaded from the Federal Election Committee (FEC) to track, analyze, and visualize political campaign contributions. Our platform allows users to: Explore contributions by industry sectors like Pharmaceuticals, Military & Defense, Oil & Gas View detailed profiles of politicians with their funding sources Track industry influence across different senators and representatives Visualize financial relationships between corporations and politicians

### How we built it

Lots and lots of struggling and revision. Back-end Developed a Node.js/Express server to handle API requests efficiently Used a SQLite database for fast querying Implemented data processing pipelines to transform raw FEC data into insights Created specialized endpoints for industry-specific and candidate-specific queries ### Front-end We used Figma to prototype and React, ChakraUI, and TypeScript for rapid development.

### Challenges we ran into

The text files we dealt with were massive (as in gigabytes). Unfortunately, MongoDB ran out of storage after attempting to add our first table. After talking to teammates around us, we learned that SQLite is great for parsing relatively large data and decided to switch. This entailed MUCH faster queries. As of April, every government related API was shutting down or was already shut down perhaps due to recent government cuts. The OpenSecrets API was unavailable, OpenFEC had a limit of 40 calls/hour, congress.gov’s API was broken, Google Civic API was very limited and extremely slow and ProPublica's Congress API is no longer available. So we decided to just store and parse all the data ourselves with SQLite. The queries were extremely fast and understanding the syntax grew easier by the end We had never queried data so big before, or really queried in general. Our computer would crash if we even tried to open one of the .txt files We also are not political science majors so the jargon was tricky to understand.

### Accomplishments we're proud of

We accomplished this much as a team of 2. Of the 4 APIs we planned to use, only one somewhat worked, and even then, we decided to scrap it and do it ourselves by downloading a bunch of campaign data and essentially making our own personalized api. We messed with a ton of technologies that we had no idea about: Model Context Protocol, SQLite databases

### What we learned

Extreme improvisation: having all the API’s fail on us, and then deciding to just build it ourselves was a cool experience

### What's next

for OpenPocket One of our biggest limiting factors was the deactivation of the OpenSecrets API, which is one of the biggest tools in tracking political finances. This API will come back new and improved soon, and it is a very potent tool for us to leverage to gain insight on political transactions. One of the biggest features we wanted to implement was a Gemini agent that worked off of a Model Context Protocol server connected to our database to answer any questions about a politician's financial integrity. We were successful in creating it, but not so much in integrating it into our front-end. We also created methods to see a politician's active industries and a list of their highest donating political action committees, but we weren’t to integrate it into our ui at the end.

## README (from the GitHub repository)

# OpenPockets

A React + TypeScript app with a Node/Express + SQLite backend that helps you explore U.S. politicians’ committees, corporate connections, and recent votes. It aggregates FEC committee data, corporate PAC links, and industry connections and presents them with a clean UI powered by Chakra UI.

## Watch the Video here:

[![Watch the video](https://img.youtube.com/vi/99JXE8DeNJA/hqdefault.jpg)](https://youtu.be/99JXE8DeNJA)

## Inspiration

The United States is in a politically complex and pivotal moment. We wanted to make a meaningful impact by helping people better understand the flow of money in politics. Super PACs and PACs wield significant influence, often shaping outcomes behind the scenes. Open Pocket aims to surface those financial relationships in a transparent and navigable way. R

## Features

- Politician profile with:
  - Recent House vote info via Congress.gov API
  - Affiliated committees and contribution totals
  - Corporate connections grouped by industry
- Explore contributions by industry sectors (e.g., Pharmaceuticals, Military & Defense, Oil & Gas)
- Track industry influence across senators and representatives
- SQLite database (`src/services/politicaldata.db`) seeded with FEC-derived tables

## Tech Stack

- Frontend: React, TypeScript, Vite, Chakra UI
- Backend: Node.js (Express) in `src/server.js`
- Data/Services: Python utilities in `src/services/*.py`, SQLite (`politicaldata.db`)

## Getting Started

### Prerequisites
- Node.js 18+ and npm
- Python 3.x
- SQLite (CLI optional but useful)

### 1) Install dependencies
```bash
npm install
```

### 2) Environment variables
Create `.env` or `.env.local` (Vite-compatible) in the project root:
```bash
VITE_CONGRESS_API_KEY=your_congress_gov_api_key
```
This key is used by the frontend to fetch vote details from Congress.gov.

### 3) Database
The app uses a SQLite database located at:
```
src/services/politicaldata.db
```
It contains tables for committees, candidate linkages, and optionally `linkedin_companies`.

Initialize/update schema (optional):
```bash
python src/services/initialize_db.py
```

### 4) Run the backend
```bash
node src/server.js
```
By default it serves the API on http://localhost:3001.

### 5) Run the frontend
```bash
npm run dev
```
Vite will start the app (typically on http://localhost:5173).

## How We Built It

- Back-end
  - Node.js/Express server for API endpoints and request handling
  - SQLite for fast, local querying of large FEC-derived datasets
  - Python data-processing scripts to transform raw FEC text files into queryable tables
  - Candidate- and industry-specific endpoints to support the UI
- Front-end
  - Figma for rapid prototyping
  - React + TypeScript + Chakra UI for fast iteration and clean component design
  - Keyword-based industry classifier to robustly categorize companies when third-party APIs are limited

## Challenges

- Data scale: FEC text files are massive (gigabytes). Attempting to import to MongoDB quickly exhausted storage. SQLite proved significantly faster for parsing and querying locally.
- API instability/limits (as of April):
  - OpenSecrets API unavailable
  - OpenFEC rate-limited (≈40 calls/hour)
  - Congress.gov unstable; ProPublica Congress API deprecated; Google Civic slow/limited
  - We pivoted to downloading and parsing the data ourselves with SQLite, which yielded very fast queries once structured
- Learning curve: Working with very large datasets and unfamiliar political finance terminology

## Accomplishments

- Built a full-stack system as a team of two, despite multiple API failures
- Switched to a self-hosted data pipeline (SQLite) and effectively made a tailored “internal API” for our needs
- Experimented with new tech: Model Context Protocol ideas, SQLite-based analytics, and robust data wrangling

## What We Learned

- Extreme improvisation pays off: when external APIs failed, building our own pipeline with SQLite let us move forward quickly
- Practical strategies for handling large, messy public datasets and turning them into fast queries and usable UI

## What’s Next for Open Pocket

- Reintegrate external data sources when they stabilize (e.g., the returning OpenSecrets API) to enhance scope and accuracy
- Ship an AI agent powered by a Model Context Protocol server connected to our database for natural-language Q&A about politicians’ finances
- Expand UI to include deeper “active industries” views and top-donating PACs per politician, integrated directly into the profile pages

## Key Pages

- `src/pages/Politician.tsx` – Main profile view with Committees, Industries, and Recent Votes
- `src/pages/IndustryDetail.tsx` – Deeper dive for a specific industry

## Data Flow (High-level)

- Frontend fetches politician details and UI sections from the backend API
- Backend calls Python helpers to read from `politicaldata.db` and assemble committee and corporate-connection data
- Industry grouping uses a keyword-based classifier to ensure every company gets a reasonable category even if LinkedIn data is sparse

## API Endpoints (selected)

- GET `http://localhost:3001/api/congressman/:id`
  - Returns the politician record used by `Politician.tsx`
- GET `http://localhost:3001/api/senator/:id/committees`
  - Returns affiliated committees and summary stats
- GET `http://localhost:3001/api/senator/:id/industries`
  - Returns companies grouped by industry; uses keyword-based classification as a fallback
- GET `http://localhost:3001/api/politician/:lastName/industries?firstName=Optional`
  - Alternative industry view by name

Note: The frontend passes `VITE_CONGRESS_API_KEY` as `x-api-key` where required.

## Troubleshooting

- Industries all show “Unknown”
  - Ensure frontend classification is running: refresh and check browser console logs from `Politician.tsx`
  - Verify the backend at http://localhost:3001 is running
  - Confirm `VITE_CONGRESS_API_KEY` is set for vote lookups (not required for industry classification but useful for the page)
- DB file not found
  - Confirm `src/services/politicaldata.db` exists; re-run `python src/services/initialize_db.py` if needed

## Project Structure (partial)

```
src/
  pages/
    Politician.tsx
    IndustryDetail.tsx
  components/
    RecentVoteInfo.tsx
    ChatBot.tsx
    CongressmanBanner.tsx
  services/
    server.js          # Express API
    *.py               # Python helpers, DB utilities
    politicaldata.db   # SQLite database
```

## License
MIT (or project-specific license if different).


## Detected evidence (automated analysis)

Indexed codebase: 30 recognized source files, 110 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- MongoDB (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (38 of 38)

```
.gitignore
eslint.config.js
index.html
package.json
README.md
src/api/senators.ts
src/App.css
src/App.tsx
src/assets/data/contributions-from-committees/con-from-com-header.csv
src/components/Footer.tsx
src/components/Navbar.tsx
src/db/setup.ts
src/index.css
src/main.tsx
src/pages/About.tsx
src/pages/Explore.tsx
src/pages/IndustryDetail.tsx
src/pages/Landing.tsx
src/pages/Politician.tsx
src/pages/Profile.tsx
src/scripts/populateDb.ts
src/server.js
src/services/api.ts
src/services/candidate_functions.py
src/services/candidateFunctions.ts
src/services/contribute.py
src/services/initialize_db.py
src/services/politicaldata.db
src/services/query_contributors.py
src/services/query_senators.py
src/services/requirements.txt
src/services/senator_contributors.py
src/types/index.ts
src/vite-env.d.ts
tsconfig.app.json
tsconfig.json
tsconfig.node.json
vite.config.ts
```

### Dependencies

- package.json: @chakra-ui/icons@^2.2.4, @chakra-ui/react@^2.10.7, @emotion/react@^11.14.0, @emotion/styled@^11.14.0, @eslint/js@^9.21.0, @types/cors@^2.8.17, @types/express@^5.0.1, @types/react@^19.0.10, @types/react-dom@^19.0.4, @types/react-router-dom@^5.3.3, @types/sqlite3@^3.1.11, @vitejs/plugin-react@^4.3.4, axios@^1.8.4, cors@^2.8.5, dotenv@^16.5.0, eslint@^9.21.0, eslint-plugin-react-hooks@^5.1.0, eslint-plugin-react-refresh@^0.4.19, express@^5.1.0, framer-motion@^12.6.5, globals@^15.15.0, mongoose@^8.13.2, react@^19.0.0, react-dom@^19.0.0, react-router-dom@^7.5.0, sqlite@^5.1.1, sqlite3@^5.1.7, ts-node@^10.9.2, typescript@~5.7.2, typescript-eslint@^8.24.1, vite@^6.2.0
- src/services/requirements.txt: httpx, python-dotenv

### Recent commits (newest first)

- Update README.md
- Update README.md
- Update README.md with proper thumbnail
- Update README.md with video
- Update README.md
- Merge pull request #2 from riachx/querying
- adding politian profile
- adding dropdown
- explore fixxed
- minor ui/ux stuff
- ui/ux stuff
- clean profile branch
- adding db
- adding candidate to contribution
- Initial commit

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

### package.json

```
{
  "name": "OpenPocket",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview",
    "server": "node src/server.js",
    "start": "npm run build && npm run server",
    "populate-db": "node --loader ts-node/esm src/scripts/populateDb.ts"
  },
  "dependencies": {
    "@chakra-ui/icons": "^2.2.4",
    "@chakra-ui/react": "^2.10.7",
    "@emotion/react": "^11.14.0",
    "@emotion/styled": "^11.14.0",
    "@types/cors": "^2.8.17",
    "@types/react-router-dom": "^5.3.3",
    "axios": "^1.8.4",
    "cors": "^2.8.5",
    "dotenv": "^16.5.0",
    "express": "^5.1.0",
    "framer-motion": "^12.6.5",
    "mongoose": "^8.13.2",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-router-dom": "^7.5.0",
    "sqlite": "^5.1.1",
    "sqlite3": "^5.1.7"
  },
  "devDependencies": {
    "@eslint/js": "^9.21.0",
    "@types/express": "^5.0.1",
    "@types/react": "^19.0.10",
    "@types/react-dom": "^19.0.4",
    "@types/sqlite3": "^3.1.11",
    "@vitejs/plugin-react": "^4.3.4",
    "eslint": "^9.21.0",
    "eslint-plugin-react-hooks": "^5.1.0",
    "eslint-plugin-react-refresh": "^0.4.19",
    "globals": "^15.15.0",
    "ts-node": "^10.9.2",
    "typescript": "~5.7.2",
    "typescript-eslint": "^8.24.1",
    "vite": "^6.2.0"
  }
}

```

### src/services/requirements.txt

```
httpx
python-dotenv
```

### src/main.tsx

```typescript
// src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)
```

### src/App.tsx

```typescript
// src/App.tsx
import { ChakraProvider, extendTheme } from '@chakra-ui/react'  // Change this import
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
import Landing from './pages/Landing';
import Explore from './pages/Explore';
import Profile from './pages/Profile';
import About from './pages/About';
import IndustryDetail from './pages/IndustryDetail';
import Politician from './pages/Politician';

// Create a custom theme with DM Sans font
const theme = extendTheme({
  fonts: {
    heading: "'DM Sans', sans-serif",
    body: "'DM Sans', sans-serif",
  },
})

function App() {
  return (
    <ChakraProvider theme={theme}>
      <Router>
        <div className="App" style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
          <Navbar />
          <Routes>
            <Route path="/" element={<Landing />} />
            <Route path="/about" element={<About />} />
            <Route path="/explore" element={<Explore />} />
            <Route path="/industry/:industry" element={<IndustryDetail />} />
            <Route path="/profile" element={<Profile />} />
            <Route path="/politician/:id" element={<Politician />} />
          </Routes>
          <Footer />
        </div>
      </Router>
    </ChakraProvider>
  );
}

export default App;
```

### src/types/index.ts

```typescript
export interface Politician {
    id: string;
    name: string;
    party: string;
    
  }
  
  export interface User {
    id: string;
    username: string;
    email: string;
    
  }
```

### src/server.js

```javascript
import express from 'express';
import cors from 'cors';
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import path from 'path';
import { fileURLToPath } from 'url';
import { spawn } from 'child_process';
import fs from 'fs';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();
const PORT = process.env.PORT || 3001;

// Enable CORS
app.use(cors());

// Database setup
async function setupDatabase() {
  const dbPath = path.resolve(__dirname, 'services/politicaldata.db');
  
  // Check if file exists
  if (!fs.existsSync(dbPath)) {
    console.error(`Database file does not exist at: ${dbPath}`);
    throw new Error(`Database file not found at: ${dbPath}`);
  }
  
  //console.log(`Database file exists at: ${dbPath}`);
  
  try {
    // Open the database connection with consistent configuration
    const db = await open({
      filename: dbPath,
      driver: sqlite3.Database
    });
    
    // Verify connection by running a simple query
    const tables = await db.all("SELECT name FROM sqlite_master WHERE type='table';");
    //console.log(`Available tables: ${tables.map(t => t.name).join(', ')}`);
    
    return db;
  } catch (error) {
    console.error('Error opening database:', error);
    throw error;
  }
}

// Helper function to run Python code and get the result
async function runPythonFunction(functionName, args = []) {
  return new Promise((resolve, reject) => {
    // Create a Python script that imports and calls the function
    const pythonCode = `
import sys
import json
sys.path.append('${path.resolve(__dirname, 'services')}')
from candidate_functions import ${functionName}

try:
    # Call the function with the provided arguments
    result = ${functionName}(${args.map(arg => JSON.stringify(arg)).join(', ')})
    # Print the result as JSON
    print(json.dumps(result))
except Exception as e:
    # Print error message
    print(json.dumps({"error": str(e)}))
`;

    // Run the Python code
    const pythonProcess = spawn('python', ['-c', pythonCode]);
    
    let resultData = '';
    let errorData = '';

    pythonProcess.stdout.on('data', (data) => {
      resultData += data.toString();
    });

    pythonProcess.stderr.on('data', (data) => {
      errorData += data.toString();
    });

    pythonProcess.on('close', (code) => {
      if (code !== 0) {
        console.error(`Python process exited with code ${code}`);
        console.error('Error:', errorData);
        reject(new Error(`Python process failed: ${errorData}`));
        return;
      }

      try {
        const result = JSON.parse(resultData);
        if (result && result.error) {
          reject(new Error(result.error));
        } else {
          resolve(result);
        }
      } catch (e) {
        reject(new Error(`Failed to parse Python output: ${resultData}`));
      }
    });
  });
}

// API Routes
app.get('/api/senators', async (req, res) => {
  let db;
  try {
    console.log('Setting up database connection for /api/senators');
    db = await setupDatabase();
    
    console.log('Executing query to get senators');
    const senators = await db.all('SELECT * FROM senate');
    
    console.log(`Successfully retrieved ${senators.length} senators`);
    
    // Parse the phones JSON string
    const formattedSenators = senators.map(senator => ({
      ...senator,
      photoUrl: senator.photoUrl || 'https://i.imgur.com/VlKTQWO.png',
      phones: JSON.parse(senator.phones || '[]')
    }));
    
    res.json(formattedSenators);
  } catch (error) {
    console.error('Error fetching senators:', error);
    
    // Check for specific error types and provide more detailed response
    if (error.code === 'SQLITE_CANTOPEN') {
      res.status(500).json({ 
        error: 'Database not found or cannot be opened', 
        details: error.message,
        path: error.path 
      });
    } else if (error.code === 'SQLITE_ERROR') {
      res.status(500).json({ 
        error: 'SQL query error', 
        details: error.message 
      });
    } else {
      res.status(500).json({ 
        error: 'Failed to fetch senators', 
        details: error.message 
      });
    }
  } finally {
    if (db) {
      try {
        await db.close();
        console.log('Database connection closed');
      } catch (closeError) {
        console.error('Error closing database connection:', closeError);
      }
    }
  }
});

// Industry contributions endpoint
app.get('/api/industry-contributions/:industry', async (req, res) => {
  let db;
  try {
    const { industry } = req.params;
    db = await setupDatabase();
    console.log(`Fetching contributions for industry: ${industry}`);
    
    // Industry classification logic from contribute.py
    const industryKeywords = {
      'Pharmaceuticals': ['health', 'pharmaceutical', 'drugs', 'medical', 'pharma', 'medicine', 'healthcare'],
      'Military & Defense': ['defense', 'military', 'army', 'navy', 'lockheed', 'marine', 'force', 'security'],
      'Insurance': ['insurance', 'finance', 'fund', 'financial', 'capital', 'invest', 'bank'],
      'Oil & Gas': ['energy', 'oil', 'gas', 'petroleum', 'pipeline', 'drill', 'fuel'],
      'Electronics & Tech': ['tech', 'electronics', 'digital', 'communications', 'google', 'facebook', 'meta', 'software']
    };
    
    // Get keywords for the selected industry
    const keywords = industryKeywords[industry] || [industry.toLowerCase()];
    
    // Build LIKE clauses for each keyword
    const whereClauses = keywords.map(() => 'LOWER(cc.contributor_name) LIKE ?').join(' OR ');
    const params = keywords.map(keyword => `%${keyword.toLowerCase()}%`);
    
    // First, get all contributions that match the industry
    const contributionsQuery = `
      SELECT 
        cc.candidate_id, 
        cc.contributor_name, 
        cc.amount
      FROM 
        contributorsFromCommittees cc 
      WHERE 
        (${whereClauses})
        AND cc.entity_type != 'IND' -- Skip individu
[truncated — 21471 more characters]
```

### vite.config.ts

```typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
})

```

### index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,100..1000;1,100..1000&display=swap" rel="stylesheet">
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### 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 },
      ],
    },
  },
)

```

### src/vite-env.d.ts

```typescript
/// <reference types="vite/client" />

```

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