Fix TB timeseries fallback
Browse files- backend/api/routes/sensors.py +27 -28
backend/api/routes/sensors.py
CHANGED
|
@@ -100,19 +100,36 @@ async def soil_moisture_history(
|
|
| 100 |
return []
|
| 101 |
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
@router.get("/air-leaf-delta")
|
| 104 |
async def air_leaf_delta(
|
| 105 |
hours: int = Query(168, ge=1, le=8760, description="Hours of history"),
|
| 106 |
):
|
| 107 |
"""Hourly air-leaf temperature delta from ThingsBoard (Air2 under panels)."""
|
| 108 |
try:
|
| 109 |
-
|
| 110 |
-
from src.data.thingsboard_client import ThingsBoardClient
|
| 111 |
-
client = ThingsBoardClient()
|
| 112 |
-
end = datetime.now(tz=timezone.utc)
|
| 113 |
-
start = end - timedelta(hours=hours)
|
| 114 |
-
df = client.get_timeseries("Air2", ["airLeafDeltaT"], start=start, end=end,
|
| 115 |
-
interval_ms=3_600_000, agg="AVG", limit=2000)
|
| 116 |
if df.empty:
|
| 117 |
return []
|
| 118 |
return [{"timestamp": ts.isoformat(), "value": round(float(row["airLeafDeltaT"]), 2)}
|
|
@@ -128,13 +145,7 @@ async def temp_humidity(
|
|
| 128 |
):
|
| 129 |
"""Hourly temperature + humidity from ThingsBoard (Air2 under panels)."""
|
| 130 |
try:
|
| 131 |
-
|
| 132 |
-
from src.data.thingsboard_client import ThingsBoardClient
|
| 133 |
-
client = ThingsBoardClient()
|
| 134 |
-
end = datetime.now(tz=timezone.utc)
|
| 135 |
-
start = end - timedelta(hours=hours)
|
| 136 |
-
df = client.get_timeseries("Air2", ["airTemperature", "airHumidity"], start=start, end=end,
|
| 137 |
-
interval_ms=3_600_000, agg="AVG", limit=2000)
|
| 138 |
if df.empty:
|
| 139 |
return []
|
| 140 |
rows = []
|
|
@@ -157,15 +168,9 @@ async def ndvi_history(
|
|
| 157 |
):
|
| 158 |
"""Hourly NDVI from ThingsBoard — treatment (Air2) and reference (Air1)."""
|
| 159 |
try:
|
| 160 |
-
from datetime import datetime, timezone, timedelta
|
| 161 |
-
from src.data.thingsboard_client import ThingsBoardClient
|
| 162 |
-
client = ThingsBoardClient()
|
| 163 |
-
end = datetime.now(tz=timezone.utc)
|
| 164 |
-
start = end - timedelta(hours=hours)
|
| 165 |
rows = []
|
| 166 |
for device, label in [("Air2", "treatment"), ("Air1", "ambient")]:
|
| 167 |
-
df =
|
| 168 |
-
interval_ms=3_600_000, agg="AVG", limit=2000)
|
| 169 |
if not df.empty and "NDVI" in df.columns:
|
| 170 |
for ts, row in df.iterrows():
|
| 171 |
v = row.get("NDVI")
|
|
@@ -183,13 +188,7 @@ async def vpd_history(
|
|
| 183 |
):
|
| 184 |
"""Hourly VPD from ThingsBoard (Air2 under panels)."""
|
| 185 |
try:
|
| 186 |
-
|
| 187 |
-
from src.data.thingsboard_client import ThingsBoardClient
|
| 188 |
-
client = ThingsBoardClient()
|
| 189 |
-
end = datetime.now(tz=timezone.utc)
|
| 190 |
-
start = end - timedelta(hours=hours)
|
| 191 |
-
df = client.get_timeseries("Air2", ["VPD"], start=start, end=end,
|
| 192 |
-
interval_ms=3_600_000, agg="AVG", limit=2000)
|
| 193 |
if df.empty:
|
| 194 |
return []
|
| 195 |
return [{"timestamp": ts.isoformat(), "value": round(float(row["VPD"]), 2)}
|
|
|
|
| 100 |
return []
|
| 101 |
|
| 102 |
|
| 103 |
+
def _tb_timeseries(device: str, keys: list, hours: int):
|
| 104 |
+
"""Fetch TB time-series with fallback: try AVG aggregation first, then NONE."""
|
| 105 |
+
from datetime import datetime, timezone, timedelta
|
| 106 |
+
from src.data.thingsboard_client import ThingsBoardClient
|
| 107 |
+
client = ThingsBoardClient()
|
| 108 |
+
end = datetime.now(tz=timezone.utc)
|
| 109 |
+
start = end - timedelta(hours=hours)
|
| 110 |
+
|
| 111 |
+
# Try aggregated first
|
| 112 |
+
df = client.get_timeseries(device, keys, start=start, end=end,
|
| 113 |
+
interval_ms=3_600_000, agg="AVG", limit=2000)
|
| 114 |
+
if not df.empty:
|
| 115 |
+
return df
|
| 116 |
+
|
| 117 |
+
# Fallback: raw data, resample locally
|
| 118 |
+
import pandas as pd
|
| 119 |
+
df = client.get_timeseries(device, keys, start=start, end=end,
|
| 120 |
+
interval_ms=0, agg="NONE", limit=10000)
|
| 121 |
+
if df.empty:
|
| 122 |
+
return df
|
| 123 |
+
return df.resample("1h").mean(numeric_only=True).dropna(how="all")
|
| 124 |
+
|
| 125 |
+
|
| 126 |
@router.get("/air-leaf-delta")
|
| 127 |
async def air_leaf_delta(
|
| 128 |
hours: int = Query(168, ge=1, le=8760, description="Hours of history"),
|
| 129 |
):
|
| 130 |
"""Hourly air-leaf temperature delta from ThingsBoard (Air2 under panels)."""
|
| 131 |
try:
|
| 132 |
+
df = _tb_timeseries("Air2", ["airLeafDeltaT"], hours)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
if df.empty:
|
| 134 |
return []
|
| 135 |
return [{"timestamp": ts.isoformat(), "value": round(float(row["airLeafDeltaT"]), 2)}
|
|
|
|
| 145 |
):
|
| 146 |
"""Hourly temperature + humidity from ThingsBoard (Air2 under panels)."""
|
| 147 |
try:
|
| 148 |
+
df = _tb_timeseries("Air2", ["airTemperature", "airHumidity"], hours)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
if df.empty:
|
| 150 |
return []
|
| 151 |
rows = []
|
|
|
|
| 168 |
):
|
| 169 |
"""Hourly NDVI from ThingsBoard — treatment (Air2) and reference (Air1)."""
|
| 170 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
rows = []
|
| 172 |
for device, label in [("Air2", "treatment"), ("Air1", "ambient")]:
|
| 173 |
+
df = _tb_timeseries(device, ["NDVI"], hours)
|
|
|
|
| 174 |
if not df.empty and "NDVI" in df.columns:
|
| 175 |
for ts, row in df.iterrows():
|
| 176 |
v = row.get("NDVI")
|
|
|
|
| 188 |
):
|
| 189 |
"""Hourly VPD from ThingsBoard (Air2 under panels)."""
|
| 190 |
try:
|
| 191 |
+
df = _tb_timeseries("Air2", ["VPD"], hours)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
if df.empty:
|
| 193 |
return []
|
| 194 |
return [{"timestamp": ts.isoformat(), "value": round(float(row["VPD"]), 2)}
|