Files
Ai/location_data/serializers.py
2026-05-13 16:45:54 +03:30

492 lines
19 KiB
Python

from rest_framework import serializers
from .models import (
AnalysisGridObservation,
BlockSubdivision,
RemoteSensingClusterBlock,
RemoteSensingRun,
RemoteSensingClusterAssignment,
RemoteSensingSubdivisionResult,
RemoteSensingSubdivisionOption,
RemoteSensingSubdivisionOptionBlock,
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 ClusterCropRecommendationRequestSerializer(serializers.Serializer):
farm_uuid = serializers.UUIDField(required=True, help_text="شناسه یکتای مزرعه")
class RemoteSensingClusterBlockLiveRequestSerializer(serializers.Serializer):
temporal_start = serializers.DateField(required=False)
temporal_end = serializers.DateField(required=False)
days = serializers.IntegerField(required=False, min_value=1, max_value=90, default=30)
def validate(self, attrs):
temporal_start = attrs.get("temporal_start")
temporal_end = attrs.get("temporal_end")
if bool(temporal_start) != bool(temporal_end):
raise serializers.ValidationError(
"برای بازه سفارشی باید هر دو فیلد temporal_start و temporal_end ارسال شوند."
)
if temporal_start and temporal_end and temporal_start > temporal_end:
raise serializers.ValidationError(
{"temporal_start": ["temporal_start نمی‌تواند بعد از temporal_end باشد."]}
)
return attrs
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",
"soil_vv",
"soil_vv_db",
"metadata",
]
class RemoteSensingSummarySerializer(serializers.Serializer):
cell_count = serializers.IntegerField()
ndvi_mean = serializers.FloatField(allow_null=True)
ndwi_mean = serializers.FloatField(allow_null=True)
soil_vv_db_mean = serializers.FloatField(allow_null=True)
class RemoteSensingClusterBlockLiveMetricsSerializer(serializers.Serializer):
ndvi = serializers.FloatField(allow_null=True)
ndwi = serializers.FloatField(allow_null=True)
soil_vv = serializers.FloatField(allow_null=True)
soil_vv_db = 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):
metadata_status = (obj.metadata or {}).get("status_label")
return metadata_status or obj.normalized_status
def get_pipeline_status(self, obj):
metadata_status = (obj.metadata or {}).get("status_label")
return metadata_status or 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 RemoteSensingClusterBlockSerializer(serializers.ModelSerializer):
class Meta:
model = RemoteSensingClusterBlock
fields = [
"uuid",
"sub_block_code",
"cluster_label",
"chunk_size_sqm",
"centroid_lat",
"centroid_lon",
"center_cell_code",
"center_cell_lat",
"center_cell_lon",
"cell_count",
"cell_codes",
"geometry",
"metadata",
"created_at",
"updated_at",
]
class RemoteSensingSubdivisionOptionBlockSerializer(serializers.ModelSerializer):
class Meta:
model = RemoteSensingSubdivisionOptionBlock
fields = [
"cluster_label",
"sub_block_code",
"chunk_size_sqm",
"centroid_lat",
"centroid_lon",
"center_cell_code",
"center_cell_lat",
"center_cell_lon",
"cell_count",
"cell_codes",
"geometry",
"metadata",
]
class RemoteSensingSubdivisionOptionSerializer(serializers.ModelSerializer):
cluster_blocks = RemoteSensingSubdivisionOptionBlockSerializer(many=True, read_only=True)
class Meta:
model = RemoteSensingSubdivisionOption
fields = [
"id",
"requested_k",
"effective_cluster_count",
"is_active",
"is_recommended",
"selection_source",
"metadata",
"cluster_blocks",
"created_at",
"updated_at",
]
class RemoteSensingSubdivisionOptionActivateSerializer(serializers.Serializer):
requested_k = serializers.IntegerField(min_value=1)
class RemoteSensingSubdivisionResultSerializer(serializers.ModelSerializer):
assignments = serializers.SerializerMethodField()
cluster_blocks = RemoteSensingClusterBlockSerializer(many=True, read_only=True)
available_k_options = 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
def get_available_k_options(self, obj):
options = obj.options.all().order_by("requested_k")
return RemoteSensingSubdivisionOptionSerializer(options, 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",
"available_k_options",
"cluster_blocks",
"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)
task = serializers.JSONField(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)
class RemoteSensingClusterBlockLiveResponseSerializer(serializers.Serializer):
status = serializers.CharField()
source = serializers.CharField()
cluster_block = RemoteSensingClusterBlockSerializer()
temporal_extent = serializers.JSONField()
selected_features = serializers.ListField(
child=serializers.CharField(),
allow_empty=False,
)
summary = RemoteSensingSummarySerializer()
metrics = RemoteSensingClusterBlockLiveMetricsSerializer()
metadata = serializers.JSONField()
class ClusterCropRegisteredPlantSerializer(serializers.Serializer):
plant_id = serializers.IntegerField()
plant_name = serializers.CharField()
position = serializers.IntegerField(allow_null=True)
stage = serializers.CharField(allow_blank=True)
class ClusterCropCandidateSerializer(serializers.Serializer):
plant_id = serializers.IntegerField(allow_null=True)
plant_name = serializers.CharField()
position = serializers.IntegerField(allow_null=True)
stage = serializers.CharField(allow_blank=True)
score = serializers.FloatField()
predicted_yield = serializers.FloatField(allow_null=True)
predicted_yield_tons = serializers.FloatField(allow_null=True)
biomass = serializers.FloatField(allow_null=True)
max_lai = serializers.FloatField(allow_null=True)
simulation_engine = serializers.CharField(allow_null=True)
simulation_model_name = serializers.CharField(allow_null=True)
simulation_warning = serializers.CharField(allow_null=True, allow_blank=True)
supporting_metrics = serializers.JSONField()
class ClusterCropRecommendationClusterSerializer(serializers.Serializer):
block_code = serializers.CharField(allow_blank=True)
cluster_uuid = serializers.CharField(allow_null=True, allow_blank=True)
sub_block_code = serializers.CharField()
cluster_label = serializers.IntegerField(allow_null=True)
temporal_extent = serializers.JSONField(allow_null=True)
cluster_block = RemoteSensingClusterBlockSerializer(allow_null=True)
satellite_metrics = serializers.JSONField()
sensor_metrics = serializers.JSONField()
resolved_metrics = serializers.JSONField()
candidate_plants = ClusterCropCandidateSerializer(many=True)
suggested_plant = ClusterCropCandidateSerializer(allow_null=True)
source_metadata = serializers.JSONField()
class ClusterCropRecommendationResponseSerializer(serializers.Serializer):
farm_uuid = serializers.CharField()
location_id = serializers.IntegerField()
evaluated_plant_count = serializers.IntegerField()
cluster_count = serializers.IntegerField()
registered_plants = ClusterCropRegisteredPlantSerializer(many=True)
clusters = ClusterCropRecommendationClusterSerializer(many=True)
source_metadata = serializers.JSONField()
class RemoteSensingSubdivisionOptionListResponseSerializer(serializers.Serializer):
result_id = serializers.IntegerField()
active_requested_k = serializers.IntegerField(allow_null=True)
recommended_requested_k = serializers.IntegerField(allow_null=True)
options = RemoteSensingSubdivisionOptionSerializer(many=True)
class RemoteSensingSubdivisionOptionActivateResponseSerializer(serializers.Serializer):
result_id = serializers.IntegerField()
activated_requested_k = serializers.IntegerField()
subdivision_result = RemoteSensingSubdivisionResultSerializer()