Plotly OFTW App Building Challenge

One more issue I noticed was not every date has an entry in the “fred” CSVs. I believe Sat Sun.
If amount is not NAN, but amount_usd is NAN, try to match the max(previous 2 dates). Something along those lines.

UPDATE: I see you have used

# Interpolate the missing values using linear method
merged_df[currency] = merged_df[currency].interpolate(method='linear')

But that won’t use Friday value which is usually the case right?

UPDATE 2: From what I am reading, conversion happens on settlement date, not transaction date. While most of the world uses Friday rate, USD and CAD transactions are settled next business day. So in this case, missing values will have to look for the next available date.

# First, create a complete date range
date_range = pd.date_range(start=df_conversion.index.min(), end=df_conversion.index.max(), freq='D')

# Reindex the dataframe with the complete date range and backward fill the values
df_filled = df_conversion.reindex(date_range).bfill()

That’s really good information. Thank you @mavwolverine
So for CAD USD conversions we need to use the Friday rate, and for the rest of the conversions we need to use the Monday rate? Did I get that right?

No. Since we are converting to USD, Monday always in our case.

If it was INR to SGD, it would be Friday (I presume based on what I have read so far).

@adamschroeder

simplified version

from datetime import datetime
import math

# import numpy as np

import pandas as pd
import pandas_datareader.data as pd_dr_web

import plotly.graph_objects as go

df_pledges = pd.read_json("https://storage.googleapis.com/plotly-app-challenge/one-for-the-world-pledges.json")
df_payments = pd.read_json("https://storage.googleapis.com/plotly-app-challenge/one-for-the-world-payments.json")

start_date = datetime.strptime('2024-07-01', '%Y-%m-%d')
end_date = min(
    datetime.strptime('2025-06-30', '%Y-%m-%d'), 
    df_payments['date'].max()
)  # .strftime('%Y-%m-%d')

df_payments_ytd = df_payments[
    (df_payments['date'] >= start_date)
    & (df_payments['date'] <= end_date)
    & (~df_payments['portfolio'].isin((
        'One for the World Discretionary Fund', 
        'One for the World Operating Costs')
    ))
].copy()  # copy is important to avoid slice warning

df_payments_ytd.reset_index(drop=True, inplace=True)

df_conversion = pd_dr_web.DataReader(
    ["DEXUSUK", "DEXCAUS", "DEXUSAL", "DEXUSEU", "DEXSIUS", "DEXSZUS"], 
    "fred",
    start_date,
    end_date
)

# First, create a complete date range
date_range = pd.date_range(
    start=start_date,
    end=min(end_date, df_conversion.index.max()),
    freq='D'
)

# Reindex the dataframe with the complete date range and back fill the values
df_conversion = df_conversion.reindex(date_range).bfill()

# Create a dictionary for each currency's conversion rates
conversion_rates = {
    'USD': 1.0,
    'GBP': df_conversion['DEXUSUK'],
    'CAD': 1/df_conversion['DEXCAUS'],  # Inverting for division
    'AUD': df_conversion['DEXUSAL'],
    'EUR': df_conversion['DEXUSEU'],
    'SGD': 1/df_conversion['DEXSIUS'],  # Inverting for division
    'CHF': 1/df_conversion['DEXSZUS']   # Inverting for division
}

# Create the conversion multiplier series based on currency
df_payments_ytd['multiplier'] = df_payments_ytd.apply(
    lambda row: 1 if row["currency"] == "USD"
        else conversion_rates[row["currency"]][row["date"]],
    axis=1
)

# Multiply amount by the conversion rate
df_payments_ytd['amount_usd'] = (
    df_payments_ytd['amount'] * df_payments_ytd['multiplier']
).round(2)

# Optionally drop the multiplier column if you don't need it
df_payments_ytd.drop('multiplier', axis=1, inplace=True)
  • avoid multiple web.DataReader calls.
  • avoid multiple merge

Please validate if you get the same results.

@mavwolverine
can you please share the complete reproducible code, with the libraries and incorporating the df_payments dataset?

@adamschroeder updated the code above

Is there a specific reason you’re limited the dataset to not include One for the World Discretionary Fund and One for the World Operating Costs?

notes from row #9 says to ignore these values. I was working of that.

In the payments dataset, excluding "One for the World Discretionary Fund "One for the World Operating Costs" and "One for the World Operating Costs" from the portfolio column

I see. Ok, Good luck.
Here’s the final exchange-rates.py file based on your suggestions, @mavwolverine . But one would also need to run the currency-converter.py file after that.

import pandas_datareader.data as web
import datetime
import pandas as pd


def get_historical_exchange_rates(start_date="2020-01-01", end_date=None):
    """Fetch historical GBP to USD exchange rates from FRED."""
    if end_date is None:
        end_date = datetime.datetime.today().strftime("%Y-%m-%d")

    try:
        for currency in ['DEXUSUK', 'DEXCAUS', 'DEXUSAL', 'DEXUSEU', 'DEXSIUS', 'DEXSZUS']:
            df = web.DataReader(currency, "fred", start_date, end_date)

            # Create a new DataFrame with all dates in the range
            date_range = pd.date_range(start=df.index.min(), end=df.index.max(), freq='D')

            df_filled = df.reindex(date_range).bfill()
            df_filled.reset_index(inplace=True)
            df_filled.rename(columns={'index': 'DATE'}, inplace=True)

            # save df as a csv sheet
            df_filled.to_csv(f"{currency}_exchange_rates.csv", index=False)


    except Exception as e:
        print("Error fetching data:", e)

get_historical_exchange_rates("2014-03-01", "2025-02-28")

@adamschroeder
I think this piece covers the currency-converter.py

# Create a dictionary for each currency's conversion rates
conversion_rates = {
    'USD': 1.0,
    'GBP': df_conversion['DEXUSUK'],
    'CAD': 1/df_conversion['DEXCAUS'],  # Inverting for division
    'AUD': df_conversion['DEXUSAL'],
    'EUR': df_conversion['DEXUSEU'],
    'SGD': 1/df_conversion['DEXSIUS'],  # Inverting for division
    'CHF': 1/df_conversion['DEXSZUS']   # Inverting for division
}

# Create the conversion multiplier series based on currency
df_payments_ytd['multiplier'] = df_payments_ytd.apply(
    lambda row: 1 if row["currency"] == "USD"
        else conversion_rates[row["currency"]][row["date"]],
    axis=1
)

# Multiply amount by the conversion rate
df_payments_ytd['amount_usd'] = (
    df_payments_ytd['amount'] * df_payments_ytd['multiplier']
).round(2)

# Optionally drop the multiplier column if you don't need it
df_payments_ytd.drop('multiplier', axis=1, inplace=True)
2 Likes

Where do I submit an app for this challenge? I don’t see any submissions by others

hi @ribab
You should submit them in this topic’s thread, right here.
The community usually works on their data apps until the very end. We’ll probably start seeing submissions later this week. But feel free to post your today.

Hi everyone,

I’m excited to share my solution for the Plotly OFTW App Building Challenge! I built an interactive dashboard that provides insights into monetary pledges and payments. The app allows users to filter data by payment platform and date range, view key performance metrics (KPIs), and visualize data through histograms, time series, and pie charts. Additionally, the dashboard integrates with Google Generative AI to generate actionable insights from the computed metrics.

Try the app here:

View the source code on GitHub:

I look forward to your feedback and suggestions!

7 Likes

Hi everyone!

I’m really excited to share the app I built for this challenge. This time, I focused on improving the way I plan and structure Dash apps, especially when it comes to managing callbacks efficiently. It’s something I’ve found super important when deploying on lightweight servers like a t2.micro (gotta squeeze out every bit of performance!).

This time around, it was a bit tricky to get the metrics calculated propery. For future challenges, it would be awesome to have the dataset a bit more “ready to use” so we can focus more on building the app itself.

I’d love to check out what you’ve all created too! These challenges are such a great way to learn, level up your skills, and get inspired by others’ work.

You can see the live app here:

And here’s the GitHub repo:

5 Likes

Hi everyone! I’m super excited to share the dashboard I built for the Plotly x OFTW Dash App Challenge. This interactive dashboard is designed for One for the World (OFTW) and provides insights into key metrics such as the pledge attrition rate, money moved from payments, active donors, annualized run rate (ARR), and much more. It also includes all the wishlist metrics outlined by OFTW. One of the the features is the integration of Gemini AI, which allows users to ask questions about the metrics directly within the app for quick insights.

I learned so much from this project and I’m really eager to see the what others have built.

You Can Check The DashBoard Here:

The Github Repo Contains All The Details:
soubhagya2003jan/Plotly-x-OFTW-Dash-App-Challenge: Plotly x OFTW Dash App Challenge


6 Likes

The One for the World Dashboard app is a web-based interface for exploring various metrics and insights. The design features a dark theme and a vibrant gradient header, and provides navigation to four main sections:

  1. Home – Overview of the dashboard and general metrics.
  2. Objectics – Insights and analysis of Objectics data
  3. Money Moved – Detailed information on the movement of funds with a chart
  4. Pledges – Learn about pledges and their evolution over time.

At the top, the navigation bar contains the website title and links to each section. The home page includes a welcome message and short descriptions to help users navigate and understand the different parts of the dashboard.

Pythonanywhere

GitHub

5 Likes

Hey everyone :waving_hand:

Excited to share my submission for the OFTW dashboard challenge!

:backhand_index_pointing_right: Live app: https://oftw-dashboard-nwk7wca5uq-ue.a.run.app/
:open_file_folder: GitHub repo: GitHub - Tanguy9862/oftw-dashboard

I wanted to create something that gives a clear and immediate sense of performance — without needing to dig around or switch pages. Everything is kept on a single view to help users instantly spot what’s on track, what’s slightly behind, or what’s off.

You can click any metric in the left panel and the charts on the right update accordingly (time series + breakdown). The app supports fiscal vs calendar year, quarter filtering, and dynamic year-over-year comparisons.

Behind the scenes, there’s quite a bit of preprocessing involved — targets are broken down by quarter (if needed) based on historical distributions, and noisy years are excluded to keep it reliable. Notebooks used for data prep are in the repo if you’re curious!

It’s fully responsive too — here’s a quick preview:

Would love to hear any thoughts or feedback — and thanks a lot to Plotly and OFTW for the opportunity :folded_hands:

Best regards,
Tanguy

3 Likes

Some great contribution by everyone I see, the quality of every dash this time is much higher, it’s really interesting to see what can be done, I learned quite a lot thanks to you guys.

On my side this is my proposition for this challenge (on my personal gcloud) :
I tried to go for a different approach, while there is the summary metrics, here using a pivot table you can create you explor your metric in depth and also create ur own metric, edit the current existing metric and then find them again in your report page.
Graph and pivot are automatic :slight_smile:
Quite a lot of thing could be added but I didn’t had time.
https://onetap-dot-nifty-motif-418123.ew.r.appspot.com/

3 Likes

We’re excited to submit our proof-of-concept dashboard for One For The World (OFTW) as part of this month’s Plotly Dash app challenge.

About the App

As requested, this internal-facing dashboard is built to provide insights aligned with OFTW’s metrics Wishlist — with scalability in mind and lots of room for future enhancements. We’re looking forward to community feedback and ideas to take it further.

Key Features:

  • YTD, PY, MTD comparisons across donor chapter, frequency, and cohort
  • Visuals to surface trends and contribution gaps
  • Smart fiscal calendar logic
  • LLM-driven insights architecture integrated
  • Interactive form for creating/editing pledge targets — minimizing human-in-the-loop interventions going forward

Huge thanks to the volunteers at Arkimetrix who gave up their weekend for a meaningful cause. Grateful to the OFTW team for the opportunity, and to Plotly for creating a challenge that brings together data, design, and purpose.

Link to the App:

Link to git:

3 Likes