agent.py (4972B)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | import uuid import base64 import io from datetime import datetime import numpy as np from pathlib import Path # Ensure these imports match your project structure from Sentinel.Encoders.Vision import VisionEncoder from Sentinel.Encoders.TimeSeries import SensorEncoder from Sentinel.fmu import FMU from Qdrant.Store import store_fmu class FMUBuilder: def __init__(self): self.vision = VisionEncoder() self.sensors = SensorEncoder() def create_fmu(self, image_input, sensor_data, metadata=None): """ Creates an FMU from either: - A file path (str/Path) - A Base64 encoded image string Args: image_input: Either a file path string or base64 string sensor_data: Dictionary of sensor readings metadata: Optional metadata dictionary """ # Detect if input is base64 or file path if self._is_base64(image_input): # Handle Base64 input img_vec = self._encode_from_base64(image_input) else: # Handle file path input (original behavior) img_vec = self.vision.encode(image_input) # Encode sensor data sensor_vec = self.sensors.encode(sensor_data) # Combine vectors fmu_vector = np.concatenate([img_vec, sensor_vec]).tolist() # --- UPDATE START --- # 1. Ensure metadata is a dictionary if metadata is None: metadata = {} final_payload = { "timestamp": datetime.utcnow().isoformat(), # ✅ STORE RAW SENSORS (For Humans/Frontend) "sensors": sensor_data, # ✅ UNPACK METADATA (crop, stage, etc.) **metadata, # ✅ ENFORCE CRITICAL FIELDS (Defaults if missing) "crop_id": metadata.get("crop_id", "UNKNOWN_CROP"), "sequence_number": metadata.get("sequence_number", 1), "action_taken": metadata.get("action_taken", "PENDING_ACTION"), "outcome": metadata.get("outcome", "PENDING_OBSERVATION"), "explanation_log": metadata.get("explanation_log", "PENDING_ANALYSIS") } return FMU( id=str(uuid.uuid4()), vector=fmu_vector, metadata=final_payload # This becomes the Qdrant Payload ) def _is_base64(self, s): """ Detect if string is base64 or a file path. Returns True if it looks like base64, False if it looks like a path. """ if not isinstance(s, str): return False # If it has path separators, it's probably a path if '/' in s or '\\' in s or Path(s).exists(): return False # If it has base64 header, it's definitely base64 if s.startswith('data:image'): return True # Check if it's valid base64 (after removing potential header) test_str = s.split(',')[-1] if ',' in s else s # Base64 strings are typically very long and only contain valid b64 chars if len(test_str) > 100: # Arbitrary threshold try: base64.b64decode(test_str, validate=True) return True except Exception: return False return False def _encode_from_base64(self, image_base64): """ Decode base64 string and encode the image. """ # Remove header if present (e.g., "data:image/png;base64,...") if "," in image_base64: image_base64 = image_base64.split(",")[1] # Add padding if necessary (fix the "multiple of 4" error) missing_padding = len(image_base64) % 4 if missing_padding: image_base64 += '=' * (4 - missing_padding) # Decode to bytes image_bytes = base64.b64decode(image_base64) # Create file-like object image_stream = io.BytesIO(image_bytes) # Encode using VisionEncoder return self.vision.encode(image_stream) # if __name__ == "__main__": # builder = FMUBuilder() # sensors = { # "pH": 5.9, # "EC": 1.3, # "temp": 25.0, # "humidity": 72.0 # } # # Test with base64 # sample_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=" # fmu = builder.create_fmu(sample_base64, sensors, { # "crop": "lettuce", # "stage": "vegetative" # }) # print("✅ FMU ID:", fmu.id) # print("✅ Vector length:", len(fmu.vector)) # # Check for the new fields in the output # print("\n🔍 Checking Schema:") # print(f" - Action: {fmu.metadata.get('action_taken')}") # print(f" - Outcome: {fmu.metadata.get('outcome')}") # print(f" - Sensors Saved: {'sensors' in fmu.metadata}") |