Dashboard Features: - 8 navigation sections: Overview, Outcomes, Poor CX, FCR, Churn, Agent, Call Explorer, Export - Beyond Brand Identity styling (colors #6D84E3, Outfit font) - RCA Sankey diagram (Driver → Outcome → Churn Risk flow) - Correlation heatmaps (driver co-occurrence, driver-outcome) - Outcome Deep Dive (root causes, correlation, duration analysis) - Export functionality (Excel, HTML, JSON) Blueprint Compliance: - FCR: 4 categories (Primera Llamada/Rellamada × Sin/Con Riesgo de Fuga) - Churn: Binary view (Sin Riesgo de Fuga / En Riesgo de Fuga) - Agent: Talento Para Replicar / Oportunidades de Mejora - Fixed FCR rate calculation (only FIRST_CALL counts as success) Technical: - Streamlit + Plotly for interactive visualizations - Light theme configuration (.streamlit/config.toml) - Fixed Plotly colorbar titlefont deprecation Documentation: - Updated PROJECT_CONTEXT.md, TODO.md, CHANGELOG.md - Added 4 new technical decisions (TD-014 to TD-017) - Created TROUBLESHOOTING.md with 10 common issues Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
412 lines
11 KiB
Python
412 lines
11 KiB
Python
"""
|
|
CXInsights Dashboard - Configuration & Branding
|
|
Based on Beyond Brand Identity Guidelines v1.0
|
|
"""
|
|
|
|
import streamlit as st
|
|
|
|
# =============================================================================
|
|
# BEYOND BRAND COLORS
|
|
# =============================================================================
|
|
|
|
COLORS = {
|
|
# Primary colors
|
|
"black": "#000000", # Beyond Black - Primary
|
|
"blue": "#6D84E3", # Beyond Blue - Accent (ONLY accent color)
|
|
"grey": "#B1B1B0", # Beyond Grey - Secondary
|
|
"light_grey": "#E4E4E4", # Beyond Light Grey - Backgrounds
|
|
"white": "#FFFFFF",
|
|
|
|
# Derived colors for UI states
|
|
"blue_hover": "#5A6FD1", # Blue darkened 10%
|
|
"blue_light": "#DBE2FC", # Light blue for subtle backgrounds
|
|
|
|
# Chart colors (ordered by importance) - light theme
|
|
"chart_primary": "#6D84E3", # Blue - main data
|
|
"chart_secondary": "#B1B1B0", # Grey - comparison/benchmark
|
|
"chart_tertiary": "#7A7A7A", # Dark grey - third series
|
|
"chart_quaternary": "#E4E4E4", # Light grey - fourth series
|
|
|
|
# Gradients for charts - light theme
|
|
"gradient_blue": ["#E4E4E4", "#B1B1B0", "#6D84E3"],
|
|
"gradient_grey": ["#FFFFFF", "#E4E4E4", "#B1B1B0", "#7A7A7A"],
|
|
"gradient_red": ["#E4E4E4", "#B1B1B0", "#6D84E3", "#5A6FD1"], # For severity
|
|
}
|
|
|
|
# Chart color sequence (for Plotly) - light theme
|
|
CHART_COLORS = [
|
|
COLORS["blue"], # Primary
|
|
COLORS["grey"], # Secondary
|
|
COLORS["chart_tertiary"], # Dark grey - Tertiary
|
|
COLORS["light_grey"], # Quaternary
|
|
]
|
|
|
|
# =============================================================================
|
|
# TYPOGRAPHY (Outfit font via Google Fonts)
|
|
# =============================================================================
|
|
|
|
FONTS = {
|
|
"family": "'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
"sizes": {
|
|
"h1": "40px",
|
|
"h2": "35px",
|
|
"h3": "21px",
|
|
"body": "17px",
|
|
"small": "12px",
|
|
"caption": "10px",
|
|
},
|
|
"weights": {
|
|
"black": 900,
|
|
"bold": 700,
|
|
"medium": 500,
|
|
"regular": 400,
|
|
"light": 300,
|
|
"thin": 100,
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# THEME CONFIG FOR PLOTLY CHARTS
|
|
# =============================================================================
|
|
|
|
THEME_CONFIG = {
|
|
"layout": {
|
|
"font": {
|
|
"family": FONTS["family"],
|
|
"color": COLORS["black"],
|
|
},
|
|
"paper_bgcolor": COLORS["white"],
|
|
"plot_bgcolor": COLORS["white"],
|
|
"title": {
|
|
"font": {
|
|
"size": 18,
|
|
"family": FONTS["family"],
|
|
"color": COLORS["black"],
|
|
},
|
|
"x": 0,
|
|
"xanchor": "left",
|
|
},
|
|
"legend": {
|
|
"font": {"size": 14},
|
|
"bgcolor": "rgba(255,255,255,0)",
|
|
},
|
|
"xaxis": {
|
|
"gridcolor": COLORS["light_grey"],
|
|
"linecolor": COLORS["grey"],
|
|
"tickfont": {"size": 12, "color": COLORS["grey"]},
|
|
"title_font": {"size": 14, "color": COLORS["grey"]},
|
|
},
|
|
"yaxis": {
|
|
"gridcolor": COLORS["light_grey"],
|
|
"linecolor": COLORS["grey"],
|
|
"tickfont": {"size": 12, "color": COLORS["grey"]},
|
|
"title_font": {"size": 14, "color": COLORS["grey"]},
|
|
"rangemode": "tozero", # Always start at 0 (McKinsey standard)
|
|
},
|
|
"margin": {"l": 60, "r": 40, "t": 60, "b": 60},
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# STREAMLIT CUSTOM CSS
|
|
# =============================================================================
|
|
|
|
def apply_custom_css():
|
|
"""Apply Beyond brand CSS to Streamlit app."""
|
|
|
|
st.markdown("""
|
|
<style>
|
|
/* Import Outfit font from Google Fonts */
|
|
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@100;300;400;500;700;900&display=swap');
|
|
|
|
/* Global font */
|
|
html, body, [class*="css"] {
|
|
font-family: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
}
|
|
|
|
/* Headers */
|
|
h1, h2, h3, h4, h5, h6 {
|
|
font-family: 'Outfit', sans-serif !important;
|
|
font-weight: 700 !important;
|
|
color: #000000 !important;
|
|
}
|
|
|
|
h1 { font-size: 40px !important; }
|
|
h2 { font-size: 35px !important; }
|
|
h3 { font-size: 21px !important; }
|
|
|
|
/* Body text */
|
|
p, li, span, div {
|
|
font-family: 'Outfit', sans-serif;
|
|
font-weight: 400;
|
|
color: #000000;
|
|
}
|
|
|
|
/* Sidebar styling */
|
|
[data-testid="stSidebar"] {
|
|
background-color: #FFFFFF;
|
|
border-right: 1px solid #E4E4E4;
|
|
}
|
|
|
|
[data-testid="stSidebar"] h1,
|
|
[data-testid="stSidebar"] h2,
|
|
[data-testid="stSidebar"] h3 {
|
|
color: #000000 !important;
|
|
}
|
|
|
|
/* Main content area */
|
|
.main .block-container {
|
|
padding-top: 2rem;
|
|
max-width: 1200px;
|
|
}
|
|
|
|
/* Metric cards - Beyond style */
|
|
[data-testid="stMetric"] {
|
|
background-color: #FFFFFF;
|
|
border: 1px solid #E4E4E4;
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
}
|
|
|
|
[data-testid="stMetric"] label {
|
|
font-family: 'Outfit', sans-serif !important;
|
|
font-weight: 300 !important;
|
|
font-size: 14px !important;
|
|
color: #B1B1B0 !important;
|
|
}
|
|
|
|
[data-testid="stMetric"] [data-testid="stMetricValue"] {
|
|
font-family: 'Outfit', sans-serif !important;
|
|
font-weight: 700 !important;
|
|
font-size: 32px !important;
|
|
color: #000000 !important;
|
|
}
|
|
|
|
[data-testid="stMetric"] [data-testid="stMetricDelta"] {
|
|
font-family: 'Outfit', sans-serif !important;
|
|
font-weight: 400 !important;
|
|
}
|
|
|
|
/* Buttons - Beyond style (light theme) */
|
|
.stButton > button {
|
|
font-family: 'Outfit', sans-serif !important;
|
|
font-weight: 700 !important;
|
|
background-color: #6D84E3 !important;
|
|
color: #FFFFFF !important;
|
|
border: none !important;
|
|
border-radius: 4px !important;
|
|
padding: 0.5rem 1.5rem !important;
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
|
|
.stButton > button:hover {
|
|
background-color: #5A6FD1 !important;
|
|
color: #FFFFFF !important;
|
|
}
|
|
|
|
/* Secondary buttons */
|
|
.stButton > button[kind="secondary"] {
|
|
background-color: #FFFFFF !important;
|
|
color: #6D84E3 !important;
|
|
border: 2px solid #6D84E3 !important;
|
|
}
|
|
|
|
.stButton > button[kind="secondary"]:hover {
|
|
background-color: #6D84E3 !important;
|
|
color: #FFFFFF !important;
|
|
}
|
|
|
|
/* Selectbox styling */
|
|
[data-testid="stSelectbox"] label {
|
|
font-family: 'Outfit', sans-serif !important;
|
|
font-weight: 700 !important;
|
|
color: #000000 !important;
|
|
}
|
|
|
|
/* Radio buttons */
|
|
[data-testid="stRadio"] label {
|
|
font-family: 'Outfit', sans-serif !important;
|
|
}
|
|
|
|
/* Expander headers */
|
|
.streamlit-expanderHeader {
|
|
font-family: 'Outfit', sans-serif !important;
|
|
font-weight: 700 !important;
|
|
color: #000000 !important;
|
|
background-color: #F8F8F8 !important;
|
|
}
|
|
|
|
/* Tables - Light theme */
|
|
[data-testid="stTable"] th {
|
|
background-color: #F8F8F8 !important;
|
|
color: #000000 !important;
|
|
font-family: 'Outfit', sans-serif !important;
|
|
font-weight: 700 !important;
|
|
border-bottom: 2px solid #6D84E3 !important;
|
|
}
|
|
|
|
[data-testid="stTable"] tr:nth-child(even) {
|
|
background-color: #FAFAFA;
|
|
}
|
|
|
|
/* Dataframe styling - Light theme */
|
|
.dataframe th {
|
|
background-color: #F8F8F8 !important;
|
|
color: #000000 !important;
|
|
font-family: 'Outfit', sans-serif !important;
|
|
font-weight: 700 !important;
|
|
text-align: left !important;
|
|
border-bottom: 2px solid #6D84E3 !important;
|
|
}
|
|
|
|
.dataframe td {
|
|
font-family: 'Outfit', sans-serif !important;
|
|
text-align: left !important;
|
|
color: #000000 !important;
|
|
}
|
|
|
|
.dataframe tr:nth-child(even) {
|
|
background-color: #FAFAFA;
|
|
}
|
|
|
|
/* Info/Warning/Error boxes */
|
|
.stAlert {
|
|
font-family: 'Outfit', sans-serif !important;
|
|
border-radius: 4px !important;
|
|
}
|
|
|
|
/* Links - Beyond Blue */
|
|
a {
|
|
color: #6D84E3 !important;
|
|
text-decoration: none !important;
|
|
}
|
|
|
|
a:hover {
|
|
color: #5A6FD1 !important;
|
|
text-decoration: underline !important;
|
|
}
|
|
|
|
/* Caption/small text */
|
|
.caption, small, .stCaption {
|
|
font-family: 'Outfit', sans-serif !important;
|
|
font-weight: 300 !important;
|
|
color: #B1B1B0 !important;
|
|
font-size: 12px !important;
|
|
}
|
|
|
|
/* Divider line */
|
|
hr {
|
|
border: none;
|
|
border-top: 1px solid #E4E4E4;
|
|
margin: 1.5rem 0;
|
|
}
|
|
|
|
/* Custom KPI card class */
|
|
.kpi-card {
|
|
background: #FFFFFF;
|
|
border: 1px solid #E4E4E4;
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.kpi-card .kpi-value {
|
|
font-size: 48px;
|
|
font-weight: 700;
|
|
color: #000000;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.kpi-card .kpi-label {
|
|
font-size: 14px;
|
|
font-weight: 300;
|
|
color: #B1B1B0;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.kpi-card .kpi-delta {
|
|
font-size: 14px;
|
|
font-weight: 400;
|
|
color: #6D84E3;
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
/* Highlight card (with blue accent) */
|
|
.highlight-card {
|
|
background: #FFFFFF;
|
|
border-left: 4px solid #6D84E3;
|
|
border-radius: 4px;
|
|
padding: 1rem 1.5rem;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
/* Evidence quote styling */
|
|
.evidence-quote {
|
|
background: #F8F8F8;
|
|
border-left: 3px solid #6D84E3;
|
|
padding: 1rem;
|
|
margin: 0.5rem 0;
|
|
font-style: italic;
|
|
color: #000000;
|
|
}
|
|
|
|
.evidence-speaker {
|
|
font-weight: 700;
|
|
color: #B1B1B0;
|
|
font-size: 12px;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
/* Footer styling */
|
|
.footer {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background: #FFFFFF;
|
|
border-top: 1px solid #E4E4E4;
|
|
padding: 0.5rem 2rem;
|
|
font-size: 12px;
|
|
color: #B1B1B0;
|
|
z-index: 1000;
|
|
}
|
|
|
|
/* Hide Streamlit branding */
|
|
#MainMenu {visibility: hidden;}
|
|
footer {visibility: hidden;}
|
|
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
def get_plotly_layout(title: str = "", height: int = 400) -> dict:
|
|
"""Get standard Plotly layout with Beyond branding."""
|
|
layout = THEME_CONFIG["layout"].copy()
|
|
layout["height"] = height
|
|
if title:
|
|
layout["title"]["text"] = title
|
|
return layout
|
|
|
|
|
|
def format_metric_card(value: str, label: str, delta: str = None) -> str:
|
|
"""Generate HTML for a branded KPI card."""
|
|
delta_html = f'<div class="kpi-delta">{delta}</div>' if delta else ""
|
|
return f"""
|
|
<div class="kpi-card">
|
|
<div class="kpi-value">{value}</div>
|
|
<div class="kpi-label">{label}</div>
|
|
{delta_html}
|
|
</div>
|
|
"""
|
|
|
|
|
|
def format_evidence_quote(text: str, speaker: str = None) -> str:
|
|
"""Format evidence text with Beyond styling."""
|
|
speaker_html = f'<div class="evidence-speaker">— {speaker}</div>' if speaker else ""
|
|
return f"""
|
|
<div class="evidence-quote">
|
|
"{text}"
|
|
{speaker_html}
|
|
</div>
|
|
"""
|