@@ -173,15 +173,76 @@ def on_get(self, req, resp):
173173
174174
175175class HealthResource :
176- """Health check endpoint."""
176+ """Health check endpoint with component status ."""
177177
178178 def on_get (self , req , resp ):
179- """Handle GET request to /health"""
179+ """
180+ Handle GET request to /health
181+
182+ Returns component-level health status:
183+ - cache: Status cache freshness
184+ - analytics: Analytics database status
185+ - overall: Aggregated health (healthy/degraded/unhealthy)
186+ """
187+ components = {}
188+ overall_status = 'healthy'
189+
190+ # Check status cache
191+ try :
192+ cache_data = read_cache ()
193+ if cache_data :
194+ cached_at = datetime .fromisoformat (cache_data .get ('cached_at' , '' ))
195+ cache_age = (datetime .now () - cached_at ).total_seconds ()
196+ is_stale = cache_age > CACHE_MAX_AGE
197+
198+ components ['cache' ] = {
199+ 'status' : 'degraded' if is_stale else 'healthy' ,
200+ 'cache_age_seconds' : round (cache_age , 1 ),
201+ 'is_stale' : is_stale ,
202+ 'last_status' : cache_data .get ('best_status' , {}).get ('status' )
203+ }
204+ if is_stale :
205+ overall_status = 'degraded'
206+ else :
207+ components ['cache' ] = {
208+ 'status' : 'unhealthy' ,
209+ 'error' : 'No cache data available'
210+ }
211+ overall_status = 'degraded'
212+ except Exception as e :
213+ components ['cache' ] = {
214+ 'status' : 'unhealthy' ,
215+ 'error' : str (e )
216+ }
217+ overall_status = 'degraded'
218+
219+ # Check analytics database
220+ try :
221+ from lib .analytics import get_db_connection , init_db
222+ init_db () # Ensure tables exist
223+ conn = get_db_connection ()
224+ cursor = conn .cursor ()
225+ cursor .execute ('SELECT COUNT(*) as count FROM status_checks' )
226+ count = cursor .fetchone ()['count' ]
227+ conn .close ()
228+
229+ components ['analytics' ] = {
230+ 'status' : 'healthy' ,
231+ 'total_checks' : count
232+ }
233+ except Exception as e :
234+ components ['analytics' ] = {
235+ 'status' : 'degraded' ,
236+ 'error' : str (e )
237+ }
238+ # Analytics failure doesn't affect overall health critically
239+
180240 resp .status = falcon .HTTP_200
181241 resp .media = {
182- 'status' : 'ok' ,
242+ 'status' : overall_status ,
183243 'service' : 'muni-status-api' ,
184- 'timestamp' : datetime .now ().isoformat ()
244+ 'timestamp' : datetime .now ().isoformat (),
245+ 'components' : components
185246 }
186247
187248
0 commit comments