Skip to content

Hea 592/inventory dashboard #138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/viz/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# viz/apps.py
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _


class VizConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "viz"
verbose_name = _("viz")

def ready(self):
import viz.dash.inventory_dashboard.app # noqa
Empty file.
206 changes: 206 additions & 0 deletions apps/viz/dash/inventory_dashboard/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import dash_bootstrap_components as dbc
import pandas as pd
import plotly.express as px
from dash import dash_table, dcc, html
from dash.dependencies import Input, Output
from django_plotly_dash import DjangoDash

from .functions import clean_livelihood_data, clean_wealth_group_data

# Unique countries and livelihood zones for dropdowns
unique_countries = sorted(clean_livelihood_data["country_code"].dropna().unique())
unique_zones = sorted(clean_livelihood_data["livelihood_zone_baseline_label"].dropna().unique())

# Dash app
app = DjangoDash("Inventory_dashboard", suppress_callback_exceptions=True, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.title = "HEA Dashboard"

# Layout
app.layout = html.Div(
[
html.H1("HEA Data Inventory Dashboard", style={"textAlign": "center"}),
html.Div(
[
html.Div(
[
dcc.Dropdown(
id="country-dropdown",
options=[{"label": country, "value": country} for country in unique_countries],
placeholder=f"Select Country(s) (e.g., {unique_countries[0]})",
multi=True,
),
],
style={"flex": "1", "marginRight": "10px"},
),
html.Div(
[
dcc.Dropdown(
id="livelihood-zone-dropdown",
options=[{"label": zone, "value": zone} for zone in unique_zones],
placeholder=f"Select Livelihood Zone(s) (e.g., {unique_zones[0]})",
multi=True,
),
],
style={"flex": "1"},
),
],
style={
"display": "flex",
"width": "100%",
"justifyContent": "space-between",
"marginBottom": "20px",
},
),
html.Div(
[
html.Div(
dcc.Graph(id="wealth-chart"),
style={"width": "30%", "display": "inline-block"},
),
html.Div(
dcc.Graph(id="livelihood-chart"),
style={"width": "30%", "display": "inline-block"},
),
html.Div(
dcc.Graph(id="wealth-monthly-chart"),
style={"width": "30%", "display": "inline-block"},
),
],
),
html.Div(
[
html.H3("Data Table", style={"textAlign": "center"}),
dash_table.DataTable(
id="data-table",
columns=[
{"name": "Livelihood Zone", "id": "livelihood_zone_baseline_label"},
{"name": "Total Strategy Type Count", "id": "Strategy Type Count"},
{"name": "Summary Wealth Characteristics", "id": "Summary Data"},
{"name": "Community Wealth Characteristics", "id": "Community Data"},
],
style_table={"overflowX": "auto"},
style_cell={
"textAlign": "left",
"fontSize": "15px", # Increase font size
"padding": "10px", # Add padding
},
style_header={
"fontSize": "18px", # Increase font size for header
"fontWeight": "bold", # Make header bold
"textAlign": "center",
},
page_size=10,
),
],
className="inventory-filter inventory-filter-last",
),
],
className="div-wrapper control-panel-wrapper",
)


# Callbacks
@app.callback(
[
Output("livelihood-zone-dropdown", "options"),
Output("wealth-chart", "figure"),
Output("livelihood-chart", "figure"),
Output("wealth-monthly-chart", "figure"),
Output("data-table", "data"),
],
[Input("country-dropdown", "value"), Input("livelihood-zone-dropdown", "value")],
)
def update_charts(selected_countries, selected_zones):
# Handle multi-country selection
if selected_countries:
filtered_livelihood = clean_livelihood_data[clean_livelihood_data["country_code"].isin(selected_countries)]
filtered_wealth = clean_wealth_group_data[clean_wealth_group_data["country_code"].isin(selected_countries)]
filtered_zones = sorted(filtered_livelihood["livelihood_zone_baseline_label"].unique())
else:
filtered_livelihood = clean_livelihood_data
filtered_wealth = clean_wealth_group_data
filtered_zones = unique_zones

# Update options for livelihood zone dropdown
zone_options = [{"label": zone, "value": zone} for zone in filtered_zones]

# Filter data based on selected livelihood zones
if selected_zones:
filtered_livelihood = filtered_livelihood[
filtered_livelihood["livelihood_zone_baseline_label"].isin(selected_zones)
]
filtered_wealth = filtered_wealth[filtered_wealth["livelihood_zone_baseline_label"].isin(selected_zones)]

# Group and aggregate Strategy Type Count
livelihood_grouped = (
filtered_livelihood.groupby(["livelihood_zone_baseline_label", "strategy_type_label"])
.size()
.reset_index(name="Strategy Type Count")
)
livelihood_summary = (
livelihood_grouped.groupby("livelihood_zone_baseline_label")["Strategy Type Count"].sum().reset_index()
)

# Group and pivot Wealth Characteristics Count
wealth_grouped = (
filtered_wealth.groupby(["livelihood_zone_baseline_label", "Record Type"])
.size()
.reset_index(name="Wealth Characteristics Count")
)
wealth_grouped_pivot = wealth_grouped.pivot_table(
index="livelihood_zone_baseline_label",
columns="Record Type",
values="Wealth Characteristics Count",
aggfunc="sum",
fill_value=0,
).reset_index()

# Merge livelihood and wealth data
merged_data = pd.merge(livelihood_summary, wealth_grouped_pivot, on="livelihood_zone_baseline_label", how="left")

# Create charts
wealth_fig = px.bar(
wealth_grouped,
x="livelihood_zone_baseline_label",
y="Wealth Characteristics Count",
color="Record Type",
barmode="stack",
title="Wealth Characteristics by Type",
labels={
"livelihood_zone_baseline_label": "Livelihood Zone",
},
)

livelihood_fig = px.bar(
livelihood_grouped,
x="strategy_type_label",
y="Strategy Type Count",
color="livelihood_zone_baseline_label",
barmode="stack",
title="Strategy Types by Baseline",
labels={
"strategy_type_label": "Strategy Type",
"livelihood_zone_baseline_label": "Baseline Zone",
},
)

wealth_monthly_fig = px.bar(
filtered_wealth.groupby(["created_month", "Record Type"])
.size()
.reset_index(name="Wealth Characteristics Count"),
x="created_month",
y="Wealth Characteristics Count",
color="Record Type",
barmode="stack",
title="Wealth Characteristics by Month",
labels={
"created_month": "Month",
},
)

return zone_options, wealth_fig, livelihood_fig, wealth_monthly_fig, merged_data.to_dict("records")


# Run the app
if __name__ == "__main__":
app.run_server(debug=True)
67 changes: 67 additions & 0 deletions apps/viz/dash/inventory_dashboard/functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import pandas as pd
import requests

# API Endpoints
LIVELIHOOD_STRATEGY_URL = "https://headev.fews.net/api/livelihoodstrategy/"
WEALTH_GROUP_URL = "https://headev.fews.net/api/wealthgroupcharacteristicvalue/"
LIVELIHOOD_ACTIVITY_URL = "https://headev.fews.net/api/livelihoodactivity/"


def fetch_data(api_url):
"""
Fetch data from the given API endpoint and return as a Pandas DataFrame.
"""
try:
response = requests.get(api_url)
response.raise_for_status()
data = response.json()
return pd.DataFrame(data)
except Exception as e:
print(f"Error fetching data: {e}")
return pd.DataFrame()


def prepare_livelihood_data(df):
"""
Prepare livelihood strategy data for visualization.
"""
df.rename(columns={"livelihood_zone_country": "country_code"}, inplace=True)
df["ls_baseline_date"] = df["livelihood_zone_baseline_label"].str.split(": ").str[1]
df["ls_baseline_month"] = pd.to_datetime(df["ls_baseline_date"], errors="coerce").dt.month
return df


def prepare_wealth_group_data(df):
"""
Prepare wealth group data for visualization.
"""
df.rename(columns={"livelihood_zone_country_code": "country_code"}, inplace=True)

if "livelihood_zone_baseline_label" in df.columns:
df["ls_baseline_date"] = df["livelihood_zone_baseline_label"].str.split(": ").str[1]
else:
df["ls_baseline_date"] = None

df["ls_baseline_month"] = pd.to_datetime(df["ls_baseline_date"], errors="coerce").dt.month

month_mapping = {month: pd.Timestamp(f"2023-{month:02}-01").strftime("%B") for month in range(1, 13)}

if "created_date" in df.columns:
df["created_month"] = pd.to_datetime(df["created_date"], errors="coerce").dt.month.map(month_mapping)
else:
df["created_month"] = pd.to_datetime(df["ls_baseline_date"], errors="coerce").dt.month.map(month_mapping)

if "community_name" in df.columns:
df["Record Type"] = df["community_name"].apply(lambda x: "Summary Data" if pd.isna(x) else "Community Data")
else:
df["Record Type"] = "Community Data"

return df


# Fetch and prepare data
livelihood_data = fetch_data(LIVELIHOOD_STRATEGY_URL)
wealth_group_data = fetch_data(WEALTH_GROUP_URL)

clean_livelihood_data = prepare_livelihood_data(livelihood_data)
clean_wealth_group_data = prepare_wealth_group_data(wealth_group_data)
16 changes: 16 additions & 0 deletions apps/viz/dash_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django_plotly_dash import DjangoDash


class SecureDjangoDash(DjangoDash):
"""
An extended version of DjangoDash that allows fine-grained control of permissions
and clickjacking protection
"""

xframe_options = "DENY" # None (i.e. Allow) or SAMEORIGIN or DENY
login_required = True
permission_required = None

def __init__(self, *args, **kwargs):
self.permission_required = kwargs.pop("permission_required", None)
super().__init__(*args, **kwargs)
Loading
Loading