phy_dig_twin / app.py
cryogenic22's picture
Update app.py
7efc75f verified
import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
import time
import random
# Import from other modules
from ui_components import (
set_custom_css,
metric_card,
create_sidebar,
header_section,
create_tabs
)
from data_generators import (
generate_physician_segments,
generate_prescription_data,
generate_key_drivers,
generate_regional_data,
generate_formulary_scenario_data,
generate_message_testing_data
)
from api_simulator import simulate_claude_api_call
# Main application
def main():
# Set page configuration
st.set_page_config(
page_title="PhysicianTwin™ - Digital Twin Simulator",
page_icon="🧬",
layout="wide",
initial_sidebar_state="expanded"
)
# Apply custom CSS
set_custom_css()
# Create sidebar and get parameters
sidebar_params = create_sidebar()
brand = sidebar_params["brand"]
therapeutic_area = sidebar_params["therapeutic_area"]
# Main content header
header_section(brand, therapeutic_area)
# Create tabs for different analyses
tabs = create_tabs()
# Generate simulated data
df_segments = generate_physician_segments()
df_prescriptions = generate_prescription_data()
df_drivers = generate_key_drivers()
df_regional = generate_regional_data()
df_formulary = generate_formulary_scenario_data()
df_messages = generate_message_testing_data()
# Tab 1: Market Overview
with tabs[0]:
display_market_overview(df_prescriptions, df_regional, df_drivers)
# Tab 2: Physician Segments
with tabs[1]:
display_physician_segments(df_segments)
# Tab 3: Message Testing
with tabs[2]:
display_message_testing(df_messages)
# Tab 4: Predictive Scenarios
with tabs[3]:
display_predictive_scenarios(df_formulary)
# Tab 5: Interactive Twin
with tabs[4]:
display_interactive_twin()
def display_market_overview(df_prescriptions, df_regional, df_drivers):
"""Display Market Overview tab content"""
st.markdown("## Market Performance Dashboard")
st.markdown("Analyze current market position and trends for XenoGlip in the diabetes market")
# Metrics row
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric(label="Market Share", value="11.8%")
with col2:
st.metric(label="YoY Growth", value="+3.2%")
with col3:
st.metric(label="Formulary Access", value="42.3%")
with col4:
st.metric(label="Physician Awareness", value="68.7%")
st.markdown("---")
# Prescription trends chart
st.markdown("### Prescription Trends")
# Chart for prescription trends
fig_rx = px.line(
df_prescriptions,
x='Date',
y='Prescriptions',
color='Product',
title='Weekly Prescription Volume',
color_discrete_sequence=px.colors.qualitative.Bold
)
fig_rx.update_layout(
xaxis_title="Date",
yaxis_title="Total Prescriptions",
legend_title="Product",
height=450
)
st.plotly_chart(fig_rx, use_container_width=True)
# Regional breakdown and key drivers in two columns
col1, col2 = st.columns(2)
with col1:
st.markdown("### Regional Performance")
fig_regional = px.bar(
df_regional,
x='Region',
y='Market Share',
color='Growth Rate',
color_continuous_scale='RdYlGn',
title='Market Share by Region',
text_auto='.1%'
)
fig_regional.update_layout(height=400)
st.plotly_chart(fig_regional, use_container_width=True)
with col2:
st.markdown("### Key Performance Drivers")
fig_drivers = px.bar(
df_drivers[df_drivers['Segment'] == 'PCP'].sort_values('Importance', ascending=False).head(6),
x='Importance',
y='Driver',
orientation='h',
title='Key Drivers for PCPs',
color='Importance',
color_continuous_scale='Blues'
)
fig_drivers.update_traces(texttemplate='%{x:.0%}', textposition='outside')
fig_drivers.update_layout(height=400)
st.plotly_chart(fig_drivers, use_container_width=True)
def display_physician_segments(df_segments):
"""Display Physician Segments tab content"""
st.markdown("## Physician Segment Analysis")
st.markdown("Analyze behaviors and preferences across physician microsegments")
# Segment selection
selected_segments = st.multiselect(
"Select segments to compare:",
df_segments['Segment'].tolist(),
default=df_segments['Segment'].tolist()[:3]
)
if not selected_segments:
st.warning("Please select at least one segment to display.")
else:
filtered_df = df_segments[df_segments['Segment'].isin(selected_segments)]
# Segment comparison chart
st.markdown("### Segment Comparison")
# Normalize prescribing volume to 0-1 range for radar chart
radar_df = filtered_df.copy()
max_rx = radar_df['Prescribing Volume'].max()
min_rx = radar_df['Prescribing Volume'].min()
radar_df['Normalized Prescribing'] = (radar_df['Prescribing Volume'] - min_rx) / (max_rx - min_rx) if max_rx > min_rx else 0.5
# Radar chart for segment comparison
categories = ['Normalized Prescribing', 'Digital Engagement', 'XenoGlip Affinity', 'Message Receptivity']
fig_radar = go.Figure()
for segment in selected_segments:
segment_data = radar_df[radar_df['Segment'] == segment]
values = segment_data[categories].values.flatten().tolist()
# Add the first value again to close the loop
values.append(values[0])
fig_radar.add_trace(go.Scatterpolar(
r=values,
theta=categories + [categories[0]],
fill='toself',
name=segment
))
fig_radar.update_layout(
polar=dict(
radialaxis=dict(
visible=True,
range=[0, 1]
)),
showlegend=True,
height=500
)
st.plotly_chart(fig_radar, use_container_width=True)
# Segment details table
st.markdown("### Segment Details")
# Format the dataframe for display
display_df = filtered_df.copy()
display_df['Digital Engagement'] = display_df['Digital Engagement'].map('{:.1%}'.format)
display_df['XenoGlip Affinity'] = display_df['XenoGlip Affinity'].map('{:.1%}'.format)
display_df['Message Receptivity'] = display_df['Message Receptivity'].map('{:.1%}'.format)
st.dataframe(display_df, use_container_width=True)
# Segment profiles
st.markdown("### Detailed Segment Profiles")
for segment in selected_segments:
with st.expander(f"Profile: {segment}"):
segment_data = df_segments[df_segments['Segment'] == segment].iloc[0]
st.markdown(f"**Size:** {segment_data['Size']:,} physicians")
st.markdown(f"**Prescribing Volume:** {segment_data['Prescribing Volume']} Rx/month (average)")
st.markdown(f"**XenoGlip Affinity:** {segment_data['XenoGlip Affinity']:.1%}")
st.markdown("#### Behavioral Characteristics")
if "High Volume" in segment:
st.markdown("- Treats large numbers of diabetes patients")
st.markdown("- Typically relies on established treatment algorithms")
st.markdown("- Highly sensitive to formulary positioning")
st.markdown("- Values patient affordability and adherence")
elif "Early Adopter" in segment:
st.markdown("- Quickly evaluates and adopts new treatments")
st.markdown("- Actively follows clinical trial data")
st.markdown("- Influenced by scientific evidence more than commercial messaging")
st.markdown("- Often participates in speaker bureaus and advisory boards")
else:
st.markdown("- Moderate adoption timeline for new treatments")
st.markdown("- Evidence-based decision making")
st.markdown("- Balance of clinical and practical considerations")
st.markdown("- Responsive to peer influence and educational programs")
def display_message_testing(df_messages):
"""Display Message Testing tab content"""
st.markdown("## Message Testing & Optimization")
st.markdown("Evaluate message effectiveness across different physician segments")
# Message testing chart
st.markdown("### Message Effectiveness by Segment")
messages_df = df_messages.pivot(index='Message', columns='Segment', values='Receptivity')
fig_heatmap = px.imshow(
messages_df,
text_auto='.0%',
aspect="auto",
color_continuous_scale='Blues',
title="Message Receptivity Heatmap"
)
fig_heatmap.update_layout(height=500)
st.plotly_chart(fig_heatmap, use_container_width=True)
# Message impact chart
st.markdown("### Message Impact Analysis")
# Calculate average receptivity across segments
message_impact = df_messages.groupby('Message').agg({
'Receptivity': 'mean',
'Impact Score': 'mean'
}).reset_index()
fig_impact = px.scatter(
message_impact,
x='Receptivity',
y='Impact Score',
text='Message',
size='Impact Score',
color='Receptivity',
color_continuous_scale='Viridis',
title="Message Impact vs. Receptivity"
)
fig_impact.update_traces(textposition='top center')
fig_impact.update_layout(height=500)
st.plotly_chart(fig_impact, use_container_width=True)
# Message optimization tool
st.markdown("### Message Optimization Tool")
st.markdown("Generate optimized messaging for specific physician segments")
col1, col2 = st.columns(2)
with col1:
target_segment = st.selectbox(
"Target Physician Segment",
["Primary Care Physicians", "Endocrinologists", "Cardiologists"]
)
message_focus = st.multiselect(
"Message Focus Areas",
["Efficacy", "Safety", "Convenience", "Cost", "Guidelines", "Mechanism of Action"],
default=["Efficacy", "Safety"]
)
with col2:
clinical_context = st.selectbox(
"Clinical Context",
["Second-line after metformin", "Add-on to current therapy", "Special populations", "Switch from competitor"]
)
message_tone = st.select_slider(
"Message Tone",
options=["Very Clinical", "Balanced", "Practice-Oriented"],
value="Balanced"
)
if st.button("Generate Optimized Messages"):
with st.spinner("Analyzing digital twin data and generating messages..."):
time.sleep(2) # Simulate processing time
st.markdown("### Optimized Message Recommendations")
if target_segment == "Primary Care Physicians":
st.markdown("#### Primary Message (92% predicted effectiveness)")
st.markdown("**XenoGlip delivers reliable A1C reduction with once-daily dosing and a proven safety profile, making it an ideal choice for your patients after metformin.**")
st.markdown("#### Supporting Points:")
st.markdown("• A1C reductions of 0.7-0.9% in clinical trials when added to metformin")
st.markdown("• Established cardiovascular safety profile with no increased risk of major adverse cardiac events")
st.markdown("• Simple once-daily dosing that doesn't require titration")
elif target_segment == "Endocrinologists":
st.markdown("#### Primary Message (87% predicted effectiveness)")
st.markdown("**XenoGlip provides a flexible treatment option with established efficacy and minimal drug interactions, complementing your individualized approach to glycemic control.**")
st.markdown("#### Supporting Points:")
st.markdown("• Demonstrated efficacy across diverse patient populations in 25+ clinical trials")
st.markdown("• Compatible with multiple antihyperglycemic agents for tailored therapy")
st.markdown("• Minimal risk of drug-drug interactions with commonly prescribed medications")
def display_predictive_scenarios(df_formulary):
"""Display Predictive Scenarios tab content"""
st.markdown("## Predictive Scenario Simulation")
st.markdown("Explore the impact of different market events and strategic decisions")
# Scenario selection
scenario_type = st.radio(
"Select Scenario Type:",
["Formulary Change Impact", "Competitive Launch", "Clinical Data Update", "Campaign Optimization"],
horizontal=True
)
# Formulary Change Scenario
if scenario_type == "Formulary Change Impact":
# Create a grouped bar chart for formulary scenarios
formulary_melted = df_formulary.copy()
fig_formulary = px.bar(
formulary_melted,
x='Scenario',
y='Value',
color='Metric',
barmode='group',
title='Projected Impact of Formulary Changes',
color_discrete_sequence=px.colors.qualitative.Safe
)
fig_formulary.update_layout(
xaxis_title="Formulary Scenario",
yaxis_title="Impact Value",
yaxis_tickformat='.0%',
height=500
)
st.plotly_chart(fig_formulary, use_container_width=True)
# Other scenario types would be implemented similarly
else:
st.info(f"Selected scenario: {scenario_type}. Configure settings and run the simulation to see results.")
def display_interactive_twin():
"""Display Interactive Twin tab content"""
st.markdown("## Interactive Physician Digital Twin")
st.markdown("Ask questions to the digital twin model and receive AI-powered insights about physician behavior and preferences")
# Create a text input for questions
query = st.text_area(
"Ask a question about physician behavior and prescribing patterns:",
placeholder="Example: What factors drive primary care physicians to prescribe XenoGlip for diabetes?",
height=100
)
# Add some example questions
with st.expander("Example questions to ask the Digital Twin"):
st.markdown("""
- What messaging resonates best with endocrinologists for XenoGlip?
- How would a formulary change from Tier 3 to Tier 2 impact prescribing?
- What patient profile is most likely to receive XenoGlip from PCPs?
- How do cardiologists view XenoGlip compared to other diabetes medications?
""")
# Additional filters for the query
col1, col2 = st.columns(2)
with col1:
specialty_filter = st.multiselect(
"Filter by specialty (optional):",
["Primary Care", "Endocrinology", "Cardiology", "NP/PA"],
default=[]
)
with col2:
region_filter = st.multiselect(
"Filter by region (optional):",
["Northeast", "Southeast", "Midwest", "Southwest", "West"],
default=[]
)
# Submit button for the query
if st.button("Ask Digital Twin") and query:
with st.spinner("Digital twin is analyzing physician data..."):
# In a real app, this would make an API call to Claude
# Here we'll simulate it with our function
time.sleep(2)
# Prepare the prompt with filters if applicable
prompt = query
if specialty_filter:
prompt += f" Focus on {', '.join(specialty_filter)}."
if region_filter:
prompt += f" Consider regional factors in {', '.join(region_filter)}."
# Get simulated response from Claude
response = simulate_claude_api_call(prompt)
# Display response in a nice format
st.markdown("### Digital Twin Response")
# Use a card-like container for the response
with st.container():
st.markdown(f'<div class="highlight">{response}</div>', unsafe_allow_html=True)
# Display confidence metrics
st.markdown("### Analysis Confidence")
col1, col2, col3 = st.columns(3)
with col1:
# Simulate confidence scores
data_confidence = random.uniform(0.7, 0.95)
st.metric(
label="Data Confidence",
value=f"{data_confidence:.0%}"
)
with col2:
response_consistency = random.uniform(0.75, 0.98)
st.metric(
label="Response Consistency",
value=f"{response_consistency:.0%}"
)
with col3:
physician_sample = random.randint(1500, 8000)
st.metric(
label="Physician Sample Size",
value=f"{physician_sample:,}"
)
if __name__ == "__main__":
main()