UPDATE
This commit is contained in:
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SoilConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "soil"
|
||||
verbose_name = "Soil"
|
||||
@@ -0,0 +1,83 @@
|
||||
AVG_SOIL_MOISTURE = {
|
||||
"id": "avg_soil_moisture",
|
||||
"title": "میانگین رطوبت خاک",
|
||||
"subtitle": "کل مزرعه",
|
||||
"stats": "65%",
|
||||
"avatarColor": "primary",
|
||||
"avatarIcon": "tabler-plant-2",
|
||||
"chipText": "بهینه",
|
||||
"chipColor": "success",
|
||||
}
|
||||
|
||||
|
||||
SENSOR_RADAR_CHART = {
|
||||
"labels": ["دما", "رطوبت", "pH", "هدایت الکتریکی", "نور", "باد"],
|
||||
"series": [
|
||||
{"name": "امروز", "data": [75, 65, 80, 70, 85, 60]},
|
||||
{"name": "ایده آل", "data": [80, 70, 75, 75, 90, 50]},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
SENSOR_COMPARISON_CHART = {
|
||||
"currentValue": 48,
|
||||
"vsLastWeek": "+5%",
|
||||
"vsLastWeekValue": 5,
|
||||
"categories": ["دوشنبه", "سه شنبه", "چهارشنبه", "پنج شنبه", "جمعه", "شنبه", "یکشنبه"],
|
||||
"series": [
|
||||
{"name": "امروز", "data": [42, 45, 48, 52, 50, 48, 46]},
|
||||
{"name": "هفته قبل", "data": [38, 40, 42, 45, 43, 40, 38]},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
ANOMALY_DETECTION_CARD = {
|
||||
"anomalies": [
|
||||
{
|
||||
"sensor": "رطوبت خاک زون 3",
|
||||
"value": "38%",
|
||||
"expected": "45-65%",
|
||||
"deviation": "-12%",
|
||||
"severity": "warning",
|
||||
},
|
||||
{
|
||||
"sensor": "pH بخش 2",
|
||||
"value": "5.2",
|
||||
"expected": "6.0-7.0",
|
||||
"deviation": "-0.8",
|
||||
"severity": "error",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
SOIL_MOISTURE_HEATMAP = {
|
||||
"zones": ["زون 1", "زون 2", "زون 3", "زون 4", "زون 5", "زون 6", "زون 7"],
|
||||
"hours": ["6 ص", "8 ص", "10 ص", "12 ظ", "14 ع", "16 ع", "18 ع"],
|
||||
"series": [
|
||||
{
|
||||
"name": "زون 1",
|
||||
"data": [
|
||||
{"x": "6 ص", "y": 52},
|
||||
{"x": "8 ص", "y": 48},
|
||||
{"x": "10 ص", "y": 55},
|
||||
{"x": "12 ظ", "y": 60},
|
||||
{"x": "14 ع", "y": 58},
|
||||
{"x": "16 ع", "y": 54},
|
||||
{"x": "18 ع", "y": 50},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "زون 2",
|
||||
"data": [
|
||||
{"x": "6 ص", "y": 45},
|
||||
{"x": "8 ص", "y": 42},
|
||||
{"x": "10 ص", "y": 48},
|
||||
{"x": "12 ظ", "y": 52},
|
||||
{"x": "14 ع", "y": 50},
|
||||
{"x": "16 ع", "y": 47},
|
||||
{"x": "18 ع", "y": 44},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
from django.db import models
|
||||
@@ -0,0 +1,66 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class SoilKpiSerializer(serializers.Serializer):
|
||||
id = serializers.CharField(required=False, allow_blank=True)
|
||||
title = serializers.CharField(required=False, allow_blank=True)
|
||||
subtitle = serializers.CharField(required=False, allow_blank=True)
|
||||
stats = serializers.CharField(required=False, allow_blank=True)
|
||||
avatarColor = serializers.CharField(required=False, allow_blank=True)
|
||||
avatarIcon = serializers.CharField(required=False, allow_blank=True)
|
||||
chipText = serializers.CharField(required=False, allow_blank=True)
|
||||
chipColor = serializers.CharField(required=False, allow_blank=True)
|
||||
|
||||
|
||||
class SoilRadarSeriesSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(required=False, allow_blank=True)
|
||||
data = serializers.ListField(child=serializers.FloatField(), required=False)
|
||||
|
||||
|
||||
class SoilRadarChartSerializer(serializers.Serializer):
|
||||
labels = serializers.ListField(child=serializers.CharField(), required=False)
|
||||
series = SoilRadarSeriesSerializer(many=True, required=False)
|
||||
|
||||
|
||||
class SoilComparisonChartSerializer(serializers.Serializer):
|
||||
currentValue = serializers.FloatField(required=False)
|
||||
vsLastWeek = serializers.CharField(required=False, allow_blank=True)
|
||||
vsLastWeekValue = serializers.FloatField(required=False)
|
||||
categories = serializers.ListField(child=serializers.CharField(), required=False)
|
||||
series = SoilRadarSeriesSerializer(many=True, required=False)
|
||||
|
||||
|
||||
class SoilAnomalyItemSerializer(serializers.Serializer):
|
||||
sensor = serializers.CharField(required=False, allow_blank=True)
|
||||
value = serializers.CharField(required=False, allow_blank=True)
|
||||
expected = serializers.CharField(required=False, allow_blank=True)
|
||||
deviation = serializers.CharField(required=False, allow_blank=True)
|
||||
severity = serializers.CharField(required=False, allow_blank=True)
|
||||
|
||||
|
||||
class SoilAnomalyDetectionSerializer(serializers.Serializer):
|
||||
anomalies = SoilAnomalyItemSerializer(many=True, required=False)
|
||||
|
||||
|
||||
class SoilHeatmapPointSerializer(serializers.Serializer):
|
||||
x = serializers.CharField(required=False, allow_blank=True)
|
||||
y = serializers.FloatField(required=False)
|
||||
|
||||
|
||||
class SoilHeatmapSeriesSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(required=False, allow_blank=True)
|
||||
data = SoilHeatmapPointSerializer(many=True, required=False)
|
||||
|
||||
|
||||
class SoilMoistureHeatmapSerializer(serializers.Serializer):
|
||||
zones = serializers.ListField(child=serializers.CharField(), required=False)
|
||||
hours = serializers.ListField(child=serializers.CharField(), required=False)
|
||||
series = SoilHeatmapSeriesSerializer(many=True, required=False)
|
||||
|
||||
|
||||
class SoilSummarySerializer(serializers.Serializer):
|
||||
avgSoilMoisture = SoilKpiSerializer(required=False)
|
||||
sensorRadarChart = SoilRadarChartSerializer(required=False)
|
||||
sensorComparisonChart = SoilComparisonChartSerializer(required=False)
|
||||
anomalyDetectionCard = SoilAnomalyDetectionSerializer(required=False)
|
||||
soilMoistureHeatmap = SoilMoistureHeatmapSerializer(required=False)
|
||||
@@ -0,0 +1,84 @@
|
||||
from copy import deepcopy
|
||||
|
||||
from farm_alerts.models import AnomalyDetection
|
||||
|
||||
from .mock_data import (
|
||||
ANOMALY_DETECTION_CARD,
|
||||
AVG_SOIL_MOISTURE,
|
||||
SENSOR_COMPARISON_CHART,
|
||||
SENSOR_RADAR_CHART,
|
||||
SOIL_MOISTURE_HEATMAP,
|
||||
)
|
||||
|
||||
|
||||
def get_avg_soil_moisture_data(farm=None):
|
||||
data = deepcopy(AVG_SOIL_MOISTURE)
|
||||
heatmap = get_soil_moisture_heatmap_data(farm)
|
||||
values = [
|
||||
point.get("y")
|
||||
for series in heatmap.get("series", [])
|
||||
for point in series.get("data", [])
|
||||
if point.get("y") is not None
|
||||
]
|
||||
|
||||
if not values:
|
||||
return data
|
||||
|
||||
average = round(sum(values) / len(values))
|
||||
data["stats"] = f"{average}%"
|
||||
if average >= 60:
|
||||
data["chipText"] = "بهینه"
|
||||
data["chipColor"] = "success"
|
||||
elif average >= 45:
|
||||
data["chipText"] = "متوسط"
|
||||
data["chipColor"] = "warning"
|
||||
else:
|
||||
data["chipText"] = "کم"
|
||||
data["chipColor"] = "error"
|
||||
data["avatarColor"] = "warning"
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_sensor_radar_chart_data(farm=None):
|
||||
return deepcopy(SENSOR_RADAR_CHART)
|
||||
|
||||
|
||||
def get_sensor_comparison_chart_data(farm=None):
|
||||
return deepcopy(SENSOR_COMPARISON_CHART)
|
||||
|
||||
|
||||
def get_anomaly_detection_card_data(farm=None):
|
||||
if farm is None:
|
||||
return deepcopy(ANOMALY_DETECTION_CARD)
|
||||
|
||||
anomalies = list(AnomalyDetection.objects.filter(farm=farm)[:10])
|
||||
if not anomalies:
|
||||
return deepcopy(ANOMALY_DETECTION_CARD)
|
||||
|
||||
return {
|
||||
"anomalies": [
|
||||
{
|
||||
"sensor": anomaly.sensor,
|
||||
"value": anomaly.value,
|
||||
"expected": anomaly.expected,
|
||||
"deviation": anomaly.deviation,
|
||||
"severity": anomaly.severity,
|
||||
}
|
||||
for anomaly in anomalies
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def get_soil_moisture_heatmap_data(farm=None):
|
||||
return deepcopy(SOIL_MOISTURE_HEATMAP)
|
||||
|
||||
|
||||
def get_soil_summary_data(farm=None):
|
||||
return {
|
||||
"avgSoilMoisture": get_avg_soil_moisture_data(farm),
|
||||
"sensorRadarChart": get_sensor_radar_chart_data(farm),
|
||||
"sensorComparisonChart": get_sensor_comparison_chart_data(farm),
|
||||
"anomalyDetectionCard": get_anomaly_detection_card_data(farm),
|
||||
"soilMoistureHeatmap": get_soil_moisture_heatmap_data(farm),
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import (
|
||||
AvgSoilMoistureView,
|
||||
SensorComparisonChartView,
|
||||
SensorRadarChartView,
|
||||
SoilAnomalyDetectionView,
|
||||
SoilMoistureHeatmapView,
|
||||
SoilSummaryView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("avg-moisture/", AvgSoilMoistureView.as_view(), name="soil-avg-moisture"),
|
||||
path("sensor-radar-chart/", SensorRadarChartView.as_view(), name="soil-sensor-radar-chart"),
|
||||
path("sensor-comparison-chart/", SensorComparisonChartView.as_view(), name="soil-sensor-comparison-chart"),
|
||||
path("anomalies/", SoilAnomalyDetectionView.as_view(), name="soil-anomalies"),
|
||||
path("moisture-heatmap/", SoilMoistureHeatmapView.as_view(), name="soil-moisture-heatmap"),
|
||||
path("summary/", SoilSummaryView.as_view(), name="soil-summary"),
|
||||
]
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
|
||||
from config.swagger import status_response
|
||||
from farm_hub.models import FarmHub
|
||||
|
||||
from .serializers import (
|
||||
SoilAnomalyDetectionSerializer,
|
||||
SoilComparisonChartSerializer,
|
||||
SoilKpiSerializer,
|
||||
SoilMoistureHeatmapSerializer,
|
||||
SoilRadarChartSerializer,
|
||||
SoilSummarySerializer,
|
||||
)
|
||||
from .services import (
|
||||
get_anomaly_detection_card_data,
|
||||
get_avg_soil_moisture_data,
|
||||
get_sensor_comparison_chart_data,
|
||||
get_sensor_radar_chart_data,
|
||||
get_soil_moisture_heatmap_data,
|
||||
get_soil_summary_data,
|
||||
)
|
||||
|
||||
|
||||
def _get_farm_from_request(request):
|
||||
farm_uuid = request.query_params.get("farm_uuid")
|
||||
if not farm_uuid:
|
||||
return None
|
||||
try:
|
||||
return FarmHub.objects.get(farm_uuid=farm_uuid)
|
||||
except (FarmHub.DoesNotExist, Exception):
|
||||
return None
|
||||
|
||||
|
||||
class AvgSoilMoistureView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Soil"],
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="farm_uuid",
|
||||
type=OpenApiTypes.UUID,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=False,
|
||||
description="UUID of the farm for average soil moisture.",
|
||||
default="11111111-1111-1111-1111-111111111111",
|
||||
),
|
||||
],
|
||||
responses={200: status_response("AvgSoilMoistureResponse", data=SoilKpiSerializer())},
|
||||
)
|
||||
def get(self, request):
|
||||
return Response(
|
||||
{"status": "success", "data": get_avg_soil_moisture_data(_get_farm_from_request(request))},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class SensorRadarChartView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Soil"],
|
||||
parameters=[
|
||||
OpenApiParameter(name="farm_uuid", type=OpenApiTypes.UUID, location=OpenApiParameter.QUERY, required=False),
|
||||
],
|
||||
responses={200: status_response("SensorRadarChartResponse", data=SoilRadarChartSerializer())},
|
||||
)
|
||||
def get(self, request):
|
||||
return Response(
|
||||
{"status": "success", "data": get_sensor_radar_chart_data(_get_farm_from_request(request))},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class SensorComparisonChartView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Soil"],
|
||||
parameters=[
|
||||
OpenApiParameter(name="farm_uuid", type=OpenApiTypes.UUID, location=OpenApiParameter.QUERY, required=False),
|
||||
],
|
||||
responses={200: status_response("SensorComparisonChartResponse", data=SoilComparisonChartSerializer())},
|
||||
)
|
||||
def get(self, request):
|
||||
return Response(
|
||||
{"status": "success", "data": get_sensor_comparison_chart_data(_get_farm_from_request(request))},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class SoilAnomalyDetectionView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Soil"],
|
||||
parameters=[
|
||||
OpenApiParameter(name="farm_uuid", type=OpenApiTypes.UUID, location=OpenApiParameter.QUERY, required=False),
|
||||
],
|
||||
responses={200: status_response("SoilAnomalyDetectionResponse", data=SoilAnomalyDetectionSerializer())},
|
||||
)
|
||||
def get(self, request):
|
||||
return Response(
|
||||
{"status": "success", "data": get_anomaly_detection_card_data(_get_farm_from_request(request))},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class SoilMoistureHeatmapView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Soil"],
|
||||
parameters=[
|
||||
OpenApiParameter(name="farm_uuid", type=OpenApiTypes.UUID, location=OpenApiParameter.QUERY, required=False),
|
||||
],
|
||||
responses={200: status_response("SoilMoistureHeatmapResponse", data=SoilMoistureHeatmapSerializer())},
|
||||
)
|
||||
def get(self, request):
|
||||
return Response(
|
||||
{"status": "success", "data": get_soil_moisture_heatmap_data(_get_farm_from_request(request))},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class SoilSummaryView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Soil"],
|
||||
parameters=[
|
||||
OpenApiParameter(name="farm_uuid", type=OpenApiTypes.UUID, location=OpenApiParameter.QUERY, required=False),
|
||||
],
|
||||
responses={200: status_response("SoilSummaryResponse", data=SoilSummarySerializer())},
|
||||
)
|
||||
def get(self, request):
|
||||
return Response(
|
||||
{"status": "success", "data": get_soil_summary_data(_get_farm_from_request(request))},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
Reference in New Issue
Block a user