@@ -327,20 +327,30 @@ def importCSV(self, file_storage=None, json_content=None):
327327 return {"success" : True , "inserted" : row_count , "skipped_lines" : skipped }
328328
329329 def getTotals (self ):
330- """Get device totals by status."""
330+ """Get device totals by status.
331+
332+ Returns a list of 6 counts in the documented positional order:
333+ [all, connected, favorites, new, down, archived]
334+
335+ IMPORTANT: This order is a public API contract consumed by:
336+ - presence.php (reads indices 0-5)
337+ - /devices/totals/named (maps indices 0-5 to named fields)
338+ - homepage widget datav2 (reads /devices/totals indices)
339+ DO NOT change the order or add/remove fields without a breaking-change release.
340+ """
331341 conn = get_temp_db_connection ()
332342 sql = conn .cursor ()
333343
334- conditions = get_device_conditions ()
344+ all_conditions = get_device_conditions ()
335345
336- # Build sub-selects dynamically for all dictionary entries
337- sub_queries = []
338- for key , condition in conditions .items ():
339- # Make sure the alias is SQL-safe (no spaces or special chars)
340- alias = key .replace (" " , "_" ).lower ()
341- sub_queries .append (f'(SELECT COUNT(*) FROM DevicesView { condition } ) AS "{ alias } "' )
346+ # Only the 6 public fields, in documented positional order.
347+ # DO NOT change this order — it is a stable API contract.
348+ keys = ["all" , "connected" , "favorites" , "new" , "down" , "archived" ]
349+ sub_queries = [
350+ f'(SELECT COUNT(*) FROM DevicesView { all_conditions [key ]} ) AS "{ key } "'
351+ for key in keys
352+ ]
342353
343- # Join all sub-selects with commas
344354 query = "SELECT\n " + ",\n " .join (sub_queries )
345355 sql .execute (query )
346356 row = sql .fetchone ()
0 commit comments