axiom-coding-agent-setup 1.0.10 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/agent-browser/SKILL.md +55 -0
- package/.agents/skills/developing-with-streamlit/SKILL.md +220 -0
- package/.agents/skills/developing-with-streamlit/skills/building-streamlit-chat-ui/SKILL.md +195 -0
- package/.agents/skills/developing-with-streamlit/skills/building-streamlit-custom-components-v2/SKILL.md +233 -0
- package/.agents/skills/developing-with-streamlit/skills/building-streamlit-custom-components-v2/references/packaged-components.md +212 -0
- package/.agents/skills/developing-with-streamlit/skills/building-streamlit-custom-components-v2/references/state-sync.md +149 -0
- package/.agents/skills/developing-with-streamlit/skills/building-streamlit-custom-components-v2/references/theme-css-variables.md +243 -0
- package/.agents/skills/developing-with-streamlit/skills/building-streamlit-custom-components-v2/references/troubleshooting.md +121 -0
- package/.agents/skills/developing-with-streamlit/skills/building-streamlit-dashboards/SKILL.md +147 -0
- package/.agents/skills/developing-with-streamlit/skills/building-streamlit-multipage-apps/SKILL.md +218 -0
- package/.agents/skills/developing-with-streamlit/skills/choosing-streamlit-selection-widgets/SKILL.md +139 -0
- package/.agents/skills/developing-with-streamlit/skills/connecting-streamlit-to-snowflake/SKILL.md +188 -0
- package/.agents/skills/developing-with-streamlit/skills/creating-streamlit-themes/SKILL.md +486 -0
- package/.agents/skills/developing-with-streamlit/skills/displaying-streamlit-data/SKILL.md +199 -0
- package/.agents/skills/developing-with-streamlit/skills/improving-streamlit-design/SKILL.md +191 -0
- package/.agents/skills/developing-with-streamlit/skills/optimizing-streamlit-performance/SKILL.md +323 -0
- package/.agents/skills/developing-with-streamlit/skills/organizing-streamlit-code/SKILL.md +91 -0
- package/.agents/skills/developing-with-streamlit/skills/setting-up-streamlit-environment/SKILL.md +128 -0
- package/.agents/skills/developing-with-streamlit/skills/using-streamlit-cli/SKILL.md +165 -0
- package/.agents/skills/developing-with-streamlit/skills/using-streamlit-custom-components/SKILL.md +170 -0
- package/.agents/skills/developing-with-streamlit/skills/using-streamlit-layouts/SKILL.md +229 -0
- package/.agents/skills/developing-with-streamlit/skills/using-streamlit-markdown/SKILL.md +207 -0
- package/.agents/skills/developing-with-streamlit/skills/using-streamlit-session-state/SKILL.md +144 -0
- package/.agents/skills/developing-with-streamlit/templates/.gitattributes +3 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/README.md +188 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-companies/pyproject.toml +12 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-companies/streamlit_app.py +365 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute/pyproject.toml +12 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute/streamlit_app.py +461 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute-snowflake/.streamlit/secrets.toml.example +20 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute-snowflake/pyproject.toml +11 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute-snowflake/snowflake.yml +18 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute-snowflake/streamlit_app.py +527 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-feature-usage/pyproject.toml +12 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-feature-usage/streamlit_app.py +307 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics/pyproject.toml +12 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics/streamlit_app.py +426 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics-snowflake/.streamlit/secrets.toml.example +20 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics-snowflake/pyproject.toml +11 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics-snowflake/snowflake.yml +18 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics-snowflake/streamlit_app.py +463 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-seattle-weather/pyproject.toml +12 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-seattle-weather/streamlit_app.py +252 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers/pyproject.toml +12 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers/streamlit_app.py +342 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers-snowflake/.streamlit/secrets.toml.example +20 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers-snowflake/pyproject.toml +11 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers-snowflake/snowflake.yml +18 -0
- package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers-snowflake/streamlit_app.py +384 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/README.md +128 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/_configs/dracula.toml +39 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/_configs/github.toml +37 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/_configs/minimal.toml +39 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/_configs/nord.toml +39 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/_configs/snowflake.toml +42 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/_configs/solarized-light.toml +38 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/_configs/spotify.toml +34 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/_configs/stripe.toml +35 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/_shared/streamlit_app.py +336 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/_templates/pyproject.toml.tmpl +12 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/dracula/.streamlit/config.toml +39 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/dracula/pyproject.toml +10 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/dracula/streamlit_app.py +337 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/github/.streamlit/config.toml +37 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/github/pyproject.toml +10 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/github/streamlit_app.py +337 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/manage.py +332 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/minimal/.streamlit/config.toml +39 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/minimal/pyproject.toml +10 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/minimal/streamlit_app.py +337 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/nord/.streamlit/config.toml +39 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/nord/pyproject.toml +10 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/nord/streamlit_app.py +337 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/snowflake/.streamlit/config.toml +42 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/snowflake/pyproject.toml +10 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/snowflake/streamlit_app.py +337 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/solarized-light/.streamlit/config.toml +38 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/solarized-light/pyproject.toml +10 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/solarized-light/streamlit_app.py +337 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/spotify/.streamlit/config.toml +34 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/spotify/pyproject.toml +10 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/spotify/streamlit_app.py +337 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/stripe/.streamlit/config.toml +35 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/stripe/pyproject.toml +10 -0
- package/.agents/skills/developing-with-streamlit/templates/themes/stripe/streamlit_app.py +337 -0
- package/.agents/skills/fastapi/SKILL.md +436 -0
- package/.agents/skills/fastapi/references/dependencies.md +142 -0
- package/.agents/skills/fastapi/references/other-tools.md +76 -0
- package/.agents/skills/fastapi/references/streaming.md +105 -0
- package/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/gradio/SKILL.md +84 -0
- package/.agents/skills/gradio/references/api-signatures.md +115 -0
- package/.agents/skills/gradio/references/event-listeners.md +130 -0
- package/.agents/skills/gradio/references/examples.md +613 -0
- package/.agents/skills/ui-ux-pro-max/SKILL.md +659 -0
- package/.agents/skills/ui-ux-pro-max/data +1 -0
- package/.agents/skills/ui-ux-pro-max/scripts +1 -0
- package/.env.axiom +8 -0
- package/AGENTS.md +2 -1
- package/README.md +19 -3
- package/bin/cli.js +11 -1
- package/opencode.json +7 -4
- package/package.json +1 -1
- package/skills-lock.json +36 -0
- package/.agents/skills/n8n-patterns/SKILL.md +0 -272
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Metrics Dashboard Template (Snowflake Edition)
|
|
3
|
+
|
|
4
|
+
A comprehensive metrics dashboard demonstrating:
|
|
5
|
+
- Snowflake connection via st.connection("snowflake")
|
|
6
|
+
- Parameterized queries for safe data loading
|
|
7
|
+
- Time series visualization with Altair (line, area, bar, point charts)
|
|
8
|
+
- Metric cards with chart/table toggle and popover filters
|
|
9
|
+
- Time range filtering (1M, 6M, 1Y, QTD, YTD, All)
|
|
10
|
+
- Line options (Daily, 7-day MA)
|
|
11
|
+
|
|
12
|
+
This template creates synthetic data in Snowflake. You can:
|
|
13
|
+
1. Replace the synthetic data generation with your actual tables
|
|
14
|
+
2. Modify the queries to match your schema (using parameterized queries)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from datetime import date, timedelta
|
|
18
|
+
import pandas as pd
|
|
19
|
+
import streamlit as st
|
|
20
|
+
import altair as alt
|
|
21
|
+
|
|
22
|
+
st.set_page_config(
|
|
23
|
+
page_title="Metrics Dashboard (Snowflake)",
|
|
24
|
+
page_icon=":material/monitoring:",
|
|
25
|
+
layout="wide",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# =============================================================================
|
|
30
|
+
# Constants
|
|
31
|
+
# =============================================================================
|
|
32
|
+
|
|
33
|
+
TIME_RANGES = ["1M", "6M", "1Y", "QTD", "YTD", "All"]
|
|
34
|
+
CHART_HEIGHT = 300
|
|
35
|
+
|
|
36
|
+
# Metric configurations (used for synthetic data generation)
|
|
37
|
+
METRIC_CONFIGS = {
|
|
38
|
+
"users": {"base_value": 5000, "growth_rate": 0.002},
|
|
39
|
+
"sessions": {"base_value": 15000, "growth_rate": 0.003},
|
|
40
|
+
"revenue": {"base_value": 50000, "growth_rate": 0.001},
|
|
41
|
+
"conversions": {"base_value": 500, "growth_rate": 0.0015},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# =============================================================================
|
|
46
|
+
# Snowflake Connection and Data Loading
|
|
47
|
+
# =============================================================================
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_snowflake_connection():
|
|
51
|
+
"""Get Snowflake connection via st.connection.
|
|
52
|
+
|
|
53
|
+
Displays an error and stops the app if the connection fails.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
return st.connection("snowflake")
|
|
57
|
+
except Exception as e:
|
|
58
|
+
st.error(f"Failed to connect to Snowflake: {e}")
|
|
59
|
+
st.info(
|
|
60
|
+
"Make sure you have configured your Snowflake connection in "
|
|
61
|
+
"`.streamlit/secrets.toml` or via environment variables."
|
|
62
|
+
)
|
|
63
|
+
st.stop()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# SQL query template for synthetic data generation.
|
|
67
|
+
# Uses positional parameters (?) for Snowflake connector compatibility.
|
|
68
|
+
SYNTHETIC_DATA_QUERY = """
|
|
69
|
+
WITH date_series AS (
|
|
70
|
+
SELECT
|
|
71
|
+
DATEADD(day, -seq4(), CURRENT_DATE() - 1) AS ds
|
|
72
|
+
FROM TABLE(GENERATOR(ROWCOUNT => 730))
|
|
73
|
+
),
|
|
74
|
+
base_data AS (
|
|
75
|
+
SELECT
|
|
76
|
+
ds,
|
|
77
|
+
? * POWER(1 + ?, DATEDIFF(day, '2023-01-01', ds)) AS base_trend,
|
|
78
|
+
CASE WHEN DAYOFWEEK(ds) IN (0, 6) THEN 0.7 ELSE 1.0 END AS seasonality,
|
|
79
|
+
1 + (RANDOM() / 10000000000000000000.0 - 0.5) * 0.2 AS noise
|
|
80
|
+
FROM date_series
|
|
81
|
+
WHERE ds >= '2023-01-01'
|
|
82
|
+
)
|
|
83
|
+
SELECT
|
|
84
|
+
ds,
|
|
85
|
+
ROUND(base_trend * seasonality * noise, 2) AS daily_value,
|
|
86
|
+
ROUND(AVG(base_trend * seasonality * noise) OVER (
|
|
87
|
+
ORDER BY ds ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
|
|
88
|
+
), 2) AS value_7d_ma
|
|
89
|
+
FROM base_data
|
|
90
|
+
ORDER BY ds
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@st.cache_data(ttl=3600, show_spinner="Loading metrics from Snowflake...")
|
|
95
|
+
def load_metric_from_snowflake(metric_name: str) -> pd.DataFrame:
|
|
96
|
+
"""Load metric data from Snowflake using parameterized queries.
|
|
97
|
+
|
|
98
|
+
In production, replace the synthetic query with your actual table query:
|
|
99
|
+
|
|
100
|
+
PRODUCTION_QUERY = '''
|
|
101
|
+
SELECT ds, daily_value, value_7d_ma
|
|
102
|
+
FROM your_schema.your_metrics_table
|
|
103
|
+
WHERE metric_name = ?
|
|
104
|
+
ORDER BY ds
|
|
105
|
+
'''
|
|
106
|
+
|
|
107
|
+
df = conn.query(PRODUCTION_QUERY, params=[metric_name])
|
|
108
|
+
"""
|
|
109
|
+
conn = get_snowflake_connection()
|
|
110
|
+
config = METRIC_CONFIGS[metric_name]
|
|
111
|
+
|
|
112
|
+
# Use parameterized query with positional parameters (list)
|
|
113
|
+
df = conn.query(
|
|
114
|
+
SYNTHETIC_DATA_QUERY,
|
|
115
|
+
params=[config["base_value"], config["growth_rate"]],
|
|
116
|
+
)
|
|
117
|
+
df.columns = df.columns.str.lower() # Normalize column names
|
|
118
|
+
return df
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@st.cache_data(ttl=3600)
|
|
122
|
+
def load_all_metrics() -> dict[str, pd.DataFrame]:
|
|
123
|
+
"""Load all metrics from Snowflake."""
|
|
124
|
+
return {
|
|
125
|
+
"users": load_metric_from_snowflake("users"),
|
|
126
|
+
"sessions": load_metric_from_snowflake("sessions"),
|
|
127
|
+
"revenue": load_metric_from_snowflake("revenue"),
|
|
128
|
+
"conversions": load_metric_from_snowflake("conversions"),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# =============================================================================
|
|
133
|
+
# Chart Utilities
|
|
134
|
+
# =============================================================================
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def filter_by_time_range(df: pd.DataFrame, x_col: str, time_range: str) -> pd.DataFrame:
|
|
138
|
+
"""Filter dataframe by time range."""
|
|
139
|
+
if time_range == "All" or df.empty:
|
|
140
|
+
return df
|
|
141
|
+
|
|
142
|
+
df = df.copy()
|
|
143
|
+
df[x_col] = pd.to_datetime(df[x_col])
|
|
144
|
+
max_date = df[x_col].max()
|
|
145
|
+
|
|
146
|
+
if time_range == "1M":
|
|
147
|
+
min_date = max_date - timedelta(days=30)
|
|
148
|
+
elif time_range == "6M":
|
|
149
|
+
min_date = max_date - timedelta(days=180)
|
|
150
|
+
elif time_range == "1Y":
|
|
151
|
+
min_date = max_date - timedelta(days=365)
|
|
152
|
+
elif time_range == "QTD":
|
|
153
|
+
quarter_month = ((max_date.month - 1) // 3) * 3 + 1
|
|
154
|
+
min_date = pd.Timestamp(date(max_date.year, quarter_month, 1))
|
|
155
|
+
elif time_range == "YTD":
|
|
156
|
+
min_date = pd.Timestamp(date(max_date.year, 1, 1))
|
|
157
|
+
else:
|
|
158
|
+
return df
|
|
159
|
+
|
|
160
|
+
return df[df[x_col] >= min_date]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def render_line_chart(
|
|
164
|
+
df: pd.DataFrame,
|
|
165
|
+
x_col: str,
|
|
166
|
+
y_cols: list[str],
|
|
167
|
+
labels: list[str],
|
|
168
|
+
height: int = CHART_HEIGHT,
|
|
169
|
+
) -> alt.Chart:
|
|
170
|
+
"""Render a multi-line chart."""
|
|
171
|
+
# Melt for Altair
|
|
172
|
+
melted = df.melt(
|
|
173
|
+
id_vars=[x_col],
|
|
174
|
+
value_vars=y_cols,
|
|
175
|
+
var_name="series",
|
|
176
|
+
value_name="value",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Map to labels
|
|
180
|
+
label_map = dict(zip(y_cols, labels))
|
|
181
|
+
melted["series"] = melted["series"].map(label_map)
|
|
182
|
+
|
|
183
|
+
chart = (
|
|
184
|
+
alt.Chart(melted)
|
|
185
|
+
.mark_line()
|
|
186
|
+
.encode(
|
|
187
|
+
x=alt.X(f"{x_col}:T", title=None),
|
|
188
|
+
y=alt.Y("value:Q", title=None, scale=alt.Scale(zero=False)),
|
|
189
|
+
color=alt.Color("series:N", title=None, legend=alt.Legend(orient="bottom")),
|
|
190
|
+
strokeDash=alt.condition(
|
|
191
|
+
alt.datum.series == "7-day MA",
|
|
192
|
+
alt.value([5, 5]),
|
|
193
|
+
alt.value([0]),
|
|
194
|
+
),
|
|
195
|
+
tooltip=[
|
|
196
|
+
alt.Tooltip(f"{x_col}:T", title="Date", format="%Y-%m-%d"),
|
|
197
|
+
alt.Tooltip("series:N", title="Series"),
|
|
198
|
+
alt.Tooltip("value:Q", title="Value", format=",.0f"),
|
|
199
|
+
],
|
|
200
|
+
)
|
|
201
|
+
.properties(height=height)
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return chart
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def render_area_chart(
|
|
208
|
+
df: pd.DataFrame,
|
|
209
|
+
x_col: str,
|
|
210
|
+
y_cols: list[str],
|
|
211
|
+
labels: list[str],
|
|
212
|
+
height: int = CHART_HEIGHT,
|
|
213
|
+
) -> alt.Chart:
|
|
214
|
+
"""Render a stacked area chart."""
|
|
215
|
+
melted = df.melt(
|
|
216
|
+
id_vars=[x_col],
|
|
217
|
+
value_vars=y_cols,
|
|
218
|
+
var_name="series",
|
|
219
|
+
value_name="value",
|
|
220
|
+
)
|
|
221
|
+
label_map = dict(zip(y_cols, labels))
|
|
222
|
+
melted["series"] = melted["series"].map(label_map)
|
|
223
|
+
|
|
224
|
+
chart = (
|
|
225
|
+
alt.Chart(melted)
|
|
226
|
+
.mark_area(opacity=0.6, line=True)
|
|
227
|
+
.encode(
|
|
228
|
+
x=alt.X(f"{x_col}:T", title=None),
|
|
229
|
+
y=alt.Y("value:Q", title=None, scale=alt.Scale(zero=False)),
|
|
230
|
+
color=alt.Color("series:N", title=None, legend=alt.Legend(orient="bottom")),
|
|
231
|
+
tooltip=[
|
|
232
|
+
alt.Tooltip(f"{x_col}:T", title="Date", format="%Y-%m-%d"),
|
|
233
|
+
alt.Tooltip("series:N", title="Series"),
|
|
234
|
+
alt.Tooltip("value:Q", title="Value", format=",.0f"),
|
|
235
|
+
],
|
|
236
|
+
)
|
|
237
|
+
.properties(height=height)
|
|
238
|
+
)
|
|
239
|
+
return chart
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def render_bar_chart(
|
|
243
|
+
df: pd.DataFrame,
|
|
244
|
+
x_col: str,
|
|
245
|
+
y_cols: list[str],
|
|
246
|
+
labels: list[str],
|
|
247
|
+
height: int = CHART_HEIGHT,
|
|
248
|
+
) -> alt.Chart:
|
|
249
|
+
"""Render a bar chart (weekly aggregation for readability)."""
|
|
250
|
+
df = df.copy()
|
|
251
|
+
df[x_col] = pd.to_datetime(df[x_col])
|
|
252
|
+
df["week"] = df[x_col].dt.to_period("W").dt.start_time
|
|
253
|
+
|
|
254
|
+
# Aggregate by week
|
|
255
|
+
agg_df = df.groupby("week")[y_cols].mean().reset_index()
|
|
256
|
+
|
|
257
|
+
melted = agg_df.melt(
|
|
258
|
+
id_vars=["week"],
|
|
259
|
+
value_vars=y_cols,
|
|
260
|
+
var_name="series",
|
|
261
|
+
value_name="value",
|
|
262
|
+
)
|
|
263
|
+
label_map = dict(zip(y_cols, labels))
|
|
264
|
+
melted["series"] = melted["series"].map(label_map)
|
|
265
|
+
|
|
266
|
+
chart = (
|
|
267
|
+
alt.Chart(melted)
|
|
268
|
+
.mark_bar(opacity=0.8)
|
|
269
|
+
.encode(
|
|
270
|
+
x=alt.X("week:T", title=None),
|
|
271
|
+
y=alt.Y("value:Q", title=None, scale=alt.Scale(zero=False)),
|
|
272
|
+
color=alt.Color("series:N", title=None, legend=alt.Legend(orient="bottom")),
|
|
273
|
+
xOffset="series:N",
|
|
274
|
+
tooltip=[
|
|
275
|
+
alt.Tooltip("week:T", title="Week", format="%Y-%m-%d"),
|
|
276
|
+
alt.Tooltip("series:N", title="Series"),
|
|
277
|
+
alt.Tooltip("value:Q", title="Value", format=",.0f"),
|
|
278
|
+
],
|
|
279
|
+
)
|
|
280
|
+
.properties(height=height)
|
|
281
|
+
)
|
|
282
|
+
return chart
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def render_point_chart(
|
|
286
|
+
df: pd.DataFrame,
|
|
287
|
+
x_col: str,
|
|
288
|
+
y_cols: list[str],
|
|
289
|
+
labels: list[str],
|
|
290
|
+
height: int = CHART_HEIGHT,
|
|
291
|
+
) -> alt.Chart:
|
|
292
|
+
"""Render a scatter/point chart with trend line."""
|
|
293
|
+
melted = df.melt(
|
|
294
|
+
id_vars=[x_col],
|
|
295
|
+
value_vars=y_cols,
|
|
296
|
+
var_name="series",
|
|
297
|
+
value_name="value",
|
|
298
|
+
)
|
|
299
|
+
label_map = dict(zip(y_cols, labels))
|
|
300
|
+
melted["series"] = melted["series"].map(label_map)
|
|
301
|
+
|
|
302
|
+
points = (
|
|
303
|
+
alt.Chart(melted)
|
|
304
|
+
.mark_point(opacity=0.5, size=20)
|
|
305
|
+
.encode(
|
|
306
|
+
x=alt.X(f"{x_col}:T", title=None),
|
|
307
|
+
y=alt.Y("value:Q", title=None, scale=alt.Scale(zero=False)),
|
|
308
|
+
color=alt.Color("series:N", title=None, legend=alt.Legend(orient="bottom")),
|
|
309
|
+
tooltip=[
|
|
310
|
+
alt.Tooltip(f"{x_col}:T", title="Date", format="%Y-%m-%d"),
|
|
311
|
+
alt.Tooltip("series:N", title="Series"),
|
|
312
|
+
alt.Tooltip("value:Q", title="Value", format=",.0f"),
|
|
313
|
+
],
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Add trend line for 7-day MA only
|
|
318
|
+
trend = (
|
|
319
|
+
alt.Chart(melted[melted["series"] == "7-day MA"])
|
|
320
|
+
.mark_line(strokeDash=[5, 5], strokeWidth=2)
|
|
321
|
+
.encode(
|
|
322
|
+
x=alt.X(f"{x_col}:T"),
|
|
323
|
+
y=alt.Y("value:Q"),
|
|
324
|
+
color=alt.Color("series:N"),
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return (points + trend).properties(height=height)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
# =============================================================================
|
|
332
|
+
# Metric Card Component
|
|
333
|
+
# =============================================================================
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def metric_card(
|
|
337
|
+
title: str,
|
|
338
|
+
df: pd.DataFrame,
|
|
339
|
+
key_prefix: str,
|
|
340
|
+
chart_type: str = "line",
|
|
341
|
+
):
|
|
342
|
+
"""Display a metric card with chart/table toggle and popover filters.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
title: Card title
|
|
346
|
+
df: DataFrame with ds, daily_value, value_7d_ma columns
|
|
347
|
+
key_prefix: Unique prefix for widget keys
|
|
348
|
+
chart_type: One of "line", "area", "bar", "point"
|
|
349
|
+
"""
|
|
350
|
+
chart_renderers = {
|
|
351
|
+
"line": render_line_chart,
|
|
352
|
+
"area": render_area_chart,
|
|
353
|
+
"bar": render_bar_chart,
|
|
354
|
+
"point": render_point_chart,
|
|
355
|
+
}
|
|
356
|
+
render_chart = chart_renderers.get(chart_type, render_line_chart)
|
|
357
|
+
|
|
358
|
+
with st.container(border=True):
|
|
359
|
+
# Header row with title, view toggle, and filters
|
|
360
|
+
with st.container(
|
|
361
|
+
horizontal=True,
|
|
362
|
+
horizontal_alignment="distribute",
|
|
363
|
+
vertical_alignment="center",
|
|
364
|
+
):
|
|
365
|
+
st.markdown(f"**{title}**")
|
|
366
|
+
|
|
367
|
+
view_mode = st.segmented_control(
|
|
368
|
+
"View",
|
|
369
|
+
options=[":material/show_chart:", ":material/table:"],
|
|
370
|
+
default=":material/show_chart:",
|
|
371
|
+
key=f"{key_prefix}_view",
|
|
372
|
+
label_visibility="collapsed",
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
with st.popover("Filters", type="tertiary"):
|
|
376
|
+
line_options = st.pills(
|
|
377
|
+
"Lines",
|
|
378
|
+
options=["Daily", "7-day MA"],
|
|
379
|
+
default=["Daily", "7-day MA"],
|
|
380
|
+
selection_mode="multi",
|
|
381
|
+
key=f"{key_prefix}_lines",
|
|
382
|
+
)
|
|
383
|
+
time_range = st.segmented_control(
|
|
384
|
+
"Time range",
|
|
385
|
+
options=TIME_RANGES,
|
|
386
|
+
default="All",
|
|
387
|
+
key=f"{key_prefix}_time",
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# Apply filters
|
|
391
|
+
line_options = line_options or ["7-day MA"]
|
|
392
|
+
filtered_df = filter_by_time_range(df, "ds", time_range)
|
|
393
|
+
|
|
394
|
+
# Determine which columns to show
|
|
395
|
+
y_cols = []
|
|
396
|
+
labels = []
|
|
397
|
+
if "Daily" in line_options:
|
|
398
|
+
y_cols.append("daily_value")
|
|
399
|
+
labels.append("Daily")
|
|
400
|
+
if "7-day MA" in line_options:
|
|
401
|
+
y_cols.append("value_7d_ma")
|
|
402
|
+
labels.append("7-day MA")
|
|
403
|
+
|
|
404
|
+
# Render view
|
|
405
|
+
if "table" in (view_mode or ""):
|
|
406
|
+
st.dataframe(
|
|
407
|
+
filtered_df,
|
|
408
|
+
height=CHART_HEIGHT,
|
|
409
|
+
hide_index=True,
|
|
410
|
+
)
|
|
411
|
+
else:
|
|
412
|
+
if y_cols:
|
|
413
|
+
st.altair_chart(
|
|
414
|
+
render_chart(filtered_df, "ds", y_cols, labels),
|
|
415
|
+
)
|
|
416
|
+
else:
|
|
417
|
+
st.info("Select at least one line option.")
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# =============================================================================
|
|
421
|
+
# Page Header Component
|
|
422
|
+
# =============================================================================
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def render_page_header(title: str):
|
|
426
|
+
"""Render page header with title and reset button."""
|
|
427
|
+
with st.container(
|
|
428
|
+
horizontal=True, horizontal_alignment="distribute", vertical_alignment="center"
|
|
429
|
+
):
|
|
430
|
+
st.markdown(title)
|
|
431
|
+
if st.button(":material/restart_alt: Reset", type="tertiary"):
|
|
432
|
+
st.session_state.clear()
|
|
433
|
+
st.rerun()
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# =============================================================================
|
|
437
|
+
# Page Layout
|
|
438
|
+
# =============================================================================
|
|
439
|
+
|
|
440
|
+
# Load data from Snowflake
|
|
441
|
+
metrics_data = load_all_metrics()
|
|
442
|
+
|
|
443
|
+
# Page header
|
|
444
|
+
render_page_header("# :material/monitoring: Metrics Dashboard")
|
|
445
|
+
st.caption(":material/cloud: Powered by Snowflake")
|
|
446
|
+
|
|
447
|
+
# Row 1: Users and Sessions
|
|
448
|
+
row1 = st.columns(2)
|
|
449
|
+
|
|
450
|
+
with row1[0]:
|
|
451
|
+
metric_card("Active Users", metrics_data["users"], "users", chart_type="line")
|
|
452
|
+
|
|
453
|
+
with row1[1]:
|
|
454
|
+
metric_card("Sessions", metrics_data["sessions"], "sessions", chart_type="area")
|
|
455
|
+
|
|
456
|
+
# Row 2: Revenue and Conversions
|
|
457
|
+
row2 = st.columns(2)
|
|
458
|
+
|
|
459
|
+
with row2[0]:
|
|
460
|
+
metric_card("Revenue", metrics_data["revenue"], "revenue", chart_type="bar")
|
|
461
|
+
|
|
462
|
+
with row2[1]:
|
|
463
|
+
metric_card("Conversions", metrics_data["conversions"], "conversions", chart_type="point")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dashboard-seattle-weather"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "An example dashboard exploring the Seattle Weather dataset"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"altair>=5.5.0",
|
|
8
|
+
"pandas>=2.2.3",
|
|
9
|
+
"snowflake-connector-python>=3.3.0",
|
|
10
|
+
"streamlit[snowflake]>=1.54.0",
|
|
11
|
+
"vega-datasets>=0.9.0",
|
|
12
|
+
]
|