# Project export: voter.flw

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: UC Berkeley AI Hackathon 2025
- Tagline: Federated learning model analyzing voter suppression risk, advocating for equity in modern politics.
- Devpost: https://devpost.com/software/voter-flw
- GitHub: https://github.com/hhannahhwang/voter.flw
- Demo: https://hhannahhwang.github.io/aihacks_frontend/
- Video: https://www.youtube.com/embed/bZjIbCgkQBI?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Hannah Hwang (8 commits), Kevin Loritsch (7 commits), adi c. (3 commits)

## Devpost submission (written by the team)

### Inspiration

The voting systems that uphold the foundation of our republic, its policy, and its power are among the most sacred tools that we, the people, have in order to ensure our representation in our government. But what happens when those very systems are corrupted, or otherwise tampered with? In light of recent lawsuits regarding possible voting irregularities in New York, an observed increase in voter suppression efforts since 2020, and 79 restrictive voter laws being passed from 2021 to 2024, we wanted to analyze an area that implemented these policies prior to the 2024 election. We placed a focus on analyzing how big of an impact these policies actually had on voter participation through the use of various demographic statistics.

### What it does

Using Flower.ai, we implemented a federated learning-based model in order to accurately predict voter results based on past voting trends and demographic data. If there were any significant discrepancies in expected results and actual data, or there were demographic variables that pointed towards possible risk of suppression, it was flagged as "high-risk" of possibly voter-suppression activities. The output of our federated learning (FL) model includes a county-specific risk score, represented as a decimal between 0 and 1. A lower score (closer to 0) indicates a higher risk of voter suppression, while a higher score (closer to 1) indicates lower risk. Additionally, the model uses SHAP analysis to identify the 10 demographic variables that most heavily influenced each county's risk score, along with each variable’s corresponding weight. We then ran this raw data through an AI wrapper, implementing Google's Gemini API to cross-reference voting policy changes with counties that were flagged by our model as being "high risk" of possible voter suppression. Using compiled news articles outlining voter policy changes since 2022, in addition to the demographic variables that most heavily contributed to the risk score calculation, Gemini generates a sophisticated explanation as to what is most likely to cause voter suppression. These justifications for higher risk scores included a focus on groups that could be at risk of being targeted by voter suppression, and other policies like underfunding, gerrymandering, and an increases in incidents regarding partisan poll-watchers. After the data was provided justifiable causes by the Gemini wrapper, it was then aggregated into a interactive map visualization using front-end frameworks like TailwindCSS, Next.js, and React, in order to accurately display county data, their risk scores, and the justifications for each risk score. Hovering over each individual county would display its information, and an organized sidebar with a search feature allows the user to easily find any specific county in the state.

### How we built it

We initially started by scouring the web, trying to find a dataset. We eventually found a North Carolina collection of election data, https://www.ncsbe.gov/results-data, which gave us access to a county-by-county basis of individuals, the party they voted for, their demographics, and their method of voting. We then spent time aggregating data, trying to create a clean efficient dataset to predict from. Afterwards, we used scikit-learn to create an initial Random Forest Classifier, aiming to predict whether a person should, if no outside factors were to change, vote in the 2024 election. Once we began to get favorable results on a single county, we made a federated learning loop. With the current version of the project, we had nearly 100 separate counties, each of varying sizes. As we hope to later expand, we would need to manage over 3000 counties, some of which are incredibly large. Holding all of this data in one place is quite unrealistic, especially if we wanted to continue training our model on older and older data. As a result, since each county already stores their own data, we found it easier to use federated learning, sending the model to each county, instead of having to combine an enormous, and unrealistic for us, dataset. We then trained and saved a model on each county, using Flower.ai. Afterwards, we used that combined model, computing each individual person's likelihood to vote, compared that to each individual's actual chance of voting, and computed a mean value for each county, discovering how "off" they were from what we anticipated. We then used this to create a uniform distributed risk factor, noticing that when places tended to be in risk of having a lower score than they should. We then did excessive research into specific North Carolina polling changes, and aggregated this information with the SHAP analysis from each training loop, piecing together the most likely reasons for each county to be at risk. Combined, we have the relevant data to flag areas as potentially experiencing voter suppression, and the likely reasons for this to occur, allowing government officials to provide a quick and simple solution. To verify that this information was accurate, we found that actual data of voter turnout, per county, in 2020 and 2024, and calculated a percentage of increase or decrease in voters. Obviously, there are millions of factors that will influence the difference in numbers between these two times. But our goal is not to find the exact counties that will experience change, but those that are most at risk for potential suppression. As a result, though we do have some false negatives/positives, in correlation to that exact data, it is simply a trend, not our entire model. See the second image below for the described graph.

### Challenges we ran into

One of the main challenges we ran into was trying to integrate an LLM API into the wrapper portion of the project. At first, the project was setup to use Anthropic's Claude API to generate our explanations based off of policy changes and the model data, but we realized that we hadn't pre-registered for the trial credits that were offered by Anthropic for the purpose of this hackathon. After refactoring the code to use OpenAi's GPT-3.5 instead, we ran into a similar issue, in that OpenAI discontinued their program that offered trial API credits to first-time users. In the end, we ended up using Google's Gemini API, as it was free (with the trade-off of being rate-limited). To get around the rate limitations, we just ran our analysis script multiple times, skipping counties that had already been covered by the previous iterations. We also had some challenges implementing the map visualization, as we had to find an svg, transform it into an interactive map by identifying the paths of each separate county boundary, and dealing with animations. Having to deal with functionality on top of aesthetic made the front-end development portion a bit more challenging than anticipated.

### Accomplishments we're proud of

This was our first introduction to federated learning, so being able to implement our idea using it, learning how the model worked, and tweaking it until the results were accurate to real data was quite fulfilling. Additionally, this was our first real experience with making our own model. We found our data, preprocessed it, and tweaked hyperparameters of varying models. We fully made our own design, and were able to use it exactly how we wanted.

### What we learned

The main thing that we learned during this project was about the concept of federated learning. Prior to coming here, we had never heard of the concept, but after spending a day exploring it, we feel it is an essential and under-utilized aspect of machine learning. We were fascinated by how it guaranteed user privacy, allowed for new kinds of optimizations, and how it fit our project design perfectly. We also had never actually trained our own model completely from scratch before, and we were overjoyed to be able to go through the entire development pipeline completely on our own, and now feel much more prepared to continuing developing and improving models going forward.

### What's next

Right now, due to the data we found, voter.flw only highlights voter suppression for North Carolina. This, though an effective demo, would be the first thing that we would want to resolve, aiming to instead cover a complete national audience. Another idea that we wanted to continue with was using something such as Apache Kafka to allow for real time additions to our models. Right now, we can use data from 2024 to confirm that voter suppression may have occurred in 2024. With access to real time trends, we can continuously check for voter suppression throughout the entire voting process. Furthermore, due to our federated learning training loop, this is not a violation of any voter privacy, as we will guarantee that any voter information does not leave the county database. This would make our tool an essential part of the entire election cycle, guaranteeing fair and safe access to the polls.

## README (from the GitHub repository)

# CAL AIhacks

design : [figma file](https://www.figma.com/design/MrwOplFUpVYOCeHn1Ki35T/aihacks?node-id=0-1&p=f&t=VoieXtVuW1p85li8-0)

## Inspiration
The voting systems that uphold the foundation of our republic, its policy, and its power are among the most sacred tools that we, the people, have in order to ensure our representation in our government. But what happens when those very systems are corrupted, or otherwise tampered with? In light of recent lawsuits regarding possible voting irregularities in New York, an observed increase in voter suppression efforts since 2020, and 79 restrictive voter laws being passed from 2021 to 2024, we wanted to analyze an area that implemented these policies prior to the 2024 election. We placed a focus on analyzing how big of an impact these policies actually had on voter participation through the use of various demographic statistics.  

## What it does
Using Flower.ai, we implemented a federated learning-based model in order to accurately predict voter results based on past voting trends and demographic data. If there were any significant discrepancies in expected results and actual data, or there were demographic variables that pointed towards possible risk of suppression, it was flagged as "high-risk" of possibly voter-suppression activities. 

The output of our federated learning (FL) model includes a county-specific risk score, represented as a decimal between 0 and 1. A lower score (closer to 0) indicates a higher risk of voter suppression, while a higher score (closer to 1) indicates lower risk. Additionally, the model uses SHAP analysis to identify the 10 demographic variables that most heavily influenced each county's risk score, along with each variable’s corresponding weight.

We then ran this raw data through an AI wrapper, implementing Google's Gemini API to cross-reference voting policy changes with counties that were flagged by our model as being "high risk" of possible voter suppression. Using compiled news articles outlining voter policy changes since 2022, in addition to the demographic variables that most heavily contributed to the risk score calculation, Gemini generates a sophisticated explanation as to what is most likely to cause voter suppression. These justifications for higher risk scores included a focus on groups that could be at risk of being targeted by voter suppression, and other policies like underfunding, gerrymandering, and an increases in incidents regarding partisan poll-watchers.

After the data was provided justifiable causes by the Gemini wrapper, it was then aggregated into a interactive map visualization using front-end frameworks like TailwindCSS, Next.js, and React, in order to accurately display county data, their risk scores, and the justifications for each risk score. Hovering over each individual county would display its information, and an organized sidebar with a search feature allows the user to easily find any specific county in the state.

## How we built it
We initially started by scouring the web, trying to find a dataset. We eventually found a North Carolina collection of election data, https://www.ncsbe.gov/results-data, which gave us access to a county-by-county basis of individuals, the party they voted for, their demographics, and their method of voting.

We then spent time aggregating data, trying to create a clean efficient dataset to predict from. Afterwards, we used scikit-learn to create an initial Random Forest Classifier, aiming to predict whether a person should, if no outside factors were to change, vote in the 2024 election. Once we began to get favorable results on a single county, we made a federated learning loop.

With the current version of the project, we had nearly 100 separate counties, each of varying sizes. As we hope to later expand, we would need to manage over 3000 counties, some of which are incredibly large. Holding all of this data in one place is quite unrealistic, especially if we wanted to continue training our model on older and older data. As a result, since each county already stores their own data, we found it easier to use federated learning, sending the model to each county, instead of having to combine an enormous, and unrealistic for us, dataset.

We then trained and saved a model on each county, using Flower.ai. Afterwards, we used that combined model, computing each individual person's likelihood to vote, compared that to each individual's actual chance of voting, and computed a mean value for each county, discovering how "off" they were from what we anticipated. We then used this to create a uniform distributed risk factor, noticing that when places tended to be in risk of having a lower score than they should.

We then did excessive research into specific North Carolina polling changes, and aggregated this information with the SHAP analysis from each training loop, piecing together the most likely reasons for each county to be at risk. Combined, we have the relevant data to flag areas as potentially experiencing voter suppression, and the likely reasons for this to occur, allowing government officials to provide a quick and simple solution.

To verify that this information was accurate, we found that actual data of voter turnout, per county, in 2020 and 2024, and calculated a percentage of increase or decrease in voters. Obviously, there are millions of factors that will influence the difference in numbers between these two times. But our goal is not to find the exact counties that will experience change, but those that are most at risk for potential suppression. As a result, though we do have some false negatives/positives, in correlation to that exact data, it is simply a trend, not our entire model. See the second image below for the described graph.

## Challenges we ran into
One of the main challenges we ran into was trying to integrate an LLM API into the wrapper portion of the project. At first, the project was setup to use Anthropic's Claude API to generate our explanations based off of policy changes and the model data, but we realized that we hadn't pre-registered for the trial credits that were offered by Anthropic for the purpose of this hackathon. After refactoring the code to use OpenAi's GPT-3.5 instead, we ran into a similar issue, in that OpenAI discontinued their program that offered trial API credits to first-time users. In the end, we ended up using Google's Gemini API, as it was free (with the trade-off of being rate-limited). To get around the rate limitations, we just ran our analysis script multiple times, skipping counties that had already been covered by the previous iterations.

We also had some challenges implementing the map visualization, as we had to find an svg, transform it into an interactive map by identifying the paths of each separate county boundary, and dealing with animations. Having to deal with functionality on top of aesthetic made the front-end development portion a bit more challenging than anticipated.  

## Accomplishments that we're proud of
This was our first introduction to federated learning, so being able to implement our idea using it, learning how the model worked, and tweaking it until the results were accurate to real data was quite fulfilling. Additionally, this was our first real experience with making our own model. We found our data, preprocessed it, and tweaked hyperparameters of varying models. We fully made our own design, and were able to use it exactly how we wanted.

## What we learned
The main thing that we learned during this project was about the concept of federated learning. Prior to coming here, we had never heard of the concept, but after spending a day exploring it, we feel it is an essential and under-utilized aspect of machine learning. We were fascinated by how it guaranteed user privacy, allowed for new kinds of optimizations, and how it fit our project design perfectly.

We also had never actually trained our own model completely f

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 13 recognized source files, 28 KB.
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- PyTorch (technology) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- Next.js (technology) — claimed on Devpost, not found in the code
- React (technology) — claimed on Devpost, not found in the code
- Tailwind CSS (technology) — claimed on Devpost, not found in the code
- TypeScript (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 517)

```
.gitignore
.gitmodules
aihacks/.gitignore
aihacks/aihacks/__init__.py
aihacks/aihacks/client_app.py
aihacks/aihacks/county_risk_dashboard_scaled.csv
aihacks/aihacks/county_risk_dashboard.csv
aihacks/aihacks/county_shap_analysis.csv
aihacks/aihacks/feature_names.json
aihacks/aihacks/fed_turnout.joblib
aihacks/aihacks/generate_features.py
aihacks/aihacks/result/alamance_Merged.csv
aihacks/aihacks/result/alexander_Merged.csv
aihacks/aihacks/result/alleghany_Merged.csv
aihacks/aihacks/result/anson_Merged.csv
aihacks/aihacks/result/ashe_Merged.csv
aihacks/aihacks/result/avery_Merged.csv
aihacks/aihacks/result/beaufort_Merged.csv
aihacks/aihacks/result/bertie_Merged.csv
aihacks/aihacks/result/bladen_Merged.csv
aihacks/aihacks/result/brunswick_Merged.csv
aihacks/aihacks/result/burke_Merged.csv
aihacks/aihacks/result/cabarrus_Merged.csv
aihacks/aihacks/result/caldwell_Merged.csv
aihacks/aihacks/result/camden_Merged.csv
aihacks/aihacks/result/carteret_Merged.csv
aihacks/aihacks/result/caswell_Merged.csv
aihacks/aihacks/result/catawba_Merged.csv
aihacks/aihacks/result/chatham_Merged.csv
aihacks/aihacks/result/cherokee_Merged.csv
aihacks/aihacks/result/chowan_Merged.csv
aihacks/aihacks/result/clay_Merged.csv
aihacks/aihacks/result/cleveland_Merged.csv
aihacks/aihacks/result/columbus_Merged.csv
aihacks/aihacks/result/craven_Merged.csv
aihacks/aihacks/result/cumberland_Merged.csv
aihacks/aihacks/result/currituck_Merged.csv
aihacks/aihacks/result/dare_Merged.csv
aihacks/aihacks/result/davidson_Merged.csv
aihacks/aihacks/result/davie_Merged.csv
aihacks/aihacks/result/duplin_Merged.csv
aihacks/aihacks/result/durham_Merged.csv
aihacks/aihacks/result/edgecombe_Merged.csv
aihacks/aihacks/result/forsyth_Merged.csv
aihacks/aihacks/result/franklin_Merged.csv
aihacks/aihacks/result/gaston_Merged.csv
aihacks/aihacks/result/gates_Merged.csv
aihacks/aihacks/result/graham_Merged.csv
aihacks/aihacks/result/granville_Merged.csv
aihacks/aihacks/result/greene_Merged.csv
aihacks/aihacks/result/guilford_Merged.csv
aihacks/aihacks/result/halifax_Merged.csv
aihacks/aihacks/result/harnett_Merged.csv
aihacks/aihacks/result/haywood_Merged.csv
aihacks/aihacks/result/henderson_Merged.csv
aihacks/aihacks/result/hertford_Merged.csv
aihacks/aihacks/result/hoke_Merged.csv
aihacks/aihacks/result/hyde_Merged.csv
aihacks/aihacks/result/iredell_Merged.csv
aihacks/aihacks/result/jackson_Merged.csv
aihacks/aihacks/result/johnston_Merged.csv
aihacks/aihacks/result/jones_Merged.csv
aihacks/aihacks/result/lee_Merged.csv
aihacks/aihacks/result/lenoir_Merged.csv
aihacks/aihacks/result/lincoln_Merged.csv
aihacks/aihacks/result/macon_Merged.csv
aihacks/aihacks/result/madison_Merged.csv
aihacks/aihacks/result/martin_Merged.csv
aihacks/aihacks/result/mcdowell_Merged.csv
aihacks/aihacks/result/mecklenburg_Merged.csv
aihacks/aihacks/result/mitchell_Merged.csv
aihacks/aihacks/result/montgomery_Merged.csv
aihacks/aihacks/result/moore_Merged.csv
aihacks/aihacks/result/nash_Merged.csv
aihacks/aihacks/result/northampton_Merged.csv
aihacks/aihacks/result/onslow_Merged.csv
aihacks/aihacks/result/orange_Merged.csv
aihacks/aihacks/result/pamlico_Merged.csv
aihacks/aihacks/result/pasquotank_Merged.csv
aihacks/aihacks/result/pender_Merged.csv
aihacks/aihacks/result/perquimans_Merged.csv
aihacks/aihacks/result/person_Merged.csv
aihacks/aihacks/result/pitt_Merged.csv
aihacks/aihacks/result/polk_Merged.csv
aihacks/aihacks/result/randolph_Merged.csv
aihacks/aihacks/result/richmond_Merged.csv
aihacks/aihacks/result/robeson_Merged.csv
aihacks/aihacks/result/rockingham_Merged.csv
aihacks/aihacks/result/rowan_Merged.csv
aihacks/aihacks/result/rutherford_Merged.csv
aihacks/aihacks/result/sampson_Merged.csv
aihacks/aihacks/result/scotland_Merged.csv
aihacks/aihacks/result/stanly_Merged.csv
aihacks/aihacks/result/stokes_Merged.csv
aihacks/aihacks/result/surry_Merged.csv
aihacks/aihacks/result/swain_Merged.csv
aihacks/aihacks/result/transylvania_Merged.csv
aihacks/aihacks/result/tyrrell_Merged.csv
aihacks/aihacks/result/union_Merged.csv
aihacks/aihacks/result/vance_Merged.csv
aihacks/aihacks/result/wake_Merged.csv
aihacks/aihacks/result/warren_Merged.csv
aihacks/aihacks/result/washington_Merged.csv
aihacks/aihacks/result/watauga_Merged.csv
aihacks/aihacks/result/wayne_Merged.csv
aihacks/aihacks/result/wilkes_Merged.csv
aihacks/aihacks/result/wilson_Merged.csv
aihacks/aihacks/result/yadkin_Merged.csv
aihacks/aihacks/result/yancey_Merged.csv
aihacks/aihacks/score_results.py
aihacks/aihacks/server_app.py
aihacks/aihacks/task.py
aihacks/gpt3_5_integration/all_raw.txt
aihacks/gpt3_5_integration/analysis.py
aihacks/gpt3_5_integration/articles/articles.txt
aihacks/gpt3_5_integration/batch_convert_features.py
aihacks/gpt3_5_integration/data/ALAMANCE_analysis.txt
aihacks/gpt3_5_integration/data/ALAMANCE_fincsv.csv
aihacks/gpt3_5_integration/data/ALEXANDER_analysis.txt
aihacks/gpt3_5_integration/data/ALEXANDER_fincsv.csv
[397 more files omitted for size]
```

### Dependencies

- aihacks/gpt3_5_integration/requirements.txt: openai, pandas, python-dotenv
- aihacks/pyproject.toml: flwr-datasets[vision]@>=0.5.0, flwr[simulation]@>=1.19.0, torch@==2.5.1, torchvision@==0.20.1

### Recent commits (newest first)

- Update README.md
- Update README.md
- Updated DURHAM_analysis.txt
- Merge pull request #5 from hhannahhwang/kevinloritsch/meow2
- Merge pull request #6 from hhannahhwang/adidi-c/OpenAI_Integration
- Update README.md
- Merge pull request #7 from hhannahhwang/frontend
- updated frontend submodule
- all [old] raw data processed into csv, and then put through ai wrapper (gemini) to generate reasoning using reported policy change as possible reasoning
- first
- meow
- Merge pull request #4 from hhannahhwang/frontend
- update frontend
- Merge pull request #3 from hhannahhwang/frontend
- added frontend submodule
- Merge pull request #2 from hhannahhwang/data
- data
- Update README.md
- Update README.md
- Update README.md

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

### aihacks/pyproject.toml

```
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "aihacks"
version = "1.0.0"
description = ""
license = "Apache-2.0"
dependencies = [
    "flwr[simulation]>=1.19.0",
    "flwr-datasets[vision]>=0.5.0",
    "torch==2.5.1",
    "torchvision==0.20.1",
]

[tool.hatch.build.targets.wheel]
packages = ["."]

[tool.flwr.app]
publisher = "hhannahh"

[tool.flwr.app.components]
serverapp = "aihacks.server_app:app"
clientapp = "aihacks.client_app:app"

[tool.flwr.app.config]
num-server-rounds = 3
fraction-fit = 0.5
local-epochs = 1

[tool.flwr.federations]
default = "local-simulation"

[tool.flwr.federations.local-simulation]
options.num-supernodes = 10

```

### aihacks/gpt3_5_integration/requirements.txt

```
openai
python-dotenv
pandas

```

### main.py

```python
import pandas as pd, pathlib as p

data_dir = p.Path("data")
votehistory_dir = data_dir / "votehistory"
result_dir = data_dir / "result"
result_dir.mkdir(exist_ok=True)

county_files = list(votehistory_dir.glob("*.txt"))
counties = [f.stem for f in county_files]

for cslug in counties:
    try:
        abs16 = pd.read_csv(f"data/absentee2016/absentee_{cslug}_20161108.csv", dtype=str, encoding="latin1")
        abs20 = pd.read_csv(f"data/absentee2020/{cslug}_absentee_20201103.csv", dtype=str, encoding="latin1")
        abs24 = pd.read_csv(f"data/absentee2024/{cslug}_absentee_20241105.csv", dtype=str, encoding="latin1")
        hist  = pd.read_csv(f"data/votehistory/{cslug}.txt", sep="\t", dtype=str, encoding="latin1")

        for df in (abs16, abs20, abs24, hist):
            df["voter_reg_num"] = df["voter_reg_num"].str.zfill(12)

        voters = pd.concat([abs24, abs20, abs16], ignore_index=True).drop_duplicates("voter_reg_num")

        keep = ["voter_reg_num","county_desc","race","gender","age",
                "voter_city","voter_state","voter_zip","voter_party_code"]
        voters = voters[keep]

        def flag(df, date):
            ids = df[df["election_lbl"]==date]["voter_reg_num"]
            return voters["voter_reg_num"].isin(ids).astype(int)

        voters["voted_2016"] = flag(hist,"11/08/2016")
        voters["voted_2020"] = flag(hist,"11/03/2020")
        voters["voted_2024"] = flag(hist,"11/05/2024")

        outfile = f"data/result/{cslug}_Merged.csv"
        voters.to_csv(outfile, index=False)
        print(voters.head())
    
    except FileNotFoundError as e:
        print(f"File not found for county {cslug}: {e}")
    except Exception as e:
        print(f"Error processing {cslug}: {e}")
```

### aihacks/aihacks/__init__.py

```python
"""AIhacks: A Flower / PyTorch app."""

```

### aihacks/aihacks/generate_features.py

```python
# generate_features.py
import pandas as pd
import json

df = pd.read_csv("result/alamance_Merged.csv", dtype={"voter_reg_num": str})
base_cols = [
    "race", "gender", "county_desc", "voter_party_code",
    "voter_zip", "age", "voted_2016", "voted_2020"
]
X = pd.get_dummies(df[base_cols], dummy_na=True)
for col in [c for c in X.columns if "voter_party_code_" in c]:
    X[f"{col}_w"] = X[col] * 2
X["voted_2016_w"] = df["voted_2016"] * 1.5
X["voted_2020_w"] = df["voted_2020"] * 0.75

with open("feature_names.json", "w") as f:
    json.dump(list(X.columns), f)

```

### aihacks/gpt3_5_integration/split_all_raw.py

```python
import os
import re

input_path = "all_raw.txt"
output_dir = "raw"
os.makedirs(output_dir, exist_ok=True)

with open(input_path, "r", encoding="utf-8") as f:
    lines = f.readlines()

current_lines = []
county_name = None

for line in lines:
    # Detect county start
    match = re.match(r"^([A-Z\s\-]+)\s*-\s*Risk Score:\s*(\d+\.\d+)", line)
    if match:
        # If we were processing a previous county, save it
        if county_name and current_lines:
            out_path = os.path.join(output_dir, f"{county_name.lower().strip().replace(' ', '_')}_raw.txt")
            with open(out_path, "w", encoding="utf-8") as out_file:
                out_file.writelines(current_lines)
            print(f"✅ Saved {county_name} to {out_path}")
            current_lines = []

        # Start new county
        county_name = match.group(1).strip()
    
    if county_name:
        current_lines.append(line)

# Save the last county
if county_name and current_lines:
    out_path = os.path.join(output_dir, f"{county_name.lower().strip().replace(' ', '_')}_raw.txt")
    with open(out_path, "w", encoding="utf-8") as out_file:
        out_file.writelines(current_lines)
    print(f"✅ Saved {county_name} to {out_path}")
```

### aihacks/gpt3_5_integration/analysis.py

```python
import openai
import os

class OpenAIVoterSuppressionAnalyzer:
    def __init__(self, api_key, model="gpt-3.5-turbo"):
        openai.api_key = api_key
        self.model = model

    def build_prompt(self, county, features, articles):
        feature_text = "\n".join([f"{feat}: {importance:.6f}" for feat, importance in features.items()])
        article_text = "\n\n".join([f"Article {i+1}:\n{a}" for i, a in enumerate(articles)])

        return f"""
You are a political analyst.

Using the following top features from {county} county and current news articles, write a short data-informed paragraph explaining how voter behavior in this county might suggest voter suppression patterns.

Top Features:
{feature_text}

News Articles:
{article_text}

Give 1 paragraph that includes demographic or behavioral insights suggesting potential voter suppression tactics or policy impacts.
"""

    def analyze(self, county, features, articles):
        prompt = self.build_prompt(county, features, articles)
        response = openai.ChatCompletion.create(
            model=self.model,
            messages=[{"role": "user", "content": prompt}],
            max_tokens=800,
            temperature=0.7
        )
        return response["choices"][0]["message"]["content"]

```

### aihacks/gpt3_5_integration/batch_convert_features.py

```python
import os
import glob

input_dir = "raw"
output_dir = "data"
os.makedirs(output_dir, exist_ok=True)

for path in glob.glob(os.path.join(input_dir, "*_raw.txt")):
    with open(path, "r", encoding="utf-8") as f:
        lines = f.readlines()

    county = None
    risk_score = None
    features = []

    for line in lines:
        line = line.strip()

        if " - Risk Score:" in line:
            # Match: "ALEXANDER - Risk Score: 0.991"
            parts = line.split(" - Risk Score:")
            county = parts[0].strip().upper()
            risk_score = float(parts[1].strip())

        elif line and any(line.startswith(f"{i}.") for i in range(1, 11)):
            # Match feature lines: "1. voted_2020_w 0.0280"
            parts = line.split()
            if len(parts) >= 3:
                feature = parts[1]
                value = float(parts[-1])
                features.append((feature, value))

    if county and risk_score is not None:
        out_path = os.path.join(output_dir, f"{county}_fincsv.csv")
        with open(out_path, "w", encoding="utf-8") as out:
            out.write(f"RISK_SCORE,{risk_score:.3f}\n")
            for feat, imp in features:
                out.write(f"{feat},{imp:.6f}\n")
        print(f"✅ Converted: {out_path}")
    else:
        print(f"❌ Failed to process {path}")

```

### aihacks/aihacks/task.py

```python
import pandas as pd
import numpy as np
from sklearn.linear_model import SGDClassifier
import json, os

FEATURES_PATH = "feature_names.json"

def build_xy(csv_path: str):
    df = pd.read_csv(csv_path, dtype={"voter_reg_num": str})

    base_cols = [
        "race", "gender", "county_desc", "voter_party_code",
        "voter_zip", "age", "voted_2016", "voted_2020"
    ]
    X = pd.get_dummies(df[base_cols], dummy_na=True)

    for c in [c for c in X.columns if "voter_party_code_" in c]:
        X[f"{c}_w"] = X[c] * 2
    X["voted_2016_w"] = X["voted_2016"] * 1.5
    X["voted_2020_w"] = X["voted_2020"] * 1.5

    if os.path.exists(FEATURES_PATH):
        feat_order = json.load(open(FEATURES_PATH))
        for col in feat_order:
            if col not in X.columns:
                X[col] = 0
        X = X[feat_order]
    else:
        json.dump(list(X.columns), open(FEATURES_PATH, "w"))

    y = df["voted_2024"].values
    X = X.fillna(0)
    return X.values.astype(np.float32), y.astype(np.int8)

# task.py
from sklearn.neural_network import MLPClassifier


def make_model(n_features: int):
    return MLPClassifier(
        hidden_layer_sizes=(64,),
        activation="relu",
        solver="adam",
        alpha=1e-4,
        batch_size=64,
        learning_rate_init=1e-3,
        max_iter=1,
        warm_start=True
    )



```

### aihacks/aihacks/server_app.py

```python
import flwr as fl
import numpy as np
import json
import joblib
from flwr.common import parameters_to_ndarrays
from task import make_model, FEATURES_PATH


class SaveModel(fl.server.strategy.FedAvg):
    def __init__(self, rounds=10):
        super().__init__(
            fraction_fit=0.85,
            min_fit_clients=50,
            min_available_clients=50
        )
        self.rounds = rounds
        self.params = None

    def aggregate_fit(self, rnd, results, failures):
        agg = super().aggregate_fit(rnd, results, failures)
        if agg:
            self.params = agg[0]

            if rnd == self.rounds:
                # Load feature names
                features = json.load(open(FEATURES_PATH))

                # Make model and initialize it with dummy fit
                model = make_model(len(features))
                dummy_X = np.zeros((2, len(features)))
                dummy_y = np.array([0, 1])
                model.partial_fit(dummy_X, dummy_y, classes=np.array([0, 1]))

                # Convert FL parameters → model weights
                weights = parameters_to_ndarrays(self.params)
                n_layers = len(model.coefs_)
                model.coefs_ = weights[:n_layers]
                model.intercepts_ = weights[n_layers:]

                # Save final model
                joblib.dump({"model": model, "features": features}, "fed_turnout.joblib")
                print("✅ saved fed_turnout.joblib")

        return agg


# Instantiate strategy
strategy = SaveModel(rounds=10)

# Start server
if __name__ == "__main__":
    fl.server.start_server(
        server_address="0.0.0.0:8080",
        config=fl.server.ServerConfig(num_rounds=10),
        strategy=strategy
    )

```

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