# WebIndex.py from datetime import datetime from zoneinfo import ZoneInfo from collections import defaultdict import PersistentData TZ = ZoneInfo("Europe/Zurich") UTC = ZoneInfo("UTC") def _utc_to_local(ts_str): """Converts a UTC timestamp string to Europe/Zurich ISO string for Chart.js.""" try: dt = datetime.strptime(ts_str, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=UTC) return dt.astimezone(TZ).strftime("%Y-%m-%dT%H:%M:%S") except Exception: try: dt = datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S").replace(tzinfo=UTC) return dt.astimezone(TZ).strftime("%Y-%m-%dT%H:%M:%S") except Exception: return ts_str def _utc_sql_to_local(ts_str): """Converts a UTC SQLite timestamp string to Europe/Zurich ISO string.""" try: dt = datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S").replace(tzinfo=UTC) return dt.astimezone(TZ).strftime("%Y-%m-%dT%H:%M:%S") except Exception: return ts_str def on_tickets_updated(ui, count): """Called after each successful ticket update — notifies the dashboard.""" now = datetime.now(ZoneInfo("Europe/Zurich")).strftime("%H:%M:%S") ui.send_message("tickets_updated", {"count": count, "time": now}) def init_dashboard_interface(ui): """ Registers the bridge between index.html (dashboard) and Python logic. """ def handle_get_sensor_data(_, data): period = data.get("period", "1d") period_map = { "1d": ("-1d", "5m"), "1w": ("-7d", "15m"), "1m": ("-30d", "1h"), } start_from, aggr_window = period_map.get(period, ("-1d", "5m")) try: co2 = PersistentData.tsdb.read_samples("co2", start_from=start_from, aggr_window=aggr_window, aggr_func="mean") temp = PersistentData.tsdb.read_samples("temperature", start_from=start_from, aggr_window=aggr_window, aggr_func="mean") humi = PersistentData.tsdb.read_samples("humidity", start_from=start_from, aggr_window=aggr_window, aggr_func="mean") def to_list(samples): return [{"ts": s[1], "v": round(s[2], 2)} for s in (samples or [])] ui.send_message("sensor_data", { "period": period, "co2": to_list(co2), "temperature": to_list(temp), "humidity": to_list(humi), }) except Exception as e: print(f"ERROR fetching sensor data: {e}") ui.send_message("sensor_data", {"period": period, "co2": [], "temperature": [], "humidity": []}) def handle_get_ticket_filters(_, __): """Returns available filter options: selected orgs and types with selected priorities.""" orgs = PersistentData.db.read("organizations", condition="selected = 1 AND enabled = 1") or [] org_list = [{"id": str(o["id"]), "name": o["name"]} for o in sorted(orgs, key=lambda x: x["name"])] prio_rows = PersistentData.db.read("objects", condition="type = 'TICKET_PRIORITY' AND selected = 1") or [] types = sorted(set(p["subType"] for p in prio_rows if p.get("subType"))) ui.send_message("ticket_filters", {"orgs": org_list, "types": types}) def handle_get_ticket_history(_, data): """Returns TICKETS_TOTAL and TICKETS_FILTERED aggregated by timestamp.""" period = data.get("period", "1d") org_ids = set(str(x) for x in data.get("org_ids", [])) types = set(data.get("types", [])) period_hours = {"1d": 24, "1w": 168, "1m": 720} hours = period_hours.get(period, 24) try: rows = PersistentData.db.execute_sql(f""" SELECT timestamp, kind, value FROM history WHERE (kind LIKE 'TICKETS_TOTAL_%' OR kind LIKE 'TICKETS_FILTERED_%') AND timestamp >= datetime('now', '-{hours} hours') ORDER BY timestamp ASC """) or [] total_by_ts = defaultdict(float) filtered_by_ts = defaultdict(float) for row in rows: ts = row["timestamp"] kind = row["kind"] val = row["value"] parts = kind.split("_") if len(parts) < 4: continue org_id = parts[2] sub_type = parts[3] if org_ids and org_id not in org_ids: continue if types and sub_type not in types: continue if parts[1] == "TOTAL": total_by_ts[ts] += val elif parts[1] == "FILTERED": filtered_by_ts[ts] += val all_ts = sorted(total_by_ts.keys()) ui.send_message("ticket_history", { "period": period, "labels": [_utc_sql_to_local(ts) for ts in all_ts], "total": [round(total_by_ts.get(ts, 0), 2) for ts in all_ts], "filtered": [round(filtered_by_ts.get(ts, 0), 2) for ts in all_ts], }) except Exception as e: print(f"ERROR fetching ticket history: {e}") ui.send_message("ticket_history", {"period": period, "labels": [], "total": [], "filtered": []}) ui.on_message("get_sensor_data", handle_get_sensor_data) ui.on_message("get_ticket_filters", handle_get_ticket_filters) ui.on_message("get_ticket_history", handle_get_ticket_history)