AI UPDATE
This commit is contained in:
@@ -0,0 +1,260 @@
|
|||||||
|
# مستندات جداول پایگاه داده — CropLogic AI
|
||||||
|
|
||||||
|
این سند تمام جداول (مدلهای Django) موجود در پروژه را به همراه توضیح ستونها و روابط بین آنها شرح میدهد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست جداول
|
||||||
|
|
||||||
|
| اپ | جدول | توضیح کوتاه |
|
||||||
|
|---|---|---|
|
||||||
|
| `location_data` | `SoilLocation` | موقعیت جغرافیایی (lat/lon) |
|
||||||
|
| `location_data` | `SoilDepthData` | دادههای خاک به تفکیک عمق |
|
||||||
|
| `sensor_data` | `SensorData` | آخرین خوانش سنسور برای یک موقعیت |
|
||||||
|
| `sensor_data` | `SensorDataHistory` | تاریخچه خوانشهای سنسور |
|
||||||
|
| `sensor_data` | `SensorParameter` | تعریف پارامترهای سنسور |
|
||||||
|
| `sensor_data` | `ParameterUpdateLog` | لاگ تغییرات پارامترهای سنسور |
|
||||||
|
| `weather` | `WeatherParameter` | تعریف پارامترهای هواشناسی |
|
||||||
|
| `weather` | `WeatherForecast` | پیشبینی آبوهوای روزانه |
|
||||||
|
| `plant` | `Plant` | اطلاعات گیاهان |
|
||||||
|
| `irrigation` | `IrrigationMethod` | روشهای آبیاری |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## اپ: `location_data`
|
||||||
|
|
||||||
|
### جدول `SoilLocation`
|
||||||
|
|
||||||
|
موقعیتهای جغرافیایی که دادههای خاک و سنسور به آنها متصل هستند.
|
||||||
|
|
||||||
|
| ستون | نوع | توضیح |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | PK (auto) | شناسه اتوماتیک |
|
||||||
|
| `latitude` | DecimalField (9,6) | عرض جغرافیایی |
|
||||||
|
| `longitude` | DecimalField (9,6) | طول جغرافیایی |
|
||||||
|
| `task_id` | CharField | شناسه تسک Celery در حال پردازش |
|
||||||
|
| `created_at` | DateTimeField | زمان ایجاد |
|
||||||
|
| `updated_at` | DateTimeField | آخرین زمان بهروزرسانی |
|
||||||
|
|
||||||
|
**محدودیتها:**
|
||||||
|
- ترکیب `(latitude, longitude)` باید یکتا باشد.
|
||||||
|
|
||||||
|
**روابط:**
|
||||||
|
- ← `SoilDepthData.soil_location` (یک به چند)
|
||||||
|
- ← `SensorData.location` (یک به چند)
|
||||||
|
- ← `WeatherForecast.location` (یک به چند)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### جدول `SoilDepthData`
|
||||||
|
|
||||||
|
دادههای خاک از API SoilGrids برای سه عمق مختلف، مرتبط با هر `SoilLocation`.
|
||||||
|
|
||||||
|
| ستون | نوع | توضیح |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | PK (auto) | شناسه اتوماتیک |
|
||||||
|
| `soil_location` | FK → SoilLocation | موقعیت مکانی مرتبط |
|
||||||
|
| `depth_label` | CharField | عمق: `0-5cm` / `5-15cm` / `15-30cm` |
|
||||||
|
| `bdod` | FloatField | چگالی ظاهری خاک (Bulk Density) |
|
||||||
|
| `cec` | FloatField | ظرفیت تبادل کاتیونی (CEC) |
|
||||||
|
| `cfvo` | FloatField | درصد حجمی سنگریزه |
|
||||||
|
| `clay` | FloatField | درصد رس |
|
||||||
|
| `nitrogen` | FloatField | نیتروژن کل |
|
||||||
|
| `ocd` | FloatField | تراکم کربن آلی |
|
||||||
|
| `ocs` | FloatField | ذخیره کربن آلی |
|
||||||
|
| `phh2o` | FloatField | pH خاک در آب |
|
||||||
|
| `sand` | FloatField | درصد شن |
|
||||||
|
| `silt` | FloatField | درصد سیلت |
|
||||||
|
| `soc` | FloatField | کربن آلی خاک (SOC) |
|
||||||
|
| `wv0010` | FloatField | رطوبت حجمی در ۱۰ kPa |
|
||||||
|
| `wv0033` | FloatField | ظرفیت زراعی — رطوبت در ۳۳ kPa |
|
||||||
|
| `wv1500` | FloatField | نقطه پژمردگی — رطوبت در ۱۵۰۰ kPa |
|
||||||
|
| `created_at` | DateTimeField | زمان ایجاد |
|
||||||
|
|
||||||
|
**محدودیتها:**
|
||||||
|
- ترکیب `(soil_location, depth_label)` باید یکتا باشد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## اپ: `sensor_data`
|
||||||
|
|
||||||
|
### جدول `SensorData`
|
||||||
|
|
||||||
|
آخرین خوانش سنسور فیزیکی برای یک موقعیت. هنگام بهروزرسانی، نسخه قبلی به `SensorDataHistory` منتقل میشود.
|
||||||
|
|
||||||
|
| ستون | نوع | توضیح |
|
||||||
|
|---|---|---|
|
||||||
|
| `uuid_sensor` | UUIDField (PK) | شناسه یکتای سنسور |
|
||||||
|
| `location` | FK → SoilLocation | موقعیت مکانی (ستون DB: `location_id`) |
|
||||||
|
| `soil_moisture` | FloatField | رطوبت خاک |
|
||||||
|
| `soil_temperature` | FloatField | دمای خاک |
|
||||||
|
| `soil_ph` | FloatField | pH خاک |
|
||||||
|
| `electrical_conductivity` | FloatField | هدایت الکتریکی (EC) |
|
||||||
|
| `nitrogen` | FloatField | ازت (N) |
|
||||||
|
| `phosphorus` | FloatField | فسفر (P) |
|
||||||
|
| `potassium` | FloatField | پتاسیم (K) |
|
||||||
|
| `plants` | M2M → Plant | گیاهان مرتبط با این سنسور |
|
||||||
|
| `created_at` | DateTimeField | زمان ایجاد |
|
||||||
|
| `updated_at` | DateTimeField | آخرین زمان بهروزرسانی |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### جدول `SensorDataHistory`
|
||||||
|
|
||||||
|
تاریخچه کامل خوانشهای سنسور. هر بار که `SensorData` بهروز میشود، نسخه قبلی اینجا ذخیره میشود.
|
||||||
|
|
||||||
|
| ستون | نوع | توضیح |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | PK (auto) | شناسه اتوماتیک |
|
||||||
|
| `uuid_sensor` | UUIDField | شناسه سنسور اصلی |
|
||||||
|
| `location_id` | IntegerField | شناسه موقعیت مکانی |
|
||||||
|
| `soil_moisture` | FloatField | رطوبت خاک |
|
||||||
|
| `soil_temperature` | FloatField | دمای خاک |
|
||||||
|
| `soil_ph` | FloatField | pH خاک |
|
||||||
|
| `electrical_conductivity` | FloatField | هدایت الکتریکی |
|
||||||
|
| `nitrogen` | FloatField | ازت |
|
||||||
|
| `phosphorus` | FloatField | فسفر |
|
||||||
|
| `potassium` | FloatField | پتاسیم |
|
||||||
|
| `recorded_at` | DateTimeField | زمان ثبت در تاریخچه |
|
||||||
|
|
||||||
|
> **نکته:** این جدول FK مستقیم به SoilLocation ندارد تا در صورت حذف موقعیت، تاریخچه حفظ شود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### جدول `SensorParameter`
|
||||||
|
|
||||||
|
کاتالوگ پارامترهای قابل اندازهگیری توسط سنسورها.
|
||||||
|
|
||||||
|
| ستون | نوع | توضیح |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | PK (auto) | شناسه اتوماتیک |
|
||||||
|
| `code` | CharField (unique) | کد یکتا (مثال: `soil_moisture`) |
|
||||||
|
| `name_fa` | CharField | نام فارسی پارامتر |
|
||||||
|
| `unit` | CharField | واحد اندازهگیری |
|
||||||
|
| `created_at` | DateTimeField | زمان ایجاد |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### جدول `ParameterUpdateLog`
|
||||||
|
|
||||||
|
لاگ تغییرات (افزودن یا ویرایش) پارامترهای سنسور.
|
||||||
|
|
||||||
|
| ستون | نوع | توضیح |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | PK (auto) | شناسه اتوماتیک |
|
||||||
|
| `parameter` | FK → SensorParameter | پارامتر مرتبط |
|
||||||
|
| `action` | CharField | نوع عملیات: `added` یا `modified` |
|
||||||
|
| `updated_at` | DateTimeField | زمان ثبت لاگ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## اپ: `weather`
|
||||||
|
|
||||||
|
### جدول `WeatherParameter`
|
||||||
|
|
||||||
|
کاتالوگ پارامترهای هواشناسی تعریفشده در سیستم.
|
||||||
|
|
||||||
|
| ستون | نوع | توضیح |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | PK (auto) | شناسه اتوماتیک |
|
||||||
|
| `code` | CharField (unique) | کد یکتا (مثال: `temperature_max`) |
|
||||||
|
| `name_fa` | CharField | نام فارسی پارامتر |
|
||||||
|
| `unit` | CharField | واحد اندازهگیری |
|
||||||
|
| `created_at` | DateTimeField | زمان ایجاد |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### جدول `WeatherForecast`
|
||||||
|
|
||||||
|
پیشبینی روزانه آبوهوا (تا ۷ روز آینده) برای هر موقعیت مکانی.
|
||||||
|
|
||||||
|
| ستون | نوع | توضیح |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | PK (auto) | شناسه اتوماتیک |
|
||||||
|
| `location` | FK → SoilLocation | موقعیت مکانی مرتبط |
|
||||||
|
| `forecast_date` | DateField | تاریخ پیشبینی |
|
||||||
|
| `temperature_min` | FloatField | حداقل دمای هوا (°C) |
|
||||||
|
| `temperature_max` | FloatField | حداکثر دمای هوا (°C) |
|
||||||
|
| `temperature_mean` | FloatField | میانگین دمای هوا (°C) |
|
||||||
|
| `precipitation` | FloatField | مجموع بارش (mm) |
|
||||||
|
| `precipitation_probability` | FloatField | احتمال بارش (%) |
|
||||||
|
| `humidity_mean` | FloatField | میانگین رطوبت نسبی (%) |
|
||||||
|
| `wind_speed_max` | FloatField | حداکثر سرعت باد (km/h) |
|
||||||
|
| `et0` | FloatField | تبخیر-تعرق مرجع ET₀ (mm/day) |
|
||||||
|
| `weather_code` | IntegerField | کد وضعیت آبوهوا (WMO) |
|
||||||
|
| `fetched_at` | DateTimeField | آخرین زمان واکشی از API |
|
||||||
|
| `created_at` | DateTimeField | زمان ایجاد |
|
||||||
|
|
||||||
|
**محدودیتها:**
|
||||||
|
- ترکیب `(location, forecast_date)` باید یکتا باشد.
|
||||||
|
|
||||||
|
**Property:**
|
||||||
|
- `will_rain` → `True` اگر `precipitation > 0`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## اپ: `plant`
|
||||||
|
|
||||||
|
### جدول `Plant`
|
||||||
|
|
||||||
|
اطلاعات گیاهان شامل شرایط کاشت، نگهداری و برداشت.
|
||||||
|
|
||||||
|
| ستون | نوع | توضیح |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | PK (auto) | شناسه اتوماتیک |
|
||||||
|
| `name` | CharField (unique) | نام گیاه |
|
||||||
|
| `light` | CharField | نور مورد نیاز |
|
||||||
|
| `watering` | CharField | نیاز آبیاری |
|
||||||
|
| `soil` | CharField | نوع خاک مناسب |
|
||||||
|
| `temperature` | CharField | دمای مناسب رشد |
|
||||||
|
| `planting_season` | CharField | فصل کاشت |
|
||||||
|
| `harvest_time` | CharField | زمان برداشت |
|
||||||
|
| `spacing` | CharField | فاصله کاشت |
|
||||||
|
| `fertilizer` | CharField | کود مناسب |
|
||||||
|
| `created_at` | DateTimeField | زمان ایجاد |
|
||||||
|
| `updated_at` | DateTimeField | آخرین زمان بهروزرسانی |
|
||||||
|
|
||||||
|
**روابط:**
|
||||||
|
- ← `SensorData.plants` (M2M از طریق جدول واسط)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## اپ: `irrigation`
|
||||||
|
|
||||||
|
### جدول `IrrigationMethod`
|
||||||
|
|
||||||
|
مشخصات فنی روشهای مختلف آبیاری.
|
||||||
|
|
||||||
|
| ستون | نوع | توضیح |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | PK (auto) | شناسه اتوماتیک |
|
||||||
|
| `name` | CharField (unique) | نام روش آبیاری |
|
||||||
|
| `category` | CharField | دستهبندی (موضعی / تحت فشار / سطحی) |
|
||||||
|
| `description` | TextField | توضیحات کامل |
|
||||||
|
| `water_efficiency_percent` | FloatField | راندمان مصرف آب (%) |
|
||||||
|
| `water_pressure_required` | CharField | فشار مورد نیاز |
|
||||||
|
| `flow_rate` | CharField | دبی / میزان جریان آب |
|
||||||
|
| `coverage_area` | CharField | مساحت قابل پوشش |
|
||||||
|
| `soil_type` | CharField | نوع خاک مناسب |
|
||||||
|
| `climate_suitability` | CharField | اقلیم مناسب |
|
||||||
|
| `created_at` | DateTimeField | زمان ایجاد |
|
||||||
|
| `updated_at` | DateTimeField | آخرین زمان بهروزرسانی |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## نمودار روابط (خلاصه)
|
||||||
|
|
||||||
|
```
|
||||||
|
SoilLocation
|
||||||
|
├── SoilDepthData (1:N — depth_label: 0-5cm, 5-15cm, 15-30cm)
|
||||||
|
├── SensorData (1:N — uuid_sensor PK)
|
||||||
|
│ └── Plant (M:N — جدول واسط)
|
||||||
|
└── WeatherForecast (1:N — یکتا per location+date)
|
||||||
|
|
||||||
|
SensorParameter
|
||||||
|
└── ParameterUpdateLog (1:N — action: added/modified)
|
||||||
|
|
||||||
|
Plant (مستقل — از طریق M2M به SensorData متصل)
|
||||||
|
IrrigationMethod (مستقل — بدون FK)
|
||||||
|
WeatherParameter (مستقل — کاتالوگ)
|
||||||
|
```
|
||||||
@@ -31,6 +31,7 @@ INSTALLED_APPS = [
|
|||||||
"weather",
|
"weather",
|
||||||
"plant",
|
"plant",
|
||||||
"irrigation",
|
"irrigation",
|
||||||
|
"fertilization",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
# لحن توصیه کودهی
|
# لحن توصیه کودهی
|
||||||
|
|
||||||
سبک پاسخ:
|
سبک پاسخ:
|
||||||
- تخصصی و دقیق: نوع کود، مقدار و زمان مصرف را مشخص کن
|
- تخصصی و دقیق: نسبت NPK، مقدار در هکتار، روش مصرف و فاصله زمانی را مشخص کن
|
||||||
- بر اساس دادههای NPK خاک، pH، و نوع محصول
|
- بر اساس دادههای NPK خاک، pH، و نوع محصول
|
||||||
- فرمت خروجی: JSON با فیلدهای fertilizer_needed (bool), fertilizer_type (str), amount_kg_per_hectare (float), reason (str), npk_status (dict)
|
- فرمت خروجی حتماً JSON باشد و دقیقاً به شکل زیر:
|
||||||
- اگر سطح NPK خاک مناسب باشد، کودهی لازم نیست
|
{
|
||||||
|
"plan": {
|
||||||
|
"npkRatio": "<str - نسبت NPK مثل 20-20-20 (NPK)>",
|
||||||
|
"amountPerHectare": "<str - مقدار مصرف در هکتار مثل 150 kg/ha>",
|
||||||
|
"applicationMethod": "<str - روش مصرف مثل Foliar spray + soil broadcast>",
|
||||||
|
"applicationInterval": "<str - فاصله زمانی مصرف مثل Every 14 days>",
|
||||||
|
"reasoning": "<str - توضیح دقیق دلیل انتخاب این برنامه کودهی بر اساس شرایط خاک، آب و گیاه>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- فقط JSON خروجی بده، بدون توضیح اضافی
|
||||||
|
- اگر سطح NPK خاک مناسب باشد، در reasoning ذکر کن و مقدار کمتر پیشنهاد بده
|
||||||
- هشدارهای مهم درباره مصرف بیش از حد کود را ذکر کن
|
- هشدارهای مهم درباره مصرف بیش از حد کود را ذکر کن
|
||||||
|
- npkRatio بر اساس مرحله رشد گیاه و وضعیت خاک تعیین شود
|
||||||
|
- amountPerHectare بر اساس نوع خاک و نیاز گیاه
|
||||||
|
- applicationMethod بر اساس نوع کود و شرایط مزرعه
|
||||||
|
- applicationInterval بر اساس مرحله رشد و سرعت جذب
|
||||||
|
- reasoning باید شامل تحلیل EC خاک، pH، و مواد آلی باشد
|
||||||
|
- مقادیر را به انگلیسی و با واحد بنویس (مثل kg/ha)
|
||||||
|
|||||||
@@ -1,8 +1,23 @@
|
|||||||
# لحن توصیه آبیاری
|
# لحن توصیه آبیاری
|
||||||
|
|
||||||
سبک پاسخ:
|
سبک پاسخ:
|
||||||
- مستقیم و عملیاتی: زمان، مقدار و روش آبیاری را مشخص کن
|
- مستقیم و عملیاتی: زمان، مدت، تعداد دفعات و روش آبیاری را مشخص کن
|
||||||
- بر اساس دادههای هواشناسی (بارش، ET0، دما) و رطوبت خاک
|
- بر اساس دادههای هواشناسی (بارش، ET0، دما) و رطوبت خاک
|
||||||
- فرمت خروجی: JSON با فیلدهای irrigation_needed (bool), amount_mm (float), reason (str), next_check_date (str)
|
- فرمت خروجی حتماً JSON باشد و دقیقاً به شکل زیر:
|
||||||
|
{
|
||||||
|
"plan": {
|
||||||
|
"frequencyPerWeek": <int - تعداد دفعات آبیاری در هفته>,
|
||||||
|
"durationMinutes": <int - مدت هر بار آبیاری به دقیقه>,
|
||||||
|
"bestTimeOfDay": "<str - بهترین زمان آبیاری مثل 05:00 - 07:00>",
|
||||||
|
"moistureLevel": <int - سطح رطوبت مطلوب خاک به درصد>,
|
||||||
|
"warning": "<str - هشدار یا توصیه مهم>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- فقط JSON خروجی بده، بدون توضیح اضافی
|
||||||
- اگر بارش پیشبینی شده باشد، آبیاری را به تعویق بینداز
|
- اگر بارش پیشبینی شده باشد، آبیاری را به تعویق بینداز
|
||||||
- اگر رطوبت خاک کافی است، آبیاری لازم نیست
|
- اگر رطوبت خاک کافی است، آبیاری لازم نیست
|
||||||
|
- هشدارها را در فیلد warning قرار بده
|
||||||
|
- مقادیر عددی را بر اساس نوع گیاه، روش آبیاری و مرحله رشد محاسبه کن
|
||||||
|
- bestTimeOfDay باید بر اساس شرایط آب و هوایی و فصل تعیین شود
|
||||||
|
- frequencyPerWeek بر اساس نیاز آبی گیاه و شرایط خاک
|
||||||
|
- durationMinutes بر اساس روش آبیاری و ظرفیت خاک
|
||||||
|
|||||||
@@ -19,4 +19,5 @@ urlpatterns = [
|
|||||||
path("api/sensor-data/", include("sensor_data.urls")),
|
path("api/sensor-data/", include("sensor_data.urls")),
|
||||||
path("api/plants/", include("plant.urls")),
|
path("api/plants/", include("plant.urls")),
|
||||||
path("api/irrigation/", include("irrigation.urls")),
|
path("api/irrigation/", include("irrigation.urls")),
|
||||||
|
path("api/fertilization/", include("fertilization.urls")),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class FertilizationConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "fertilization"
|
||||||
|
verbose_name = "Fertilization"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class FertilizationRecommendRequestSerializer(serializers.Serializer):
|
||||||
|
"""سریالایزر ورودی برای درخواست توصیه کودهی."""
|
||||||
|
|
||||||
|
sensor_uuid = serializers.CharField(help_text="شناسه یکتای سنسور (اجباری)")
|
||||||
|
plant_name = serializers.CharField(required=False, allow_blank=True, help_text="نام گیاه")
|
||||||
|
growth_stage = serializers.CharField(required=False, allow_blank=True, help_text="مرحله رشد گیاه")
|
||||||
|
query = serializers.CharField(required=False, allow_blank=True, help_text="سوال اختیاری")
|
||||||
|
|
||||||
|
|
||||||
|
class FertilizationPlanSerializer(serializers.Serializer):
|
||||||
|
"""سریالایزر خروجی برای پلن توصیه کودهی."""
|
||||||
|
|
||||||
|
npkRatio = serializers.CharField(help_text="نسبت NPK مثل 20-20-20 (NPK)")
|
||||||
|
amountPerHectare = serializers.CharField(help_text="مقدار مصرف در هکتار مثل 150 kg/ha")
|
||||||
|
applicationMethod = serializers.CharField(help_text="روش مصرف کود")
|
||||||
|
applicationInterval = serializers.CharField(help_text="فاصله زمانی مصرف")
|
||||||
|
reasoning = serializers.CharField(help_text="توضیح دقیق دلیل انتخاب برنامه کودهی")
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from .views import FertilizationRecommendView, FertilizationRecommendStatusView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("recommend/", FertilizationRecommendView.as_view(), name="fertilization-recommend"),
|
||||||
|
path("recommend/<str:task_id>/status/", FertilizationRecommendStatusView.as_view(), name="fertilization-recommend-status"),
|
||||||
|
]
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
from drf_spectacular.utils import (
|
||||||
|
OpenApiExample,
|
||||||
|
OpenApiResponse,
|
||||||
|
extend_schema,
|
||||||
|
)
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from .serializers import FertilizationRecommendRequestSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class FertilizationRecommendView(APIView):
|
||||||
|
"""
|
||||||
|
توصیه کودهی با Celery.
|
||||||
|
POST با sensor_uuid، plant_name، growth_stage.
|
||||||
|
اطلاعات گیاه از plant app دریافت میشود.
|
||||||
|
نیازی به دریافت نوع آبیاری نیست.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
tags=["Fertilization Recommendation"],
|
||||||
|
summary="درخواست توصیه کودهی",
|
||||||
|
description=(
|
||||||
|
"دادههای سنسور و گیاه را دریافت کرده و یک تسک Celery "
|
||||||
|
"برای تولید توصیه کودهی در صف قرار میدهد. "
|
||||||
|
"اطلاعات گیاه از جدول Plant بارگذاری میشود."
|
||||||
|
),
|
||||||
|
request=FertilizationRecommendRequestSerializer,
|
||||||
|
responses={
|
||||||
|
202: OpenApiResponse(description="تسک در صف قرار گرفت"),
|
||||||
|
400: OpenApiResponse(description="پارامتر ورودی نامعتبر"),
|
||||||
|
},
|
||||||
|
examples=[
|
||||||
|
OpenApiExample(
|
||||||
|
"نمونه درخواست",
|
||||||
|
value={
|
||||||
|
"sensor_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"growth_stage": "گلدهی",
|
||||||
|
},
|
||||||
|
request_only=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def post(self, request):
|
||||||
|
from rag.tasks import fertilization_recommendation_task
|
||||||
|
|
||||||
|
serializer = FertilizationRecommendRequestSerializer(data=request.data)
|
||||||
|
if not serializer.is_valid():
|
||||||
|
return Response(
|
||||||
|
{"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
validated = serializer.validated_data
|
||||||
|
sensor_uuid = validated["sensor_uuid"]
|
||||||
|
plant_name = validated.get("plant_name")
|
||||||
|
growth_stage = validated.get("growth_stage")
|
||||||
|
query = validated.get("query")
|
||||||
|
|
||||||
|
task = fertilization_recommendation_task.delay(
|
||||||
|
sensor_uuid=sensor_uuid,
|
||||||
|
plant_name=plant_name,
|
||||||
|
growth_stage=growth_stage,
|
||||||
|
query=query,
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"code": 202,
|
||||||
|
"msg": "تسک توصیه کودهی در صف قرار گرفت.",
|
||||||
|
"data": {
|
||||||
|
"task_id": task.id,
|
||||||
|
"status_url": f"/api/fertilization/recommend/{task.id}/status/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status=status.HTTP_202_ACCEPTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FertilizationRecommendStatusView(APIView):
|
||||||
|
"""وضعیت تسک توصیه کودهی."""
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
tags=["Fertilization Recommendation"],
|
||||||
|
summary="وضعیت تسک توصیه کودهی",
|
||||||
|
description="وضعیت تسک Celery توصیه کودهی را برمیگرداند.",
|
||||||
|
responses={
|
||||||
|
200: OpenApiResponse(description="وضعیت تسک"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def get(self, request, task_id):
|
||||||
|
from celery.result import AsyncResult
|
||||||
|
|
||||||
|
result = AsyncResult(task_id)
|
||||||
|
data = {"task_id": task_id, "status": result.state}
|
||||||
|
if result.state == "PENDING":
|
||||||
|
data["message"] = "تسک در صف یا یافت نشد."
|
||||||
|
elif result.state == "PROGRESS":
|
||||||
|
data["progress"] = result.info
|
||||||
|
elif result.state == "SUCCESS":
|
||||||
|
data["result"] = result.result
|
||||||
|
elif result.state == "FAILURE":
|
||||||
|
data["error"] = str(result.result)
|
||||||
|
return Response(
|
||||||
|
{"code": 200, "msg": "success", "data": data},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
@@ -23,3 +23,33 @@ class IrrigationMethodSerializer(serializers.ModelSerializer):
|
|||||||
"updated_at",
|
"updated_at",
|
||||||
]
|
]
|
||||||
read_only_fields = ["id", "created_at", "updated_at"]
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
|
||||||
|
class IrrigationRecommendRequestSerializer(serializers.Serializer):
|
||||||
|
"""سریالایزر ورودی برای درخواست توصیه آبیاری."""
|
||||||
|
|
||||||
|
sensor_uuid = serializers.CharField(help_text="شناسه یکتای سنسور (اجباری)")
|
||||||
|
plant_name = serializers.CharField(required=False, allow_blank=True, help_text="نام گیاه")
|
||||||
|
growth_stage = serializers.CharField(required=False, allow_blank=True, help_text="مرحله رشد گیاه")
|
||||||
|
irrigation_method_name = serializers.CharField(
|
||||||
|
required=False, allow_blank=True, help_text="نام روش آبیاری"
|
||||||
|
)
|
||||||
|
query = serializers.CharField(required=False, allow_blank=True, help_text="سوال اختیاری")
|
||||||
|
|
||||||
|
|
||||||
|
class IrrigationPlanSerializer(serializers.Serializer):
|
||||||
|
"""سریالایزر خروجی برای پلن توصیه آبیاری."""
|
||||||
|
|
||||||
|
frequencyPerWeek = serializers.IntegerField(help_text="تعداد دفعات آبیاری در هفته")
|
||||||
|
durationMinutes = serializers.IntegerField(help_text="مدت هر بار آبیاری به دقیقه")
|
||||||
|
bestTimeOfDay = serializers.CharField(help_text="بهترین زمان آبیاری")
|
||||||
|
moistureLevel = serializers.IntegerField(help_text="سطح رطوبت مطلوب خاک به درصد")
|
||||||
|
warning = serializers.CharField(help_text="هشدار یا توصیه مهم", allow_blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class IrrigationRecommendResponseSerializer(serializers.Serializer):
|
||||||
|
"""سریالایزر خروجی برای ریسپانس توصیه آبیاری."""
|
||||||
|
|
||||||
|
code = serializers.IntegerField()
|
||||||
|
msg = serializers.CharField()
|
||||||
|
data = serializers.DictField(child=serializers.JSONField())
|
||||||
|
|||||||
+8
-1
@@ -1,8 +1,15 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import IrrigationMethodDetailView, IrrigationMethodListCreateView
|
from .views import (
|
||||||
|
IrrigationMethodDetailView,
|
||||||
|
IrrigationMethodListCreateView,
|
||||||
|
IrrigationRecommendView,
|
||||||
|
IrrigationRecommendStatusView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", IrrigationMethodListCreateView.as_view(), name="irrigation-list-create"),
|
path("", IrrigationMethodListCreateView.as_view(), name="irrigation-list-create"),
|
||||||
path("<int:pk>/", IrrigationMethodDetailView.as_view(), name="irrigation-detail"),
|
path("<int:pk>/", IrrigationMethodDetailView.as_view(), name="irrigation-detail"),
|
||||||
|
path("recommend/", IrrigationRecommendView.as_view(), name="irrigation-recommend"),
|
||||||
|
path("recommend/<str:task_id>/status/", IrrigationRecommendStatusView.as_view(), name="irrigation-recommend-status"),
|
||||||
]
|
]
|
||||||
|
|||||||
+104
-1
@@ -8,7 +8,10 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from .models import IrrigationMethod
|
from .models import IrrigationMethod
|
||||||
from .serializers import IrrigationMethodSerializer
|
from .serializers import (
|
||||||
|
IrrigationMethodSerializer,
|
||||||
|
IrrigationRecommendRequestSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class IrrigationMethodListCreateView(APIView):
|
class IrrigationMethodListCreateView(APIView):
|
||||||
@@ -28,6 +31,106 @@ class IrrigationMethodListCreateView(APIView):
|
|||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IrrigationRecommendView(APIView):
|
||||||
|
"""
|
||||||
|
توصیه آبیاری با Celery.
|
||||||
|
POST با sensor_uuid، plant_name، growth_stage، irrigation_method_name.
|
||||||
|
اطلاعات گیاه از plant app و روش آبیاری از irrigation app دریافت میشود.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
tags=["Irrigation Recommendation"],
|
||||||
|
summary="درخواست توصیه آبیاری",
|
||||||
|
description=(
|
||||||
|
"دادههای سنسور، گیاه و روش آبیاری را دریافت کرده و یک تسک Celery "
|
||||||
|
"برای تولید توصیه آبیاری در صف قرار میدهد. "
|
||||||
|
"اطلاعات گیاه از جدول Plant و روش آبیاری از جدول IrrigationMethod بارگذاری میشود."
|
||||||
|
),
|
||||||
|
request=IrrigationRecommendRequestSerializer,
|
||||||
|
responses={
|
||||||
|
202: OpenApiResponse(description="تسک در صف قرار گرفت"),
|
||||||
|
400: OpenApiResponse(description="پارامتر ورودی نامعتبر"),
|
||||||
|
},
|
||||||
|
examples=[
|
||||||
|
OpenApiExample(
|
||||||
|
"نمونه درخواست",
|
||||||
|
value={
|
||||||
|
"sensor_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"growth_stage": "گلدهی",
|
||||||
|
"irrigation_method_name": "آبیاری قطرهای",
|
||||||
|
},
|
||||||
|
request_only=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def post(self, request):
|
||||||
|
from rag.tasks import irrigation_recommendation_task
|
||||||
|
|
||||||
|
serializer = IrrigationRecommendRequestSerializer(data=request.data)
|
||||||
|
if not serializer.is_valid():
|
||||||
|
return Response(
|
||||||
|
{"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
validated = serializer.validated_data
|
||||||
|
sensor_uuid = validated["sensor_uuid"]
|
||||||
|
plant_name = validated.get("plant_name")
|
||||||
|
growth_stage = validated.get("growth_stage")
|
||||||
|
irrigation_method_name = validated.get("irrigation_method_name")
|
||||||
|
query = validated.get("query")
|
||||||
|
|
||||||
|
task = irrigation_recommendation_task.delay(
|
||||||
|
sensor_uuid=sensor_uuid,
|
||||||
|
plant_name=plant_name,
|
||||||
|
growth_stage=growth_stage,
|
||||||
|
irrigation_method_name=irrigation_method_name,
|
||||||
|
query=query,
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"code": 202,
|
||||||
|
"msg": "تسک توصیه آبیاری در صف قرار گرفت.",
|
||||||
|
"data": {
|
||||||
|
"task_id": task.id,
|
||||||
|
"status_url": f"/api/irrigation/recommend/{task.id}/status/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status=status.HTTP_202_ACCEPTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IrrigationRecommendStatusView(APIView):
|
||||||
|
"""وضعیت تسک توصیه آبیاری."""
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
tags=["Irrigation Recommendation"],
|
||||||
|
summary="وضعیت تسک توصیه آبیاری",
|
||||||
|
description="وضعیت تسک Celery توصیه آبیاری را برمیگرداند.",
|
||||||
|
responses={
|
||||||
|
200: OpenApiResponse(description="وضعیت تسک"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def get(self, request, task_id):
|
||||||
|
from celery.result import AsyncResult
|
||||||
|
|
||||||
|
result = AsyncResult(task_id)
|
||||||
|
data = {"task_id": task_id, "status": result.state}
|
||||||
|
if result.state == "PENDING":
|
||||||
|
data["message"] = "تسک در صف یا یافت نشد."
|
||||||
|
elif result.state == "PROGRESS":
|
||||||
|
data["progress"] = result.info
|
||||||
|
elif result.state == "SUCCESS":
|
||||||
|
data["result"] = result.result
|
||||||
|
elif result.state == "FAILURE":
|
||||||
|
data["error"] = str(result.result)
|
||||||
|
return Response(
|
||||||
|
{"code": 200, "msg": "success", "data": data},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
tags=["Irrigation"],
|
tags=["Irrigation"],
|
||||||
summary="ایجاد روش آبیاری جدید",
|
summary="ایجاد روش آبیاری جدید",
|
||||||
|
|||||||
@@ -17,10 +17,18 @@ KB_NAME = "fertilization"
|
|||||||
DEFAULT_FERTILIZATION_PROMPT = (
|
DEFAULT_FERTILIZATION_PROMPT = (
|
||||||
"بر اساس دادههای خاک (NPK، pH)، مشخصات گیاه، مرحله رشد و پایگاه دانش کودهی، "
|
"بر اساس دادههای خاک (NPK، pH)، مشخصات گیاه، مرحله رشد و پایگاه دانش کودهی، "
|
||||||
"یک توصیه کودهی دقیق بده. "
|
"یک توصیه کودهی دقیق بده. "
|
||||||
"پاسخ حتماً به فرمت JSON با فیلدهای زیر باشد:\n"
|
"پاسخ حتماً به فرمت JSON با ساختار زیر باشد:\n"
|
||||||
"fertilizer_needed (bool), fertilizer_type (str), amount_kg_per_hectare (float), "
|
'{\n'
|
||||||
"reason (str), npk_status (dict با کلیدهای nitrogen, phosphorus, potassium و مقادیر low/normal/high)\n"
|
' "plan": {\n'
|
||||||
|
' "npkRatio": "<str>",\n'
|
||||||
|
' "amountPerHectare": "<str>",\n'
|
||||||
|
' "applicationMethod": "<str>",\n'
|
||||||
|
' "applicationInterval": "<str>",\n'
|
||||||
|
' "reasoning": "<str>"\n'
|
||||||
|
' }\n'
|
||||||
|
'}\n'
|
||||||
"فقط JSON خروجی بده، بدون توضیح اضافی. "
|
"فقط JSON خروجی بده، بدون توضیح اضافی. "
|
||||||
|
"مقادیر را بر اساس شرایط واقعی خاک و گیاه محاسبه کن."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -101,11 +109,9 @@ def get_fertilization_recommendation(
|
|||||||
result = json.loads(cleaned)
|
result = json.loads(cleaned)
|
||||||
except (json.JSONDecodeError, ValueError):
|
except (json.JSONDecodeError, ValueError):
|
||||||
result = {
|
result = {
|
||||||
"fertilizer_needed": None,
|
"plan": {
|
||||||
"fertilizer_type": None,
|
"reasoning": raw,
|
||||||
"amount_kg_per_hectare": None,
|
},
|
||||||
"reason": raw,
|
|
||||||
"npk_status": None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result["raw_response"] = raw
|
result["raw_response"] = raw
|
||||||
|
|||||||
@@ -17,9 +17,18 @@ KB_NAME = "irrigation"
|
|||||||
DEFAULT_IRRIGATION_PROMPT = (
|
DEFAULT_IRRIGATION_PROMPT = (
|
||||||
"بر اساس دادههای خاک، هواشناسی، مشخصات گیاه، روش آبیاری و پایگاه دانش آبیاری، "
|
"بر اساس دادههای خاک، هواشناسی، مشخصات گیاه، روش آبیاری و پایگاه دانش آبیاری، "
|
||||||
"یک توصیه آبیاری دقیق بده. "
|
"یک توصیه آبیاری دقیق بده. "
|
||||||
"پاسخ حتماً به فرمت JSON با فیلدهای زیر باشد:\n"
|
"پاسخ حتماً به فرمت JSON با ساختار زیر باشد:\n"
|
||||||
"irrigation_needed (bool), amount_mm (float), reason (str), next_check_date (str)\n"
|
'{\n'
|
||||||
|
' "plan": {\n'
|
||||||
|
' "frequencyPerWeek": <int>,\n'
|
||||||
|
' "durationMinutes": <int>,\n'
|
||||||
|
' "bestTimeOfDay": "<str>",\n'
|
||||||
|
' "moistureLevel": <int>,\n'
|
||||||
|
' "warning": "<str>"\n'
|
||||||
|
' }\n'
|
||||||
|
'}\n'
|
||||||
"فقط JSON خروجی بده، بدون توضیح اضافی. "
|
"فقط JSON خروجی بده، بدون توضیح اضافی. "
|
||||||
|
"مقادیر عددی را بر اساس شرایط واقعی محاسبه کن."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -105,10 +114,9 @@ def get_irrigation_recommendation(
|
|||||||
result = json.loads(cleaned)
|
result = json.loads(cleaned)
|
||||||
except (json.JSONDecodeError, ValueError):
|
except (json.JSONDecodeError, ValueError):
|
||||||
result = {
|
result = {
|
||||||
"irrigation_needed": None,
|
"plan": {
|
||||||
"amount_mm": None,
|
"warning": raw,
|
||||||
"reason": raw,
|
},
|
||||||
"next_check_date": None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result["raw_response"] = raw
|
result["raw_response"] = raw
|
||||||
|
|||||||
Reference in New Issue
Block a user