from rest_framework import serializers from .models import ( AnalysisGridObservation, BlockSubdivision, RemoteSensingRun, RemoteSensingClusterAssignment, RemoteSensingSubdivisionResult, SoilLocation, ) from .satellite_snapshot import build_location_block_satellite_snapshots class SoilDataRequestSerializer(serializers.Serializer): """ورودی ثبت مزرعه و بلوک‌های تعریف‌شده توسط کشاورز.""" class BlockInputSerializer(serializers.Serializer): block_code = serializers.CharField(max_length=64) boundary = serializers.JSONField() order = serializers.IntegerField(required=False, min_value=1) lon = serializers.DecimalField(max_digits=9, decimal_places=6, required=True) lat = serializers.DecimalField(max_digits=9, decimal_places=6, required=True) block_count = serializers.IntegerField(required=False, min_value=1, default=1) block_code = serializers.CharField(required=False, default="block-1", max_length=64) farm_boundary = serializers.JSONField(required=False) blocks = BlockInputSerializer(many=True, required=False) def validate(self, attrs): blocks = attrs.get("blocks") or [] if self.context.get("require_farm_boundary") and not attrs.get("farm_boundary"): raise serializers.ValidationError( {"farm_boundary": ["مختصات گوشه‌های کل زمین باید ارسال شود."]} ) if self.context.get("require_farm_boundary") and not blocks: raise serializers.ValidationError( {"blocks": ["مختصات بلوک‌های تعریف‌شده توسط کشاورز باید ارسال شود."]} ) if blocks: attrs["block_count"] = len(blocks) return attrs class SoilLocationResponseSerializer(serializers.ModelSerializer): """سریالایزر خروجی برای SoilLocation همراه با خلاصه سنجش‌ازدور.""" lon = serializers.DecimalField( source="longitude", max_digits=9, decimal_places=6, read_only=True, ) lat = serializers.DecimalField( source="latitude", max_digits=9, decimal_places=6, read_only=True, ) input_block_count = serializers.IntegerField(read_only=True) farm_boundary = serializers.JSONField(read_only=True) block_layout = serializers.JSONField(read_only=True) block_subdivisions = serializers.SerializerMethodField() satellite_snapshots = serializers.SerializerMethodField() class Meta: model = SoilLocation fields = [ "id", "lon", "lat", "input_block_count", "farm_boundary", "block_layout", "block_subdivisions", "satellite_snapshots", ] def get_block_subdivisions(self, obj): subdivisions = obj.block_subdivisions.all().order_by("block_code", "id") return BlockSubdivisionSerializer(subdivisions, many=True).data def get_satellite_snapshots(self, obj): return build_location_block_satellite_snapshots(obj) class BlockSubdivisionSerializer(serializers.ModelSerializer): elbow_plot = serializers.ImageField(read_only=True) class Meta: model = BlockSubdivision fields = [ "block_code", "chunk_size_sqm", "grid_points", "centroid_points", "grid_point_count", "centroid_count", "elbow_plot", "status", "metadata", "created_at", "updated_at", ] class SoilDataTaskResponseSerializer(serializers.Serializer): """سریالایزر خروجی وقتی تسک در صف قرار گرفته (۲۰۲).""" source = serializers.CharField(default="task") task_id = serializers.UUIDField() lon = serializers.FloatField(source="longitude") lat = serializers.FloatField(source="latitude") status_url = serializers.CharField(required=False) class NdviHealthRequestSerializer(serializers.Serializer): farm_uuid = serializers.UUIDField(required=True, help_text="شناسه یکتای مزرعه") class NdviHealthDataItemSerializer(serializers.Serializer): title = serializers.CharField() value = serializers.JSONField() color = serializers.CharField() icon = serializers.CharField() class NdviHealthResponseSerializer(serializers.Serializer): ndviIndex = serializers.FloatField(allow_null=True, required=False) mean_ndvi = serializers.FloatField(allow_null=True) ndvi_map = serializers.JSONField() vegetation_health_class = serializers.CharField(allow_null=True) observation_date = serializers.CharField(allow_null=True) satellite_source = serializers.CharField(allow_null=True) healthData = NdviHealthDataItemSerializer(many=True) class RemoteSensingFarmRequestSerializer(serializers.Serializer): farm_uuid = serializers.UUIDField(required=True, help_text="شناسه یکتای مزرعه") force_refresh = serializers.BooleanField(required=False, default=False) page = serializers.IntegerField(required=False, min_value=1, default=1) page_size = serializers.IntegerField(required=False, min_value=1, max_value=200, default=100) class RemoteSensingCellObservationSerializer(serializers.ModelSerializer): cell_code = serializers.CharField(source="cell.cell_code", read_only=True) block_code = serializers.CharField(source="cell.block_code", read_only=True) chunk_size_sqm = serializers.IntegerField(source="cell.chunk_size_sqm", read_only=True) centroid_lat = serializers.DecimalField(source="cell.centroid_lat", max_digits=9, decimal_places=6, read_only=True) centroid_lon = serializers.DecimalField(source="cell.centroid_lon", max_digits=9, decimal_places=6, read_only=True) geometry = serializers.JSONField(source="cell.geometry", read_only=True) class Meta: model = AnalysisGridObservation fields = [ "cell_code", "block_code", "chunk_size_sqm", "centroid_lat", "centroid_lon", "geometry", "temporal_start", "temporal_end", "ndvi", "ndwi", "lst_c", "soil_vv", "soil_vv_db", "dem_m", "slope_deg", "metadata", ] class RemoteSensingSummarySerializer(serializers.Serializer): cell_count = serializers.IntegerField() ndvi_mean = serializers.FloatField(allow_null=True) ndwi_mean = serializers.FloatField(allow_null=True) lst_c_mean = serializers.FloatField(allow_null=True) soil_vv_db_mean = serializers.FloatField(allow_null=True) dem_m_mean = serializers.FloatField(allow_null=True) slope_deg_mean = serializers.FloatField(allow_null=True) class RemoteSensingRunSerializer(serializers.ModelSerializer): status_label = serializers.SerializerMethodField() pipeline_status = serializers.SerializerMethodField() stage = serializers.SerializerMethodField() selected_features = serializers.SerializerMethodField() requested_cluster_count = serializers.SerializerMethodField() def get_status_label(self, obj): return obj.normalized_status def get_pipeline_status(self, obj): return obj.normalized_status def get_stage(self, obj): return (obj.metadata or {}).get("stage") def get_selected_features(self, obj): return (obj.metadata or {}).get("selected_features", []) def get_requested_cluster_count(self, obj): return (obj.metadata or {}).get("requested_cluster_count") class Meta: model = RemoteSensingRun fields = [ "id", "block_code", "chunk_size_sqm", "temporal_start", "temporal_end", "status", "status_label", "pipeline_status", "stage", "selected_features", "requested_cluster_count", "metadata", "error_message", "started_at", "finished_at", "created_at", "updated_at", ] class RemoteSensingClusterAssignmentSerializer(serializers.ModelSerializer): cell_code = serializers.CharField(source="cell.cell_code", read_only=True) centroid_lat = serializers.DecimalField(source="cell.centroid_lat", max_digits=9, decimal_places=6, read_only=True) centroid_lon = serializers.DecimalField(source="cell.centroid_lon", max_digits=9, decimal_places=6, read_only=True) class Meta: model = RemoteSensingClusterAssignment fields = [ "cell_code", "cluster_label", "centroid_lat", "centroid_lon", "raw_feature_values", "scaled_feature_values", ] class RemoteSensingSubdivisionResultSerializer(serializers.ModelSerializer): assignments = serializers.SerializerMethodField() def get_assignments(self, obj): assignments = self.context.get("paginated_assignments") if assignments is None: assignments = obj.assignments.all().order_by("cluster_label", "cell__cell_code") return RemoteSensingClusterAssignmentSerializer(assignments, many=True).data class Meta: model = RemoteSensingSubdivisionResult fields = [ "id", "block_code", "chunk_size_sqm", "temporal_start", "temporal_end", "cluster_count", "selected_features", "skipped_cell_codes", "metadata", "assignments", "created_at", "updated_at", ] class RemoteSensingResponseSerializer(serializers.Serializer): status = serializers.CharField() source = serializers.CharField() location = SoilLocationResponseSerializer() block_code = serializers.CharField(allow_blank=True) chunk_size_sqm = serializers.IntegerField(allow_null=True) temporal_extent = serializers.JSONField() summary = RemoteSensingSummarySerializer() cells = RemoteSensingCellObservationSerializer(many=True) run = RemoteSensingRunSerializer(allow_null=True) subdivision_result = RemoteSensingSubdivisionResultSerializer(allow_null=True) pagination = serializers.JSONField(required=False) class RemoteSensingRunStatusResponseSerializer(serializers.Serializer): status = serializers.CharField() source = serializers.CharField() run = RemoteSensingRunSerializer() task_id = serializers.UUIDField(allow_null=True, required=False) location = SoilLocationResponseSerializer(required=False) block_code = serializers.CharField(allow_blank=True, required=False) chunk_size_sqm = serializers.IntegerField(allow_null=True, required=False) temporal_extent = serializers.JSONField(required=False) summary = RemoteSensingSummarySerializer(required=False) cells = RemoteSensingCellObservationSerializer(many=True, required=False) subdivision_result = RemoteSensingSubdivisionResultSerializer(allow_null=True, required=False) pagination = serializers.JSONField(required=False) class RemoteSensingRunResultResponseSerializer(serializers.Serializer): status = serializers.CharField() source = serializers.CharField() location = SoilLocationResponseSerializer() block_code = serializers.CharField(allow_blank=True) chunk_size_sqm = serializers.IntegerField(allow_null=True) temporal_extent = serializers.JSONField() summary = RemoteSensingSummarySerializer() cells = RemoteSensingCellObservationSerializer(many=True) run = RemoteSensingRunSerializer() subdivision_result = RemoteSensingSubdivisionResultSerializer(allow_null=True) pagination = serializers.JSONField(required=False)