1111from app import app , rt
1212from constants import *
1313from data import get_data
14- from components import _create_controls
14+ from components import _create_controls , create_batch_card , create_header
1515from charts import ChartManager
16+ from batch_stats import calculate_batch_stats
1617
1718
1819log = logging .getLogger ("anomstack" )
@@ -35,7 +36,7 @@ def index(request: Request):
3536 """
3637 )
3738
38- # Get batch stats
39+ # Calculate batch stats
3940 batch_stats = {}
4041 for batch_name in app .state .metric_batches :
4142 if batch_name not in app .state .df_cache :
@@ -50,52 +51,7 @@ def index(request: Request):
5051 else :
5152 df = app .state .df_cache [batch_name ]
5253
53- # Calculate average score and alert count for the batch, handling NaN values
54- avg_score = (
55- df ["metric_score" ].fillna (0 ).mean ()
56- if not df .empty and "metric_score" in df .columns
57- else 0
58- )
59- alert_count = (
60- df ["metric_alert" ].fillna (0 ).sum ()
61- if not df .empty and "metric_alert" in df .columns
62- else 0
63- )
64-
65- latest_timestamp = df ["metric_timestamp" ].max () if not df .empty else "No data"
66- if latest_timestamp != "No data" :
67- from datetime import datetime
68-
69- # Parse the ISO format timestamp and format it to show date and time
70- dt = datetime .fromisoformat (latest_timestamp .replace ("Z" , "+00:00" ))
71-
72- # Calculate time difference
73- now = datetime .now (dt .tzinfo )
74- diff_seconds = (now - dt ).total_seconds ()
75-
76- # Choose appropriate time unit
77- if diff_seconds < 3600 : # Less than 1 hour
78- minutes_ago = round (diff_seconds / 60 , 1 )
79- time_ago_str = (
80- f"{ minutes_ago :.1f} minute{ 's' if minutes_ago != 1 else '' } ago"
81- )
82- elif diff_seconds < 86400 : # Less than 24 hours
83- hours_ago = round (diff_seconds / 3600 , 1 )
84- time_ago_str = (
85- f"{ hours_ago :.1f} hour{ 's' if hours_ago != 1 else '' } ago"
86- )
87- else : # Days or more
88- days_ago = round (diff_seconds / 86400 , 1 )
89- time_ago_str = f"{ days_ago :.1f} day{ 's' if days_ago != 1 else '' } ago"
90-
91- latest_timestamp = f"{ time_ago_str } "
92-
93- batch_stats [batch_name ] = {
94- "unique_metrics" : len (df ["metric_name" ].unique ()),
95- "latest_timestamp" : latest_timestamp ,
96- "avg_score" : avg_score ,
97- "alert_count" : alert_count # Add alert count to stats
98- }
54+ batch_stats [batch_name ] = calculate_batch_stats (df , batch_name )
9955
10056 # Sort the metric batches by alert count (primary) and avg score (secondary)
10157 sorted_batch_names = sorted (
@@ -108,25 +64,7 @@ def index(request: Request):
10864
10965 main_content = Div (
11066 Card (
111- DivLAligned (
112- H2 (
113- "Anomstack" ,
114- P (
115- "Painless open source anomaly detection for your metrics 📈📉🚀" ,
116- cls = TextPresets .muted_sm ,
117- ),
118- cls = "mb-2" ,
119- ),
120- A (
121- DivLAligned (UkIcon ("github" )),
122- href = "https://github.com/andrewm4894/anomstack" ,
123- target = "_blank" ,
124- cls = "uk-button uk-button-secondary" ,
125- uk_tooltip = "View on GitHub" ,
126- ),
127- style = "justify-content: space-between;" ,
128- cls = "mb-6" ,
129- ),
67+ create_header (),
13068 # Show warning if no metric batches
13169 (
13270 Div (
@@ -141,73 +79,11 @@ def index(request: Request):
14179 cls = "mb-6" ,
14280 )
14381 if not app .state .metric_batches
144- else None
145- ),
146- (
147- Grid (
148- * [
149- Card (
150- DivLAligned (
151- Div (
152- H4 (batch_name , cls = "mb-2" ),
153- DivLAligned (
154- Div (
155- DivLAligned (
156- UkIcon ("activity" , cls = "text-blue-500" ),
157- P (
158- f"{ batch_stats [batch_name ]['unique_metrics' ]} metrics" ,
159- cls = TextPresets .muted_sm ,
160- ),
161- cls = "space-x-2" ,
162- ),
163- DivLAligned (
164- UkIcon ("clock" , cls = "text-green-500" ),
165- P (
166- f"{ batch_stats [batch_name ]['latest_timestamp' ]} " ,
167- cls = TextPresets .muted_sm ,
168- ),
169- cls = "space-x-2" ,
170- ),
171- DivLAligned (
172- UkIcon ("bar-chart" , cls = "text-purple-500" ),
173- P (
174- f"Avg Score: { batch_stats [batch_name ]['avg_score' ]:.1%} " ,
175- cls = TextPresets .muted_sm ,
176- ),
177- cls = "space-x-2" ,
178- ),
179- DivLAligned (
180- UkIcon ("alert-circle" , cls = "text-red-500" ),
181- P (
182- f"{ batch_stats [batch_name ]['alert_count' ]} alerts" ,
183- cls = TextPresets .muted_sm ,
184- ),
185- cls = "space-x-2" ,
186- ),
187- cls = "space-y-2" ,
188- )
189- ),
190- ),
191- Button (
192- batch_name ,
193- hx_get = f"/batch/{ batch_name } " ,
194- hx_push_url = f"/batch/{ batch_name } " ,
195- hx_target = "#main-content" ,
196- hx_indicator = "#loading" ,
197- cls = ButtonT .primary ,
198- ),
199- style = "justify-content: space-between;" ,
200- cls = "flex-row items-center" ,
201- ),
202- cls = "p-6 hover:border-primary transition-colors duration-200" ,
203- )
204- for batch_name in sorted_batch_names # Use sorted list instead of app.state.metric_batches
205- ],
82+ else Grid (
83+ * [create_batch_card (name , batch_stats [name ]) for name in sorted_batch_names ],
20684 cols = 3 ,
20785 gap = 4 ,
20886 )
209- if app .state .metric_batches
210- else None
21187 ),
21288 cls = "p-6" ,
21389 ),
0 commit comments