Spaces:
Sleeping
Sleeping
| """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) | |
| 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(), | |
| } | |
| 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()} | |
| 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()} | |