Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions nutify/core/options/api_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,14 @@ def update_initial_setup_options():
getattr(variable_row, 'target_id', resolved_scope),
)

# monitoring_profile may have changed; drop the cached workspace profile
# so the next read reflects the new value instead of a stale 30s TTL.
try:
from core.settings import invalidate_workspace_profile_cache
invalidate_workspace_profile_cache()
except Exception:
pass

try:
if str(getattr(variable_row, 'timezone', 'UTC') or 'UTC') != previous_timezone:
runtime_timezone = pytz.timezone(variable_row.timezone or 'UTC')
Expand Down
54 changes: 40 additions & 14 deletions nutify/core/settings/settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Runtime settings loader and compatibility accessors."""

import os
import time
from pathlib import Path
import pytz
from datetime import datetime
Expand Down Expand Up @@ -395,27 +396,52 @@ def get_ups_realpower_nominal(target_id=None):
return None


_WORKSPACE_PROFILE_CACHE: dict = {"value": None, "expires": 0.0}
_WORKSPACE_PROFILE_TTL_SECONDS = 30.0


def get_workspace_monitoring_profile():
"""Get workspace monitoring profile from nutify_master_control."""
"""Get workspace monitoring profile from nutify_master_control.

The result is cached for a short TTL: this is called once per data row
by the energy/power/battery history builders, and the previous
implementation created a fresh SQLAlchemy engine and reflected the
entire schema on every call, which made single-day history requests
take several seconds.
"""
now = time.monotonic()
if _WORKSPACE_PROFILE_CACHE["value"] is not None and _WORKSPACE_PROFILE_CACHE["expires"] > now:
return _WORKSPACE_PROFILE_CACHE["value"]

value = 'single'
try:
row = _fetch_master_control_row('monitoring_profile')
if row is not None:
return _normalize_monitoring_profile(row[0])
value = _normalize_monitoring_profile(row[0])
except Exception as e:
logger.debug(f"Error getting monitoring profile from master control: {str(e)}")

try:
engine = create_engine(DB_URI)
inspector = inspect(engine)
if not _table_exists(inspector, 'ups_monitor_targets'):
try:
engine = create_engine(DB_URI)
inspector = inspect(engine)
if not _table_exists(inspector, 'ups_monitor_targets'):
return 'single'
with engine.connect() as connection:
enabled_targets = connection.execute(
text("SELECT COUNT(*) FROM ups_monitor_targets WHERE enabled = 1")
).scalar()
value = 'multi' if int(enabled_targets or 0) > 1 else 'single'
except Exception:
return 'single'
with engine.connect() as connection:
enabled_targets = connection.execute(
text("SELECT COUNT(*) FROM ups_monitor_targets WHERE enabled = 1")
).scalar()
return 'multi' if int(enabled_targets or 0) > 1 else 'single'
except Exception:
return 'single'

_WORKSPACE_PROFILE_CACHE["value"] = value
_WORKSPACE_PROFILE_CACHE["expires"] = now + _WORKSPACE_PROFILE_TTL_SECONDS
return value


def invalidate_workspace_profile_cache() -> None:
"""Drop the cached workspace monitoring profile (call after it changes)."""
_WORKSPACE_PROFILE_CACHE["value"] = None
_WORKSPACE_PROFILE_CACHE["expires"] = 0.0


def get_target_timezone(target_id=None):
Expand Down