from django.db import models DEFAULT_SENSOR_KEY = "sensor-7-1" DEFAULT_SENSOR_DATA_TYPE = "float" class SensorPayloadMixin: """دسترسی سازگار به مقادیر سنسور از payload پویا.""" sensor_payload: dict def _payload(self) -> dict: if isinstance(self.sensor_payload, dict): return self.sensor_payload return {} def get_sensor_block(self, sensor_key: str | None = None) -> dict: payload = self._payload() if sensor_key: block = payload.get(sensor_key, {}) return block if isinstance(block, dict) else {} for block in payload.values(): if isinstance(block, dict): return block return {} def get_metric(self, metric_name: str, sensor_key: str | None = None): block = self.get_sensor_block(sensor_key) if metric_name in block: return block.get(metric_name) for candidate in self._payload().values(): if isinstance(candidate, dict) and metric_name in candidate: return candidate.get(metric_name) return None @property def soil_moisture(self): return self.get_metric("soil_moisture") @property def soil_temperature(self): return self.get_metric("soil_temperature") @property def soil_ph(self): return self.get_metric("soil_ph") @property def electrical_conductivity(self): return self.get_metric("electrical_conductivity") @property def nitrogen(self): return self.get_metric("nitrogen") @property def phosphorus(self): return self.get_metric("phosphorus") @property def potassium(self): return self.get_metric("potassium") class SensorData(SensorPayloadMixin, models.Model): """ داده‌های مزرعه/سنسور برای مرکز زمین. مقادیر سنسورها به‌صورت JSON ذخیره می‌شوند تا بتوان چند نوع سنسور و پارامترهای دلخواه را در یک رکورد نگه داشت. نمونه: { "sensor-7-1": { "soil_moisture": 22.4, "soil_temperature": 18.1 }, "leaf-sensor": { "leaf_wetness": 11 } } """ farm_uuid = models.UUIDField( primary_key=True, editable=False, help_text="شناسه یکتای farm که از API دریافت می‌شود", ) center_location = models.ForeignKey( "location_data.SoilLocation", on_delete=models.CASCADE, related_name="farm_data", db_column="center_location_id", help_text="مرکز زمین مرتبط از جدول location_data.SoilLocation", ) weather_forecast = models.ForeignKey( "weather.WeatherForecast", on_delete=models.SET_NULL, null=True, blank=True, related_name="farm_data_entries", db_column="weather_forecast_id", help_text="رکورد آب وهوای مرتبط با مرکز زمین", ) sensor_payload = models.JSONField( default=dict, blank=True, help_text='اطلاعات سنسورها در فرمت {"sensor-7-1": {...}}', ) plants = models.ManyToManyField( "plant.Plant", blank=True, db_table="farm_data_sensordata_plants", related_name="farm_data", help_text="گیاهان مرتبط با این farm", ) irrigation_method = models.ForeignKey( "irrigation.IrrigationMethod", on_delete=models.SET_NULL, null=True, blank=True, related_name="farm_data", db_column="irrigation_method_id", help_text="روش آبیاری انتخاب‌شده برای این farm", ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = "farm_data_sensordata" ordering = ["-updated_at"] verbose_name = "farm-data" verbose_name_plural = "farm-data" def __str__(self): return ( f"SensorData({self.farm_uuid}, center_location={self.center_location_id}, " f"weather_forecast={self.weather_forecast_id})" ) @property def location(self): return self.center_location @location.setter def location(self, value): self.center_location = value @property def location_id(self): return self.center_location_id class SensorParameter(models.Model): """ تعریف پارامترهای سنسور برای هر نوع سنسور. با این ساختار می‌توان برای sensor-7-1 یا هر سنسور جدید، پارامترهای اختصاصی تعریف کرد. """ sensor_key = models.CharField( max_length=64, db_index=True, default=DEFAULT_SENSOR_KEY, help_text='کلید سنسور داخل JSON مثل "sensor-7-1" یا "leaf-sensor"', ) code = models.CharField( max_length=64, db_index=True, help_text="کد پارامتر (مثلاً soil_moisture)", ) name_fa = models.CharField(max_length=128, help_text="نام فارسی") unit = models.CharField(max_length=32, blank=True, help_text="واحد اندازه‌گیری") data_type = models.CharField( max_length=32, default=DEFAULT_SENSOR_DATA_TYPE, help_text="نوع داده پارامتر مثل float, int, string, bool", ) metadata = models.JSONField( default=dict, blank=True, help_text="اطلاعات تکمیلی پارامتر مثل بازه مجاز، توضیح یا تنظیمات UI", ) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "farm_data_sensorparameter" ordering = ["sensor_key", "code"] constraints = [ models.UniqueConstraint( fields=["sensor_key", "code"], name="sensor_parameter_unique_sensor_code", ) ] verbose_name = "پارامتر سنسور" verbose_name_plural = "پارامترهای سنسور" def __str__(self): return f"{self.sensor_key}.{self.code} ({self.name_fa})" class ParameterUpdateLog(models.Model): """ لاگ آپدیت لیست پارامترها. """ ACTION_ADDED = "added" ACTION_MODIFIED = "modified" ACTION_CHOICES = [ (ACTION_ADDED, "اضافه شده"), (ACTION_MODIFIED, "ویرایش شده"), ] parameter = models.ForeignKey( SensorParameter, on_delete=models.CASCADE, related_name="update_logs", ) action = models.CharField(max_length=16, choices=ACTION_CHOICES) payload = models.JSONField( default=dict, blank=True, help_text="خلاصه تغییرات پارامتر برای audit", ) updated_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "farm_data_parameterupdatelog" ordering = ["-updated_at"] verbose_name = "لاگ آپدیت پارامتر" verbose_name_plural = "لاگ آپدیت پارامترها" def __str__(self): return f"{self.parameter.code} - {self.action} - {self.updated_at}"