scheduler / api /settings.py
umangchaudhry's picture
Upload 31 files
0d04b76 verified
Raw
History Blame Contribute Delete
5.14 kB
"""Settings routes β€” admin-editable runtime configuration."""
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from typing import Any
from core import data_manager
from core import settings_manager as settings
from core.scheduling_engine import normalize_time_str
from api.deps import require_admin_session
router = APIRouter(prefix="/api/settings", tags=["settings"])
class SettingsUpdate(BaseModel):
min_lead_time_hours: int | None = None
buffer_minutes: int | None = None
visit_levels: dict | None = None
on_call_start_hour: str | None = None
region_name: str | None = None
timezone: str | None = None
service_area: str | None = None
openai_model: str | None = None
def _audit(user: str, detail: str) -> None:
data_manager.log_action(user, "UPDATE_SETTING", detail)
@router.get("")
def get_settings(session: dict = Depends(require_admin_session)):
"""Return current effective values plus the per-key defaults."""
return {
"current": settings.get_all_settings(),
"defaults": settings.get_schema_defaults(),
}
@router.put("")
def update_settings(req: SettingsUpdate, session: dict = Depends(require_admin_session)):
current = settings.get_all_settings()
changes: list[str] = []
if req.min_lead_time_hours is not None and req.min_lead_time_hours != current["min_lead_time_hours"]:
if req.min_lead_time_hours < 0 or req.min_lead_time_hours > 168:
raise HTTPException(status_code=400, detail="min_lead_time_hours must be 0-168")
settings.set_setting("min_lead_time_hours", int(req.min_lead_time_hours))
changes.append(f"min_lead_time_hours: {current['min_lead_time_hours']} β†’ {req.min_lead_time_hours}")
if req.buffer_minutes is not None and req.buffer_minutes != current["buffer_minutes"]:
if req.buffer_minutes < 0 or req.buffer_minutes > 120:
raise HTTPException(status_code=400, detail="buffer_minutes must be 0-120")
settings.set_setting("buffer_minutes", int(req.buffer_minutes))
changes.append(f"buffer_minutes: {current['buffer_minutes']} β†’ {req.buffer_minutes}")
if req.on_call_start_hour is not None and req.on_call_start_hour != current["on_call_start_hour"]:
normalized = normalize_time_str(req.on_call_start_hour)
if normalized is None:
raise HTTPException(status_code=400, detail="on_call_start_hour must be HH:MM (e.g. 07:00)")
settings.set_setting("on_call_start_hour", normalized)
changes.append(f"on_call_start_hour: {current['on_call_start_hour']} β†’ {normalized}")
if req.visit_levels is not None and req.visit_levels != current["visit_levels"]:
# Validate shape: each value must have 'label' and integer 'duration_minutes'.
for k, v in req.visit_levels.items():
if not isinstance(v, dict) or "duration_minutes" not in v:
raise HTTPException(status_code=400, detail=f"visit_levels[{k}] missing duration_minutes")
try:
int(v["duration_minutes"])
except Exception:
raise HTTPException(status_code=400, detail=f"visit_levels[{k}].duration_minutes not an integer")
# Re-key: convert digit-string keys back to ints so the format matches the schema.
normalized_levels = {
(int(k) if isinstance(k, str) and k.isdigit() else k): {
"label": v.get("label", str(k)),
"duration_minutes": int(v["duration_minutes"]),
}
for k, v in req.visit_levels.items()
}
settings.set_setting("visit_levels", normalized_levels)
changes.append("visit_levels updated")
if req.region_name is not None and req.region_name != current["region_name"]:
settings.set_setting("region_name", req.region_name)
changes.append(f"region_name: {current['region_name']} β†’ {req.region_name}")
if req.timezone is not None and req.timezone != current["timezone"]:
settings.set_setting("timezone", req.timezone)
changes.append(f"timezone: {current['timezone']} β†’ {req.timezone}")
if req.service_area is not None and req.service_area != current["service_area"]:
settings.set_setting("service_area", req.service_area)
changes.append(f"service_area: {current['service_area']} β†’ {req.service_area}")
if req.openai_model is not None and req.openai_model != current["openai_model"]:
settings.set_setting("openai_model", req.openai_model)
changes.append(f"openai_model: {current['openai_model']} β†’ {req.openai_model}")
if changes:
_audit(session["current_user"], "; ".join(changes))
return {"changes": changes, "current": settings.get_all_settings()}
@router.post("/reset")
def reset_to_defaults(session: dict = Depends(require_admin_session)):
defaults = settings.get_schema_defaults()
for key, default in defaults.items():
settings.set_setting(key, default)
_audit(session["current_user"], "Reset all settings to defaults")
return {"current": settings.get_all_settings()}