fix: remove hardcoded timezone and region names for portability

- Timezone now configurable (default America/Boise)
- Router prompt generates region name instructions from config
- Any operator can run MeshAI for their region without code changes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
zvx-echo6 2026-05-12 15:39:54 -06:00
commit c5f4dac8b6
4 changed files with 25 additions and 6 deletions

View file

@ -68,6 +68,7 @@ class AlertEngine:
subscription_manager: "SubscriptionManager", subscription_manager: "SubscriptionManager",
config: "MeshIntelligenceConfig", config: "MeshIntelligenceConfig",
db_path: str = "", db_path: str = "",
timezone: str = "America/Boise",
): ):
self._health = health_engine self._health = health_engine
self._reporter = reporter self._reporter = reporter
@ -75,6 +76,7 @@ class AlertEngine:
self._rules = config.alert_rules self._rules = config.alert_rules
self._critical_nodes = set(n.upper() for n in (config.critical_nodes or [])) self._critical_nodes = set(n.upper() for n in (config.critical_nodes or []))
self._db_path = db_path self._db_path = db_path
self._timezone = timezone
self._states: dict[str, AlertState] = {} self._states: dict[str, AlertState] = {}
self._prev_infra_online: dict[int, bool] = {} self._prev_infra_online: dict[int, bool] = {}
@ -266,7 +268,7 @@ class AlertEngine:
if self._rules.solar_not_charging and getattr(node, "has_solar", False) and 0 < bat <= 100: if self._rules.solar_not_charging and getattr(node, "has_solar", False) and 0 < bat <= 100:
try: try:
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
tz = ZoneInfo("America/Boise") tz = ZoneInfo(self._timezone)
hour = datetime.now(tz).hour hour = datetime.now(tz).hour
if 8 <= hour <= 18: if 8 <= hour <= 18:
prev_bat = self._prev_battery.get(node_num) prev_bat = self._prev_battery.get(node_num)

View file

@ -330,6 +330,9 @@ class DashboardConfig:
class Config: class Config:
"""Main configuration container.""" """Main configuration container."""
# Global settings
timezone: str = "America/Boise" # IANA timezone for local time display
bot: BotConfig = field(default_factory=BotConfig) bot: BotConfig = field(default_factory=BotConfig)
connection: ConnectionConfig = field(default_factory=ConnectionConfig) connection: ConnectionConfig = field(default_factory=ConnectionConfig)
response: ResponseConfig = field(default_factory=ResponseConfig) response: ResponseConfig = field(default_factory=ResponseConfig)

View file

@ -330,6 +330,7 @@ class MeshAI:
subscription_manager=self.subscription_manager, subscription_manager=self.subscription_manager,
config=mi, config=mi,
db_path="/data/mesh_history.db", db_path="/data/mesh_history.db",
timezone=self.config.timezone,
) )
logger.info(f"Alert engine initialized (critical: {mi.critical_nodes}, channel: {mi.alert_channel})") logger.info(f"Alert engine initialized (critical: {mi.critical_nodes}, channel: {mi.alert_channel})")
@ -574,7 +575,7 @@ class MeshAI:
from datetime import datetime from datetime import datetime
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
tz = ZoneInfo("America/Boise") tz = ZoneInfo(self.config.timezone)
now = datetime.now(tz) now = datetime.now(tz)
current_hhmm = now.strftime("%H%M") current_hhmm = now.strftime("%H%M")
current_day = now.strftime("%a").lower() current_day = now.strftime("%a").lower()

View file

@ -110,8 +110,8 @@ coverage gap, and problem node on the mesh. USE THIS DATA in your response.
RESPONSE STYLE: RESPONSE STYLE:
- DETAILED, data-driven responses. Reference specific node names, scores, gateway counts. - DETAILED, data-driven responses. Reference specific node names, scores, gateway counts.
- Use LOCAL NAMES from the region descriptions (Magic Valley, Treasure Valley, etc.) - Use LOCAL NAMES from the region descriptions when available.
- ALWAYS use local region names: say "Treasure Valley" not "South Western ID", say "Magic Valley" not "South Central ID". The code names mean nothing to users. {region_name_instructions}
- When listing nodes, be concise: "BT Base c8d5 — via AIDA" not "BT Base c8d5 (c8d5) is connected via AIDA-MeshMonitor in the South Western ID region." - When listing nodes, be concise: "BT Base c8d5 — via AIDA" not "BT Base c8d5 (c8d5) is connected via AIDA-MeshMonitor in the South Western ID region."
- Don't repeat the region on every line when listing multiple nodes in the same region. Say the region once at the top, then just list the nodes. - Don't repeat the region on every line when listing multiple nodes in the same region. Say the region once at the top, then just list the nodes.
- Don't include shortnames in parentheses when you're already giving the full name it's noise. - Don't include shortnames in parentheses when you're already giving the full name it's noise.
@ -721,8 +721,21 @@ class MessageRouter:
if recommendations: if recommendations:
system_prompt += "\n\n" + recommendations system_prompt += "\n\n" + recommendations
# Add mesh awareness instructions # Add mesh awareness instructions with dynamic region name mappings
system_prompt += _MESH_AWARENESS_PROMPT region_name_instructions = ""
if self.config.mesh_intelligence and self.config.mesh_intelligence.regions:
# Build region name mappings for the prompt
mappings = []
for region in self.config.mesh_intelligence.regions:
local = getattr(region, "local_name", "") or ""
if local and local != region.name:
mappings.append(f'say "{local}" not "{region.name}"')
if mappings:
region_name_instructions = f"- ALWAYS use local region names: {', '.join(mappings)}. The code names mean nothing to users."
system_prompt += _MESH_AWARENESS_PROMPT.format(
region_name_instructions=region_name_instructions
)
# Build region geography from config dynamically # Build region geography from config dynamically
if self.config.mesh_intelligence and self.config.mesh_intelligence.regions: if self.config.mesh_intelligence and self.config.mesh_intelligence.regions: