safraeli commited on
Commit
0231141
·
verified ·
1 Parent(s): 6d7f91e

Add auth to control endpoints, sigmoid Farquhar transition

Browse files
backend/api/routes/control.py CHANGED
@@ -6,6 +6,7 @@ import logging
6
 
7
  from fastapi import APIRouter, Depends, HTTPException
8
 
 
9
  from backend.api.deps import get_datahub, get_redis_client
10
  from src.data.data_providers import DataHub
11
 
@@ -14,7 +15,7 @@ router = APIRouter()
14
 
15
 
16
  @router.get("/status")
17
- async def control_status():
18
  """Last ControlLoop tick result (stored in Redis by the worker)."""
19
  redis = get_redis_client()
20
  if redis:
@@ -25,7 +26,7 @@ async def control_status():
25
 
26
 
27
  @router.get("/plan")
28
- async def control_plan():
29
  """Current day-ahead plan."""
30
  redis = get_redis_client()
31
  if redis:
@@ -36,17 +37,24 @@ async def control_plan():
36
  try:
37
  import json
38
  from config.settings import DAILY_PLAN_PATH
39
- with open(DAILY_PLAN_PATH) as f:
 
 
 
 
 
40
  return json.load(f)
41
  except FileNotFoundError:
42
  return {"status": "waiting", "message": "No plan generated yet", "slots": []}
 
 
43
  except Exception as exc:
44
  log.error("Failed to load plan from file: %s", exc)
45
  raise HTTPException(status_code=500, detail="Plan loading failed")
46
 
47
 
48
  @router.get("/budget")
49
- async def control_budget():
50
  """Current energy budget state."""
51
  redis = get_redis_client()
52
  if redis:
 
6
 
7
  from fastapi import APIRouter, Depends, HTTPException
8
 
9
+ from backend.api.auth import optional_auth
10
  from backend.api.deps import get_datahub, get_redis_client
11
  from src.data.data_providers import DataHub
12
 
 
15
 
16
 
17
  @router.get("/status")
18
+ async def control_status(user: dict = Depends(optional_auth)):
19
  """Last ControlLoop tick result (stored in Redis by the worker)."""
20
  redis = get_redis_client()
21
  if redis:
 
26
 
27
 
28
  @router.get("/plan")
29
+ async def control_plan(user: dict = Depends(optional_auth)):
30
  """Current day-ahead plan."""
31
  redis = get_redis_client()
32
  if redis:
 
37
  try:
38
  import json
39
  from config.settings import DAILY_PLAN_PATH
40
+ from pathlib import Path
41
+
42
+ plan_path = Path(DAILY_PLAN_PATH)
43
+ if not plan_path.is_relative_to(plan_path.parent.parent):
44
+ raise HTTPException(status_code=400, detail="Invalid plan path")
45
+ with open(plan_path) as f:
46
  return json.load(f)
47
  except FileNotFoundError:
48
  return {"status": "waiting", "message": "No plan generated yet", "slots": []}
49
+ except HTTPException:
50
+ raise
51
  except Exception as exc:
52
  log.error("Failed to load plan from file: %s", exc)
53
  raise HTTPException(status_code=500, detail="Plan loading failed")
54
 
55
 
56
  @router.get("/budget")
57
+ async def control_budget(user: dict = Depends(optional_auth)):
58
  """Current energy budget state."""
59
  redis = get_redis_client()
60
  if redis:
src/models/farquhar_model.py CHANGED
@@ -272,12 +272,22 @@ class FarquharModel:
272
  Ac, Aj, Rd = self._compute_rates(PAR, Tleaf, CO2, VPD, CWSI or 0.0)
273
  An = min(Ac, Aj) - Rd
274
 
275
- if Tleaf < transition_temp:
 
 
 
 
 
 
276
  state = "RuBP_Limited"
277
- shading_helps = False
278
- else:
279
  state = "Rubisco_Limited"
280
- shading_helps = (Aj > Ac)
 
 
 
 
 
281
 
282
  return float(max(0.0, An)), state, shading_helps
283
 
 
272
  Ac, Aj, Rd = self._compute_rates(PAR, Tleaf, CO2, VPD, CWSI or 0.0)
273
  An = min(Ac, Aj) - Rd
274
 
275
+ # Smooth sigmoid transition (28–32°C) instead of hard threshold.
276
+ # rubisco_weight = 0 below 28°C, 1 above 32°C, sigmoid in between.
277
+ import math
278
+ _TRANSITION_WIDTH = 2.0 # °C half-width of sigmoid zone
279
+ rubisco_weight = 1.0 / (1.0 + math.exp(-(Tleaf - transition_temp) / (_TRANSITION_WIDTH / 2.5)))
280
+
281
+ if rubisco_weight < 0.3:
282
  state = "RuBP_Limited"
283
+ elif rubisco_weight > 0.7:
 
284
  state = "Rubisco_Limited"
285
+ else:
286
+ state = "Transition"
287
+
288
+ # shading_helps is weighted: only meaningful when Rubisco-limited
289
+ # AND light is abundant relative to enzyme capacity (Aj > Ac)
290
+ shading_helps = rubisco_weight > 0.5 and (Aj > Ac)
291
 
292
  return float(max(0.0, An)), state, shading_helps
293