Find Your Most Efficient Local Charity

Build a Python tool that ranks local charities by how much they spend on programs.

Introduction

30 Second Summary

When you donate to charity, you want your money to actually reach the people who need it. Some charities spend over 90% of every dollar on their programs, while others burn through most of it on overhead and fundraising.

In this project, you will build a Python script that pulls real financial data for charities in your state and ranks them by how efficiently they spend their money. You will generate a polished HTML dashboard showing exactly where your donation has the most impact.

What You'll Build

You run the script, type in your state abbreviation, and within seconds a styled report opens in your browser showing your area's most efficient charities ranked with visual progress bars and a highlighted winner.

By the end of this project, you'll have:

  • A working Python script that queries a real API for live nonprofit financial data, so you can type in any US state and instantly see which charities put the highest percentage toward their cause.
  • A styled HTML dashboard that opens in your browser with color-coded progress bars, charity cards showing each organization's mission and efficiency score, and a highlighted winner at the top.
  • A reusable tool you can re-run for any state to compare charities before making a donation decision.
  • Secret Mission: Add a cause filter so users can search for the most efficient charity in a specific category like "food" or "education".

Are there any prerequisites?

None. This guide includes setup instructions for everything you need. You will install Python's requests library during Step 1, and the project uses a free public API that requires no account or API key.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters to you personally.

Set Up Your Project

To build your charity efficiency finder, you need two things: a project folder to keep your work organized, and a library that lets Python talk to web APIs.

The requests library handles all the complexity of making HTTP calls to external services. In this step, you'll create your workspace and install this library so you're ready to start coding.

In this step, get ready to:
  • Create a project folder and open it in VS Code.
  • Install and verify the requests library.
Open Terminal and create your project folder

You need a dedicated folder on your Desktop to keep all your charity finder files in one place.

  • Press Cmd+Space to open Spotlight search.
  • Type Terminal and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir charity-finder
cd charity-finder
  • Confirm the folder was created by running:
pwd

You should see a path ending in /charity-finder.

  • Open this folder in VS Code by running:
code .

VS Code opens with your empty charity-finder folder in the sidebar.

Seeing command not found for code?

The code command needs to be added to your PATH. Open VS Code manually, press Cmd+Shift+P, type shell command, and select Shell Command: Install 'code' command in PATH. Restart your Terminal and try code . again.

help me fix the code command not found error in my terminal

Install the requests library

The requests library is what lets your Python script send HTTP requests to the CharitySense API and get data back.

  • Switch back to your Terminal and install the library by running:
  • Run one of the following commands python -m venv .venv or python3 -m venv .venv
pip3 install requests==2.34.2

You'll see pip downloading and installing the package. It prints a success message when finished.

What does this command do?

The pip3 command is Python's package manager. It downloads libraries from the Python Package Index (PyPI) and installs them on your machine. The ==2.34.2 part pins the exact version so your setup matches this project.

Once installed, you can import requests in any Python script on your machine.

Before you run the next command, what do you think it will print if the install worked correctly?

  • Verify the installation by running:
python3 -c "import requests; print(requests.__version__)"

You should see 2.34.2 printed in your terminal. That confirms Python can find and load the library.

Seeing an error instead of a version number?

If you see ModuleNotFoundError, the install may have gone to a different Python version. Try running python3 -m pip install requests==2.34.2 instead, then retry the verify command.

If pip3 itself was not found, your Python installation may not include pip. Run python3 -m ensurepip first, then retry the install.

help me fix a ModuleNotFoundError when importing requests in Python

Your workspace is ready and Python can talk to the outside world. Next up, you'll write your first API call to search for real charities in your state.

Make Your First API Call

Your project folder and dependencies are ready. Now it's time for the exciting part: making a real API call and seeing live charity data flow into your terminal.

The CharitySense API provides free access to IRS financial data for nonprofits across the United States. Before you can calculate which charities are most efficient, you need to see what data the API actually returns. That is what this step is about.

In this step, get ready to:
  • Create charity_finder.py with a function that queries the CharitySense API for charities in your state.
  • Run the script and see real charity names printed in your terminal.
Create your charity search function

Your script needs a function that builds a search query and sends it to the CharitySense API. This function takes a state code and returns a list of charities.

  • In VS Code, create a new file called charity_finder.py in your charity-finder folder.
  • Add the following imports and get_charities function:
import requests
import webbrowser
import os

def get_charities(state):
    """Search for charities in a given state."""
    url = "https://data.charitysense.com/api/v2/search"
    params = {
        "Query": "nonprofit",
        "State": state,
        "Mode": "Discovery",
        "Limit": 10,
        "Sort": "Revenue"
    }
    response = requests.get(url, params=params)
    data = response.json()
    return data.get("Hits", [])

What does this code do?

  • url points to the CharitySense search endpoint. This is a free, public API that returns real IRS data.
  • params builds the search query. Mode=Discovery searches by cause and mission text. Sort=Revenue returns larger charities first (they are more likely to have complete financial data).
  • requests.get() sends an HTTP GET request to the API and .json() parses the response into a Python dictionary.
  • data.get("Hits", []) extracts the list of charities from the response. If no charities are found, it returns an empty list.
Add the main function to run your script

The get_charities function handles the API call, but your script still needs code that asks the user for input and displays the results.

  • Add the following main function and entry point below your get_charities function in charity_finder.py:
def main():
    print("=" * 50)
    print("  CHARITY EFFICIENCY FINDER")
    print("=" * 50)
    print()

    state = input("Enter your 2-letter state code (e.g., TX, CA, NY): ").strip().upper()

    if len(state) != 2 or not state.isalpha():
        print("Invalid state code. Please enter a 2-letter abbreviation.")
        return

    print(f"\nSearching for charities in {state}...")
    charities = get_charities(state)

    if not charities:
        print("No charities found for that state.")
        return

    for charity in charities:
        print(f"{charity.get('Name')} - {charity.get('Location')} - {charity.get('Cause')}")


if __name__ == "__main__":
    main()

What does this code do?

  • input() prompts the user to type their state code. .strip().upper() cleans up whitespace and converts to uppercase so "ca" and "CA" both work.
  • The validation check ensures the user typed exactly 2 letters. If not, the script exits early with a helpful message.
  • The for loop prints each charity's Name, Location, and Cause from the API response.
  • if __name__ == "__main__" ensures main() only runs when you execute the file directly (not when importing it as a module).
  • Save charity_finder.py.

✔️ Awesome, I've got everything!

Great. Make sure you have saved your file before running it in the next substep.

ⓧ I'd like to double check the full code

Your charity_finder.py should contain these two sections in order:

  • The imports (requests, webbrowser, os) followed by the get_charities function (chunk 1 above).
  • The main function followed by the if __name__ == "__main__" entry point (chunk 2 above).

Scroll up and confirm each block is present and in order. Make sure there is one blank line between the end of get_charities and the start of main.

File not saving?

Make sure you created the file inside your charity-finder folder, not in a parent directory. Check the VS Code Explorer sidebar to confirm charity_finder.py appears at the top level.

Help me figure out why my file isn't in the right folder.

Run the script and explore the results

Your script is ready to talk to the CharitySense API. Before you run it, think about what kind of data you expect the API to return for each charity.

  • In your terminal, run the script:
python3 charity_finder.py
  • When prompted, enter your 2-letter state code (for example, TX or CA or NY).

You should see a list of charity names, locations, and causes printed in your terminal. Something like:

==================================================
  CHARITY EFFICIENCY FINDER
==================================================

Enter your 2-letter state code (e.g., TX, CA, NY): TX

Searching for charities in TX...
Houston Food Bank - Houston, TX - Food
Texas Children's Hospital - Houston, TX - Health
...

Notice what is missing. You can see names, locations, and causes. But there is no information about how efficiently each charity spends its money. No percentages, no program expense ratio, no way to tell which charity puts the most toward its actual mission.

Why is there no efficiency data?

The search endpoint returns basic identity information: who the charity is and what they do. Financial efficiency data lives on each charity's detailed profile page, which is a separate API endpoint.

This is the gap you will close in the next step by fetching each charity's full profile.

🙋‍♀️ Seeing an error when you run the script?

  • ModuleNotFoundError: No module named 'requests' means the library is not installed. Run pip3 install requests==2.34.2 and try again.
  • If you see a ConnectionError, check your internet connection. The script needs to reach the CharitySense API.
  • If the script prints "No charities found for that state", double-check you entered a valid 2-letter US state abbreviation.

Help me debug my CharitySense API call.

You now have a working script that pulls real charity data from an API. But names and causes alone are not enough to make a donation decision. Next up, you will fetch each charity's financial profile to find out exactly how much of every dollar goes to programs versus overhead.

Fetch Financial Efficiency Data

In Step 2, you saw charity names and descriptions. But you did not see the one thing that matters most for deciding where to donate: how much of each dollar actually goes to the cause.

The search endpoint only returns identity information. To get spending data, you need to fetch each charity's full profile from a separate endpoint. That profile contains a Program Expense Ratio that tells you exactly what percentage of total spending goes to actual programs versus overhead.

In this step, get ready to:
  • Add helper functions to fetch and parse financial data from the CharitySense API.
  • Update your main function to analyze each charity and rank them by efficiency.
  • See a sorted list of charities with their percentage-to-programs in your terminal.
Add the profile-fetching function

The CharitySense /page endpoint returns a charity's full profile, including all the financial metrics you need. Each charity has a unique EIN (Employer Identification Number) that acts as its ID in the API.

  • In charity_finder.py, add this new function directly below the get_charities function:
def get_charity_details(ein):
    """Fetch a charity's full profile page to get financial metrics."""
    url = f"https://data.charitysense.com/api/v2/charity/{ein}/page"
    response = requests.get(url)
    data = response.json()
    return data

What does this function do?

This function takes a charity's EIN and calls the /api/v2/charity/{ein}/page endpoint. The response contains the full profile: mission statement, location, and financial metrics. It returns the entire JSON so other functions can dig into specific parts.

Add the ratio-extraction function

The profile JSON has a deeply nested structure. The efficiency metric lives at Verdict > Categories > Financial Efficiency > Metrics > ProgramExpenseRatio. You need a function that navigates this tree and safely handles charities with missing data.

  • Add this function directly below get_charity_details:
def extract_program_ratio(page_data):
    """Extract the Program Expense Ratio from a charity's page data."""
    # Check if this charity has been scored
    verdict = page_data.get("Verdict", {})
    if verdict.get("State") != "Scored":
        return None

    # Navigate to the Financial Efficiency category
    categories = verdict.get("Categories", [])
    for category in categories:
        if category.get("Name") == "Financial Efficiency":
            metrics = category.get("Metrics", [])
            for metric in metrics:
                if metric.get("Key") == "ProgramExpenseRatio":
                    return metric.get("Value")
    return None

What does this function do?

  • First, it checks if Verdict.State equals "Scored". Some small charities file a 990N form with no financial detail, so they cannot be scored.
  • Then it loops through the Categories array looking for the one named "Financial Efficiency" and drills into its Metrics to find the ProgramExpenseRatio key.
  • The returned value is a decimal (e.g., 0.9367 means 93.67% goes to programs). If anything is missing, it returns None.
  • Save charity_finder.py.
  • Verify both functions work together by running this quick test in your terminal:
python3 -c "from charity_finder import get_charity_details, extract_program_ratio; print(extract_program_ratio(get_charity_details('742181456')))"

You should see a decimal number printed, something like 0.9367. That is the Houston Food Bank's program expense ratio, confirming both functions work.

Seeing an error instead of a number?

  • If you see ModuleNotFoundError, make sure your terminal is in the charity-finder folder (the same folder that contains charity_finder.py).
  • If you see None instead of a number, double-check that your extract_program_ratio function checks for "Scored" (capital S) and "Financial Efficiency" (exact capitalization matters).
  • If you see a connection error, check your internet connection. The API is free and requires no authentication.

Help me debug my extract_program_ratio function

Replace the main function's print loop with data collection

Now you have two working helpers. It is time to wire them into main() so the script fetches financial data for every charity from the search results, skips the unscored ones, and collects the rest into a list.

  • In charity_finder.py, scroll down to the main() function and find this code at the bottom:
    for charity in charities:
        print(f"{charity.get('Name')} - {charity.get('Location')} - {charity.get('Cause')}")
  • Delete those two lines and replace them with the following code that fetches financial data for each charity:
    print(f"Found {len(charities)} charities. Fetching financial data...\n")

    # Collect financial data for each charity
    charities_data = []
    for charity in charities:
        ein = charity.get("EIN")
        name = charity.get("Name", "Unknown")
        print(f"  Analyzing: {name}...")

        # Fetch full profile and extract efficiency ratio
        page_data = get_charity_details(ein)
        ratio = extract_program_ratio(page_data)

        # Only keep charities with valid financial data
        if ratio is not None:
            hero = page_data.get("Hero", {})
            mission = hero.get("Mission", hero.get("Description", "No mission statement available."))
            if len(mission) > 150:
                mission = mission[:150] + "..."

            location_data = hero.get("Location", {})
            location = f"{location_data.get('City', '')}, {location_data.get('State', '')}"

            score = page_data.get("Verdict", {}).get("Score", "N/A")

            charities_data.append({
                "name": name,
                "mission": mission,
                "location": location,
                "ratio_pct": ratio * 100,
                "score": score
            })

What does this code do?

  • For each charity from the search results, it grabs the EIN and calls get_charity_details to get the full profile.
  • It passes the profile data to extract_program_ratio. If the ratio is None (meaning the charity has no scored financial data), it skips that charity entirely.
  • For scored charities, it pulls out the mission, location, and overall score from the Hero section. It also multiplies the ratio by 100 to convert from decimal to a percentage.
  • Save charity_finder.py and run the script to confirm the API calls work:
python3 charity_finder.py
  • Enter a state code when prompted (e.g., TX).

You should see "Analyzing: ..." messages for each charity as the script fetches their profiles. The script then exits because you have not added the sorting and printing yet.

Seeing a KeyError or crash during analysis?

  • Make sure your get_charity_details function is defined between get_charities and extract_program_ratio. Python reads top-to-bottom, so functions must be defined before they are called.
  • Check that the indentation of the new code inside main() uses 4 spaces (matching the rest of the function). Mixed indentation causes errors in Python.

Help me debug my charity data collection loop

Sort and display the ranked results

The data collection works. Now add the code that sorts charities by efficiency (highest first) and prints a ranked list so you can see which ones put the most money toward their cause.

  • Add this code at the bottom of the main() function, directly below the for loop you just added (still inside main(), at the same indentation level as charities_data = []):
    # Sort by efficiency ratio, highest first
    charities_data.sort(key=lambda x: x["ratio_pct"], reverse=True)

    print(f"\n{len(charities_data)} charities have financial efficiency data.\n")

    # Print the top 5 results
    if charities_data:
        print("Top results:")
        for i, c in enumerate(charities_data[:5]):
            print(f"  {i+1}. {c['name']} - {c['ratio_pct']:.1f}% to programs")

What does this code do?

  • The .sort() call ranks charities by their ratio_pct value in descending order, so the most efficient charity appears first.
  • The :.1f format specifier rounds the percentage to one decimal place (e.g., 93.7 instead of 93.6700...).
  • Save charity_finder.py.

Before you run the script, what do you think the output will look like? Will every charity from the search results appear, or will some be filtered out?

  • Run the full script to see your ranked results:
python3 charity_finder.py
  • Enter a state code when prompted.

You should see each charity being analyzed, followed by a ranked list showing names and percentages like "93.7% to programs". Some charities from the search results will be filtered out because they lack scored financial data.

Seeing 0 charities have financial efficiency data?

  • Try a larger state like TX or CA. Smaller states may have fewer charities with full 990 filings.
  • Check that extract_program_ratio is checking verdict.get("State") (uppercase S in State) against "Scored" (uppercase S). The API uses UpperCamelCase for all field names.

Why is my ranked charity list empty?

💡 What is the Program Expense Ratio?

The Program Expense Ratio measures what share of a charity's total spending goes to its actual programs versus overhead and fundraising. A ratio of 0.937 means 93.7 cents of every dollar goes directly to the cause. Higher is better.

✔️ Awesome, I've got everything!

Great. Double check you have saved charity_finder.py and confirmed the ranked output in your terminal.

ⓧ I'd like to double check the full code

Your charity_finder.py should now contain these sections in order. Scroll up and confirm each one is present:

  • The import statements at the top (requests, webbrowser, os).
  • The get_charities(state) function that calls the search endpoint.
  • The get_charity_details(ein) function that calls the page endpoint.
  • The extract_program_ratio(page_data) function that navigates the nested JSON.
  • The main() function with the data collection loop, sort, and ranked print.
  • The if __name__ == "__main__": guard at the very bottom.

Your script now fetches real financial data and ranks charities by efficiency. Next up, you will turn that terminal output into a polished HTML dashboard you can open in your browser and share with anyone.

Generate Your HTML Dashboard

Your script can now fetch charity data, calculate efficiency ratios, and print a ranked list in the terminal. That works for you right now, but it is not something you can share with a friend or revisit later.

In this step, you will build an HTML generator that turns your data into a polished visual report. When you run the script, it will create a styled dashboard file and open it directly in your browser.

In this step, get ready to:
  • Build an HTML generator function with a winner highlight and ranked charity cards.
  • Style the dashboard with colored progress bars showing efficiency.
  • Write the report to a file and open it automatically in your browser.
Build the winner and charity card generators

The HTML report needs dynamic content. Each time the script runs, the charities and their scores will be different. You need a function that takes your ranked data and builds the HTML strings that display it.

  • In charity_finder.py, find the line that says return None at the end of your extract_program_ratio function.
  • Below that function, add the start of a new generate_html function that builds the winner card by copying this code:
def generate_html(state, charities_data):
    """Generate an HTML dashboard from the charity data."""
    if not charities_data:
        cards_html = '<p class="no-results">No charities with financial data found for this search.</p>'
        winner_html = ""
    else:
        winner = charities_data[0]
        winner_html = f"""
        <div class="winner-card">
            <div class="winner-label">Most Efficient Charity</div>
            <h2>{winner['name']}</h2>
            <p class="mission">{winner['mission']}</p>
            <div class="ratio-display">{winner['ratio_pct']:.1f}%</div>
            <p class="ratio-label">goes directly to programs</p>
        </div>
        """

What does this code do?

This function takes the state abbreviation and your sorted list of charity data. If the list is empty, it prepares a "no results" message. Otherwise, it grabs the first charity (the most efficient one) and builds a highlighted winner card showing their name, mission, and efficiency percentage.

  • Still inside the else block, add the cards loop that builds a card for every charity, with color-coded progress bars:
        cards_html = ""
        for i, charity in enumerate(charities_data):
            ratio_pct = charity["ratio_pct"]
            if ratio_pct >= 80:
                bar_color = "#2ecc71"
            elif ratio_pct >= 60:
                bar_color = "#f1c40f"
            else:
                bar_color = "#e74c3c"

            cards_html += f"""
            <div class="charity-card">
                <div class="rank">#{i + 1}</div>
                <div class="card-content">
                    <h3>{charity['name']}</h3>
                    <p class="location">{charity['location']}</p>
                    <p class="mission">{charity['mission']}</p>
                    <div class="progress-bar">
                        <div class="progress-fill" style="width: {ratio_pct}%; background-color: {bar_color};"></div>
                    </div>
                    <p class="ratio-text">{ratio_pct:.1f}% to programs | Overall score: {charity['score']}/100</p>
                </div>
            </div>
            """

What does this code do?

This loop creates an HTML card for each charity in your ranked list. The progress bar color changes based on efficiency: green for 80% or above, yellow for 60-80%, and red below 60%. Each card shows the rank number, name, location, mission, a visual progress bar, and the overall score.

  • Save charity_finder.py.

Seeing a syntax error?

Make sure the cards_html loop is indented inside the else: block (two levels of indentation from the function definition). The triple-quoted f-string inside cards_html += f""" must close with matching triple quotes on its own line.

help me fix an indentation error in my generate_html function

Add the HTML template with styling

Now you need the HTML template that wraps your dynamic content into a complete web page. This template includes embedded CSS that makes the dashboard look polished.

  • Still inside the generate_html function, add this code below the cards loop (at the same indentation level as the if not charities_data: line). This is the first part of your HTML template:
    html = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Charity Efficiency Report - {state}</title>
    <style>
        body {{
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f7fa;
            color: #333;
        }}
        h1 {{
            text-align: center;
            color: #2c3e50;
        }}
        .subtitle {{
            text-align: center;
            color: #7f8c8d;
            margin-bottom: 30px;
        }}
        .winner-card {{
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border-radius: 12px;
            padding: 30px;
            text-align: center;
            margin-bottom: 30px;
        }}

What does this code do?

This starts the HTML template as a Python f-string. The double curly braces {{ are how you write literal { characters inside an f-string (since single braces are used for Python variables). The CSS sets up the page layout and gives the winner card a purple gradient background.

  • Continue the template string (no new line break, this follows directly after the previous code) with the remaining CSS styles:
        .winner-label {{
            font-size: 14px;
            text-transform: uppercase;
            letter-spacing: 2px;
            opacity: 0.9;
        }}
        .winner-card h2 {{
            margin: 10px 0;
            font-size: 24px;
        }}
        .ratio-display {{
            font-size: 48px;
            font-weight: bold;
            margin: 10px 0;
        }}
        .ratio-label {{
            opacity: 0.9;
            font-size: 16px;
        }}
        .charity-card {{
            background: white;
            border-radius: 10px;
            padding: 20px;
            margin-bottom: 15px;
            display: flex;
            align-items: flex-start;
            box-shadow: 0 2px 8px rgba(0,0,0,0.08);
        }}
        .rank {{
            font-size: 24px;
            font-weight: bold;
            color: #667eea;
            min-width: 50px;
        }}
  • Continue with the card content styles, progress bar, and footer CSS:
        .card-content {{
            flex: 1;
        }}
        .card-content h3 {{
            margin: 0 0 5px 0;
            color: #2c3e50;
        }}
        .location {{
            color: #7f8c8d;
            font-size: 14px;
            margin: 0 0 8px 0;
        }}
        .mission {{
            font-size: 14px;
            color: #555;
            margin: 0 0 12px 0;
        }}
        .progress-bar {{
            background: #ecf0f1;
            border-radius: 10px;
            height: 12px;
            overflow: hidden;
            margin-bottom: 8px;
        }}
        .progress-fill {{
            height: 100%;
            border-radius: 10px;
            transition: width 0.3s ease;
        }}
        .ratio-text {{
            font-size: 13px;
            color: #7f8c8d;
            margin: 0;
        }}
  • Finish the CSS with the no-results and footer styles, then close the <style> and <head> tags and add the HTML body:
        .no-results {{
            text-align: center;
            color: #7f8c8d;
            font-size: 18px;
            padding: 40px;
        }}
        .footer {{
            text-align: center;
            color: #95a5a6;
            font-size: 12px;
            margin-top: 30px;
            padding-top: 20px;
            border-top: 1px solid #ecf0f1;
        }}
    </style>
</head>
<body>
    <h1>Charity Efficiency Report</h1>
    <p class="subtitle">Top charities in {state} ranked by program expense ratio</p>
    {winner_html}
    {cards_html}
    <div class="footer">
        Data sourced from IRS Form 990 filings via CharitySense API.
        Program expense ratio = percentage of total expenses spent on programs.
    </div>
</body>
</html>"""
    return html

What does this code do?

This closing section finishes the CSS styles and adds the HTML body. The body injects your dynamic {winner_html} and {cards_html} variables directly into the page. The footer credits the data source. Finally, return html sends the complete HTML string back to whoever called the function.

  • Save charity_finder.py.

Getting a SyntaxError about the f-string?

The most common issue is mismatched triple quotes. The template starts with html = f"""<!DOCTYPE html> and must end with </html>""" (three double quotes, no spaces before them). Also check that every CSS brace uses doubled curly braces: {{ and }}.

help me fix my HTML template f-string syntax

Write the report and open it in your browser

Your generate_html function is ready, but nothing calls it yet. You need to update main() to generate the HTML file and open it automatically.

  • In your main() function, find the last lines that print the top results:
    if charities_data:
        print("Top results:")
        for i, c in enumerate(charities_data[:5]):
            print(f"  {i+1}. {c['name']} - {c['ratio_pct']:.1f}% to programs")
  • Add this code directly below those lines to generate the report and open it:
    # Generate the HTML report and open it in the browser
    html = generate_html(state, charities_data)

    report_path = os.path.abspath("charity_report.html")
    with open(report_path, "w") as f:
        f.write(html)

    print(f"\nReport saved to: {report_path}")
    print("Opening in your browser...")
    webbrowser.open(f"file://{report_path}")

What does this code do?

First it calls your new generate_html function to build the complete HTML string. Then os.path.abspath creates the full file path so the browser can find it. The open() and .write() calls save the HTML to a file. Finally, webbrowser.open() launches your default browser to display the report.

  • Save charity_finder.py.

Before you run the script, think about what you expect to happen. You have seen terminal output before. What do you think will be different this time?

  • Run your script by entering this command in your terminal:
python3 charity_finder.py
  • Enter your 2-letter state code when prompted (e.g., CA).

Your terminal will show the familiar charity analysis output, then print the report path and open your browser. A styled HTML dashboard appears with a purple winner card at the top, followed by ranked charity cards with colored progress bars.

Browser didn't open?

If the browser does not open automatically, look for the "Report saved to:" line in your terminal output. Copy that file path and paste it into your browser's address bar. On macOS, you can also open it by running open charity_report.html in your terminal.

help me open my charity_report.html file in a browser

✔️ Awesome, I've got everything!

Great. Double check you have saved charity_finder.py and that your browser displayed the dashboard.

ⓧ I'd like to double check the full code

Your file is long now. Scroll through and confirm these sections are present, in order:

  • The imports at the top: import requests, import webbrowser, import os.
  • The get_charities(state) function.
  • The get_charity_details(ein) function.
  • The extract_program_ratio(page_data) function.
  • The generate_html(state, charities_data) function with the winner card logic, cards loop, full HTML template with CSS, and return html at the end.
  • The main() function, ending with webbrowser.open(f"file://{report_path}").
  • The if __name__ == "__main__": block at the very bottom.

Your charity finder now produces a real, shareable report. Next up, you will tackle the Secret Mission and add cause-based filtering so users can search for efficient charities in a specific category they care about.

Secret mission

Add Cause Filtering

Your charity finder searches for all nonprofits in a state. But what if you only care about food banks, education charities, or veterans' organizations? In this mission, you'll add a cause filter so users can zero in on the most efficient charity in a category they care about.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your files for future use or delete them entirely.

Resources you used:

  • The charity-finder folder on your Desktop containing charity_finder.py and charity_report.html.
  • The requests Python package installed via pip.

Keep everything running

No action needed. Choose this if you plan to re-run the script for different states or causes.

  • Your script lives in ~/Desktop/charity-finder ready to run anytime.
  • The requests library stays available for any future Python projects that need HTTP calls.
  • Each time you run the script, charity_report.html is regenerated with fresh data.

Pause - I'll come back to this later

Since this is a local project with no running processes, there is nothing to pause. Your files sit idle until you run the script again.

  • No servers or background processes are running.
  • Your files use negligible disk space and cost nothing to keep.

Delete - I don't want to use this again

Remove all project files and the installed package to start fresh.

  • Delete the project folder by running this command in Terminal:
rm -rf ~/Desktop/charity-finder
  • Uninstall the requests library by running:
pip3 uninstall requests
  • Type y when prompted to confirm the uninstall.

Nice Work!

Nice Work!

You just built a personal charity research assistant from scratch. Your script pulls live IRS financial data, crunches the numbers, and presents a polished report that helps you make smarter donation decisions.

You've learned how to:

  • Build a Python script that calls a real REST API (CharitySense) to fetch live nonprofit financial data and parse JSON responses with deeply nested structures.
  • Extract specific metrics like the Program Expense Ratio from complex data, filter out incomplete records, and sort results to surface the most efficient charities.
  • Generate a styled HTML dashboard from Python with color-coded progress bars, conditional formatting, and a highlighted winner card that opens automatically in your browser.
  • Secret Mission: Added cause-based filtering so users can find the most efficient charity in a specific category they care about.

Ready to quiz yourself?