-
Notifications
You must be signed in to change notification settings - Fork 0
Add scheduled weather sentinel, admin weather location API and UI #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,4 +9,6 @@ pytesseract | |
| requests | ||
| PyPDF2 | ||
| chromadb | ||
| psutil | ||
| psutil | ||
| apscheduler | ||
| httpx | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import asyncio | ||
| import json | ||
| import logging | ||
| from datetime import datetime, timedelta, timezone | ||
|
|
||
| import httpx | ||
|
|
||
| from chat import connected_clients, send_to_user | ||
| from lumir.ai_engine import ask_ai | ||
| from messages import create_delivery_entries, save_message | ||
| from users import get_ai_config, get_all_users, get_default_location | ||
|
|
||
| LOGGER = logging.getLogger(__name__) | ||
|
|
||
| IST = timezone(timedelta(hours=5, minutes=30)) | ||
| DEFAULT_LAT = 26.2183 | ||
| DEFAULT_LON = 78.1828 | ||
|
|
||
|
|
||
|
|
||
| def _get_default_coordinates() -> tuple[float, float]: | ||
| try: | ||
| return get_default_location() | ||
| except Exception: | ||
| LOGGER.debug("Weather sentinel could not read configured coordinates.", exc_info=True) | ||
| return DEFAULT_LAT, DEFAULT_LON | ||
|
|
||
|
|
||
| async def _broadcast_family_warning(message_text: str) -> None: | ||
| timestamp = int(datetime.now(IST).timestamp() * 1000) | ||
| payload = { | ||
| "type": "text", | ||
| "text": message_text, | ||
| "sender": "Lumir", | ||
| "receiver": "Family Group", | ||
| "timestamp": timestamp, | ||
| } | ||
|
|
||
| msg_id = save_message( | ||
| text=message_text, | ||
| sender="Lumir", | ||
| receiver="Family Group", | ||
| msg_type="text", | ||
| ) | ||
|
|
||
| recipients = [u["username"] for u in get_all_users() if u.get("username") != "Lumir"] | ||
| if recipients: | ||
| create_delivery_entries(msg_id, recipients) | ||
|
|
||
| payload_json = json.dumps(payload) | ||
| for username in list(connected_clients.keys()): | ||
| await send_to_user(username, payload_json) | ||
|
Comment on lines
+51
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This path creates Useful? React with 👍 / 👎. |
||
|
|
||
|
|
||
| async def run_proactive_weather_sentinel() -> None: | ||
| try: | ||
| lat, lon = _get_default_coordinates() | ||
| location = f"{lat:.4f}, {lon:.4f}" | ||
|
|
||
| async with httpx.AsyncClient(timeout=15.0) as client: | ||
| response = await client.get( | ||
| "https://api.open-meteo.com/v1/forecast", | ||
| params={ | ||
| "latitude": lat, | ||
| "longitude": lon, | ||
| "daily": "precipitation_sum,temperature_2m_max", | ||
| "timezone": "Asia/Kolkata", | ||
| "forecast_days": 1, | ||
| }, | ||
| ) | ||
| response.raise_for_status() | ||
| forecast = response.json().get("daily", {}) | ||
|
|
||
| precipitation_values = forecast.get("precipitation_sum") or [] | ||
| max_temp_values = forecast.get("temperature_2m_max") or [] | ||
|
|
||
| precipitation_sum = float(precipitation_values[0]) if precipitation_values else 0.0 | ||
| temperature_2m_max = float(max_temp_values[0]) if max_temp_values else 0.0 | ||
|
|
||
| alert_reason = None | ||
| if precipitation_sum > 2.0: | ||
| alert_reason = f"{precipitation_sum:.1f}mm rain" | ||
| elif temperature_2m_max > 40.0: | ||
| alert_reason = f"{temperature_2m_max:.1f}°C heat" | ||
|
|
||
| if not alert_reason: | ||
| return | ||
|
|
||
| hidden_prompt = ( | ||
| "System Context: Aaj {location} mein {alert_reason} hone wali hai. " | ||
| "Act like a caring family member and write a warning message for the Family Group. " | ||
| "Keep it under 15 words. Do NOT use markdown. Say good morning." | ||
| ).format(location=location, alert_reason=alert_reason) | ||
|
|
||
| ai_config = get_ai_config() | ||
| generated_message = await asyncio.to_thread( | ||
| ask_ai, | ||
| prompt=hidden_prompt, | ||
| config=ai_config, | ||
| history=[], | ||
| sender="Family Group", | ||
| ) | ||
|
|
||
| await _broadcast_family_warning((generated_message or "Good morning. Please stay safe today.").strip()) | ||
|
|
||
| except Exception: | ||
| LOGGER.debug("Weather sentinel failed during scheduled execution.", exc_info=True) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This repository declares Python 3.8+ support, but
tuple[float, float]requires Python 3.9+ (unless postponed evaluation is enabled). On Python 3.8, importing this module raisesTypeError: 'type' object is not subscriptable, which preventsserver.pyfrom importingtasks.pyand can stop the backend from starting at all.Useful? React with 👍 / 👎.