from __future__ import annotations import json from pathlib import Path import pandas as pd import plotly.express as px import streamlit as st DATA_PATH = Path(__file__).with_name("public_stats.json") st.set_page_config( page_title="MapToSelf Public Stats", page_icon="MapToSelf", layout="wide", ) st.markdown( """ """, unsafe_allow_html=True, ) def load_data() -> dict: if not DATA_PATH.exists(): st.error("public_stats.json is missing. Run export_public_stats.py first.") st.stop() return json.loads(DATA_PATH.read_text(encoding="utf-8")) def df(items: list[dict]) -> pd.DataFrame: return pd.DataFrame(items or []) def metric_number(value: int | float | None) -> str: return f"{int(value or 0):,}".replace(",", " ") def line_chart(data: pd.DataFrame, x: str, y: str, title: str): if data.empty: st.info("No data yet.") return fig = px.line(data, x=x, y=y, markers=True, title=title) fig.update_layout(height=330, margin=dict(l=10, r=10, t=55, b=10)) st.plotly_chart(fig, use_container_width=True) def bar_chart(data: pd.DataFrame, x: str, y: str, title: str, color: str | None = None): if data.empty: st.info("No data yet.") return fig = px.bar(data, x=x, y=y, color=color, title=title) fig.update_layout(height=360, margin=dict(l=10, r=10, t=55, b=10)) st.plotly_chart(fig, use_container_width=True) data = load_data() project = data["project"] metrics = data["metrics"] series = data["series"] st.markdown( f"""

{project["name"]}

{project["description"]}

Public portfolio dashboard. Aggregated statistics only.

""", unsafe_allow_html=True, ) st.caption(f"Last updated: {data['generated_at']}") st.caption(data["public_note"]) cols = st.columns(5) cols[0].metric("Users", metric_number(metrics["users_total"])) cols[1].metric("New users, 30d", metric_number(metrics["new_30d"])) cols[2].metric("Reports generated", metric_number(metrics["reports_total"])) cols[3].metric("GPT interactions", metric_number(metrics["gpt_calls"])) cols[4].metric("Child charts", metric_number(metrics["child_charts"])) cols = st.columns(4) cols[0].metric("New users, 7d", metric_number(metrics["new_7d"])) cols[1].metric("Profiles completed", metric_number(metrics["profiles_complete"])) cols[2].metric("Natal charts", metric_number(metrics["with_chart"])) cols[3].metric("Reports, 7d", metric_number(metrics["reports_7d"])) st.divider() left, right = st.columns(2) with left: line_chart(df(series["users_by_day"]), "day", "users", "User growth over the last 30 days") with right: bar_chart(df(series["users_by_language"]), "language", "users", "Users by language") left, right = st.columns(2) with left: line_chart(df(series["reports_by_day"]), "day", "reports", "Reports generated over the last 30 days") with right: reports_by_type = df(series["reports_by_type"]) if not reports_by_type.empty: grouped_reports = ( reports_by_type.groupby("report_type", as_index=False)["reports"] .sum() .sort_values("reports", ascending=False) ) else: grouped_reports = reports_by_type bar_chart(grouped_reports, "report_type", "reports", "Reports by product area") st.subheader("Language distribution") lang_left, lang_right = st.columns(2) with lang_left: gpt_by_language = df(series["gpt_by_language"]) bar_chart(gpt_by_language, "language", "calls", "GPT interactions by language") with lang_right: reports_lang = df(series["reports_by_type"]) bar_chart(reports_lang, "language", "reports", "Reports by language", color="report_type") st.subheader("GPT-supported workflows") workflow = df(series["gpt_by_request_language"]) if workflow.empty: st.info("No workflow data yet.") else: bar_chart(workflow, "request_type", "calls", "GPT interactions by workflow and language", color="language") st.divider() st.subheader("What this project demonstrates") st.markdown( """ - A production Telegram bot with multilingual user flows in Russian, French and English. - Structured astrology calculations using Swiss Ephemeris and custom chart logic. - GPT-based long-form report generation with language-aware prompts. - SQLite-backed storage, safe aggregate analytics and private operations monitoring. - Cloud deployment on Hetzner with systemd services and a separate public dashboard. """ ) st.subheader("Stack") st.markdown( " ".join(f'{item}' for item in project["stack"]), unsafe_allow_html=True, )