demeter
Autonomous Hydroponic Intelligencegit clone https://github.com/maydayv7/demeter
agent/sub_agents/fetching_agent.py (4639B)
1import sys
2import os
3import requests
4from pathlib import Path
5import base64
6from io import BytesIO
7from PIL import Image
8
9current_file = Path(__file__).resolve()
10project_root = current_file.parent.parent.parent
11sys.path.append(str(project_root))
12
13from qdrant_client import models
14from Sentinel.agent import FMUBuilder
15from Qdrant.Store import COLLECTION_NAME
16from Qdrant.Client import client
17
18class FetchingAgent:
19 def __init__(self, simulator_url=None):
20 if simulator_url is None:
21 simulator_url = os.environ.get(
22 "SIMULATOR_STATE_URL", "http://localhost:8001/simulation/state"
23 )
24 self.sim_url = simulator_url
25 self.builder = FMUBuilder()
26
27 def fetch_and_process(self):
28 print(f"[Fetcher] 📡 Requesting data from {self.sim_url}...")
29
30 try:
31 response = requests.get(self.sim_url)
32
33 if response.status_code == 200:
34 data_list = response.json()
35
36 if not isinstance(data_list, list):
37 data_list = [data_list]
38
39 processed_crops = []
40
41 for data in data_list:
42 window_data = data.get("sensor_window", {})
43 image_b64 = data.get("image", "")
44 raw_meta = data.get("metadata", {})
45
46 if not image_b64:
47 img = Image.new("RGB", (512, 512), (50, 50, 50))
48 buf = BytesIO()
49 img.save(buf, format="PNG")
50 image_b64 = base64.b64encode(buf.getvalue()).decode("utf-8")
51
52 wanted_keys = {
53 "ph": "pH",
54 "ec": "EC",
55 "humidity": "humidity",
56 "temp": "temp",
57 "air_temp": "temp",
58 }
59 sensor_snapshot = {}
60 for key, value_list in window_data.items():
61 key_lower = key.lower()
62 if key_lower in wanted_keys:
63 out_name = wanted_keys[key_lower]
64 val = (
65 value_list[-1]
66 if isinstance(value_list, list) and value_list
67 else 0.0
68 )
69 sensor_snapshot[out_name] = val
70
71 crop_id = raw_meta.get("crop_id", data.get("crop_id", "UNKNOWN_CROP"))
72 next_seq = self._get_next_sequence(crop_id)
73
74 filtered_metadata = {
75 "crop": raw_meta.get("crop", "unknown"),
76 "stage": raw_meta.get("stage", "unknown"),
77 "crop_id": crop_id,
78 "sequence_number": next_seq,
79 "image_b64": image_b64,
80 }
81
82 fmu = self.builder.create_fmu(
83 image_b64, sensor_snapshot, filtered_metadata
84 )
85
86 search_results = self.find_similar_instances(fmu)
87
88 processed_crops.append({
89 "crop_id": crop_id,
90 "fmu": fmu,
91 "sensor_snapshot": sensor_snapshot,
92 "history": search_results,
93 "image_b64": image_b64
94 })
95
96 return processed_crops
97 else:
98 print(f"[Fetcher] ❌ Error: Simulator returned {response.status_code}")
99 return []
100
101 except Exception as e:
102 print(f"[Fetcher] ❌ Critical Error: {e}")
103 return []
104
105 def _get_next_sequence(self, crop_id):
106 try:
107 count_filter = models.Filter(
108 must=[
109 models.FieldCondition(
110 key="crop_id", match=models.MatchValue(value=crop_id)
111 )
112 ]
113 )
114 count_result = client.count(
115 collection_name=COLLECTION_NAME, count_filter=count_filter
116 )
117 return count_result.count + 1
118 except Exception:
119 return 1
120
121 def find_similar_instances(self, current_fmu):
122 try:
123 hits = client.search(
124 collection_name=COLLECTION_NAME,
125 query_vector=current_fmu.vector,
126 limit=3,
127 with_payload=True,
128 )
129 return [{"payload": hit.payload} for hit in hits]
130 except Exception:
131 return []