with chart_col:
        # Grouped bar: old vs new cost per month
        df_chart = df_roi[["month", "old_cost", "new_cost"]].copy()
        df_chart_long = df_chart.melt(
            id_vars="month",
            value_vars=["old_cost", "new_cost"],
            var_name="Type", value_name="AED",
        )
        df_chart_long["Type"] = df_chart_long["Type"].map(
            {"old_cost": "Traditional (AED 20k/podcast)", "new_cost": "NotebookLM ($20/month)"}
        )
        fig_bar = px.bar(
            df_chart_long, x="month", y="AED", color="Type", barmode="group",
            color_discrete_map={
                "Traditional (AED 20k/podcast)": "#ff6b6b",
                "NotebookLM ($20/month)":         "#51cf66",
            },
            title="Monthly Cost: Traditional Podcast Production vs NotebookLM",
        )
        fig_bar.update_layout(
            paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
            font_color="white", legend_title_text="",
            xaxis=dict(showgrid=False, title=""),
            yaxis=dict(gridcolor="#1e293b", title="AED"),
            margin=dict(t=40, b=10), height=320,
        )
        st.plotly_chart(fig_bar, use_container_width=True)

        # PRs with podcasts per month
        fig_count = px.bar(
            df_roi, x="month", y="pr_count",
            color_discrete_sequence=["#f59e0b"],
            title="Press Releases with Podcasts per Month (since NotebookLM)",
        )
        fig_count.update_layout(
            paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
            font_color="white",
            xaxis=dict(showgrid=False, title=""),
            yaxis=dict(gridcolor="#1e293b", title="Press Releases"),
            margin=dict(t=40, b=10), height=280,
        )
        st.plotly_chart(fig_count, use_container_width=True)

    with table_col:
        st.markdown(
            '
'
            'Cost basis: AED 20,000 per PR podcast (EN+AR counted as one production).
'
            'NotebookLM: flat $20/month regardless of volume.
',
            unsafe_allow_html=True,
        )
        rows_html = ""
        for _, r in df_roi.iterrows():
            rows_html += (
                f'
'
                f'{r["month"]}	'
                f'{int(r["pr_count"])}	'
                f'{int(r["en_episodes"])}	'
                f'{int(r["ar_episodes"])}	'
                f'AED {r["old_cost"]:,.0f}	'
                f'AED {r["new_cost"]:,.0f}	'
                f'AED {r["savings"]:,.0f}	'
                f'
'
            )
        rows_html += (
            f'
'
            f'TOTAL	'
            f'{total_prs_nlm}	'
            f'{int(df_roi["en_episodes"].sum())}	'
            f'{int(df_roi["ar_episodes"].sum())}	'
            f'AED {total_old_cost:,.0f}	'
            f'AED {total_new_cost:,.0f}	'
            f'AED {total_savings:,.0f}	'
            f'
'
        )
        st.markdown(
            f'
'
            f'

'
            f'Month	PRs	EN eps	AR eps	'
            f'Old Cost	New Cost	Savings	'
            f'

'
            f'
{rows_html}
'
            f'
',
            unsafe_allow_html=True,
        )

"""
DEWA PR Tracker — Streamlit Dashboard
Run:  streamlit run dashboard.py
"""
import math
import re as _re
from datetime import datetime, timedelta

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import streamlit as st

import database as db
from config import (
    COST_PER_PODCAST_OLD_AED,
    NOTEBOOKLM_MONTHLY_USD,
    NOTEBOOKLM_START_DATE,
    USD_TO_AED,
    DASHBOARD_TITLE,
    PAGE_SIZE,
)

# ── Page setup ────────────────────────────────────────────────────────────────
st.set_page_config(
    page_title=DASHBOARD_TITLE,
    page_icon="📰",
    layout="wide",
    initial_sidebar_state="expanded",
)

# ── SVG helpers ───────────────────────────────────────────────────────────────
def _svg(paths, size=20, color="#00b4d8"):
    return (
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{size}" height="{size}" '
        f'viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" '
        f'stroke-linecap="round" stroke-linejoin="round" '
        f'style="vertical-align:middle;margin-right:8px">{paths}</svg>'
    )

ICO_NEWS    = _svg('<path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 0-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"/><path d="M18 14h-8"/><path d="M15 18h-5"/><path d="M10 6h8v4h-8V6Z"/>')
ICO_MONEY   = _svg('<line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>', color="#51cf66")
ICO_CHART   = _svg('<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>', color="#4cc9f0")
ICO_LIST    = _svg('<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>')
ICO_MIC     = _svg('<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/>', color="#f59e0b")
ICO_MIC_SM  = _svg('<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/>', size=15, color="#f59e0b")
ICO_CLOCK   = _svg('<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>', color="#94a3b8")
ICO_LOG     = _svg('<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>', color="#94a3b8")
ICO_LINK    = _svg('<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>', size=14, color="#00b4d8")

def section_title(icon, text):
    st.markdown(
        f'
{icon}{text}
',
        unsafe_allow_html=True,
    )

# ── CSS — hide Streamlit chrome (Deploy button, footer, menu) ─────────────────
st.markdown("""

""", unsafe_allow_html=True)

# ── Data loading (cached 5 min) ───────────────────────────────────────────────
@st.cache_data(ttl=300)
def load_data():
    db.init_db()
    prs      = [dict(r) for r in db.get_all_press_releases()]
    pods     = [dict(r) for r in db.get_all_podcasts()]
    roi_rows = [dict(r) for r in db.get_roi_monthly_since(NOTEBOOKLM_START_DATE)]
    stats    = db.get_stats()
    log      = [dict(r) for r in db.get_scrape_log()]
    return prs, pods, roi_rows, stats, log

prs, pods, roi_rows, stats, scrape_log = load_data()
df_pr  = pd.DataFrame(prs)
df_pod = pd.DataFrame(pods)

if not df_pod.empty and "title" in df_pod.columns:
    df_pod["language"] = df_pod["title"].apply(
        lambda t: "ar" if _re.search(r"[\u0600-\u06FF]", str(t)) else "en"
    )

if not df_pr.empty:
    df_pr["date"]  = pd.to_datetime(df_pr["date"], errors="coerce")
    df_pr["week"]  = df_pr["date"].dt.to_period("W").astype(str)
    df_pr["month"] = df_pr["date"].dt.to_period("M").astype(str)

if not df_pod.empty:
    df_pod["date"] = pd.to_datetime(df_pod["date"], errors="coerce")

# ── ROI pre-calculations ──────────────────────────────────────────────────────
NOTEBOOKLM_AED   = NOTEBOOKLM_MONTHLY_USD * USD_TO_AED   # ~73.4 AED/month
NLM_START        = pd.Timestamp(NOTEBOOKLM_START_DATE)

df_roi = pd.DataFrame(roi_rows) if roi_rows else pd.DataFrame(
    columns=["month", "total", "en_count", "ar_count"]
)

if not df_roi.empty:
    # Cost basis: 1 podcast per press release @ AED 20,000
    # (PR cost = 0 AED; only the podcast production was charged)
    df_roi["old_cost"] = df_roi["pr_count"] * COST_PER_PODCAST_OLD_AED
    df_roi["new_cost"] = NOTEBOOKLM_AED   # flat monthly subscription
    df_roi["savings"]  = df_roi["old_cost"] - df_roi["new_cost"]

    total_prs_nlm   = int(df_roi["pr_count"].sum())
    total_eps_nlm   = int(df_roi["total_episodes"].sum())
    total_old_cost  = float(df_roi["old_cost"].sum())
    total_new_cost  = float(df_roi["new_cost"].sum())
    total_savings   = float(df_roi["savings"].sum())
    roi_pct         = (total_savings / total_old_cost * 100) if total_old_cost > 0 else 0
else:
    total_prs_nlm = total_eps_nlm = total_old_cost = total_new_cost = total_savings = roi_pct = 0

# ── Sidebar ───────────────────────────────────────────────────────────────────
with st.sidebar:
    # DEWA logo — styled text (image blocked externally)
    st.markdown(
        '
DEWA
'
        '
Dubai Electricity & Water Authority
',
        unsafe_allow_html=True,
    )
    st.markdown("---")
    st.markdown("**Filters**")

    if not df_pr.empty and df_pr["date"].notna().any():
        min_date = df_pr["date"].min().date()
        max_date = df_pr["date"].max().date()
    else:
        min_date = datetime(2019, 1, 1).date()
        max_date = datetime.today().date()

    date_from, date_to = st.date_input(
        "Date range",
        value=(NLM_START.date(), max_date),
        min_value=min_date,
        max_value=max_date,
    )

    categories = ["All"]
    if not df_pr.empty and "category" in df_pr.columns:
        cats = df_pr["category"].dropna().unique().tolist()
        categories += sorted(cats)
    selected_cat = st.selectbox("Category", categories)

    has_podcast_filter = st.checkbox("Show only PRs with podcast", value=False)

    st.divider()
    if st.button("Refresh data", use_container_width=True):
        st.cache_data.clear()
        st.rerun()

    if st.button("Run scraper now", use_container_width=True):
        with st.spinner("Scraping DEWA website…"):
            try:
                from scraper import run_scrape
                prs_n, pods_n = run_scrape()
                st.success(f"Done — {prs_n} PRs, {pods_n} podcasts saved.")
                st.cache_data.clear()
                st.rerun()
            except Exception as e:
                st.error(f"Scraper error: {e}")

    st.divider()
    st.caption(f"Last scraped: {stats.get('last_scraped', 'Never') or 'Never'}")

# ── Apply PR filters ──────────────────────────────────────────────────────────
filtered = df_pr.copy()
if not filtered.empty:
    if filtered["date"].notna().any():
        filtered = filtered[
            (filtered["date"].dt.date >= date_from) &
            (filtered["date"].dt.date <= date_to)
        ]
    if selected_cat != "All":
        filtered = filtered[filtered["category"] == selected_cat]
    if has_podcast_filter:
        filtered = filtered[filtered["podcast_count"] > 0]

# ═══════════════════════════════════════════════════════════════════════════════
# HEADER
# ═══════════════════════════════════════════════════════════════════════════════
st.markdown(
    f'
'
    f'{_svg("""<path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 0-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"/><path d="M18 14h-8"/><path d="M15 18h-5"/><path d="M10 6h8v4h-8V6Z"/>""", size=34, color="#00b4d8")}'
    f'{DASHBOARD_TITLE}
',
    unsafe_allow_html=True,
)
st.markdown(
    f'
'
    f'Tracking all DEWA press releases and NotebookLM podcast ROI since {NOTEBOOKLM_START_DATE} — '
    f'{datetime.today().strftime("%d %B %Y")}
',
    unsafe_allow_html=True,
)
st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 1 — Key Metrics
# ═══════════════════════════════════════════════════════════════════════════════
today       = datetime.today().date()
week_start  = today - timedelta(days=today.weekday())
month_start = today.replace(day=1)

if not filtered.empty and filtered["date"].notna().any():
    prs_today = len(filtered[filtered["date"].dt.date == today])
    prs_week  = len(filtered[filtered["date"].dt.date >= week_start])
    prs_month = len(filtered[filtered["date"].dt.date >= month_start])
else:
    prs_today = prs_week = prs_month = 0

c1, c2, c3, c4, c5 = st.columns(5)
with c1:
    st.metric("Total Press Releases", f"{len(filtered):,}")
with c2:
    st.metric("Podcasts (since May 2025)", f"{total_prs_nlm:,}")
with c3:
    st.metric("Published Today", prs_today)
with c4:
    st.metric("This Week", prs_week)
with c5:
    st.metric("This Month", prs_month)

st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 2 — NotebookLM ROI  (auto-calculated from May 23, 2025)
# ═══════════════════════════════════════════════════════════════════════════════
section_title(ICO_MONEY, "NotebookLM ROI — Since 23 May 2025")

# Summary cards
sc1, sc2, sc3, sc4 = st.columns(4)
with sc1:
    st.markdown(
        f'
'
        f'
Old Cost (Traditional)
'
        f'
AED {total_old_cost:,.0f}
'
        f'
{total_prs_nlm} PRs × AED 20,000/podcast
'
        f'
',
        unsafe_allow_html=True,
    )
with sc2:
    months_active = len(df_roi)
    st.markdown(
        f'
'
        f'
New Cost (NotebookLM)
'
        f'
AED {total_new_cost:,.0f}
'
        f'
{months_active} months × AED {NOTEBOOKLM_AED:.0f}/mo (${NOTEBOOKLM_MONTHLY_USD})
'
        f'
',
        unsafe_allow_html=True,
    )
with sc3:
    st.markdown(
        f'
'
        f'
Total Savings
'
        f'
AED {total_savings:,.0f}
'
        f'
Since {NOTEBOOKLM_START_DATE}
'
        f'
',
        unsafe_allow_html=True,
    )
with sc4:
    st.markdown(
        f'
'
        f'
Return on Investment
'
        f'
{roi_pct:.1f}%
'
        f'
Savings ÷ Old Cost
'
        f'
',
        unsafe_allow_html=True,
    )

st.markdown("
", unsafe_allow_html=True)

# ── Monthly detail ────────────────────────────────────────────────────────────
if not df_roi.empty:
    chart_col, table_col = st.columns([3, 2])

    with chart_col:
        # Grouped bar: old vs new cost per month
        df_chart = df_roi[["month", "old_cost", "new_cost"]].copy()
        df_chart_long = df_chart.melt(
            id_vars="month",
            value_vars=["old_cost", "new_cost"],
            var_name="Type",
            value_name="AED",
        )
        df_chart_long["Type"] = df_chart_long["Type"].map(
            {"old_cost": "Old Cost (Traditional)", "new_cost": "New Cost (NotebookLM)"}
        )
        fig_bar = px.bar(
            df_chart_long, x="month", y="AED", color="Type", barmode="group",
            color_discrete_map={
                "Old Cost (Traditional)": "#ff6b6b",
                "New Cost (NotebookLM)":  "#51cf66",
            },
            title="Monthly Cost Comparison: Traditional vs NotebookLM",
        )
        fig_bar.update_layout(
            paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
            font_color="white", legend_title_text="",
            xaxis=dict(showgrid=False, title=""),
            yaxis=dict(gridcolor="#1e293b", title="AED"),
            margin=dict(t=40, b=10), height=320,
        )
        st.plotly_chart(fig_bar, use_container_width=True)

        # Podcast count per month
        fig_count = px.bar(
            df_roi, x="month", y="total",
            color_discrete_sequence=["#f59e0b"],
            title="Podcasts Produced per Month (since NotebookLM)",
        )
        fig_count.update_layout(
            paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
            font_color="white",
            xaxis=dict(showgrid=False, title=""),
            yaxis=dict(gridcolor="#1e293b", title="Podcasts"),
            margin=dict(t=40, b=10), height=280,
        )
        st.plotly_chart(fig_count, use_container_width=True)

    with table_col:
        st.markdown(
            '
'
            'Monthly breakdown — each row = 1 calendar month
',
            unsafe_allow_html=True,
        )
        rows_html = ""
        for _, r in df_roi.iterrows():
            rows_html += (
                f'
'
                f'{r["month"]}	'
                f'{int(r["total"])}	'
                f'{int(r["en_count"])}	'
                f'{int(r["ar_count"])}	'
                f'AED {r["old_cost"]:,.0f}	'
                f'AED {r["new_cost"]:,.0f}	'
                f'AED {r["savings"]:,.0f}	'
                f'
'
            )
        # Total row
        rows_html += (
            f'
'
            f'TOTAL	'
            f'{total_pods_nlm}	'
            f'{int(df_roi["en_count"].sum())}	'
            f'{int(df_roi["ar_count"].sum())}	'
            f'AED {total_old_cost:,.0f}	'
            f'AED {total_new_cost:,.0f}	'
            f'AED {total_savings:,.0f}	'
            f'
'
        )
        st.markdown(
            f'
'
            f'

'
            f'Month	Podcasts	EN	AR	'
            f'Old Cost	New Cost	Savings	'
            f'

'
            f'
{rows_html}
'
            f'
',
            unsafe_allow_html=True,
        )

st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 3 — Publication Trends
# ═══════════════════════════════════════════════════════════════════════════════
section_title(ICO_CHART, "Publication Trends")
tab_daily, tab_weekly, tab_monthly = st.tabs(["Daily", "Weekly", "Monthly"])

def make_bar(df_g, x, y, title, color="#00b4d8"):
    fig = px.bar(df_g, x=x, y=y, title=title, color_discrete_sequence=[color])
    fig.update_layout(
        paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
        font_color="white",
        xaxis=dict(showgrid=False, title=""),
        yaxis=dict(gridcolor="#1e293b"),
        margin=dict(t=40, b=10),
    )
    return fig

with tab_daily:
    if not filtered.empty and filtered["date"].notna().any():
        daily = filtered.groupby(filtered["date"].dt.date).size().reset_index(name="count")
        daily.columns = ["date", "count"]
        st.plotly_chart(make_bar(daily, "date", "count", "Press Releases per Day"), use_container_width=True)
    else:
        st.info("No data yet. Run the scraper first.")

with tab_weekly:
    if not filtered.empty and "week" in filtered.columns and filtered["date"].notna().any():
        weekly = filtered.groupby("week").size().reset_index(name="count")
        st.plotly_chart(make_bar(weekly, "week", "count", "Press Releases per Week", "#4cc9f0"), use_container_width=True)
    else:
        st.info("No data yet.")

with tab_monthly:
    if not filtered.empty and "month" in filtered.columns and filtered["date"].notna().any():
        monthly = filtered.groupby("month").size().reset_index(name="count")
        st.plotly_chart(make_bar(monthly, "month", "count", "Press Releases per Month", "#7209b7"), use_container_width=True)
    else:
        st.info("No data yet.")

st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 4 — Press Releases Table
# ═══════════════════════════════════════════════════════════════════════════════
section_title(ICO_LIST, "Press Releases")

search = st.text_input("Search press releases", placeholder="Filter by title…")
display = (
    filtered[filtered["title"].str.contains(search, case=False, na=False)]
    if search else filtered.copy()
)

total_pages = max(1, math.ceil(len(display) / PAGE_SIZE))
page_num    = st.number_input("Page", min_value=1, max_value=total_pages, value=1, step=1)
start       = (page_num - 1) * PAGE_SIZE
end         = start + PAGE_SIZE
page_data   = display.iloc[start:end]

st.caption(
    f"Showing {start+1}–{min(end, len(display))} of {len(display)} press releases "
    f"(page {page_num}/{total_pages})"
)

for _, row in page_data.iterrows():
    with st.container():
        tc, dc, pc = st.columns([5, 2, 1])
        with tc:
            url   = row.get("url", "")
            title = row.get("title", "—")
            st.markdown(f"**[{title}]({url})**" if url else f"**{title}**")
            desc = str(row.get("description") or "")
            if desc:
                st.caption(desc[:160] + "…" if len(desc) > 160 else desc)
        with dc:
            d = row.get("date")
            st.markdown(f"`{d.strftime('%d %b %Y') if pd.notna(d) else '—'}`")
        with pc:
            pc_count = row.get("podcast_count", 0)
            st.markdown(
                ICO_MIC_SM + f" {pc_count}" if pc_count else "—",
                unsafe_allow_html=True,
            )
    st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 5 — Podcasts
# ═══════════════════════════════════════════════════════════════════════════════
if not df_pod.empty:
    section_title(ICO_MIC, "Podcasts")

    lang_filter = st.radio("Language", ["All", "English", "Arabic"], horizontal=True)
    df_pod_show = df_pod.copy()
    if lang_filter == "English":
        df_pod_show = df_pod_show[df_pod_show["language"] == "en"]
    elif lang_filter == "Arabic":
        df_pod_show = df_pod_show[df_pod_show["language"] == "ar"]

    pod_search = st.text_input("Search podcasts", placeholder="Filter by title…")
    if pod_search:
        df_pod_show = df_pod_show[
            df_pod_show["title"].str.contains(pod_search, case=False, na=False)
        ]

    st.caption(f"Showing {len(df_pod_show)} of {len(df_pod)} episodes")

    for _, pod in df_pod_show.head(100).iterrows():
        with st.expander(f"{pod.get('title', '—')}  |  {pod.get('date', '')}"):
            col_a, col_b = st.columns([3, 1])
            with col_a:
                if pod.get("pr_title"):
                    st.markdown(ICO_LINK + f" **Linked PR:** {pod['pr_title']}", unsafe_allow_html=True)
                if pod.get("url"):
                    st.markdown(f"[Open on RSS.com]({pod['url']})")
                if pod.get("audio_url"):
                    st.audio(pod["audio_url"])
            with col_b:
                if pod.get("duration"):
                    st.metric("Duration", pod["duration"])
                st.metric("Saved vs old cost", f"AED {COST_PER_PODCAST_OLD_AED:,}")

    st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 6 — Scrape Log
# ═══════════════════════════════════════════════════════════════════════════════
with st.expander("Scrape history"):
    if scrape_log:
        df_log = pd.DataFrame(scrape_log)
        st.dataframe(
            df_log[["ran_at", "prs_found", "pods_found", "status", "notes"]],
            use_container_width=True,
        )
    else:
        st.info("No scrape runs recorded yet.")

st.markdown(
    '
'
    'DEWA PR Tracker · Playwright + SQLite + Streamlit
',
    unsafe_allow_html=True,
)

+            var_name="Type", value_name="AED",

-            {"old_cost": "Old Cost (Traditional)", "new_cost": "New Cost (NotebookLM)"}

+            {"old_cost": "Traditional (AED 20k/podcast)", "new_cost": "NotebookLM ($20/month)"}

-                "Old Cost (Traditional)": "#ff6b6b",

-                "New Cost (NotebookLM)":  "#51cf66",

+                "Traditional (AED 20k/podcast)": "#ff6b6b",

-            title="Monthly Cost Comparison: Traditional vs NotebookLM",

+            title="Monthly Cost: Traditional Podcast Production vs NotebookLM",

-            df_roi, x="month", y="total",

+            df_roi, x="month", y="pr_count",

-            title="Podcasts Produced per Month (since NotebookLM)",

+            title="Press Releases with Podcasts per Month (since NotebookLM)",

-            yaxis=dict(gridcolor="#1e293b", title="Podcasts"),

+            yaxis=dict(gridcolor="#1e293b", title="Press Releases"),

-            '<p style="color:#94a3b8;font-size:.85rem;margin-bottom:.5rem">'

-            'Monthly breakdown — each row = 1 calendar month</p>',

+            '<p style="color:#94a3b8;font-size:.8rem;margin-bottom:.5rem">'

+            'Cost basis: AED 20,000 per PR podcast (EN+AR counted as one production).<br>'

+            'NotebookLM: flat $20/month regardless of volume.</p>',

+                f'<td>{int(r["en_episodes"])}</td>'

+                f'<td>{int(r["ar_episodes"])}</td>'

-            f'<td>{total_pods_nlm}</td>'

-            f'<td>{int(df_roi["en_count"].sum())}</td>'

-            f'<td>{int(df_roi["ar_count"].sum())}</td>'

+            f'<td>{total_prs_nlm}</td>'

+            f'<td>{int(df_roi["en_episodes"].sum())}</td>'

+            f'<td>{int(df_roi["ar_episodes"].sum())}</td>'

+            f'Month	PRs	EN eps	AR eps	'


Bash

pkill -f "streamlit run dashboard.py" 2>/dev/null; sleep 1
source venv/bin/activate && streamlit run dashboard.py --server.headless true &
sleep 4 && curl -s -o /dev/null -w "%{http_code}" http://localhost:8501

Restart dashboard with corrected ROI


Collecting usage statistics. To deactivate, set browser.gatherUsageStats to false.

/Users/khalidbinkalban/Projects/dewa-pr-tracker/venv/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
  warnings.warn(

  You can now view your Streamlit app in your browser.

  Local URL: http://localhost:8501
  Network URL: http://192.168.1.140:8501
  External URL: http://86.99.11.212:8501

  For better performance, install the Watchdog module:

  $ xcode-select --install
  $ pip install watchdog

200


Fixed. The corrected numbers at **http://localhost:8501**:

| | Before | After (corrected) |
|---|---|---|
| Cost basis | 689 podcast episodes | **342 press releases** |
| Old cost | AED 13,780,000 | **AED 6,840,000** |
| New cost | AED 807 | **AED 807** |
| **Total savings** | AED 13,779,193 | **AED 6,839,193** |

The table now shows: **Month | PRs | EN episodes | AR episodes | Old Cost | New Cost | Savings** — with a note clarifying that AED 20,000 is per PR podcast production (EN+AR together = one production unit).


[Image #3]
now this will show always same numbers, change one of them and think of an insight what we can add here otherwise remove it and fix the boxes and check if you can enhance the overal UI


[Image: source: /var/folders/vn/5x83t3r1077gs8kcsydcn2fw0000gn/T/TemporaryItems/NSIRD_screencaptureui_YUnXkp/Screenshot 2026-04-04 at 9.44.49 AM.png]


source venv/bin/activate && python3 -c "
import database as db, pandas as pd, re
db.init_db()
prs = [dict(r) for r in db.get_all_press_releases()]
pods = [dict(r) for r in db.get_all_podcasts()]
df_pr = pd.DataFrame(prs)
df_pod = pd.DataFrame(pods)
df_pr['date'] = pd.to_datetime(df_pr['date'], errors='coerce')
df_since = df_pr[df_pr['date'] >= '2025-05-23']

# Useful insight numbers
prs_with_pod = len(df_since[df_since['podcast_count'] > 0])
coverage = prs_with_pod / len(df_since) * 100

# Avg PRs per month since May 2025
monthly = df_since.groupby(df_since['date'].dt.to_period('M')).size()
avg_per_month = monthly.mean()

# Total EN episodes since May 2025
df_pod['date'] = pd.to_datetime(df_pod['date'], errors='coerce')
df_pod['lang'] = df_pod['title'].apply(lambda t: 'ar' if re.search(r'[\u0600-\u06FF]', str(t)) else 'en')
ep_since = df_pod[df_pod['date'] >= '2025-05-23']

print(f'PRs since May 2025:       {len(df_since)}')
print(f'PRs with podcast:         {prs_with_pod} ({coverage:.1f}%)')
print(f'Avg PRs/month:            {avg_per_month:.1f}')
print(f'Total EN episodes:        {len(ep_since[ep_since.lang==\"en\"])}')
print(f'Total AR episodes:        {len(ep_since[ep_since.lang==\"ar\"])}')
print(f'Total episodes (EN+AR):   {len(ep_since)}')
"

Get insight numbers for metrics


PRs since May 2025:       342
PRs with podcast:         329 (96.2%)
Avg PRs/month:            31.1
Total EN episodes:        344
Total AR episodes:        345
Total episodes (EN+AR):   689


Good data. Now I'll replace the duplicate card with **Podcast Coverage %** as the insight, and do a full UI enhancement pass:


145
146	if not df_roi.empty:
147	    # Cost basis: 1 podcast per press release @ AED 20,000
148	    # (PR cost = 0 AED; only the podcast production was charged)
149	    df_roi["old_cost"] = df_roi["pr_count"] * COST_PER_PODCAST_OLD_AED
150	    df_roi["new_cost"] = NOTEBOOKLM_AED   # flat monthly subscription
151	    df_roi["savings"]  = df_roi["old_cost"] - df_roi["new_cost"]
152
153	    total_prs_nlm   = int(df_roi["pr_count"].sum())
154	    total_eps_nlm   = int(df_roi["total_episodes"].sum())
155	    total_old_cost  = float(df_roi["old_cost"].sum())
156	    total_new_cost  = float(df_roi["new_cost"].sum())
157	    total_savings   = float(df_roi["savings"].sum())
158	    roi_pct         = (total_savings / total_old_cost * 100) if total_old_cost > 0 else 0
159	else:
160	    total_prs_nlm = total_eps_nlm = total_old_cost = total_new_cost = total_savings = roi_pct = 0
161
162
163	# ── Sidebar ───────────────────────────────────────────────────────────────────
164	with st.sidebar:
165	    # DEWA logo — styled text (image blocked externally)
166	    st.markdown(
167	        '
DEWA
'
168	        '
Dubai Electricity & Water Authority
',
169	        unsafe_allow_html=True,
170	    )
171	    st.markdown("---")
172	    st.markdown("**Filters**")
173
174	    if not df_pr.empty and df_pr["date"].notna().any():
175	        min_date = df_pr["date"].min().date()
176	        max_date = df_pr["date"].max().date()
177	    else:
178	        min_date = datetime(2019, 1, 1).date()
179	        max_date = datetime.today().date()
180
181	    date_from, date_to = st.date_input(
182	        "Date range",
183	        value=(NLM_START.date(), max_date),
184	        min_value=min_date,
185	        max_value=max_date,
186	    )
187
188	    categories = ["All"]
189	    if not df_pr.empty and "category" in df_pr.columns:
190	        cats = df_pr["category"].dropna().unique().tolist()
191	        categories += sorted(cats)
192	    selected_cat = st.selectbox("Category", categories)
193
194	    has_podcast_filter = st.checkbox("Show only PRs with podcast", value=False)
195
196	    st.divider()
197	    if st.button("Refresh data", use_container_width=True):
198	        st.cache_data.clear()
199	        st.rerun()
200
201	    if st.button("Run scraper now", use_container_width=True):
202	        with st.spinner("Scraping DEWA website…"):
203	            try:
204	                from scraper import run_scrape

if not df_roi.empty:
    # Cost basis: 1 podcast per press release @ AED 20,000
    # (PR cost = 0 AED; only the podcast production was charged)
    df_roi["old_cost"] = df_roi["pr_count"] * COST_PER_PODCAST_OLD_AED
    df_roi["new_cost"] = NOTEBOOKLM_AED   # flat monthly subscription
    df_roi["savings"]  = df_roi["old_cost"] - df_roi["new_cost"]

    total_prs_nlm   = int(df_roi["pr_count"].sum())
    total_eps_nlm   = int(df_roi["total_episodes"].sum())
    total_old_cost  = float(df_roi["old_cost"].sum())
    total_new_cost  = float(df_roi["new_cost"].sum())
    total_savings   = float(df_roi["savings"].sum())
    roi_pct         = (total_savings / total_old_cost * 100) if total_old_cost > 0 else 0
else:
    total_prs_nlm = total_eps_nlm = total_old_cost = total_new_cost = total_savings = roi_pct = 0

# ── Sidebar ───────────────────────────────────────────────────────────────────
with st.sidebar:
    # DEWA logo — styled text (image blocked externally)
    st.markdown(
        '
DEWA
'
        '
Dubai Electricity & Water Authority
',
        unsafe_allow_html=True,
    )
    st.markdown("---")
    st.markdown("**Filters**")

    if not df_pr.empty and df_pr["date"].notna().any():
        min_date = df_pr["date"].min().date()
        max_date = df_pr["date"].max().date()
    else:
        min_date = datetime(2019, 1, 1).date()
        max_date = datetime.today().date()

    date_from, date_to = st.date_input(
        "Date range",
        value=(NLM_START.date(), max_date),
        min_value=min_date,
        max_value=max_date,
    )

    categories = ["All"]
    if not df_pr.empty and "category" in df_pr.columns:
        cats = df_pr["category"].dropna().unique().tolist()
        categories += sorted(cats)
    selected_cat = st.selectbox("Category", categories)

    has_podcast_filter = st.checkbox("Show only PRs with podcast", value=False)

    st.divider()
    if st.button("Refresh data", use_container_width=True):
        st.cache_data.clear()
        st.rerun()

    if st.button("Run scraper now", use_container_width=True):
        with st.spinner("Scraping DEWA website…"):
            try:
                from scraper import run_scrape


215
216	# ── Apply PR filters ──────────────────────────────────────────────────────────
217	filtered = df_pr.copy()
218	if not filtered.empty:
219	    if filtered["date"].notna().any():
220	        filtered = filtered[
221	            (filtered["date"].dt.date >= date_from) &
222	            (filtered["date"].dt.date <= date_to)
223	        ]
224	    if selected_cat != "All":
225	        filtered = filtered[filtered["category"] == selected_cat]
226	    if has_podcast_filter:
227	        filtered = filtered[filtered["podcast_count"] > 0]
228
229
230	# ═══════════════════════════════════════════════════════════════════════════════
231	# HEADER
232	# ═══════════════════════════════════════════════════════════════════════════════
233	st.markdown(
234	    f'
'
235	    f'{_svg("""<path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 0-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"/><path d="M18 14h-8"/><path d="M15 18h-5"/><path d="M10 6h8v4h-8V6Z"/>""", size=34, color="#00b4d8")}'
236	    f'{DASHBOARD_TITLE}
',
237	    unsafe_allow_html=True,
238	)
239	st.markdown(
240	    f'
'
241	    f'Tracking all DEWA press releases and NotebookLM podcast ROI since {NOTEBOOKLM_START_DATE} — '
242	    f'{datetime.today().strftime("%d %B %Y")}
',
243	    unsafe_allow_html=True,
244	)
245	st.divider()
246
247
248	# ═══════════════════════════════════════════════════════════════════════════════
249	# ROW 1 — Key Metrics
250	# ═══════════════════════════════════════════════════════════════════════════════
251	today       = datetime.today().date()
252	week_start  = today - timedelta(days=today.weekday())
253	month_start = today.replace(day=1)
254
255	if not filtered.empty and filtered["date"].notna().any():
256	    prs_today = len(filtered[filtered["date"].dt.date == today])
257	    prs_week  = len(filtered[filtered["date"].dt.date >= week_start])
258	    prs_month = len(filtered[filtered["date"].dt.date >= month_start])
259	else:
260	    prs_today = prs_week = prs_month = 0
261
262	c1, c2, c3, c4, c5 = st.columns(5)
263	with c1:
264	    st.metric("Total Press Releases", f"{len(filtered):,}")
265	with c2:
266	    st.metric("Podcasts (since May 2025)", f"{total_prs_nlm:,}")
267	with c3:
268	    st.metric("Published Today", prs_today)
269	with c4:
270	    st.metric("This Week", prs_week)
271	with c5:
272	    st.metric("This Month", prs_month)
273
274	st.divider()
275
276
277	# ═══════════════════════════════════════════════════════════════════════════════
278	# ROW 2 — NotebookLM ROI  (auto-calculated from May 23, 2025)
279	# ═══════════════════════════════════════════════════════════════════════════════
280	section_title(ICO_MONEY, "NotebookLM ROI — Since 23 May 2025")
281
282	# Summary cards
283	sc1, sc2, sc3, sc4 = st.columns(4)
284	with sc1:

# ── Apply PR filters ──────────────────────────────────────────────────────────
filtered = df_pr.copy()
if not filtered.empty:
    if filtered["date"].notna().any():
        filtered = filtered[
            (filtered["date"].dt.date >= date_from) &
            (filtered["date"].dt.date <= date_to)
        ]
    if selected_cat != "All":
        filtered = filtered[filtered["category"] == selected_cat]
    if has_podcast_filter:
        filtered = filtered[filtered["podcast_count"] > 0]

# ═══════════════════════════════════════════════════════════════════════════════
# HEADER
# ═══════════════════════════════════════════════════════════════════════════════
st.markdown(
    f'
'
    f'{_svg("""<path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 0-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"/><path d="M18 14h-8"/><path d="M15 18h-5"/><path d="M10 6h8v4h-8V6Z"/>""", size=34, color="#00b4d8")}'
    f'{DASHBOARD_TITLE}
',
    unsafe_allow_html=True,
)
st.markdown(
    f'
'
    f'Tracking all DEWA press releases and NotebookLM podcast ROI since {NOTEBOOKLM_START_DATE} — '
    f'{datetime.today().strftime("%d %B %Y")}
',
    unsafe_allow_html=True,
)
st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 1 — Key Metrics
# ═══════════════════════════════════════════════════════════════════════════════
today       = datetime.today().date()
week_start  = today - timedelta(days=today.weekday())
month_start = today.replace(day=1)

if not filtered.empty and filtered["date"].notna().any():
    prs_today = len(filtered[filtered["date"].dt.date == today])
    prs_week  = len(filtered[filtered["date"].dt.date >= week_start])
    prs_month = len(filtered[filtered["date"].dt.date >= month_start])
else:
    prs_today = prs_week = prs_month = 0

c1, c2, c3, c4, c5 = st.columns(5)
with c1:
    st.metric("Total Press Releases", f"{len(filtered):,}")
with c2:
    st.metric("Podcasts (since May 2025)", f"{total_prs_nlm:,}")
with c3:
    st.metric("Published Today", prs_today)
with c4:
    st.metric("This Week", prs_week)
with c5:
    st.metric("This Month", prs_month)

st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 2 — NotebookLM ROI  (auto-calculated from May 23, 2025)
# ═══════════════════════════════════════════════════════════════════════════════
section_title(ICO_MONEY, "NotebookLM ROI — Since 23 May 2025")

# Summary cards
sc1, sc2, sc3, sc4 = st.columns(4)
with sc1:


Now I'll replace the duplicate metric with **Podcast Coverage %** and do a full UI enhancement — custom metric cards with icons and colors, better header, cleaner layout:


Edit

# ── CSS — hide Streamlit chrome (Deploy button, footer, menu) ─────────────────
st.markdown("""
<style>
    /* Hide Deploy button and top-right toolbar */
    [data-testid="stToolbar"]         { display: none !important; }
    [data-testid="stDecoration"]      { display: none !important; }
    [data-testid="stHeader"]          { display: none !important; }
    header                            { display: none !important; }
    #MainMenu                         { visibility: hidden !important; }
    footer                            { visibility: hidden !important; }

    /* Metrics */
    [data-testid="stMetric"]          { background:#1e293b; border-radius:10px; padding:14px 18px; }
    [data-testid="stMetricLabel"]     { color:#94a3b8 !important; font-size:.8rem; }
    [data-testid="stMetricValue"]     { color:#e2e8f0 !important; }

    /* General */
    h1, h2, h3                        { color:#e2e8f0 !important; }
    .cost-old  { color:#ff6b6b; font-size:1.5rem; font-weight:700; }
    .cost-new  { color:#51cf66; font-size:1.5rem; font-weight:700; }
    .savings   { color:#ffd43b; font-size:2.2rem; font-weight:700; }
    .roi-pct   { color:#00b4d8; font-size:1.8rem; font-weight:700; }

    /* ROI table */
    table.roi-table { width:100%; border-collapse:collapse; font-size:.88rem; }
    table.roi-table th {
        background:#0f172a; color:#94a3b8;
        padding:8px 12px; text-align:right; border-bottom:1px solid #1e293b;
    }
    table.roi-table th:first-child { text-align:left; }
    table.roi-table td {
        padding:7px 12px; text-align:right;
        border-bottom:1px solid #1e293b; color:#cbd5e1;
    }
    table.roi-table td:first-child { text-align:left; color:#e2e8f0; font-weight:500; }
    table.roi-table tr.total-row td { background:#0f172a; color:#ffd43b; font-weight:700; border-top:2px solid #334155; }
    table.roi-table tr:hover td    { background:#1e293b; }

    /* DEWA logo text */
    .dewa-logo {
        font-size:1.6rem; font-weight:900; letter-spacing:.12em;
        background:linear-gradient(90deg,#00b4d8,#0077b6);
        -webkit-background-clip:text; -webkit-text-fill-color:transparent;
        margin-bottom:.25rem;
    }
    .dewa-logo-sub { font-size:.65rem; color:#64748b; letter-spacing:.08em; text-transform:uppercase; }
</style>
""", unsafe_allow_html=True)

# ── CSS ───────────────────────────────────────────────────────────────────────
st.markdown("""
<style>
    /* ── Hide Streamlit chrome ── */
    [data-testid="stToolbar"],
    [data-testid="stDecoration"],
    [data-testid="stHeader"],
    header, #MainMenu, footer      { display:none !important; visibility:hidden !important; }

    /* ── Base ── */
    body, .stApp                   { background:#0a0f1a; }
    h1, h2, h3, h4                 { color:#e2e8f0 !important; }
    p, li, label                   { color:#94a3b8; }
    .stDivider                     { border-color:#1e293b !important; }

    /* ── Sidebar ── */
    [data-testid="stSidebar"]      { background:#0d1424 !important; border-right:1px solid #1e293b; }
    [data-testid="stSidebar"] *    { color:#94a3b8 !important; }
    [data-testid="stSidebar"] .stButton > button {
        background:#1e293b; border:1px solid #334155; color:#e2e8f0 !important;
        border-radius:8px; transition:all .2s;
    }
    [data-testid="stSidebar"] .stButton > button:hover { background:#334155; border-color:#00b4d8; }

    /* ── KPI cards (custom HTML) ── */
    .kpi-card {
        background:#111827;
        border:1px solid #1e293b;
        border-radius:14px;
        padding:20px 22px;
        position:relative;
        overflow:hidden;
        height:110px;
        transition:border-color .2s;
    }
    .kpi-card:hover             { border-color:#334155; }
    .kpi-card::before {
        content:"";
        position:absolute; left:0; top:0; bottom:0;
        width:4px;
        border-radius:14px 0 0 14px;
    }
    .kpi-card.blue::before      { background:#00b4d8; }
    .kpi-card.green::before     { background:#22c55e; }
    .kpi-card.amber::before     { background:#f59e0b; }
    .kpi-card.purple::before    { background:#a855f7; }
    .kpi-card.red::before       { background:#f43f5e; }
    .kpi-label {
        font-size:.72rem; font-weight:600; letter-spacing:.06em;
        text-transform:uppercase; color:#64748b; margin-bottom:6px;
        display:flex; align-items:center; gap:6px;
    }
    .kpi-value {
        font-size:2rem; font-weight:800; color:#f1f5f9; line-height:1;
    }
    .kpi-sub   { font-size:.72rem; color:#475569; margin-top:5px; }

    /* ── ROI summary cards ── */
    .roi-card {
        background:#111827; border:1px solid #1e293b;
        border-radius:14px; padding:20px 22px;
    }
    .cost-old  { color:#f43f5e; font-size:1.6rem; font-weight:800; }
    .cost-new  { color:#22c55e; font-size:1.6rem; font-weight:800; }
    .savings   { color:#fbbf24; font-size:2rem;   font-weight:800; }
    .roi-pct   { color:#00b4d8; font-size:2rem;   font-weight:800; }

    /* ── ROI table ── */
    table.roi-table { width:100%; border-collapse:collapse; font-size:.83rem; }
    table.roi-table th {
        background:#0a0f1a; color:#64748b; font-weight:600;
        padding:9px 12px; text-align:right; border-bottom:1px solid #1e293b;
        letter-spacing:.04em; text-transform:uppercase; font-size:.72rem;
    }
    table.roi-table th:first-child { text-align:left; }
    table.roi-table td {
        padding:8px 12px; text-align:right;
        border-bottom:1px solid #111827; color:#94a3b8;
    }
    table.roi-table td:first-child  { text-align:left; color:#e2e8f0; font-weight:600; }
    table.roi-table tr:hover td     { background:#1e293b66; }
    table.roi-table tr.total-row td {
        background:#0a0f1a; color:#fbbf24; font-weight:800;
        border-top:2px solid #334155; font-size:.9rem;
    }

    /* ── DEWA logo ── */
    .dewa-logo {
        font-size:1.8rem; font-weight:900; letter-spacing:.1em;
        background:linear-gradient(90deg,#00b4d8,#0284c7);
        -webkit-background-clip:text; -webkit-text-fill-color:transparent;
    }
    .dewa-logo-sub { font-size:.6rem; color:#334155; letter-spacing:.1em; text-transform:uppercase; margin-top:2px; }

    /* ── Tabs ── */
    [data-testid="stTabs"] button          { color:#64748b !important; font-weight:500; }
    [data-testid="stTabs"] button[aria-selected="true"] { color:#00b4d8 !important; border-bottom-color:#00b4d8 !important; }

    /* ── Expander ── */
    [data-testid="stExpander"]             { background:#111827; border:1px solid #1e293b; border-radius:10px; }
    [data-testid="stExpander"] summary     { color:#94a3b8 !important; }
</style>
""", unsafe_allow_html=True)


The file /Users/khalidbinkalban/Projects/dewa-pr-tracker/dashboard.py has been updated successfully.

# ── CSS — hide Streamlit chrome (Deploy button, footer, menu) ─────────────────
st.markdown("""
<style>
    /* Hide Deploy button and top-right toolbar */
    [data-testid="stToolbar"]         { display: none !important; }
    [data-testid="stDecoration"]      { display: none !important; }
    [data-testid="stHeader"]          { display: none !important; }
    header                            { display: none !important; }
    #MainMenu                         { visibility: hidden !important; }
    footer                            { visibility: hidden !important; }

    /* Metrics */
    [data-testid="stMetric"]          { background:#1e293b; border-radius:10px; padding:14px 18px; }
    [data-testid="stMetricLabel"]     { color:#94a3b8 !important; font-size:.8rem; }
    [data-testid="stMetricValue"]     { color:#e2e8f0 !important; }

    /* General */
    h1, h2, h3                        { color:#e2e8f0 !important; }
    .cost-old  { color:#ff6b6b; font-size:1.5rem; font-weight:700; }
    .cost-new  { color:#51cf66; font-size:1.5rem; font-weight:700; }
    .savings   { color:#ffd43b; font-size:2.2rem; font-weight:700; }
    .roi-pct   { color:#00b4d8; font-size:1.8rem; font-weight:700; }

    /* ROI table */
    table.roi-table { width:100%; border-collapse:collapse; font-size:.88rem; }
    table.roi-table th {
        background:#0f172a; color:#94a3b8;
        padding:8px 12px; text-align:right; border-bottom:1px solid #1e293b;
    }
    table.roi-table th:first-child { text-align:left; }
    table.roi-table td {
        padding:7px 12px; text-align:right;
        border-bottom:1px solid #1e293b; color:#cbd5e1;
    }
    table.roi-table td:first-child { text-align:left; color:#e2e8f0; font-weight:500; }
    table.roi-table tr.total-row td { background:#0f172a; color:#ffd43b; font-weight:700; border-top:2px solid #334155; }
    table.roi-table tr:hover td    { background:#1e293b; }

    /* DEWA logo text */
    .dewa-logo {
        font-size:1.6rem; font-weight:900; letter-spacing:.12em;
        background:linear-gradient(90deg,#00b4d8,#0077b6);
        -webkit-background-clip:text; -webkit-text-fill-color:transparent;
        margin-bottom:.25rem;
    }
    .dewa-logo-sub { font-size:.65rem; color:#64748b; letter-spacing:.08em; text-transform:uppercase; }
</style>
""", unsafe_allow_html=True)

# ── CSS ───────────────────────────────────────────────────────────────────────
st.markdown("""
<style>
    /* ── Hide Streamlit chrome ── */
    [data-testid="stToolbar"],
    [data-testid="stDecoration"],
    [data-testid="stHeader"],
    header, #MainMenu, footer      { display:none !important; visibility:hidden !important; }

    /* ── Base ── */
    body, .stApp                   { background:#0a0f1a; }
    h1, h2, h3, h4                 { color:#e2e8f0 !important; }
    p, li, label                   { color:#94a3b8; }
    .stDivider                     { border-color:#1e293b !important; }

    /* ── Sidebar ── */
    [data-testid="stSidebar"]      { background:#0d1424 !important; border-right:1px solid #1e293b; }
    [data-testid="stSidebar"] *    { color:#94a3b8 !important; }
    [data-testid="stSidebar"] .stButton > button {
        background:#1e293b; border:1px solid #334155; color:#e2e8f0 !important;
        border-radius:8px; transition:all .2s;
    }
    [data-testid="stSidebar"] .stButton > button:hover { background:#334155; border-color:#00b4d8; }

    /* ── KPI cards (custom HTML) ── */
    .kpi-card {
        background:#111827;
        border:1px solid #1e293b;
        border-radius:14px;
        padding:20px 22px;
        position:relative;
        overflow:hidden;
        height:110px;
        transition:border-color .2s;
    }
    .kpi-card:hover             { border-color:#334155; }
    .kpi-card::before {
        content:"";
        position:absolute; left:0; top:0; bottom:0;
        width:4px;
        border-radius:14px 0 0 14px;
    }
    .kpi-card.blue::before      { background:#00b4d8; }
    .kpi-card.green::before     { background:#22c55e; }
    .kpi-card.amber::before     { background:#f59e0b; }
    .kpi-card.purple::before    { background:#a855f7; }
    .kpi-card.red::before       { background:#f43f5e; }
    .kpi-label {
        font-size:.72rem; font-weight:600; letter-spacing:.06em;
        text-transform:uppercase; color:#64748b; margin-bottom:6px;
        display:flex; align-items:center; gap:6px;
    }
    .kpi-value {
        font-size:2rem; font-weight:800; color:#f1f5f9; line-height:1;
    }
    .kpi-sub   { font-size:.72rem; color:#475569; margin-top:5px; }

    /* ── ROI summary cards ── */
    .roi-card {
        background:#111827; border:1px solid #1e293b;
        border-radius:14px; padding:20px 22px;
    }
    .cost-old  { color:#f43f5e; font-size:1.6rem; font-weight:800; }
    .cost-new  { color:#22c55e; font-size:1.6rem; font-weight:800; }
    .savings   { color:#fbbf24; font-size:2rem;   font-weight:800; }
    .roi-pct   { color:#00b4d8; font-size:2rem;   font-weight:800; }

    /* ── ROI table ── */
    table.roi-table { width:100%; border-collapse:collapse; font-size:.83rem; }
    table.roi-table th {
        background:#0a0f1a; color:#64748b; font-weight:600;
        padding:9px 12px; text-align:right; border-bottom:1px solid #1e293b;
        letter-spacing:.04em; text-transform:uppercase; font-size:.72rem;
    }
    table.roi-table th:first-child { text-align:left; }
    table.roi-table td {
        padding:8px 12px; text-align:right;
        border-bottom:1px solid #111827; color:#94a3b8;
    }
    table.roi-table td:first-child  { text-align:left; color:#e2e8f0; font-weight:600; }
    table.roi-table tr:hover td     { background:#1e293b66; }
    table.roi-table tr.total-row td {
        background:#0a0f1a; color:#fbbf24; font-weight:800;
        border-top:2px solid #334155; font-size:.9rem;
    }

    /* ── DEWA logo ── */
    .dewa-logo {
        font-size:1.8rem; font-weight:900; letter-spacing:.1em;
        background:linear-gradient(90deg,#00b4d8,#0284c7);
        -webkit-background-clip:text; -webkit-text-fill-color:transparent;
    }
    .dewa-logo-sub { font-size:.6rem; color:#334155; letter-spacing:.1em; text-transform:uppercase; margin-top:2px; }

    /* ── Tabs ── */
    [data-testid="stTabs"] button          { color:#64748b !important; font-weight:500; }
    [data-testid="stTabs"] button[aria-selected="true"] { color:#00b4d8 !important; border-bottom-color:#00b4d8 !important; }

    /* ── Expander ── */
    [data-testid="stExpander"]             { background:#111827; border:1px solid #1e293b; border-radius:10px; }
    [data-testid="stExpander"] summary     { color:#94a3b8 !important; }
</style>
""", unsafe_allow_html=True)

"""
DEWA PR Tracker — Streamlit Dashboard
Run:  streamlit run dashboard.py
"""
import math
import re as _re
from datetime import datetime, timedelta

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import streamlit as st

import database as db
from config import (
    COST_PER_PODCAST_OLD_AED,
    NOTEBOOKLM_MONTHLY_USD,
    NOTEBOOKLM_START_DATE,
    USD_TO_AED,
    DASHBOARD_TITLE,
    PAGE_SIZE,
)

# ── Page setup ────────────────────────────────────────────────────────────────
st.set_page_config(
    page_title=DASHBOARD_TITLE,
    page_icon="📰",
    layout="wide",
    initial_sidebar_state="expanded",
)

# ── SVG helpers ───────────────────────────────────────────────────────────────
def _svg(paths, size=20, color="#00b4d8"):
    return (
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{size}" height="{size}" '
        f'viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" '
        f'stroke-linecap="round" stroke-linejoin="round" '
        f'style="vertical-align:middle;margin-right:8px">{paths}</svg>'
    )

ICO_NEWS    = _svg('<path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 0-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"/><path d="M18 14h-8"/><path d="M15 18h-5"/><path d="M10 6h8v4h-8V6Z"/>')
ICO_MONEY   = _svg('<line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>', color="#51cf66")
ICO_CHART   = _svg('<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>', color="#4cc9f0")
ICO_LIST    = _svg('<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>')
ICO_MIC     = _svg('<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/>', color="#f59e0b")
ICO_MIC_SM  = _svg('<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/>', size=15, color="#f59e0b")
ICO_CLOCK   = _svg('<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>', color="#94a3b8")
ICO_LOG     = _svg('<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>', color="#94a3b8")
ICO_LINK    = _svg('<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>', size=14, color="#00b4d8")

def section_title(icon, text):
    st.markdown(
        f'
{icon}{text}
',
        unsafe_allow_html=True,
    )

# ── CSS — hide Streamlit chrome (Deploy button, footer, menu) ─────────────────
st.markdown("""

""", unsafe_allow_html=True)

# ── Data loading (cached 5 min) ───────────────────────────────────────────────
@st.cache_data(ttl=300)
def load_data():
    db.init_db()
    prs      = [dict(r) for r in db.get_all_press_releases()]
    pods     = [dict(r) for r in db.get_all_podcasts()]
    roi_rows = [dict(r) for r in db.get_roi_monthly_since(NOTEBOOKLM_START_DATE)]
    stats    = db.get_stats()
    log      = [dict(r) for r in db.get_scrape_log()]
    return prs, pods, roi_rows, stats, log

prs, pods, roi_rows, stats, scrape_log = load_data()
df_pr  = pd.DataFrame(prs)
df_pod = pd.DataFrame(pods)

if not df_pod.empty and "title" in df_pod.columns:
    df_pod["language"] = df_pod["title"].apply(
        lambda t: "ar" if _re.search(r"[\u0600-\u06FF]", str(t)) else "en"
    )

if not df_pr.empty:
    df_pr["date"]  = pd.to_datetime(df_pr["date"], errors="coerce")
    df_pr["week"]  = df_pr["date"].dt.to_period("W").astype(str)
    df_pr["month"] = df_pr["date"].dt.to_period("M").astype(str)

if not df_pod.empty:
    df_pod["date"] = pd.to_datetime(df_pod["date"], errors="coerce")

# ── ROI pre-calculations ──────────────────────────────────────────────────────
NOTEBOOKLM_AED   = NOTEBOOKLM_MONTHLY_USD * USD_TO_AED   # ~73.4 AED/month
NLM_START        = pd.Timestamp(NOTEBOOKLM_START_DATE)

df_roi = pd.DataFrame(roi_rows) if roi_rows else pd.DataFrame(
    columns=["month", "total", "en_count", "ar_count"]
)

if not df_roi.empty:
    # Cost basis: 1 podcast per press release @ AED 20,000
    # (PR cost = 0 AED; only the podcast production was charged)
    df_roi["old_cost"] = df_roi["pr_count"] * COST_PER_PODCAST_OLD_AED
    df_roi["new_cost"] = NOTEBOOKLM_AED   # flat monthly subscription
    df_roi["savings"]  = df_roi["old_cost"] - df_roi["new_cost"]

    total_prs_nlm   = int(df_roi["pr_count"].sum())
    total_eps_nlm   = int(df_roi["total_episodes"].sum())
    total_old_cost  = float(df_roi["old_cost"].sum())
    total_new_cost  = float(df_roi["new_cost"].sum())
    total_savings   = float(df_roi["savings"].sum())
    roi_pct         = (total_savings / total_old_cost * 100) if total_old_cost > 0 else 0
else:
    total_prs_nlm = total_eps_nlm = total_old_cost = total_new_cost = total_savings = roi_pct = 0

# ── Sidebar ───────────────────────────────────────────────────────────────────
with st.sidebar:
    # DEWA logo — styled text (image blocked externally)
    st.markdown(
        '
DEWA
'
        '
Dubai Electricity & Water Authority
',
        unsafe_allow_html=True,
    )
    st.markdown("---")
    st.markdown("**Filters**")

    if not df_pr.empty and df_pr["date"].notna().any():
        min_date = df_pr["date"].min().date()
        max_date = df_pr["date"].max().date()
    else:
        min_date = datetime(2019, 1, 1).date()
        max_date = datetime.today().date()

    date_from, date_to = st.date_input(
        "Date range",
        value=(NLM_START.date(), max_date),
        min_value=min_date,
        max_value=max_date,
    )

    categories = ["All"]
    if not df_pr.empty and "category" in df_pr.columns:
        cats = df_pr["category"].dropna().unique().tolist()
        categories += sorted(cats)
    selected_cat = st.selectbox("Category", categories)

    has_podcast_filter = st.checkbox("Show only PRs with podcast", value=False)

    st.divider()
    if st.button("Refresh data", use_container_width=True):
        st.cache_data.clear()
        st.rerun()

    if st.button("Run scraper now", use_container_width=True):
        with st.spinner("Scraping DEWA website…"):
            try:
                from scraper import run_scrape
                prs_n, pods_n = run_scrape()
                st.success(f"Done — {prs_n} PRs, {pods_n} podcasts saved.")
                st.cache_data.clear()
                st.rerun()
            except Exception as e:
                st.error(f"Scraper error: {e}")

    st.divider()
    st.caption(f"Last scraped: {stats.get('last_scraped', 'Never') or 'Never'}")

# ── Apply PR filters ──────────────────────────────────────────────────────────
filtered = df_pr.copy()
if not filtered.empty:
    if filtered["date"].notna().any():
        filtered = filtered[
            (filtered["date"].dt.date >= date_from) &
            (filtered["date"].dt.date <= date_to)
        ]
    if selected_cat != "All":
        filtered = filtered[filtered["category"] == selected_cat]
    if has_podcast_filter:
        filtered = filtered[filtered["podcast_count"] > 0]

# ═══════════════════════════════════════════════════════════════════════════════
# HEADER
# ═══════════════════════════════════════════════════════════════════════════════
st.markdown(
    f'
'
    f'{_svg("""<path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 0-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"/><path d="M18 14h-8"/><path d="M15 18h-5"/><path d="M10 6h8v4h-8V6Z"/>""", size=34, color="#00b4d8")}'
    f'{DASHBOARD_TITLE}
',
    unsafe_allow_html=True,
)
st.markdown(
    f'
'
    f'Tracking all DEWA press releases and NotebookLM podcast ROI since {NOTEBOOKLM_START_DATE} — '
    f'{datetime.today().strftime("%d %B %Y")}
',
    unsafe_allow_html=True,
)
st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 1 — Key Metrics
# ═══════════════════════════════════════════════════════════════════════════════
today       = datetime.today().date()
week_start  = today - timedelta(days=today.weekday())
month_start = today.replace(day=1)

if not filtered.empty and filtered["date"].notna().any():
    prs_today = len(filtered[filtered["date"].dt.date == today])
    prs_week  = len(filtered[filtered["date"].dt.date >= week_start])
    prs_month = len(filtered[filtered["date"].dt.date >= month_start])
else:
    prs_today = prs_week = prs_month = 0

c1, c2, c3, c4, c5 = st.columns(5)
with c1:
    st.metric("Total Press Releases", f"{len(filtered):,}")
with c2:
    st.metric("Podcasts (since May 2025)", f"{total_prs_nlm:,}")
with c3:
    st.metric("Published Today", prs_today)
with c4:
    st.metric("This Week", prs_week)
with c5:
    st.metric("This Month", prs_month)

st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 2 — NotebookLM ROI  (auto-calculated from May 23, 2025)
# ═══════════════════════════════════════════════════════════════════════════════
section_title(ICO_MONEY, "NotebookLM ROI — Since 23 May 2025")

# Summary cards
sc1, sc2, sc3, sc4 = st.columns(4)
with sc1:
    st.markdown(
        f'
'
        f'
Old Cost (Traditional)
'
        f'
AED {total_old_cost:,.0f}
'
        f'
{total_prs_nlm} PRs × AED 20,000/podcast
'
        f'
',
        unsafe_allow_html=True,
    )
with sc2:
    months_active = len(df_roi)
    st.markdown(
        f'
'
        f'
New Cost (NotebookLM)
'
        f'
AED {total_new_cost:,.0f}
'
        f'
{months_active} months × AED {NOTEBOOKLM_AED:.0f}/mo (${NOTEBOOKLM_MONTHLY_USD})
'
        f'
',
        unsafe_allow_html=True,
    )
with sc3:
    st.markdown(
        f'
'
        f'
Total Savings
'
        f'
AED {total_savings:,.0f}
'
        f'
Since {NOTEBOOKLM_START_DATE}
'
        f'
',
        unsafe_allow_html=True,
    )
with sc4:
    st.markdown(
        f'
'
        f'
Return on Investment
'
        f'
{roi_pct:.1f}%
'
        f'
Savings ÷ Old Cost
'
        f'
',
        unsafe_allow_html=True,
    )

st.markdown("
", unsafe_allow_html=True)

# ── Monthly detail ────────────────────────────────────────────────────────────
if not df_roi.empty:
    chart_col, table_col = st.columns([3, 2])

    with chart_col:
        # Grouped bar: old vs new cost per month
        df_chart = df_roi[["month", "old_cost", "new_cost"]].copy()
        df_chart_long = df_chart.melt(
            id_vars="month",
            value_vars=["old_cost", "new_cost"],
            var_name="Type", value_name="AED",
        )
        df_chart_long["Type"] = df_chart_long["Type"].map(
            {"old_cost": "Traditional (AED 20k/podcast)", "new_cost": "NotebookLM ($20/month)"}
        )
        fig_bar = px.bar(
            df_chart_long, x="month", y="AED", color="Type", barmode="group",
            color_discrete_map={
                "Traditional (AED 20k/podcast)": "#ff6b6b",
                "NotebookLM ($20/month)":         "#51cf66",
            },
            title="Monthly Cost: Traditional Podcast Production vs NotebookLM",
        )
        fig_bar.update_layout(
            paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
            font_color="white", legend_title_text="",
            xaxis=dict(showgrid=False, title=""),
            yaxis=dict(gridcolor="#1e293b", title="AED"),
            margin=dict(t=40, b=10), height=320,
        )
        st.plotly_chart(fig_bar, use_container_width=True)

        # PRs with podcasts per month
        fig_count = px.bar(
            df_roi, x="month", y="pr_count",
            color_discrete_sequence=["#f59e0b"],
            title="Press Releases with Podcasts per Month (since NotebookLM)",
        )
        fig_count.update_layout(
            paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
            font_color="white",
            xaxis=dict(showgrid=False, title=""),
            yaxis=dict(gridcolor="#1e293b", title="Press Releases"),
            margin=dict(t=40, b=10), height=280,
        )
        st.plotly_chart(fig_count, use_container_width=True)

    with table_col:
        st.markdown(
            '
'
            'Cost basis: AED 20,000 per PR podcast (EN+AR counted as one production).
'
            'NotebookLM: flat $20/month regardless of volume.
',
            unsafe_allow_html=True,
        )
        rows_html = ""
        for _, r in df_roi.iterrows():
            rows_html += (
                f'
'
                f'{r["month"]}	'
                f'{int(r["pr_count"])}	'
                f'{int(r["en_episodes"])}	'
                f'{int(r["ar_episodes"])}	'
                f'AED {r["old_cost"]:,.0f}	'
                f'AED {r["new_cost"]:,.0f}	'
                f'AED {r["savings"]:,.0f}	'
                f'
'
            )
        rows_html += (
            f'
'
            f'TOTAL	'
            f'{total_prs_nlm}	'
            f'{int(df_roi["en_episodes"].sum())}	'
            f'{int(df_roi["ar_episodes"].sum())}	'
            f'AED {total_old_cost:,.0f}	'
            f'AED {total_new_cost:,.0f}	'
            f'AED {total_savings:,.0f}	'
            f'
'
        )
        st.markdown(
            f'
'
            f'

'
            f'Month	PRs	EN eps	AR eps	'
            f'Old Cost	New Cost	Savings	'
            f'

'
            f'
{rows_html}
'
            f'
',
            unsafe_allow_html=True,
        )

st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 3 — Publication Trends
# ═══════════════════════════════════════════════════════════════════════════════
section_title(ICO_CHART, "Publication Trends")
tab_daily, tab_weekly, tab_monthly = st.tabs(["Daily", "Weekly", "Monthly"])

def make_bar(df_g, x, y, title, color="#00b4d8"):
    fig = px.bar(df_g, x=x, y=y, title=title, color_discrete_sequence=[color])
    fig.update_layout(
        paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
        font_color="white",
        xaxis=dict(showgrid=False, title=""),
        yaxis=dict(gridcolor="#1e293b"),
        margin=dict(t=40, b=10),
    )
    return fig

with tab_daily:
    if not filtered.empty and filtered["date"].notna().any():
        daily = filtered.groupby(filtered["date"].dt.date).size().reset_index(name="count")
        daily.columns = ["date", "count"]
        st.plotly_chart(make_bar(daily, "date", "count", "Press Releases per Day"), use_container_width=True)
    else:
        st.info("No data yet. Run the scraper first.")

with tab_weekly:
    if not filtered.empty and "week" in filtered.columns and filtered["date"].notna().any():
        weekly = filtered.groupby("week").size().reset_index(name="count")
        st.plotly_chart(make_bar(weekly, "week", "count", "Press Releases per Week", "#4cc9f0"), use_container_width=True)
    else:
        st.info("No data yet.")

with tab_monthly:
    if not filtered.empty and "month" in filtered.columns and filtered["date"].notna().any():
        monthly = filtered.groupby("month").size().reset_index(name="count")
        st.plotly_chart(make_bar(monthly, "month", "count", "Press Releases per Month", "#7209b7"), use_container_width=True)
    else:
        st.info("No data yet.")

st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 4 — Press Releases Table
# ═══════════════════════════════════════════════════════════════════════════════
section_title(ICO_LIST, "Press Releases")

search = st.text_input("Search press releases", placeholder="Filter by title…")
display = (
    filtered[filtered["title"].str.contains(search, case=False, na=False)]
    if search else filtered.copy()
)

total_pages = max(1, math.ceil(len(display) / PAGE_SIZE))
page_num    = st.number_input("Page", min_value=1, max_value=total_pages, value=1, step=1)
start       = (page_num - 1) * PAGE_SIZE
end         = start + PAGE_SIZE
page_data   = display.iloc[start:end]

st.caption(
    f"Showing {start+1}–{min(end, len(display))} of {len(display)} press releases "
    f"(page {page_num}/{total_pages})"
)

for _, row in page_data.iterrows():
    with st.container():
        tc, dc, pc = st.columns([5, 2, 1])
        with tc:
            url   = row.get("url", "")
            title = row.get("title", "—")
            st.markdown(f"**[{title}]({url})**" if url else f"**{title}**")
            desc = str(row.get("description") or "")
            if desc:
                st.caption(desc[:160] + "…" if len(desc) > 160 else desc)
        with dc:
            d = row.get("date")
            st.markdown(f"`{d.strftime('%d %b %Y') if pd.notna(d) else '—'}`")
        with pc:
            pc_count = row.get("podcast_count", 0)
            st.markdown(
                ICO_MIC_SM + f" {pc_count}" if pc_count else "—",
                unsafe_allow_html=True,
            )
    st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 5 — Podcasts
# ═══════════════════════════════════════════════════════════════════════════════
if not df_pod.empty:
    section_title(ICO_MIC, "Podcasts")

    lang_filter = st.radio("Language", ["All", "English", "Arabic"], horizontal=True)
    df_pod_show = df_pod.copy()
    if lang_filter == "English":
        df_pod_show = df_pod_show[df_pod_show["language"] == "en"]
    elif lang_filter == "Arabic":
        df_pod_show = df_pod_show[df_pod_show["language"] == "ar"]

    pod_search = st.text_input("Search podcasts", placeholder="Filter by title…")
    if pod_search:
        df_pod_show = df_pod_show[
            df_pod_show["title"].str.contains(pod_search, case=False, na=False)
        ]

    st.caption(f"Showing {len(df_pod_show)} of {len(df_pod)} episodes")

    for _, pod in df_pod_show.head(100).iterrows():
        with st.expander(f"{pod.get('title', '—')}  |  {pod.get('date', '')}"):
            col_a, col_b = st.columns([3, 1])
            with col_a:
                if pod.get("pr_title"):
                    st.markdown(ICO_LINK + f" **Linked PR:** {pod['pr_title']}", unsafe_allow_html=True)
                if pod.get("url"):
                    st.markdown(f"[Open on RSS.com]({pod['url']})")
                if pod.get("audio_url"):
                    st.audio(pod["audio_url"])
            with col_b:
                if pod.get("duration"):
                    st.metric("Duration", pod["duration"])
                st.metric("Saved vs old cost", f"AED {COST_PER_PODCAST_OLD_AED:,}")

    st.divider()

# ═══════════════════════════════════════════════════════════════════════════════
# ROW 6 — Scrape Log
# ═══════════════════════════════════════════════════════════════════════════════
with st.expander("Scrape history"):
    if scrape_log:
        df_log = pd.DataFrame(scrape_log)
        st.dataframe(
            df_log[["ran_at", "prs_found", "pods_found", "status", "notes"]],
            use_container_width=True,
        )
    else:
        st.info("No scrape runs recorded yet.")

st.markdown(
    '
'
    'DEWA PR Tracker · Playwright + SQLite + Streamlit
',
    unsafe_allow_html=True,
)

-# ── CSS — hide Streamlit chrome (Deploy button, footer, menu) ─────────────────

+# ── CSS ───────────────────────────────────────────────────────────────────────

-    /* Hide Deploy button and top-right toolbar */

-    [data-testid="stToolbar"]         { display: none !important; }

-    [data-testid="stDecoration"]      { display: none !important; }

-    [data-testid="stHeader"]          { display: none !important; }

-    header                            { display: none !important; }

-    #MainMenu                         { visibility: hidden !important; }

-    footer                            { visibility: hidden !important; }

+    header, #MainMenu, footer      { display:none !important; visibility:hidden !important; }

-    [data-testid="stMetric"]          { background:#1e293b; border-radius:10px; padding:14px 18px; }

-    [data-testid="stMetricLabel"]     { color:#94a3b8 !important; font-size:.8rem; }

-    [data-testid="stMetricValue"]     { color:#e2e8f0 !important; }

+    body, .stApp                   { background:#0a0f1a; }

+    h1, h2, h3, h4                 { color:#e2e8f0 !important; }

+    p, li, label                   { color:#94a3b8; }

+    .stDivider                     { border-color:#1e293b !important; }

-    h1, h2, h3                        { color:#e2e8f0 !important; }

-    .cost-old  { color:#ff6b6b; font-size:1.5rem; font-weight:700; }

-    .cost-new  { color:#51cf66; font-size:1.5rem; font-weight:700; }

-    .savings   { color:#ffd43b; font-size:2.2rem; font-weight:700; }

-    .roi-pct   { color:#00b4d8; font-size:1.8rem; font-weight:700; }

+    [data-testid="stSidebar"]      { background:#0d1424 !important; border-right:1px solid #1e293b; }

+    [data-testid="stSidebar"] *    { color:#94a3b8 !important; }

+    [data-testid="stSidebar"] .stButton > button {

+        background:#1e293b; border:1px solid #334155; color:#e2e8f0 !important;

+        border-radius:8px; transition:all .2s;

+    [data-testid="stSidebar"] .stButton > button:hover { background:#334155; border-color:#00b4d8; }

-    table.roi-table { width:100%; border-collapse:collapse; font-size:.88rem; }

+    /* ── KPI cards (custom HTML) ── */

+    .kpi-card:hover             { border-color:#334155; }

+        position:absolute; left:0; top:0; bottom:0;

+    .kpi-card.blue::before      { background:#00b4d8; }

+    .kpi-card.green::before     { background:#22c55e; }

+    .kpi-card.amber::before     { background:#f59e0b; }

+    .kpi-card.purple::before    { background:#a855f7; }

+    .kpi-card.red::before       { background:#f43f5e; }

+        font-size:.72rem; font-weight:600; letter-spacing:.06em;

+        text-transform:uppercase; color:#64748b; margin-bottom:6px;

+        display:flex; align-items:center; gap:6px;

+        font-size:2rem; font-weight:800; color:#f1f5f9; line-height:1;

+    .kpi-sub   { font-size:.72rem; color:#475569; margin-top:5px; }

+        background:#111827; border:1px solid #1e293b;

+        border-radius:14px; padding:20px 22px;

+    .cost-old  { color:#f43f5e; font-size:1.6rem; font-weight:800; }

+    .cost-new  { color:#22c55e; font-size:1.6rem; font-weight:800; }

+    .savings   { color:#fbbf24; font-size:2rem;   font-weight:800; }

+    .roi-pct   { color:#00b4d8; font-size:2rem;   font-weight:800; }

+    table.roi-table { width:100%; border-collapse:collapse; font-size:.83rem; }

-        background:#0f172a; color:#94a3b8;

-        padding:8px 12px; text-align:right; border-bottom:1px solid #1e293b;

+        background:#0a0f1a; color:#64748b; font-weight:600;

+        padding:9px 12px; text-align:right; border-bottom:1px solid #1e293b;

+        letter-spacing:.04em; text-transform:uppercase; font-size:.72rem;

-        padding:7px 12px; text-align:right;

-        border-bottom:1px solid #1e293b; color:#cbd5e1;

+        padding:8px 12px; text-align:right;

+        border-bottom:1px solid #111827; color:#94a3b8;

-    table.roi-table td:first-child { text-align:left; color:#e2e8f0; font-weight:500; }

-    table.roi-table tr.total-row td { background:#0f172a; color:#ffd43b; font-weight:700; border-top:2px solid #334155; }

-    table.roi-table tr:hover td    { background:#1e293b; }

+    table.roi-table td:first-child  { text-align:left; color:#e2e8f0; font-weight:600; }

+    table.roi-table tr:hover td     { background:#1e293b66; }

+        background:#0a0f1a; color:#fbbf24; font-weight:800;

+        border-top:2px solid #334155; font-size:.9rem;

-        font-size:1.6rem; font-weight:900; letter-spacing:.12em;

-        background:linear-gradient(90deg,#00b4d8,#0077b6);

+        font-size:1.8rem; font-weight:900; letter-spacing:.1em;

+        background:linear-gradient(90deg,#00b4d8,#0284c7);

-    .dewa-logo-sub { font-size:.65rem; color:#64748b; letter-spacing:.08em; text-transform:uppercase; }

+    .dewa-logo-sub { font-size:.6rem; color:#334155; letter-spacing:.1em; text-transform:uppercase; margin-top:2px; }

+    [data-testid="stTabs"] button          { color:#64748b !important; font-weight:500; }

+    [data-testid="stTabs"] button[aria-selected="true"] { color:#00b4d8 !important; border-bottom-color:#00b4d8 !important; }

+    [data-testid="stExpander"]             { background:#111827; border:1px solid #1e293b; border-radius:10px; }

+    [data-testid="stExpander"] summary     { color:#94a3b8 !important; }


Now replace the 5 default `st.metric` cards with custom KPI cards:


today       = datetime.today().date()
week_start  = today - timedelta(days=today.weekday())
month_start = today.replace(day=1)

if not filtered.empty and filtered["date"].notna().any():
    prs_today = len(filtered[filtered["date"].dt.date == today])
    prs_week  = len(filtered[filtered["date"].dt.date >= week_start])
    prs_month = len(filtered[filtered["date"].dt.date >= month_start])
else:
    prs_today = prs_week = prs_month = 0

c1, c2, c3, c4, c5 = st.columns(5)
with c1:
    st.metric("Total Press Releases", f"{len(filtered):,}")
with c2:
    st.metric("Podcasts (since May 2025)", f"{total_prs_nlm:,}")
with c3:
    st.metric("Published Today", prs_today)
with c4:
    st.metric("This Week", prs_week)
with c5:
    st.metric("This Month", prs_month)

st.divider()

today       = datetime.today().date()
week_start  = today - timedelta(days=today.weekday())
month_start = today.replace(day=1)
last_month  = (month_start - timedelta(days=1)).replace(day=1)

if not filtered.empty and filtered["date"].notna().any():
    prs_today      = len(filtered[filtered["date"].dt.date == today])
    prs_week       = len(filtered[filtered["date"].dt.date >= week_start])
    prs_month      = len(filtered[filtered["date"].dt.date >= month_start])
    prs_last_month = len(filtered[
        (filtered["date"].dt.date >= last_month) &
        (filtered["date"].dt.date < month_start)
    ])
else:
    prs_today = prs_week = prs_month = prs_last_month = 0

# Podcast coverage: % of filtered PRs that have at least one podcast
prs_with_pod   = len(filtered[filtered["podcast_count"] > 0]) if not filtered.empty else 0
coverage_pct   = (prs_with_pod / len(filtered) * 100) if len(filtered) > 0 else 0
month_delta    = prs_month - prs_last_month
delta_sign     = "+" if month_delta >= 0 else ""
delta_color    = "#22c55e" if month_delta >= 0 else "#f43f5e"

def kpi(color, icon_path, label, value, sub=""):
    icon = _svg(icon_path, size=14, color={
        "blue":"#00b4d8","green":"#22c55e","amber":"#f59e0b","purple":"#a855f7","red":"#f43f5e"
    }[color])
    return (
        f'
'
        f'
{icon}{label}
'
        f'
{value}
'
        f'{"
" + sub + "
" if sub else ""}'
        f'
'
    )

c1, c2, c3, c4, c5 = st.columns(5)
with c1:
    st.markdown(kpi("blue",
        '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>',
        "Total Press Releases", f"{len(filtered):,}",
        f"since {date_from.strftime('%d %b %Y')}",
    ), unsafe_allow_html=True)
with c2:
    st.markdown(kpi("green",
        '<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/>',
        "Podcast Coverage", f"{coverage_pct:.1f}%",
        f"{prs_with_pod} of {len(filtered)} PRs have a podcast",
    ), unsafe_allow_html=True)
with c3:
    st.markdown(kpi("amber",
        '<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>',
        "This Month", f"{prs_month:,}",
        f'{delta_sign}{month_delta} vs last month',
    ), unsafe_allow_html=True)
with c4:
    st.markdown(kpi("purple",
        '<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>',
        "This Week", f"{prs_week:,}",
        f"since {week_start.strftime('%d %b')}",
    ), unsafe_allow_html=True)
with c5:
    st.markdown(kpi("red",
        '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
        "Today", f"{prs_today:,}",
        today.strftime("%d %B %Y"),
    ), unsafe_allow_html=True)

st.markdown("
", unsafe_allow_html=True)
st.divider()