safraeli commited on
Commit
063a7cd
Β·
verified Β·
1 Parent(s): bfbaecb

Fix HEAD support for UptimeRobot, email alerter bug, update docs

Browse files
README.md CHANGED
@@ -14,13 +14,69 @@ FastAPI backend for the SolarWine agrivoltaic vineyard control system.
14
 
15
  ## Endpoints
16
 
17
- - `GET /api/health` β€” health check
18
- - `GET /api/weather/current` β€” current weather (IMS station 43)
19
- - `GET /api/sensors/snapshot` β€” vine sensor readings (ThingsBoard)
20
- - `GET /api/energy/current` β€” current power output
21
- - `GET /api/photosynthesis/current` β€” photosynthesis rate (FvCB/ML)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  - `GET /api/control/status` β€” last control loop tick
23
- - `POST /api/chatbot/message` β€” AI vineyard advisor
24
- - `GET /api/biology/rules` β€” biology rules
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  Interactive docs at `/docs`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  ## Endpoints
16
 
17
+ ### Health & Monitoring
18
+ - `GET|HEAD /api/health` β€” system health (uptime, Redis, TB, IMS, Gemini status)
19
+ - `GET /api/health/data` β€” data freshness per source (age, ok/degraded)
20
+ - `GET /api/health/data-sources` β€” per-source green/yellow/red status with thresholds
21
+
22
+ ### Weather (IMS Station 43)
23
+ - `GET /api/weather/current` β€” latest weather readings + data age
24
+ - `GET /api/weather/history?start_date&end_date&format` β€” historical weather (hourly)
25
+ - `GET /api/weather/forecast` β€” weather forecast
26
+
27
+ ### Vine Sensors (ThingsBoard)
28
+ - `GET /api/sensors/snapshot` β€” latest readings (treatment vs reference)
29
+ - `GET /api/sensors/history?type&area&hours` β€” sensor time-series
30
+ - `GET /api/sensors/soil-moisture?hours` β€” soil moisture history
31
+ - `GET /api/sensors/rain?hours` β€” rainfall history
32
+
33
+ ### Energy
34
+ - `GET /api/energy/current` β€” current power output (kW)
35
+ - `GET /api/energy/daily/{date}` β€” daily production + hourly profile
36
+ - `GET /api/energy/history` β€” energy generation time-series
37
+ - `GET /api/energy/predict/{date}` β€” predicted daily generation
38
+
39
+ ### Photosynthesis
40
+ - `GET /api/photosynthesis/current?model` β€” current A rate (FvCB or ML)
41
+ - `GET /api/photosynthesis/forecast` β€” 24h predicted A profile
42
+
43
+ ### Control System
44
  - `GET /api/control/status` β€” last control loop tick
45
+ - `GET /api/control/plan` β€” current day-ahead plan
46
+ - `GET /api/control/budget` β€” energy budget status
47
+ - `GET /api/control/trackers` β€” tracker angles and modes
48
+
49
+ ### Chatbot (Gemini AI)
50
+ - `POST /api/chatbot/message` β€” send message to vineyard advisor
51
+ - `POST /api/chatbot/feedback` β€” submit feedback on response
52
+ - `GET /api/chatbot/briefing` β€” current system status briefing
53
+
54
+ ### Biology
55
+ - `GET /api/biology/phenology` β€” current growth stage
56
+ - `GET /api/biology/rules` β€” all biology rules
57
+ - `GET /api/biology/rules/{name}` β€” single rule detail
58
+ - `GET /api/biology/chill-units?season_start` β€” accumulated chill units
59
+
60
+ ### Auth
61
+ - `POST /api/auth/login` β€” JWT token (guest mode when JWT_SECRET unset)
62
+
63
+ ### Events
64
+ - `GET /api/events/stream` β€” SSE stream for live frontend updates
65
 
66
  Interactive docs at `/docs`.
67
+
68
+ ## Environment Variables
69
+
70
+ | Variable | Required | Description |
71
+ |----------|----------|-------------|
72
+ | `IMS_API_TOKEN` | Yes | IMS weather API token |
73
+ | `THINGSBOARD_HOST` | Yes | ThingsBoard server URL |
74
+ | `THINGSBOARD_USERNAME` | Yes | ThingsBoard login |
75
+ | `THINGSBOARD_PASSWORD` | Yes | ThingsBoard password |
76
+ | `GOOGLE_API_KEY` | No | Gemini AI for chatbot/advisor |
77
+ | `JWT_SECRET` | No | JWT signing key (guest mode if unset) |
78
+ | `ADMIN_PASSWORD` | No | Login password |
79
+ | `REDIS_URL` | No | Upstash Redis for shared cache |
80
+ | `SENTRY_DSN` | No | Sentry error tracking |
81
+ | `SMTP_HOST` | No | Email alerts (with ALERT_EMAIL_TO) |
82
+ | `ALERT_EMAIL_TO` | No | Alert recipient email(s) |
backend/HF_README.md CHANGED
@@ -14,13 +14,69 @@ FastAPI backend for the SolarWine agrivoltaic vineyard control system.
14
 
15
  ## Endpoints
16
 
17
- - `GET /api/health` β€” health check
18
- - `GET /api/weather/current` β€” current weather (IMS station 43)
19
- - `GET /api/sensors/snapshot` β€” vine sensor readings (ThingsBoard)
20
- - `GET /api/energy/current` β€” current power output
21
- - `GET /api/photosynthesis/current` β€” photosynthesis rate (FvCB/ML)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  - `GET /api/control/status` β€” last control loop tick
23
- - `POST /api/chatbot/message` β€” AI vineyard advisor
24
- - `GET /api/biology/rules` β€” biology rules
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  Interactive docs at `/docs`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  ## Endpoints
16
 
17
+ ### Health & Monitoring
18
+ - `GET|HEAD /api/health` β€” system health (uptime, Redis, TB, IMS, Gemini status)
19
+ - `GET /api/health/data` β€” data freshness per source (age, ok/degraded)
20
+ - `GET /api/health/data-sources` β€” per-source green/yellow/red status with thresholds
21
+
22
+ ### Weather (IMS Station 43)
23
+ - `GET /api/weather/current` β€” latest weather readings + data age
24
+ - `GET /api/weather/history?start_date&end_date&format` β€” historical weather (hourly)
25
+ - `GET /api/weather/forecast` β€” weather forecast
26
+
27
+ ### Vine Sensors (ThingsBoard)
28
+ - `GET /api/sensors/snapshot` β€” latest readings (treatment vs reference)
29
+ - `GET /api/sensors/history?type&area&hours` β€” sensor time-series
30
+ - `GET /api/sensors/soil-moisture?hours` β€” soil moisture history
31
+ - `GET /api/sensors/rain?hours` β€” rainfall history
32
+
33
+ ### Energy
34
+ - `GET /api/energy/current` β€” current power output (kW)
35
+ - `GET /api/energy/daily/{date}` β€” daily production + hourly profile
36
+ - `GET /api/energy/history` β€” energy generation time-series
37
+ - `GET /api/energy/predict/{date}` β€” predicted daily generation
38
+
39
+ ### Photosynthesis
40
+ - `GET /api/photosynthesis/current?model` β€” current A rate (FvCB or ML)
41
+ - `GET /api/photosynthesis/forecast` β€” 24h predicted A profile
42
+
43
+ ### Control System
44
  - `GET /api/control/status` β€” last control loop tick
45
+ - `GET /api/control/plan` β€” current day-ahead plan
46
+ - `GET /api/control/budget` β€” energy budget status
47
+ - `GET /api/control/trackers` β€” tracker angles and modes
48
+
49
+ ### Chatbot (Gemini AI)
50
+ - `POST /api/chatbot/message` β€” send message to vineyard advisor
51
+ - `POST /api/chatbot/feedback` β€” submit feedback on response
52
+ - `GET /api/chatbot/briefing` β€” current system status briefing
53
+
54
+ ### Biology
55
+ - `GET /api/biology/phenology` β€” current growth stage
56
+ - `GET /api/biology/rules` β€” all biology rules
57
+ - `GET /api/biology/rules/{name}` β€” single rule detail
58
+ - `GET /api/biology/chill-units?season_start` β€” accumulated chill units
59
+
60
+ ### Auth
61
+ - `POST /api/auth/login` β€” JWT token (guest mode when JWT_SECRET unset)
62
+
63
+ ### Events
64
+ - `GET /api/events/stream` β€” SSE stream for live frontend updates
65
 
66
  Interactive docs at `/docs`.
67
+
68
+ ## Environment Variables
69
+
70
+ | Variable | Required | Description |
71
+ |----------|----------|-------------|
72
+ | `IMS_API_TOKEN` | Yes | IMS weather API token |
73
+ | `THINGSBOARD_HOST` | Yes | ThingsBoard server URL |
74
+ | `THINGSBOARD_USERNAME` | Yes | ThingsBoard login |
75
+ | `THINGSBOARD_PASSWORD` | Yes | ThingsBoard password |
76
+ | `GOOGLE_API_KEY` | No | Gemini AI for chatbot/advisor |
77
+ | `JWT_SECRET` | No | JWT signing key (guest mode if unset) |
78
+ | `ADMIN_PASSWORD` | No | Login password |
79
+ | `REDIS_URL` | No | Upstash Redis for shared cache |
80
+ | `SENTRY_DSN` | No | Sentry error tracking |
81
+ | `SMTP_HOST` | No | Email alerts (with ALERT_EMAIL_TO) |
82
+ | `ALERT_EMAIL_TO` | No | Alert recipient email(s) |
backend/api/main.py CHANGED
@@ -261,7 +261,7 @@ app.add_middleware(
261
  CORSMiddleware,
262
  allow_origins=[o.strip() for o in allowed_origins],
263
  allow_credentials=True,
264
- allow_methods=["GET", "POST", "OPTIONS"],
265
  allow_headers=["Content-Type", "Authorization"],
266
  )
267
 
 
261
  CORSMiddleware,
262
  allow_origins=[o.strip() for o in allowed_origins],
263
  allow_credentials=True,
264
+ allow_methods=["GET", "HEAD", "POST", "OPTIONS"],
265
  allow_headers=["Content-Type", "Authorization"],
266
  )
267
 
backend/api/routes/health.py CHANGED
@@ -31,7 +31,7 @@ async def _check_thingsboard() -> bool:
31
  return False
32
 
33
 
34
- @router.get("/health")
35
  async def health():
36
  redis = get_redis_client()
37
  redis_ok = redis.ping() if redis else False
 
31
  return False
32
 
33
 
34
+ @router.api_route("/health", methods=["GET", "HEAD"])
35
  async def health():
36
  redis = get_redis_client()
37
  redis_ok = redis.ping() if redis else False
backend/services/data_flow_monitor.py CHANGED
@@ -80,6 +80,7 @@ class DataFlowMonitor:
80
  if wx and "error" not in wx:
81
  age = float(wx.get("age_minutes", -1))
82
  if age < 0:
 
83
  age = None
84
  status = _classify(age, IMS_STALE_YELLOW_MIN, IMS_STALE_RED_MIN)
85
  return {
 
80
  if wx and "error" not in wx:
81
  age = float(wx.get("age_minutes", -1))
82
  if age < 0:
83
+ log.warning("IMS weather returned negative age_minutes β€” data provider issue")
84
  age = None
85
  status = _classify(age, IMS_STALE_YELLOW_MIN, IMS_STALE_RED_MIN)
86
  return {
backend/services/email_alerter.py CHANGED
@@ -59,10 +59,11 @@ class EmailAlerter:
59
  message = info.get("message", f"{source_name} is down")
60
  age = info.get("age_minutes")
61
  subject = f"[SolarWine] Data flow alert: {source_name}"
 
62
  body = (
63
  f"Data source: {source_name}\n"
64
  f"Status: RED\n"
65
- f"Age: {age:.0f} min\n" if age is not None else ""
66
  f"Detail: {message}\n"
67
  f"\nChecked at: {status.get('checked_at', 'unknown')}\n"
68
  f"Overall system status: {status.get('overall', 'unknown')}\n"
 
59
  message = info.get("message", f"{source_name} is down")
60
  age = info.get("age_minutes")
61
  subject = f"[SolarWine] Data flow alert: {source_name}"
62
+ age_line = f"Age: {age:.0f} min\n" if age is not None else ""
63
  body = (
64
  f"Data source: {source_name}\n"
65
  f"Status: RED\n"
66
+ f"{age_line}"
67
  f"Detail: {message}\n"
68
  f"\nChecked at: {status.get('checked_at', 'unknown')}\n"
69
  f"Overall system status: {status.get('overall', 'unknown')}\n"