Update Docker Compose ports to 8081 and add new apps and URL routes for crop zoning, plant simulator, pest detection, irrigation recommendation, fertilization recommendation, and farm AI assistant.

This commit is contained in:
2026-02-25 12:21:53 +03:30
parent 608252714b
commit 2a77f90ccd
46 changed files with 4142 additions and 2 deletions
+1
View File
@@ -0,0 +1 @@
+7
View File
@@ -0,0 +1,7 @@
from django.apps import AppConfig
class PlantSimulatorConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "plant_simulator"
verbose_name = "Plant Simulator"
+176
View File
@@ -0,0 +1,176 @@
"""
Static mock data for Plant Simulator API.
Matches PLANT_SIMULATOR_API.md. No database, no dynamic values.
Smooth animation: 51 points (010s, ~5 frames per second).
"""
# ---------------------------------------------------------------------------
# GET /api/plant-simulator/config (ورود: فقط اسلایدرها)
# ---------------------------------------------------------------------------
CONFIG_SLIDERS_ONLY = {
"sliders": [
{
"key": "light",
"label": "نور",
"min": 0,
"max": 100,
"step": 5,
"unit_type": "percent",
"default_value": 75,
"icon": "☀️",
},
{
"key": "water",
"label": "آب",
"min": 0,
"max": 100,
"step": 5,
"unit_type": "percent",
"default_value": 65,
"icon": "💧",
},
{
"key": "soil_ph",
"label": "pH خاک",
"min": 4,
"max": 9,
"step": 0.5,
"unit_type": "number",
"unit": "",
"default_value": 6.5,
},
{
"key": "growth_speed",
"label": "سرعت رشد",
"min": 0.5,
"max": 5,
"step": 0.5,
"unit_type": "number",
"unit": "×",
"default_value": 1.5,
},
],
}
# ---------------------------------------------------------------------------
# POST /api/plant-simulator/start (ثابت‌ها + چارت کانفیگ + plant + progress + chart)
# ---------------------------------------------------------------------------
CONSTANTS = {
"max_height": 280,
"max_leaves": 14,
"max_branches": 6,
"max_yield": 500,
"yield_unit": "g",
"yield_rate_unit": "g/s",
"height_unit": "px",
}
CHART_CONFIG = {
"title": "پیشرفت رشد",
"x_axis_label": "زمان (ثانیه)",
"series": [
{
"key": "height",
"label": "ارتفاع (px)",
"y_axis_id": "yHeight",
"min": 0,
"max": 280,
"unit": "px",
},
{
"key": "leaves",
"label": "تعداد برگ",
"y_axis_id": "yLeaf",
"min": 0,
"max": 14,
},
{
"key": "yield",
"label": "محصول (g)",
"y_axis_id": "yYield",
"min": 0,
"max": 500,
"unit": "g",
},
{
"key": "yield_rate",
"label": "نرخ محصول (g/s)",
"y_axis_id": "yYieldRate",
"min": 0,
"unit": "g/s",
},
],
}
# 51 نقطه برای انیمیشن نرم (۰ تا ۱۰ ثانیه، هر ~۰٫۲s)
_labels = [f"{i * 0.2:.1f}s" for i in range(51)]
_height = [round(142 * (i / 50) ** 0.9) for i in range(51)] # رشد کمی شتاب‌دار
_leaf = [min(5, int(i / 10)) for i in range(51)] # 0,0..,1,1..,2,...,5
_yield = [round(12.4 * (i / 50) ** 1.2, 1) for i in range(51)] # محصول با شتاب ملایم
_yield_rate = [round(0.087 * max(0, (i - 15) / 35), 3) for i in range(51)] # نرخ از ثانیه ~۳
START_RESPONSE_DATA = {
"constants": CONSTANTS,
"chart": CHART_CONFIG,
"plant": {
"height": 142,
"leaves_count": 5,
"branches_count": 2,
"fruits_count": 0,
"yield": 12.4,
"yield_rate": 0.087,
"tick": 520,
"is_healthy": True,
"can_continue": True,
},
"progress": {
"growth_progress": 50,
"light_status": 75,
"water_status": 65,
"yield_progress": 2.5,
"yield_current": 12.4,
"yield_rate_current": 0.087,
},
"chart_history": {
"labels": _labels,
"height_history": _height,
"leaf_history": _leaf,
"yield_history": _yield,
"yield_rate_history": _yield_rate,
},
}
# ---------------------------------------------------------------------------
# GET /api/plant-simulator/state (plant + progress + chart history)
# ---------------------------------------------------------------------------
STATE_RESPONSE_DATA = {
"plant": {
"height": 142,
"leaves_count": 5,
"branches_count": 2,
"fruits_count": 0,
"yield": 12.4,
"yield_rate": 0.087,
"tick": 520,
"is_healthy": True,
"can_continue": True,
},
"progress": {
"growth_progress": 50,
"light_status": 75,
"water_status": 65,
"yield_progress": 2.5,
"yield_current": 12.4,
"yield_rate_current": 0.087,
},
"chart": {
"labels": _labels,
"height_history": _height,
"leaf_history": _leaf,
"yield_history": _yield,
"yield_rate_history": _yield_rate,
},
}
@@ -0,0 +1,101 @@
{
"info": {
"name": "Plant Simulator",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"description": "Plant Simulator API. GET config (sliders only), GET state, POST start (body: environment + growth_speed per serializers), stop/reset/environment. Static mock only."
},
"item": [
{
"name": "Get config (GET)",
"request": {
"method": "GET",
"header": [{"key": "Content-Type", "value": "application/json"}],
"url": "{{baseUrl}}/api/plant-simulator/config/",
"description": "ورود: فقط اسلایدرها. Returns sliders only (key, label, min, max, step, unit_type, default_value, icon)."
},
"response": [
{
"name": "Success",
"status": "OK",
"code": 200,
"body": "{\n \"status\": \"success\",\n \"data\": {\n \"sliders\": [\n {\"key\": \"light\", \"label\": \"نور\", \"min\": 0, \"max\": 100, \"step\": 5, \"unit_type\": \"percent\", \"default_value\": 75, \"icon\": \"☀️\"},\n {\"key\": \"water\", \"label\": \"آب\", \"min\": 0, \"max\": 100, \"step\": 5, \"unit_type\": \"percent\", \"default_value\": 65, \"icon\": \"💧\"},\n {\"key\": \"soil_ph\", \"label\": \"pH خاک\", \"min\": 4, \"max\": 9, \"step\": 0.5, \"unit_type\": \"number\", \"unit\": \"\", \"default_value\": 6.5},\n {\"key\": \"growth_speed\", \"label\": \"سرعت رشد\", \"min\": 0.5, \"max\": 5, \"step\": 0.5, \"unit_type\": \"number\", \"unit\": \"×\", \"default_value\": 1.5}\n ]\n }\n}"
}
]
},
{
"name": "Get state (GET)",
"request": {
"method": "GET",
"header": [{"key": "Content-Type", "value": "application/json"}],
"url": "{{baseUrl}}/api/plant-simulator/state/",
"description": "Returns static plant state, progress, and chart history (plant, progress, chart)."
},
"response": [
{
"name": "Success",
"status": "OK",
"code": 200,
"body": "{\n \"status\": \"success\",\n \"data\": {\n \"plant\": {\"height\": 142, \"leaves_count\": 5, \"branches_count\": 2, \"fruits_count\": 0, \"yield\": 12.4, \"yield_rate\": 0.087, \"tick\": 520, \"is_healthy\": true, \"can_continue\": true},\n \"progress\": {\"growth_progress\": 50, \"light_status\": 75, \"water_status\": 65, \"yield_progress\": 2.5, \"yield_current\": 12.4, \"yield_rate_current\": 0.087},\n \"chart\": {\n \"labels\": [\"0s\", \"1s\", \"2s\", \"3s\", \"4s\", \"5s\", \"6s\", \"7s\", \"8s\", \"9s\", \"10s\"],\n \"height_history\": [0, 5, 12, 28, 45, 68, 92, 110, 125, 135, 142],\n \"leaf_history\": [0, 0, 1, 2, 3, 4, 4, 5, 5, 5, 5],\n \"yield_history\": [0, 0, 0, 0.1, 0.5, 1.2, 3.2, 5.8, 8.2, 10.1, 12.4],\n \"yield_rate_history\": [0, 0, 0, 0.01, 0.03, 0.05, 0.06, 0.07, 0.08, 0.085, 0.087]\n }\n }\n}"
}
]
},
{
"name": "Start simulation (POST)",
"request": {
"method": "POST",
"header": [{"key": "Content-Type", "value": "application/json"}],
"body": {
"mode": "raw",
"raw": "{\n \"environment\": {\n \"light\": 75,\n \"water\": 65,\n \"soil_ph\": 6.5\n },\n \"growth_speed\": 1.5\n}"
},
"url": "{{baseUrl}}/api/plant-simulator/start/",
"description": "Body per serializers.START_REQUEST_EXAMPLE_STATIC: environment (light, water, soil_ph) + growth_speed. Returns constants, chart, plant, progress, chart_history."
},
"response": [
{
"name": "Success",
"status": "OK",
"code": 200,
"body": "{\n \"status\": \"success\",\n \"data\": {\n \"constants\": {\"max_height\": 280, \"max_leaves\": 14, \"max_branches\": 6, \"max_yield\": 500, \"yield_unit\": \"g\", \"yield_rate_unit\": \"g/s\", \"height_unit\": \"px\"},\n \"chart\": {\"title\": \"پیشرفت رشد\", \"x_axis_label\": \"زمان (ثانیه)\", \"series\": [{\"key\": \"height\", \"label\": \"ارتفاع (px)\", \"y_axis_id\": \"yHeight\", \"min\": 0, \"max\": 280, \"unit\": \"px\"}, {\"key\": \"leaves\", \"label\": \"تعداد برگ\", \"y_axis_id\": \"yLeaf\", \"min\": 0, \"max\": 14}, {\"key\": \"yield\", \"label\": \"محصول (g)\", \"y_axis_id\": \"yYield\", \"min\": 0, \"max\": 500, \"unit\": \"g\"}, {\"key\": \"yield_rate\", \"label\": \"نرخ محصول (g/s)\", \"y_axis_id\": \"yYieldRate\", \"min\": 0, \"unit\": \"g/s\"}]},\n \"plant\": {\"height\": 142, \"leaves_count\": 5, \"branches_count\": 2, \"fruits_count\": 0, \"yield\": 12.4, \"yield_rate\": 0.087, \"tick\": 520, \"is_healthy\": true, \"can_continue\": true},\n \"progress\": {\"growth_progress\": 50, \"light_status\": 75, \"water_status\": 65, \"yield_progress\": 2.5, \"yield_current\": 12.4, \"yield_rate_current\": 0.087},\n \"chart_history\": {\"labels\": [\"0.0s\", \"0.2s\", \"0.4s\", \"1.0s\", \"2.0s\", \"5.0s\", \"10.0s\"], \"height_history\": [0, 2, 5, 18, 45, 110, 142], \"leaf_history\": [0, 0, 0, 0, 0, 1, 5], \"yield_history\": [0, 0, 0, 0.2, 1.5, 6.2, 12.4], \"yield_rate_history\": [0, 0, 0, 0.01, 0.03, 0.06, 0.087]}\n }\n}"
}
]
},
{
"name": "Stop simulation (POST)",
"request": {
"method": "POST",
"header": [{"key": "Content-Type", "value": "application/json"}],
"body": {"mode": "raw", "raw": "{}"},
"url": "{{baseUrl}}/api/plant-simulator/stop/",
"description": "Empty body. Returns success only."
},
"response": [{"name": "Success", "status": "OK", "code": 200, "body": "{\n \"status\": \"success\"\n}"}]
},
{
"name": "Reset simulation (POST)",
"request": {
"method": "POST",
"header": [{"key": "Content-Type", "value": "application/json"}],
"body": {"mode": "raw", "raw": "{}"},
"url": "{{baseUrl}}/api/plant-simulator/reset/",
"description": "Empty body. Returns success only."
},
"response": [{"name": "Success", "status": "OK", "code": 200, "body": "{\n \"status\": \"success\"\n}"}]
},
{
"name": "Update environment (PATCH)",
"request": {
"method": "PATCH",
"header": [{"key": "Content-Type", "value": "application/json"}],
"body": {
"mode": "raw",
"raw": "{\n \"environment\": {\n \"light\": 80,\n \"water\": 70,\n \"soil_ph\": 6.5\n },\n \"growth_speed\": 2\n}"
},
"url": "{{baseUrl}}/api/plant-simulator/environment/",
"description": "Body: environment (same keys as sliders) + optional growth_speed. Returns success only."
},
"response": [{"name": "Success", "status": "OK", "code": 200, "body": "{\n \"status\": \"success\"\n}"}]
}
],
"variable": [{"key": "baseUrl", "value": "http://localhost:8000"}]
}
+62
View File
@@ -0,0 +1,62 @@
"""
Response serialization for Plant Simulator API.
Plain Django; no DRF. Builds response envelope only. All payloads are static mock data.
Request: on POST /start the client must send a body with keys matching the sliders
from config (light, water, soil_ph) plus growth_speed. See START_REQUEST_EXAMPLE.
No validation or use of request in response; backend returns static mock only.
"""
from .mock_data import CONFIG_SLIDERS_ONLY
# ---------------------------------------------------------------------------
# POST /start — بدنه‌ای که کلاینت باید ارسال کند (مطابق اسلایدرهای config)
# ---------------------------------------------------------------------------
# کلیدهای environment (همان key هر اسلایدر به‌جز growth_speed)
START_ENVIRONMENT_KEYS = [
item["key"]
for item in CONFIG_SLIDERS_ONLY["sliders"]
if item["key"] != "growth_speed"
]
# مقدار پیش‌فرض هر اسلایدر برای ساخت نمونه درخواست
def _defaults_from_sliders():
return {
item["key"]: item["default_value"]
for item in CONFIG_SLIDERS_ONLY["sliders"]
}
# نمونه بدنه درخواست start که کلاینت باید ارسال کند
START_REQUEST_EXAMPLE = {
"environment": {
k: v for k, v in _defaults_from_sliders().items() if k != "growth_speed"
},
"growth_speed": _defaults_from_sliders().get("growth_speed", 1.5),
}
# برای سازگاری: همان ساختار به صورت ثابت (بدون وابستگی به محاسبه)
START_REQUEST_EXAMPLE_STATIC = {
"environment": {
"light": 75,
"water": 65,
"soil_ph": 6.5,
},
"growth_speed": 1.5,
}
def success_response():
"""
Response when endpoint does not return data.
Returns: {"status": "success"}
"""
return {"status": "success"}
def success_with_data(data):
"""
Response when endpoint returns data.
Returns: {"status": "success", "data": data}
"""
return {"status": "success", "data": data}
+19
View File
@@ -0,0 +1,19 @@
from django.urls import path
from .views import (
ConfigView,
EnvironmentView,
ResetView,
StartView,
StateView,
StopView,
)
urlpatterns = [
path("config/", ConfigView.as_view(), name="plant-simulator-config"),
path("state/", StateView.as_view(), name="plant-simulator-state"),
path("start/", StartView.as_view(), name="plant-simulator-start"),
path("stop/", StopView.as_view(), name="plant-simulator-stop"),
path("reset/", ResetView.as_view(), name="plant-simulator-reset"),
path("environment/", EnvironmentView.as_view(), name="plant-simulator-environment"),
]
+161
View File
@@ -0,0 +1,161 @@
"""
Plant Simulator API views.
Plain Django only; no DRF. No database. All responses are static mock data.
Response format: {"status": "success"} or {"status": "success", "data": <payload>}. HTTP 200 only.
No processing, validation, or use of input parameters in responses.
CSRF exempt so frontend (e.g. localhost:3000) can call POST/PATCH without token.
"""
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from .mock_data import CONFIG_SLIDERS_ONLY, START_RESPONSE_DATA, STATE_RESPONSE_DATA
from .serializers import success_response, success_with_data
@method_decorator(csrf_exempt, name="dispatch")
class ConfigView(View):
"""
GET endpoint for simulator configuration (ورود).
Purpose:
Returns only sliders (min, max, step, unit, label, default_value, icon).
Used when loading/entering the simulator page.
Input parameters:
None. Query parameters, if sent, are not read or used.
Response structure:
- status: string, always "success".
- data: object with key "sliders" (array of slider configs).
No processing or validation is performed on inputs.
"""
def get(self, request):
return JsonResponse(success_with_data(CONFIG_SLIDERS_ONLY), status=200)
@method_decorator(csrf_exempt, name="dispatch")
class StateView(View):
"""
GET endpoint for plant state, progress, and chart history.
Purpose:
Returns static plant state (height, leaves_count, branches_count,
fruits_count, yield, yield_rate, tick, is_healthy, can_continue),
progress (growth_progress, light_status, water_status, yield_progress,
yield_current, yield_rate_current), and chart (labels, height_history,
leaf_history, yield_history, yield_rate_history). Used during or after
simulation for UI and chart.
Input parameters:
None. Query parameters, if sent, are not read or used.
Response structure:
- status: string, always "success".
- data: object with keys "plant", "progress", "chart" per
PLANT_SIMULATOR_API.md §3.
No processing or validation is performed on inputs.
"""
def get(self, request):
return JsonResponse(success_with_data(STATE_RESPONSE_DATA), status=200)
@method_decorator(csrf_exempt, name="dispatch")
class StartView(View):
"""
POST endpoint to start simulation.
Purpose:
Returns constants, chart config, plant state, progress, and chart
history. Body may contain environment and growth_speed; not read or used.
Input parameters:
- body (optional): JSON. May contain "environment" and "growth_speed".
Location: body. Not read or validated; not used in response.
Response structure:
- status: string, always "success".
- data: object with "constants", "chart", "plant", "progress", "chart_history".
No processing or validation is performed on inputs.
"""
def post(self, request):
return JsonResponse(success_with_data(START_RESPONSE_DATA), status=200)
@method_decorator(csrf_exempt, name="dispatch")
class StopView(View):
"""
POST endpoint to stop simulation.
Purpose:
Accepts stop request. Returns success only. No processing performed.
Body may be empty or contain session_id; not read or used.
Input parameters:
- body (optional): JSON or empty. Location: body. Not read or used.
Response structure:
- status: string, always "success".
No "data" field.
No processing or validation is performed on inputs.
"""
def post(self, request):
return JsonResponse(success_response(), status=200)
@method_decorator(csrf_exempt, name="dispatch")
class ResetView(View):
"""
POST endpoint to reset simulation.
Purpose:
Accepts reset request. Returns success only. No processing performed.
Body may be empty or contain session_id; not read or used.
Input parameters:
- body (optional): JSON or empty. Location: body. Not read or used.
Response structure:
- status: string, always "success".
No "data" field.
No processing or validation is performed on inputs.
"""
def post(self, request):
return JsonResponse(success_response(), status=200)
@method_decorator(csrf_exempt, name="dispatch")
class EnvironmentView(View):
"""
PATCH endpoint to update environment (slider values).
Purpose:
Accepts environment update. Returns success only. No processing
performed. Body may contain environment and growth_speed; not
read or used in the response.
Input parameters:
- body (optional): JSON. May contain "environment" (object)
and "growth_speed" (number). Location: body. Not read or used.
Response structure:
- status: string, always "success".
No "data" field.
No processing or validation is performed on inputs.
"""
def patch(self, request):
return JsonResponse(success_response(), status=200)