from rest_framework import serializers from location_data.serializers import SoilDepthDataSerializer from irrigation.models import IrrigationMethod from irrigation.serializers import IrrigationMethodSerializer from weather.models import WeatherForecast from .models import ( DEFAULT_SENSOR_DATA_TYPE, DEFAULT_SENSOR_KEY, FarmPlantAssignment, PlantCatalogSnapshot, SensorData, ) class SensorDataUpdateSerializer(serializers.Serializer): """ورودی آپدیت داده سنسور در ساختار JSON.""" farm_uuid = serializers.UUIDField(required=True) farm_boundary = serializers.JSONField(required=True) sensor_key = serializers.CharField(required=False, default=DEFAULT_SENSOR_KEY) sensor_payload = serializers.JSONField(required=False) plant_ids = serializers.ListField( child=serializers.IntegerField(), required=False, help_text="لیست شناسه گیاهان canonical در Backend/plants", ) irrigation_method_id = serializers.IntegerField( required=False, allow_null=True, help_text="شناسه روش آبیاری مرتبط", ) def to_internal_value(self, data): if not isinstance(data, dict): raise serializers.ValidationError("بدنه درخواست باید JSON object باشد.") payload = dict(data) known_fields = { "farm_uuid", "farm_boundary", "sensor_key", "sensor_payload", "plant_ids", "irrigation_method_id", } flat_metrics = { key: value for key, value in payload.items() if key not in known_fields } if flat_metrics: sensor_key = payload.get("sensor_key", DEFAULT_SENSOR_KEY) nested_payload = payload.get("sensor_payload") or {} if nested_payload and not isinstance(nested_payload, dict): raise serializers.ValidationError( {"sensor_payload": "sensor_payload باید object باشد."} ) merged_payload = dict(nested_payload) current_sensor_payload = merged_payload.get(sensor_key, {}) if current_sensor_payload and not isinstance(current_sensor_payload, dict): raise serializers.ValidationError( {"sensor_payload": f"مقدار {sensor_key} باید object باشد."} ) merged_sensor_payload = dict(current_sensor_payload) merged_sensor_payload.update(flat_metrics) merged_payload[sensor_key] = merged_sensor_payload payload["sensor_payload"] = merged_payload for key in flat_metrics: payload.pop(key, None) return super().to_internal_value(payload) def validate_sensor_payload(self, value): if not isinstance(value, dict): raise serializers.ValidationError("sensor_payload باید object باشد.") for sensor_key, sensor_values in value.items(): if not isinstance(sensor_values, dict): raise serializers.ValidationError( f"مقدار سنسور {sensor_key} باید object باشد." ) return value def validate_irrigation_method_id(self, value): if value is None: return value if not IrrigationMethod.objects.filter(pk=value).exists(): raise serializers.ValidationError("روش آبیاری معتبر نیست.") return value def validate(self, attrs): if ( "sensor_payload" not in attrs and "plant_ids" not in attrs and "irrigation_method_id" not in attrs ): raise serializers.ValidationError( "حداقل یکی از sensor_payload یا plant_ids یا irrigation_method_id باید ارسال شود." ) return attrs class SensorDataResponseSerializer(serializers.ModelSerializer): """سریالایزر خروجی برای SensorData.""" plant_ids = serializers.SerializerMethodField() irrigation_method_id = serializers.IntegerField( source="irrigation_method.id", read_only=True, allow_null=True, ) def get_plant_ids(self, obj): return [plant.backend_plant_id for plant in obj.plant_snapshots] class Meta: model = SensorData fields = [ "farm_uuid", "center_location_id", "weather_forecast_id", "sensor_payload", "plant_ids", "irrigation_method_id", "created_at", "updated_at", ] class SensorParameterSerializer(serializers.Serializer): """سریالایزر ورودی برای تعریف پارامترهای سنسورهای مختلف.""" sensor_key = serializers.CharField(max_length=64, required=False, default=DEFAULT_SENSOR_KEY) code = serializers.CharField(max_length=64) name_fa = serializers.CharField(max_length=128) unit = serializers.CharField(max_length=32, required=False, allow_blank=True) data_type = serializers.CharField( max_length=32, required=False, default=DEFAULT_SENSOR_DATA_TYPE, ) metadata = serializers.JSONField(required=False, default=dict) class FarmCenterLocationSerializer(serializers.Serializer): id = serializers.IntegerField() lat = serializers.DecimalField(max_digits=9, decimal_places=6) lon = serializers.DecimalField(max_digits=9, decimal_places=6) farm_boundary = serializers.JSONField() class WeatherForecastDetailSerializer(serializers.ModelSerializer): class Meta: model = WeatherForecast fields = [ "id", "forecast_date", "temperature_min", "temperature_max", "temperature_mean", "precipitation", "precipitation_probability", "humidity_mean", "wind_speed_max", "et0", "weather_code", ] class FarmSoilPayloadSerializer(serializers.Serializer): resolved_metrics = serializers.JSONField() metric_sources = serializers.JSONField() depths = SoilDepthDataSerializer(many=True) class PlantCatalogSnapshotSerializer(serializers.ModelSerializer): id = serializers.IntegerField(source="backend_plant_id", read_only=True) class Meta: model = PlantCatalogSnapshot fields = [ "id", "backend_plant_id", "name", "slug", "icon", "description", "metadata", "light", "watering", "soil", "temperature", "growth_stage", "growth_stages", "planting_season", "harvest_time", "spacing", "fertilizer", "health_profile", "irrigation_profile", "growth_profile", "is_active", "source_updated_at", "updated_at", ] class FarmPlantAssignmentSerializer(serializers.ModelSerializer): plant_id = serializers.IntegerField(source="plant.backend_plant_id", read_only=True) plant = PlantCatalogSnapshotSerializer(read_only=True) class Meta: model = FarmPlantAssignment fields = [ "plant_id", "position", "stage", "metadata", "assigned_at", "updated_at", "plant", ] class FarmDetailSerializer(serializers.Serializer): center_location = FarmCenterLocationSerializer() weather = WeatherForecastDetailSerializer(allow_null=True) sensor_payload = serializers.JSONField() sensor_schema = serializers.JSONField() soil = FarmSoilPayloadSerializer() plant_ids = serializers.ListField(child=serializers.IntegerField()) plants = PlantCatalogSnapshotSerializer(many=True) plant_assignments = FarmPlantAssignmentSerializer(many=True) irrigation_method_id = serializers.IntegerField(allow_null=True) irrigation_method = IrrigationMethodSerializer(allow_null=True) created_at = serializers.DateTimeField() updated_at = serializers.DateTimeField()