Arrcus commited on
Commit
a5857b6
·
1 Parent(s): c86612c

better api

Browse files
Files changed (3) hide show
  1. app.py +2 -2
  2. utils.py +29 -144
  3. weather_data_visualisation.py +50 -13
app.py CHANGED
@@ -110,7 +110,7 @@ def create_app():
110
  def _prepare_data(kit_id):
111
  # Fetch data for the given kit to ensure availability before enabling generation
112
  try:
113
- from utils import get_kit_measurements_df
114
 
115
  kit = 1001
116
  if kit_id is not None:
@@ -119,7 +119,7 @@ def create_app():
119
  except Exception:
120
  kit = 1001
121
 
122
- df = get_kit_measurements_df(kit)
123
  if df is None or getattr(df, "empty", True):
124
  return (
125
  f"No data found for kit {kit}. Please try another kit.",
 
110
  def _prepare_data(kit_id):
111
  # Fetch data for the given kit to ensure availability before enabling generation
112
  try:
113
+ from api_call import get_kit_measurements_df
114
 
115
  kit = 1001
116
  if kit_id is not None:
 
119
  except Exception:
120
  kit = 1001
121
 
122
+ df = get_kit_measurements_df(kit, page_size=60, max_pages=2)
123
  if df is None or getattr(df, "empty", True):
124
  return (
125
  f"No data found for kit {kit}. Please try another kit.",
utils.py CHANGED
@@ -1,149 +1,34 @@
1
  """
2
- Utility helpers for Teleagriculture kits API
3
 
4
- Provides:
5
- - BASE_URL, HEADERS (with optional Bearer from KIT_API_KEY env)
6
- - get_kit_info(kit_id)
7
- - get_kit_measurements_df(kit_id, sensors=None, page_size=100)
8
  """
9
  from __future__ import annotations
10
 
11
- import os
12
- from typing import Any, Iterable, Optional
13
-
14
- import pandas as pd
15
- import requests
16
-
17
- # API configuration
18
- BASE_URL = os.getenv("KITS_API_BASE", "https://kits.teleagriculture.org/api")
19
- KIT_API_KEY = os.getenv("KIT_API_KEY")
20
-
21
- HEADERS: dict[str, str] = {
22
- "Accept": "application/json",
23
- }
24
- if KIT_API_KEY:
25
- HEADERS["Authorization"] = f"Bearer {KIT_API_KEY}"
26
-
27
-
28
- def get_kit_info(kit_id: int) -> Optional[dict]:
29
- """Fetch metadata for a kit (board).
30
-
31
- Returns the JSON 'data' object or None if not found / error.
32
- """
33
- url = f"{BASE_URL}/kits/{kit_id}"
34
- try:
35
- r = requests.get(url, headers=HEADERS, timeout=30)
36
- if r.status_code == 200:
37
- body = r.json()
38
- return body.get("data")
39
- return None
40
- except requests.RequestException:
41
- return None
42
-
43
-
44
- def _paginate(
45
- url: str,
46
- *,
47
- params: Optional[dict] = None,
48
- headers: Optional[dict] = None,
49
- page_size: int = 100,
50
- max_pages: int = 500,
51
- ):
52
- """Cursor pagination helper yielding lists of items from {'data': [...]} pages.
53
-
54
- Stops when no next_cursor is provided or on any non-200/parse error.
55
- """
56
- q = dict(params or {})
57
- q["page[size]"] = str(page_size)
58
- cursor = None
59
- pages = 0
60
- while pages < max_pages:
61
- if cursor:
62
- q["page[cursor]"] = cursor
63
- try:
64
- r = requests.get(url, headers=headers, params=q, timeout=30)
65
- except requests.RequestException:
66
- break
67
- if r.status_code != 200:
68
- break
69
- try:
70
- payload = r.json()
71
- except Exception:
72
- break
73
- data = payload.get("data")
74
- meta = payload.get("meta", {})
75
- yield data if isinstance(data, list) else []
76
- cursor = meta.get("next_cursor")
77
- pages += 1
78
- if not cursor:
79
- break
80
-
81
-
82
- def get_kit_measurements_df(
83
- kit_id: int,
84
- sensors: Optional[Iterable[str]] = None,
85
- *,
86
- page_size: int = 100,
87
- ) -> pd.DataFrame:
88
- """Fetch all measurements for the given kit across its sensors as a DataFrame.
89
-
90
- - If sensors is None, discover sensors via get_kit_info(kit_id).
91
- - Returns columns: kit_id, sensor, timestamp, value, unit, _raw
92
- (depending on API, some fields may be None/NaT)
93
- """
94
- # Determine sensor list
95
- if sensors is None:
96
- kit = get_kit_info(kit_id)
97
- if not kit:
98
- return pd.DataFrame(columns=["kit_id", "sensor", "timestamp", "value", "unit", "_raw"])
99
- sensor_list = [
100
- s.get("name")
101
- for s in (kit.get("sensors") or [])
102
- if isinstance(s, dict) and s.get("name")
103
- ]
104
- else:
105
- sensor_list = [s for s in sensors if s]
106
-
107
- rows: list[dict[str, Any]] = []
108
-
109
- for sname in sensor_list:
110
- endpoint = f"{BASE_URL}/kits/{kit_id}/{sname}/measurements"
111
- for page in _paginate(endpoint, headers=HEADERS, page_size=page_size):
112
- for item in page:
113
- if not isinstance(item, dict):
114
- continue
115
-
116
- # Some APIs nest details under 'attributes'
117
- rec = item.get("attributes", {})
118
- rec.update({k: v for k, v in item.items() if k != "attributes"})
119
-
120
- ts = rec.get("timestamp") or rec.get("time") or rec.get("created_at") or rec.get("datetime")
121
- val = rec.get("value") or rec.get("reading") or rec.get("measurement") or rec.get("val")
122
- unit = rec.get("unit") or rec.get("units")
123
- rows.append(
124
- {
125
- "kit_id": kit_id,
126
- "sensor": sname,
127
- "timestamp": ts,
128
- "value": val,
129
- "unit": unit,
130
- "_raw": item, # preserve original
131
- }
132
- )
133
-
134
- df = pd.DataFrame(rows)
135
- if not df.empty and "timestamp" in df.columns:
136
- try:
137
- df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce", utc=True)
138
- df = df.sort_values(["sensor", "timestamp"], kind="stable")
139
- except Exception:
140
- pass
141
- return df
142
-
143
-
144
- def fetch_kit_dataframe(kit_id: int) -> pd.DataFrame:
145
- """Simplest API: return all measurements for the given kit as a DataFrame.
146
-
147
- Equivalent to get_kit_measurements_df(kit_id) with sensible defaults.
148
- """
149
- return get_kit_measurements_df(kit_id)
 
1
  """
2
+ Deprecated API shim.
3
 
4
+ All API-related functions/constants moved to `api_call.py` to keep
5
+ `utils.py` free of network concerns. Import directly from `api_call`.
6
+
7
+ This module re-exports the public API for backward compatibility.
8
  """
9
  from __future__ import annotations
10
 
11
+ import warnings
12
+
13
+ from api_call import (
14
+ BASE_URL,
15
+ HEADERS,
16
+ get_kit_info,
17
+ get_kit_measurements_df,
18
+ fetch_kit_dataframe,
19
+ )
20
+
21
+ warnings.warn(
22
+ "The API helpers were moved from utils.py to api_call.py. "
23
+ "Please import from 'api_call' going forward.",
24
+ DeprecationWarning,
25
+ stacklevel=2,
26
+ )
27
+
28
+ __all__ = [
29
+ "BASE_URL",
30
+ "HEADERS",
31
+ "get_kit_info",
32
+ "get_kit_measurements_df",
33
+ "fetch_kit_dataframe",
34
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
weather_data_visualisation.py CHANGED
@@ -7,21 +7,53 @@ import pandas as pd
7
  import matplotlib.pyplot as plt
8
  from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
9
  from PIL import Image
10
-
11
-
12
- def weather_data_visualisation(save_to_disk: bool = False) -> Image.Image:
13
- weatherdata_df = pd.read_csv("data/kit_1001_2025-09-22.csv", index_col=2)
14
- weatherdata_df.drop(columns=['kit_id', "unit","_raw"], inplace=True)
15
- weatherdata_df.dropna(inplace=True)
16
- weatherdata_df.index = pd.to_datetime(weatherdata_df.index)
17
- weatherdata_df = weatherdata_df.pivot(columns='sensor', values='value')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  # ---- Mapping to polar "Monsoon Mandala" ----
20
  # Angles map to time; radii encode a blended metric; thickness & dot size encode other variables.
21
-
22
- df = weatherdata_df
23
-
24
- theta = np.linspace(0, 2*np.pi, len(df), endpoint=False)
25
 
26
  # Normalize helpers (avoid specifying colors, per instructions).
27
  def norm(x):
@@ -96,5 +128,10 @@ def weather_data_visualisation(save_to_disk: bool = False) -> Image.Image:
96
  plt.close(fig)
97
  return pil_img
98
 
 
99
  if __name__ == "__main__":
100
- weather_data_visualisation()
 
 
 
 
 
7
  import matplotlib.pyplot as plt
8
  from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
9
  from PIL import Image
10
+ from typing import Optional
11
+ from api_call import get_kit_measurements_df
12
+
13
+
14
+ def weather_data_visualisation(
15
+ kit: int = 1001,
16
+ save_to_disk: bool = False,
17
+ df: Optional[pd.DataFrame] = None,
18
+ ) -> Image.Image:
19
+ """Generates a 'Monsoon Mandala' visualization from weather data.
20
+
21
+ Args:
22
+ kit: The kit ID to fetch data for, if df is not provided.
23
+ save_to_disk: Whether to save the output image to disk.
24
+ df: An optional DataFrame with pre-loaded weather data. If None, data will be fetched.
25
+
26
+ Returns:
27
+ A PIL.Image object of the generated visualization.
28
+ """
29
+ # If no DataFrame is provided, fetch the data using the kit ID
30
+ if df is None:
31
+ df = get_kit_measurements_df(kit)
32
+
33
+ # If data is still unavailable, return a placeholder or raise an error
34
+ if df is None or df.empty:
35
+ raise ValueError(f"No data available for kit {kit}")
36
+
37
+ # --- Data cleaning and pivoting ---
38
+ # Drop columns that are not needed for the visualization (new API df has no _raw)
39
+ df.drop(columns=['kit_id', "unit"], inplace=True, errors='ignore')
40
+ df.dropna(inplace=True)
41
+
42
+ # Ensure the index is a datetime object before pivoting
43
+ if not isinstance(df.index, pd.DatetimeIndex):
44
+ df['timestamp'] = pd.to_datetime(df['timestamp'])
45
+ df.set_index('timestamp', inplace=True)
46
+
47
+ df = df.pivot(columns='sensor', values='value')
48
+
49
+ # Ensure required columns exist (fill with zeros if missing)
50
+ for col in ['ftTemp', 'gbHum', 'NH3', 'C3H8', 'CO']:
51
+ if col not in df.columns:
52
+ df[col] = 0.0
53
 
54
  # ---- Mapping to polar "Monsoon Mandala" ----
55
  # Angles map to time; radii encode a blended metric; thickness & dot size encode other variables.
56
+ theta = np.linspace(0, 2 * np.pi, len(df), endpoint=False)
 
 
 
57
 
58
  # Normalize helpers (avoid specifying colors, per instructions).
59
  def norm(x):
 
128
  plt.close(fig)
129
  return pil_img
130
 
131
+
132
  if __name__ == "__main__":
133
+ # Example: Generate visualization for a specific kit and save it
134
+ img = weather_data_visualisation(kit=1001, save_to_disk=True)
135
+ if img:
136
+ print("Visualization generated and saved to 'output/'.")
137
+ img.show() # Display the image