axiom-coding-agent-setup 1.0.9 → 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.
Files changed (107) hide show
  1. package/.agents/skills/agent-browser/SKILL.md +55 -0
  2. package/.agents/skills/developing-with-streamlit/SKILL.md +220 -0
  3. package/.agents/skills/developing-with-streamlit/skills/building-streamlit-chat-ui/SKILL.md +195 -0
  4. package/.agents/skills/developing-with-streamlit/skills/building-streamlit-custom-components-v2/SKILL.md +233 -0
  5. package/.agents/skills/developing-with-streamlit/skills/building-streamlit-custom-components-v2/references/packaged-components.md +212 -0
  6. package/.agents/skills/developing-with-streamlit/skills/building-streamlit-custom-components-v2/references/state-sync.md +149 -0
  7. package/.agents/skills/developing-with-streamlit/skills/building-streamlit-custom-components-v2/references/theme-css-variables.md +243 -0
  8. package/.agents/skills/developing-with-streamlit/skills/building-streamlit-custom-components-v2/references/troubleshooting.md +121 -0
  9. package/.agents/skills/developing-with-streamlit/skills/building-streamlit-dashboards/SKILL.md +147 -0
  10. package/.agents/skills/developing-with-streamlit/skills/building-streamlit-multipage-apps/SKILL.md +218 -0
  11. package/.agents/skills/developing-with-streamlit/skills/choosing-streamlit-selection-widgets/SKILL.md +139 -0
  12. package/.agents/skills/developing-with-streamlit/skills/connecting-streamlit-to-snowflake/SKILL.md +188 -0
  13. package/.agents/skills/developing-with-streamlit/skills/creating-streamlit-themes/SKILL.md +486 -0
  14. package/.agents/skills/developing-with-streamlit/skills/displaying-streamlit-data/SKILL.md +199 -0
  15. package/.agents/skills/developing-with-streamlit/skills/improving-streamlit-design/SKILL.md +191 -0
  16. package/.agents/skills/developing-with-streamlit/skills/optimizing-streamlit-performance/SKILL.md +323 -0
  17. package/.agents/skills/developing-with-streamlit/skills/organizing-streamlit-code/SKILL.md +91 -0
  18. package/.agents/skills/developing-with-streamlit/skills/setting-up-streamlit-environment/SKILL.md +128 -0
  19. package/.agents/skills/developing-with-streamlit/skills/using-streamlit-cli/SKILL.md +165 -0
  20. package/.agents/skills/developing-with-streamlit/skills/using-streamlit-custom-components/SKILL.md +170 -0
  21. package/.agents/skills/developing-with-streamlit/skills/using-streamlit-layouts/SKILL.md +229 -0
  22. package/.agents/skills/developing-with-streamlit/skills/using-streamlit-markdown/SKILL.md +207 -0
  23. package/.agents/skills/developing-with-streamlit/skills/using-streamlit-session-state/SKILL.md +144 -0
  24. package/.agents/skills/developing-with-streamlit/templates/.gitattributes +3 -0
  25. package/.agents/skills/developing-with-streamlit/templates/apps/README.md +188 -0
  26. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-companies/pyproject.toml +12 -0
  27. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-companies/streamlit_app.py +365 -0
  28. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute/pyproject.toml +12 -0
  29. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute/streamlit_app.py +461 -0
  30. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute-snowflake/.streamlit/secrets.toml.example +20 -0
  31. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute-snowflake/pyproject.toml +11 -0
  32. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute-snowflake/snowflake.yml +18 -0
  33. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-compute-snowflake/streamlit_app.py +527 -0
  34. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-feature-usage/pyproject.toml +12 -0
  35. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-feature-usage/streamlit_app.py +307 -0
  36. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics/pyproject.toml +12 -0
  37. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics/streamlit_app.py +426 -0
  38. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics-snowflake/.streamlit/secrets.toml.example +20 -0
  39. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics-snowflake/pyproject.toml +11 -0
  40. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics-snowflake/snowflake.yml +18 -0
  41. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-metrics-snowflake/streamlit_app.py +463 -0
  42. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-seattle-weather/pyproject.toml +12 -0
  43. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-seattle-weather/streamlit_app.py +252 -0
  44. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers/pyproject.toml +12 -0
  45. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers/streamlit_app.py +342 -0
  46. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers-snowflake/.streamlit/secrets.toml.example +20 -0
  47. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers-snowflake/pyproject.toml +11 -0
  48. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers-snowflake/snowflake.yml +18 -0
  49. package/.agents/skills/developing-with-streamlit/templates/apps/dashboard-stock-peers-snowflake/streamlit_app.py +384 -0
  50. package/.agents/skills/developing-with-streamlit/templates/themes/README.md +128 -0
  51. package/.agents/skills/developing-with-streamlit/templates/themes/_configs/dracula.toml +39 -0
  52. package/.agents/skills/developing-with-streamlit/templates/themes/_configs/github.toml +37 -0
  53. package/.agents/skills/developing-with-streamlit/templates/themes/_configs/minimal.toml +39 -0
  54. package/.agents/skills/developing-with-streamlit/templates/themes/_configs/nord.toml +39 -0
  55. package/.agents/skills/developing-with-streamlit/templates/themes/_configs/snowflake.toml +42 -0
  56. package/.agents/skills/developing-with-streamlit/templates/themes/_configs/solarized-light.toml +38 -0
  57. package/.agents/skills/developing-with-streamlit/templates/themes/_configs/spotify.toml +34 -0
  58. package/.agents/skills/developing-with-streamlit/templates/themes/_configs/stripe.toml +35 -0
  59. package/.agents/skills/developing-with-streamlit/templates/themes/_shared/streamlit_app.py +336 -0
  60. package/.agents/skills/developing-with-streamlit/templates/themes/_templates/pyproject.toml.tmpl +12 -0
  61. package/.agents/skills/developing-with-streamlit/templates/themes/dracula/.streamlit/config.toml +39 -0
  62. package/.agents/skills/developing-with-streamlit/templates/themes/dracula/pyproject.toml +10 -0
  63. package/.agents/skills/developing-with-streamlit/templates/themes/dracula/streamlit_app.py +337 -0
  64. package/.agents/skills/developing-with-streamlit/templates/themes/github/.streamlit/config.toml +37 -0
  65. package/.agents/skills/developing-with-streamlit/templates/themes/github/pyproject.toml +10 -0
  66. package/.agents/skills/developing-with-streamlit/templates/themes/github/streamlit_app.py +337 -0
  67. package/.agents/skills/developing-with-streamlit/templates/themes/manage.py +332 -0
  68. package/.agents/skills/developing-with-streamlit/templates/themes/minimal/.streamlit/config.toml +39 -0
  69. package/.agents/skills/developing-with-streamlit/templates/themes/minimal/pyproject.toml +10 -0
  70. package/.agents/skills/developing-with-streamlit/templates/themes/minimal/streamlit_app.py +337 -0
  71. package/.agents/skills/developing-with-streamlit/templates/themes/nord/.streamlit/config.toml +39 -0
  72. package/.agents/skills/developing-with-streamlit/templates/themes/nord/pyproject.toml +10 -0
  73. package/.agents/skills/developing-with-streamlit/templates/themes/nord/streamlit_app.py +337 -0
  74. package/.agents/skills/developing-with-streamlit/templates/themes/snowflake/.streamlit/config.toml +42 -0
  75. package/.agents/skills/developing-with-streamlit/templates/themes/snowflake/pyproject.toml +10 -0
  76. package/.agents/skills/developing-with-streamlit/templates/themes/snowflake/streamlit_app.py +337 -0
  77. package/.agents/skills/developing-with-streamlit/templates/themes/solarized-light/.streamlit/config.toml +38 -0
  78. package/.agents/skills/developing-with-streamlit/templates/themes/solarized-light/pyproject.toml +10 -0
  79. package/.agents/skills/developing-with-streamlit/templates/themes/solarized-light/streamlit_app.py +337 -0
  80. package/.agents/skills/developing-with-streamlit/templates/themes/spotify/.streamlit/config.toml +34 -0
  81. package/.agents/skills/developing-with-streamlit/templates/themes/spotify/pyproject.toml +10 -0
  82. package/.agents/skills/developing-with-streamlit/templates/themes/spotify/streamlit_app.py +337 -0
  83. package/.agents/skills/developing-with-streamlit/templates/themes/stripe/.streamlit/config.toml +35 -0
  84. package/.agents/skills/developing-with-streamlit/templates/themes/stripe/pyproject.toml +10 -0
  85. package/.agents/skills/developing-with-streamlit/templates/themes/stripe/streamlit_app.py +337 -0
  86. package/.agents/skills/fastapi/SKILL.md +436 -0
  87. package/.agents/skills/fastapi/references/dependencies.md +142 -0
  88. package/.agents/skills/fastapi/references/other-tools.md +76 -0
  89. package/.agents/skills/fastapi/references/streaming.md +105 -0
  90. package/.agents/skills/frontend-design/LICENSE.txt +177 -0
  91. package/.agents/skills/frontend-design/SKILL.md +42 -0
  92. package/.agents/skills/gradio/SKILL.md +84 -0
  93. package/.agents/skills/gradio/references/api-signatures.md +115 -0
  94. package/.agents/skills/gradio/references/event-listeners.md +130 -0
  95. package/.agents/skills/gradio/references/examples.md +613 -0
  96. package/.agents/skills/ui-ux-pro-max/SKILL.md +659 -0
  97. package/.agents/skills/ui-ux-pro-max/data +1 -0
  98. package/.agents/skills/ui-ux-pro-max/scripts +1 -0
  99. package/.agents/stack.md +187 -187
  100. package/.env.axiom +8 -0
  101. package/AGENTS.md +2 -1
  102. package/README.md +19 -3
  103. package/bin/cli.js +11 -1
  104. package/opencode.json +12 -4
  105. package/package.json +1 -1
  106. package/skills-lock.json +36 -0
  107. package/.agents/skills/n8n-patterns/SKILL.md +0 -272
@@ -0,0 +1,527 @@
1
+ """
2
+ Compute/Resource Dashboard Template (Snowflake Edition)
3
+
4
+ A resource consumption dashboard demonstrating:
5
+ - Snowflake connection via st.connection("snowflake")
6
+ - Parameterized queries for safe data loading
7
+ - Multiple metric cards in a grid layout
8
+ - @st.fragment for independent widget updates
9
+ - Popover filters for each metric card
10
+ - Chart/table view toggle
11
+ - Time range filtering (1M, 6M, 1Y, QTD, YTD, All)
12
+
13
+ This template uses synthetic data generated in Snowflake. Replace the
14
+ synthetic queries with your actual table queries in production.
15
+ """
16
+
17
+ from datetime import date, timedelta
18
+ import re
19
+ import pandas as pd
20
+ import streamlit as st
21
+ import altair as alt
22
+
23
+ st.set_page_config(
24
+ page_title="Compute Dashboard (Snowflake)",
25
+ page_icon=":material/bolt:",
26
+ layout="wide",
27
+ )
28
+
29
+
30
+ # =============================================================================
31
+ # Constants
32
+ # =============================================================================
33
+
34
+ TIME_RANGES = ["1M", "6M", "1Y", "QTD", "YTD", "All"]
35
+ ACCOUNT_TYPES = ["Paying", "Trial", "Internal"]
36
+ INSTANCE_TYPES = ["Standard", "High Memory", "High CPU", "GPU"]
37
+ REGIONS = ["us-west-2", "us-east-1", "eu-west-1", "ap-northeast-1"]
38
+ CHART_HEIGHT = 350
39
+
40
+ # Base values for synthetic data generation
41
+ BASE_VALUES = {
42
+ "account_type": {"Paying": 8000, "Trial": 2000, "Internal": 1000},
43
+ "instance_type": {"Standard": 5000, "High Memory": 3000, "High CPU": 2000, "GPU": 1500},
44
+ "region": {"us-west-2": 4000, "us-east-1": 3500, "eu-west-1": 2500, "ap-northeast-1": 1500},
45
+ }
46
+
47
+
48
+ # =============================================================================
49
+ # Snowflake Connection and Data Loading
50
+ # =============================================================================
51
+
52
+
53
+ def get_snowflake_connection():
54
+ """Get Snowflake connection via st.connection.
55
+
56
+ Displays an error and stops the app if the connection fails.
57
+ """
58
+ try:
59
+ return st.connection("snowflake")
60
+ except Exception as e:
61
+ st.error(f"Failed to connect to Snowflake: {e}")
62
+ st.info(
63
+ "Make sure you have configured your Snowflake connection in "
64
+ "`.streamlit/secrets.toml` or via environment variables."
65
+ )
66
+ st.stop()
67
+
68
+
69
+ # =============================================================================
70
+ # IMPORTANT: Use parameterized queries in production
71
+ # =============================================================================
72
+ #
73
+ # This demo uses synthetic data generated via SQL. In production, always use
74
+ # parameterized queries to prevent SQL injection:
75
+ #
76
+ # # GOOD: Parameterized query (safe)
77
+ # conn = st.connection("snowflake")
78
+ # df = conn.query(
79
+ # "SELECT * FROM metrics WHERE category = :category AND ds >= :start_date",
80
+ # params={"category": selected_category, "start_date": start_date}
81
+ # )
82
+ #
83
+ # # BAD: f-string interpolation (SQL injection risk)
84
+ # df = conn.query(f"SELECT * FROM metrics WHERE category = '{user_input}'")
85
+ #
86
+ # The synthetic data generation below uses f-strings only because the values
87
+ # are hardcoded constants, not user input. Never use f-strings with user input.
88
+
89
+ def _validate_sql_identifier(name: str) -> str:
90
+ """Validate that a string is a safe SQL identifier (letters, digits, underscores).
91
+
92
+ Raises ValueError if the name contains unexpected characters. This prevents
93
+ SQL injection if the function is ever modified to accept dynamic input.
94
+ """
95
+ if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name):
96
+ raise ValueError(f"Invalid SQL identifier: {name!r}")
97
+ return name
98
+
99
+
100
+ def build_synthetic_query(category_col: str, categories: list[str], base_values: dict[str, int]) -> str:
101
+ """Build SQL query for synthetic data.
102
+
103
+ WARNING: This function uses f-strings for demo purposes only.
104
+ The categories are hardcoded constants defined in this file, not user input.
105
+ In production, always use parameterized queries with conn.query(..., params={}).
106
+ """
107
+ # Validate the column name used as a SQL identifier (appears unquoted in SQL)
108
+ _validate_sql_identifier(category_col)
109
+
110
+ # Category values appear as string literals in SQL VALUES clause.
111
+ # Escape single quotes to prevent SQL injection.
112
+ safe_categories = [cat.replace("'", "''") for cat in categories]
113
+
114
+ # Build VALUES clause for categories with their base values
115
+ values_rows = ", ".join(
116
+ f"('{cat}', {base_values.get(orig, 1000)})"
117
+ for cat, orig in zip(safe_categories, categories)
118
+ )
119
+
120
+ return f"""
121
+ WITH categories AS (
122
+ SELECT column1 AS category, column2 AS base_val
123
+ FROM VALUES {values_rows}
124
+ ),
125
+ date_series AS (
126
+ SELECT DATEADD(day, -seq4(), CURRENT_DATE() - 1) AS ds
127
+ FROM TABLE(GENERATOR(ROWCOUNT => 730))
128
+ ),
129
+ base_data AS (
130
+ SELECT
131
+ ds,
132
+ category,
133
+ base_val * POWER(1.002, DATEDIFF(day, DATEADD(year, -2, CURRENT_DATE()), ds)) AS base_trend,
134
+ CASE WHEN DAYOFWEEK(ds) IN (0, 6) THEN 0.4 ELSE 1.0 END AS seasonality,
135
+ 1 + (RANDOM() / 10000000000000000000.0 - 0.5) * 0.4 AS noise
136
+ FROM date_series
137
+ CROSS JOIN categories
138
+ WHERE ds >= DATEADD(year, -2, CURRENT_DATE())
139
+ )
140
+ SELECT
141
+ ds,
142
+ category AS {category_col},
143
+ GREATEST(0, ROUND(base_trend * seasonality * noise, 2)) AS daily_credits,
144
+ ROUND(AVG(GREATEST(0, base_trend * seasonality * noise)) OVER (
145
+ PARTITION BY category
146
+ ORDER BY ds ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
147
+ ), 2) AS credits_7d_ma
148
+ FROM base_data
149
+ ORDER BY ds, {category_col}
150
+ """
151
+
152
+
153
+ @st.cache_data(ttl=3600, show_spinner="Loading account type data...")
154
+ def load_account_type_data() -> pd.DataFrame:
155
+ """Load credits by account type from Snowflake."""
156
+ conn = get_snowflake_connection()
157
+ query = build_synthetic_query("account_type", ACCOUNT_TYPES, BASE_VALUES["account_type"])
158
+ df = conn.query(query)
159
+ df.columns = df.columns.str.lower()
160
+ return df
161
+
162
+
163
+ @st.cache_data(ttl=3600, show_spinner="Loading instance type data...")
164
+ def load_instance_type_data() -> pd.DataFrame:
165
+ """Load credits by instance type from Snowflake."""
166
+ conn = get_snowflake_connection()
167
+ query = build_synthetic_query("instance_type", INSTANCE_TYPES, BASE_VALUES["instance_type"])
168
+ df = conn.query(query)
169
+ df.columns = df.columns.str.lower()
170
+ return df
171
+
172
+
173
+ @st.cache_data(ttl=3600, show_spinner="Loading region data...")
174
+ def load_region_data() -> pd.DataFrame:
175
+ """Load credits by region from Snowflake."""
176
+ conn = get_snowflake_connection()
177
+ query = build_synthetic_query("region", REGIONS, BASE_VALUES["region"])
178
+ df = conn.query(query)
179
+ df.columns = df.columns.str.lower()
180
+ return df
181
+
182
+
183
+ # =============================================================================
184
+ # Chart Utilities
185
+ # =============================================================================
186
+
187
+
188
+ def filter_by_time_range(df: pd.DataFrame, x_col: str, time_range: str) -> pd.DataFrame:
189
+ """Filter dataframe by time range."""
190
+ if time_range == "All" or df.empty:
191
+ return df
192
+
193
+ df = df.copy()
194
+ df[x_col] = pd.to_datetime(df[x_col])
195
+ max_date = df[x_col].max()
196
+
197
+ if time_range == "1M":
198
+ min_date = max_date - timedelta(days=30)
199
+ elif time_range == "6M":
200
+ min_date = max_date - timedelta(days=180)
201
+ elif time_range == "1Y":
202
+ min_date = max_date - timedelta(days=365)
203
+ elif time_range == "QTD":
204
+ quarter_month = ((max_date.month - 1) // 3) * 3 + 1
205
+ min_date = pd.Timestamp(date(max_date.year, quarter_month, 1))
206
+ elif time_range == "YTD":
207
+ min_date = pd.Timestamp(date(max_date.year, 1, 1))
208
+ else:
209
+ return df
210
+
211
+ return df[df[x_col] >= min_date]
212
+
213
+
214
+ def create_line_chart(
215
+ df: pd.DataFrame,
216
+ x_col: str,
217
+ y_col: str,
218
+ color_col: str,
219
+ height: int,
220
+ show_percent: bool = False,
221
+ ) -> alt.Chart:
222
+ """Create a line chart."""
223
+ y_format = ".1%" if show_percent else ",.0f"
224
+
225
+ return (
226
+ alt.Chart(df)
227
+ .mark_line()
228
+ .encode(
229
+ x=alt.X(f"{x_col}:T", title=None),
230
+ y=alt.Y(f"{y_col}:Q", title="Credits", axis=alt.Axis(format=y_format)),
231
+ color=alt.Color(f"{color_col}:N", legend=alt.Legend(orient="bottom")),
232
+ tooltip=[
233
+ alt.Tooltip(f"{x_col}:T", title="Date", format="%Y-%m-%d"),
234
+ alt.Tooltip(f"{color_col}:N", title=color_col.replace("_", " ").title()),
235
+ alt.Tooltip(f"{y_col}:Q", title="Credits", format=y_format),
236
+ ],
237
+ )
238
+ .properties(height=height)
239
+ .interactive()
240
+ )
241
+
242
+
243
+ def create_bar_chart(
244
+ df: pd.DataFrame,
245
+ x_col: str,
246
+ y_col: str,
247
+ color_col: str,
248
+ height: int,
249
+ show_percent: bool = False,
250
+ ) -> alt.Chart:
251
+ """Create a stacked bar chart."""
252
+ y_format = ".1%" if show_percent else ",.0f"
253
+
254
+ return (
255
+ alt.Chart(df)
256
+ .mark_bar()
257
+ .encode(
258
+ x=alt.X(f"{x_col}:T", title=None),
259
+ y=alt.Y(
260
+ f"{y_col}:Q",
261
+ title="Credits",
262
+ stack="normalize" if show_percent else True,
263
+ axis=alt.Axis(format=y_format),
264
+ ),
265
+ color=alt.Color(f"{color_col}:N", legend=alt.Legend(orient="bottom")),
266
+ tooltip=[
267
+ alt.Tooltip(f"{x_col}:T", title="Date", format="%Y-%m-%d"),
268
+ alt.Tooltip(f"{color_col}:N"),
269
+ alt.Tooltip(f"{y_col}:Q", format=",.0f"),
270
+ ],
271
+ )
272
+ .properties(height=height)
273
+ )
274
+
275
+
276
+ # =============================================================================
277
+ # Page Header Component
278
+ # =============================================================================
279
+
280
+
281
+ def render_page_header(title: str):
282
+ """Render page header with title and reset button."""
283
+ with st.container(
284
+ horizontal=True, horizontal_alignment="distribute", vertical_alignment="center"
285
+ ):
286
+ st.markdown(title)
287
+ if st.button(":material/restart_alt: Reset", type="tertiary"):
288
+ st.session_state.clear()
289
+ st.rerun()
290
+
291
+
292
+ # =============================================================================
293
+ # Metric Card Components (using @st.fragment)
294
+ # =============================================================================
295
+
296
+
297
+ @st.fragment
298
+ def account_type_metric():
299
+ """Account type metric card with independent state."""
300
+ data = load_account_type_data()
301
+
302
+ with st.container(border=True):
303
+ with st.container(horizontal=True, horizontal_alignment="distribute", vertical_alignment="center"):
304
+ st.markdown("**Credits by account type**")
305
+
306
+ view_mode = st.segmented_control(
307
+ "View",
308
+ options=[":material/show_chart:", ":material/table:"],
309
+ default=":material/show_chart:",
310
+ key="acct_view",
311
+ label_visibility="collapsed",
312
+ )
313
+
314
+ with st.popover("Filters", type="tertiary"):
315
+ selected_types = st.pills(
316
+ "Account types",
317
+ options=ACCOUNT_TYPES,
318
+ default=["Paying"],
319
+ selection_mode="multi",
320
+ key="acct_types",
321
+ )
322
+ line_options = st.pills(
323
+ "Lines",
324
+ options=["Daily", "7-day MA"],
325
+ default=["7-day MA"],
326
+ selection_mode="multi",
327
+ key="acct_lines",
328
+ )
329
+ chart_type = st.segmented_control(
330
+ "Chart type",
331
+ options=[":material/show_chart: Line", ":material/bar_chart: Bar"],
332
+ default=":material/show_chart: Line",
333
+ key="acct_chart",
334
+ )
335
+ show_percent = st.toggle(
336
+ "Show %", value=False, key="acct_pct",
337
+ disabled="Line" in (chart_type or ""),
338
+ )
339
+ time_range = st.segmented_control(
340
+ "Time range",
341
+ options=TIME_RANGES,
342
+ default="All",
343
+ key="acct_time",
344
+ )
345
+
346
+ # Filter data
347
+ selected_types = selected_types or ["Paying"]
348
+ line_options = line_options or ["7-day MA"]
349
+ filtered = data[data["account_type"].isin(selected_types)]
350
+ filtered = filter_by_time_range(filtered, "ds", time_range)
351
+
352
+ y_col = "credits_7d_ma" if "7-day MA" in line_options else "daily_credits"
353
+
354
+ if "table" in (view_mode or ""):
355
+ st.dataframe(filtered, height=CHART_HEIGHT, hide_index=True)
356
+ else:
357
+ if "Bar" in (chart_type or ""):
358
+ st.altair_chart(
359
+ create_bar_chart(filtered, "ds", y_col, "account_type", CHART_HEIGHT, show_percent),
360
+ )
361
+ else:
362
+ st.altair_chart(
363
+ create_line_chart(filtered, "ds", y_col, "account_type", CHART_HEIGHT),
364
+ )
365
+
366
+
367
+ @st.fragment
368
+ def instance_type_metric():
369
+ """Instance type metric card with independent state."""
370
+ data = load_instance_type_data()
371
+
372
+ with st.container(border=True):
373
+ with st.container(horizontal=True, horizontal_alignment="distribute", vertical_alignment="center"):
374
+ st.markdown("**Credits by instance type**")
375
+
376
+ view_mode = st.segmented_control(
377
+ "View",
378
+ options=[":material/show_chart:", ":material/table:"],
379
+ default=":material/show_chart:",
380
+ key="inst_view",
381
+ label_visibility="collapsed",
382
+ )
383
+
384
+ with st.popover("Filters", type="tertiary"):
385
+ selected_types = st.pills(
386
+ "Instance types",
387
+ options=INSTANCE_TYPES,
388
+ default=INSTANCE_TYPES,
389
+ selection_mode="multi",
390
+ key="inst_types",
391
+ )
392
+ line_options = st.pills(
393
+ "Lines",
394
+ options=["Daily", "7-day MA"],
395
+ default=["7-day MA"],
396
+ selection_mode="multi",
397
+ key="inst_lines",
398
+ )
399
+ chart_type = st.segmented_control(
400
+ "Chart type",
401
+ options=[":material/show_chart: Line", ":material/bar_chart: Bar"],
402
+ default=":material/show_chart: Line",
403
+ key="inst_chart",
404
+ )
405
+ show_percent = st.toggle(
406
+ "Show %", value=False, key="inst_pct",
407
+ disabled="Line" in (chart_type or ""),
408
+ )
409
+ time_range = st.segmented_control(
410
+ "Time range",
411
+ options=TIME_RANGES,
412
+ default="All",
413
+ key="inst_time",
414
+ )
415
+
416
+ # Filter data
417
+ selected_types = selected_types or INSTANCE_TYPES
418
+ line_options = line_options or ["7-day MA"]
419
+ filtered = data[data["instance_type"].isin(selected_types)]
420
+ filtered = filter_by_time_range(filtered, "ds", time_range)
421
+
422
+ y_col = "credits_7d_ma" if "7-day MA" in line_options else "daily_credits"
423
+
424
+ if "table" in (view_mode or ""):
425
+ st.dataframe(filtered, height=CHART_HEIGHT, hide_index=True)
426
+ else:
427
+ if "Bar" in (chart_type or ""):
428
+ st.altair_chart(
429
+ create_bar_chart(filtered, "ds", y_col, "instance_type", CHART_HEIGHT, show_percent),
430
+ )
431
+ else:
432
+ st.altair_chart(
433
+ create_line_chart(filtered, "ds", y_col, "instance_type", CHART_HEIGHT),
434
+ )
435
+
436
+
437
+ @st.fragment
438
+ def region_metric():
439
+ """Region metric card with independent state."""
440
+ data = load_region_data()
441
+
442
+ with st.container(border=True):
443
+ with st.container(horizontal=True, horizontal_alignment="distribute", vertical_alignment="center"):
444
+ st.markdown("**Credits by region**")
445
+
446
+ view_mode = st.segmented_control(
447
+ "View",
448
+ options=[":material/show_chart:", ":material/table:"],
449
+ default=":material/show_chart:",
450
+ key="region_view",
451
+ label_visibility="collapsed",
452
+ )
453
+
454
+ with st.popover("Filters", type="tertiary"):
455
+ selected_regions = st.pills(
456
+ "Regions",
457
+ options=REGIONS,
458
+ default=REGIONS,
459
+ selection_mode="multi",
460
+ key="region_select",
461
+ )
462
+ line_options = st.pills(
463
+ "Lines",
464
+ options=["Daily", "7-day MA"],
465
+ default=["7-day MA"],
466
+ selection_mode="multi",
467
+ key="region_lines",
468
+ )
469
+ chart_type = st.segmented_control(
470
+ "Chart type",
471
+ options=[":material/show_chart: Line", ":material/bar_chart: Bar"],
472
+ default=":material/bar_chart: Bar",
473
+ key="region_chart",
474
+ )
475
+ show_percent = st.toggle(
476
+ "Show %", value=False, key="region_pct",
477
+ disabled="Line" in (chart_type or ""),
478
+ )
479
+ time_range = st.segmented_control(
480
+ "Time range",
481
+ options=TIME_RANGES,
482
+ default="All",
483
+ key="region_time",
484
+ )
485
+
486
+ # Filter data
487
+ selected_regions = selected_regions or REGIONS
488
+ line_options = line_options or ["7-day MA"]
489
+ filtered = data[data["region"].isin(selected_regions)]
490
+ filtered = filter_by_time_range(filtered, "ds", time_range)
491
+
492
+ y_col = "credits_7d_ma" if "7-day MA" in line_options else "daily_credits"
493
+
494
+ if "table" in (view_mode or ""):
495
+ st.dataframe(filtered, height=CHART_HEIGHT, hide_index=True)
496
+ else:
497
+ if "Bar" in (chart_type or ""):
498
+ st.altair_chart(
499
+ create_bar_chart(filtered, "ds", y_col, "region", CHART_HEIGHT, show_percent),
500
+ )
501
+ else:
502
+ st.altair_chart(
503
+ create_line_chart(filtered, "ds", y_col, "region", CHART_HEIGHT),
504
+ )
505
+
506
+
507
+ # =============================================================================
508
+ # Page Layout
509
+ # =============================================================================
510
+
511
+ # Check Snowflake connection
512
+ get_snowflake_connection()
513
+
514
+ render_page_header("# :material/bolt: Compute Dashboard")
515
+ st.caption(":material/cloud: Powered by Snowflake")
516
+
517
+ # Row 1: Two metrics
518
+ col1, col2 = st.columns(2)
519
+
520
+ with col1:
521
+ account_type_metric()
522
+
523
+ with col2:
524
+ instance_type_metric()
525
+
526
+ # Row 2: One metric (full width for region breakdown)
527
+ region_metric()
@@ -0,0 +1,12 @@
1
+ [project]
2
+ name = "dashboard-feature-usage"
3
+ version = "1.0.0"
4
+ description = "A feature usage analytics dashboard with filtering and starter kits"
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "altair>=5.5.0",
8
+ "numpy>=1.26.0",
9
+ "pandas>=2.2.3",
10
+ "snowflake-connector-python>=3.3.0",
11
+ "streamlit[snowflake]>=1.54.0",
12
+ ]