Spaces:
Sleeping
Sleeping
import streamlit as st | |
import pandas as pd | |
import plotly.graph_objects as go | |
import plotly.express as px | |
st.set_page_config(page_title="Interactive 12 Pubs of Christmas", layout="wide") | |
# ------------------------------------ | |
# Parameters & Constants | |
# ------------------------------------ | |
# Julia's data | |
julia_weight_lbs = 120 | |
julia_weight_kg = julia_weight_lbs * 0.45359237 | |
julia_weight_g = julia_weight_kg * 1000 | |
julia_r = 0.55 | |
# Sean's data | |
sean_weight_kg = 86.0 | |
sean_weight_g = sean_weight_kg * 1000 | |
sean_r = 0.68 | |
beta = 0.015 # g/dL/h elimination rate | |
g_per_unit = 8 | |
# Drink options | |
# We'll define a few drink types with their volumes and ABVs. | |
# For simplicity, we assume: | |
# - 1 UK pint = 568 ml | |
# - Half beer = Half pint = 284 ml | |
# - Shot = 30 ml | |
# - ABV is given in percentage. | |
# We'll approximate each drink's ethanol mass: | |
# Ethanol mass (g) = volume (ml) * density_of_ethanol(0.789 g/ml) * (ABV/100) | |
# For simplicity, we use a units-based approach: | |
# 1 unit (UK) = 10ml pure ethanol = 8g ethanol | |
# Units = (Volume in ml * ABV%) / 1000 (approx UK definition: 1 unit = 10ml pure ethanol) | |
# So ethanol in grams = Units * 8. | |
drink_options = { | |
"Guinness (Pint)": {"volume_ml": 568, "abv": 4.2}, | |
"Heineken (Pint)": {"volume_ml": 568, "abv": 5.0}, | |
"Stella Artois (Pint)": {"volume_ml": 568, "abv": 4.8}, | |
"Half Beer (Generic)": {"volume_ml": 284, "abv": 4.0}, | |
"Vodka Shot": {"volume_ml": 30, "abv": 40.0}, | |
"Whiskey Shot": {"volume_ml": 30, "abv": 40.0}, | |
"Cider (Pint)": {"volume_ml": 568, "abv": 4.5}, | |
"Red Wine (175ml)": {"volume_ml": 175, "abv": 12.0} | |
} | |
# A function to calculate ethanol grams from a given drink | |
def alcohol_in_grams(volume_ml, abv_percent): | |
# 1 unit (UK) ~ (volume_ml * abv) / 1000 * (8g/unit) | |
units = (volume_ml * abv_percent) / 1000 | |
return units * 8 # grams of ethanol | |
def bac_calculation(total_alcohol_g, W, r, t): | |
# Widmark formula approximation: | |
# BAC = (A/(W*r))*100 - beta*(t-1) if t>1 | |
bac = (total_alcohol_g / (W * r)) * 100 | |
if t > 1: | |
bac = bac - beta*(t-1) | |
return max(bac,0) | |
st.title("12 Pubs of Christmas - Interactive Drink Selection") | |
st.write(""" | |
Select the drinks you (Julia) and Sean will consume each hour over a 6-hour period. | |
We'll estimate your BAC levels and show you comparisons. | |
**Note:** These are rough estimates and not medically accurate. Actual BAC varies by individual. | |
""") | |
hours = 6 | |
col_drinks = st.columns(hours) | |
selected_drinks = [] | |
st.subheader("Select Drinks for Each Hour") | |
for i in range(hours): | |
with col_drinks[i]: | |
drink_choice = st.selectbox( | |
f"Hour {i+1} Drink", | |
list(drink_options.keys()), | |
key=f"drink_hour_{i}" | |
) | |
selected_drinks.append(drink_choice) | |
# Calculate total alcohol consumed per hour | |
alcohol_by_hour = [] | |
for i, d in enumerate(selected_drinks): | |
vol = drink_options[d]["volume_ml"] | |
abv = drink_options[d]["abv"] | |
grams = alcohol_in_grams(vol, abv) | |
alcohol_by_hour.append(grams) | |
# Cumulative alcohol and BAC calculations | |
time_points = list(range(hours+1)) # from 0 to 6 | |
julia_bac = [] | |
sean_bac = [] | |
julia_alc_over_time = [] | |
sean_alc_over_time = [] | |
for t in time_points: | |
# total alcohol consumed up to hour t | |
total_alc = sum(alcohol_by_hour[:t]) | |
# Julia BAC | |
julia_bac_val = bac_calculation(total_alc, julia_weight_g, julia_r, t) | |
julia_bac.append(julia_bac_val) | |
# Sean BAC | |
sean_bac_val = bac_calculation(total_alc, sean_weight_g, sean_r, t) | |
sean_bac.append(sean_bac_val) | |
df = pd.DataFrame({ | |
"Hour": time_points, | |
"Julia_BAC": julia_bac, | |
"Sean_BAC": sean_bac, | |
"Total_Alcohol_Consumed_g": [sum(alcohol_by_hour[:t]) for t in time_points] | |
}) | |
# Calculate difference | |
df["BAC_Difference"] = df["Julia_BAC"] - df["Sean_BAC"] | |
# Plot 1: BAC over time | |
st.subheader("BAC over Time") | |
fig_bac = go.Figure() | |
fig_bac.add_trace(go.Scatter(x=df["Hour"], y=df["Julia_BAC"], mode='lines+markers', name="Julia BAC", line=dict(color='firebrick', width=3))) | |
fig_bac.add_trace(go.Scatter(x=df["Hour"], y=df["Sean_BAC"], mode='lines+markers', name="Sean BAC", line=dict(color='royalblue', width=3))) | |
fig_bac.update_layout(title="Estimated BAC vs. Time (Hours)", xaxis_title="Hours", yaxis_title="BAC (%)", template="plotly_white") | |
st.plotly_chart(fig_bac, use_container_width=True) | |
# Plot 2: Alcohol consumed vs BAC (Dual-Axis) | |
st.subheader("Alcohol Consumed and BAC") | |
fig_bar_line = go.Figure() | |
# Bar for total alcohol consumed at each hour | |
fig_bar_line.add_trace(go.Bar(x=df["Hour"], y=df["Total_Alcohol_Consumed_g"], name="Total Alcohol (g)", marker_color='goldenrod', yaxis='y1')) | |
# Julia BAC line | |
fig_bar_line.add_trace(go.Scatter(x=df["Hour"], y=df["Julia_BAC"], name="Julia BAC", mode='lines+markers', line=dict(color='firebrick', width=3), yaxis='y2')) | |
# Sean BAC line | |
fig_bar_line.add_trace(go.Scatter(x=df["Hour"], y=df["Sean_BAC"], name="Sean BAC", mode='lines+markers', line=dict(color='royalblue', width=3), yaxis='y2')) | |
fig_bar_line.update_layout( | |
title="Total Alcohol (g) vs BAC (%) Over Time", | |
xaxis=dict(title="Hour"), | |
yaxis=dict(title="Total Alcohol (g)", side='left', range=[0, df["Total_Alcohol_Consumed_g"].max()*1.1]), | |
yaxis2=dict(title="BAC (%)", side='right', overlaying='y', range=[0, df[["Julia_BAC","Sean_BAC"]].max().max()*1.1]), | |
template="plotly_white" | |
) | |
st.plotly_chart(fig_bar_line, use_container_width=True) | |
# Plot 3: Difference in BAC | |
st.subheader("Difference in BAC (Julia - Sean)") | |
fig_diff = go.Figure(go.Bar( | |
x=df["Hour"], | |
y=df["BAC_Difference"], | |
text=[f"{d:.3f}%" for d in df["BAC_Difference"]], | |
textposition='outside', | |
marker_color='indianred' | |
)) | |
fig_diff.update_layout( | |
title="Difference in BAC Between Julia and Sean Over Time", | |
xaxis_title="Hour", | |
yaxis_title="Difference in BAC (%)", | |
template="plotly_white" | |
) | |
st.plotly_chart(fig_diff, use_container_width=True) | |
# Plot 4: Pie/Donut Chart of Alcohol Proportions if needed | |
# For a bit of fun, let's compare the total alcohol consumed to their body weight as a fraction | |
st.subheader("Alcohol as a Percentage of Body Weight") | |
# 1g = 0.001 kg | |
julia_pct_body_weight = (df["Total_Alcohol_Consumed_g"].iloc[-1] / julia_weight_g)*100 | |
sean_pct_body_weight = (df["Total_Alcohol_Consumed_g"].iloc[-1] / sean_weight_g)*100 | |
donut_data = pd.DataFrame({ | |
"Person": ["Julia", "Sean"], | |
"Alcohol_as_pct_body_weight": [julia_pct_body_weight, sean_pct_body_weight] | |
}) | |
fig_donut = px.pie(donut_data, values='Alcohol_as_pct_body_weight', names='Person', hole=0.5, | |
color='Person', color_discrete_map={"Julia":"firebrick","Sean":"royalblue"}) | |
fig_donut.update_layout(title="Alcohol Consumed as % of Body Weight", template="plotly_white") | |
st.plotly_chart(fig_donut, use_container_width=True) | |
st.write(""" | |
**Disclaimer:** | |
These BAC values are rough estimates. Actual impairment depends on many factors including metabolism, | |
recent food intake, and individual tolerance. This tool is for illustrative purposes only. | |
""") | |