440 lines
16 KiB
Python
440 lines
16 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 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 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()
|