This commit is contained in:
2026-05-11 03:27:21 +03:30
parent cf7cbb937c
commit d0e68a1a56
854 changed files with 102985 additions and 76 deletions
@@ -0,0 +1,240 @@
# Farm Dashboard API Reference
این سند، API های `dashboard` را به‌صورت کامل توضیح می‌دهد و برای هر بخش مشخص می‌کند داده از کجا دریافت می‌شود.
## Endpoint ها
### 1) دریافت کارت‌های داشبورد
- **Method:** `GET`
- **Path:** `/api/farm-dashboard/`
- **View:** `dashboard/views.py:118`
- **URL config:** `dashboard/urls.py:5`
- **Query param الزامی:** `farm_uuid`
- **Auth:** `IsAuthenticated`
### 2) دریافت تنظیمات داشبورد
- **Method:** `GET`
- **Path:** `/api/farm-dashboard-config/`
- **View:** `dashboard/views.py:67`
- **URL config:** `dashboard/urls_config.py:5`
- **Query param الزامی:** `farm_uuid`
- **Auth:** `IsAuthenticated`
### 3) ویرایش تنظیمات داشبورد
- **Method:** `PATCH`
- **Path:** `/api/farm-dashboard-config/`
- **View:** `dashboard/views.py:67`
- **Body:** `farm_uuid` + هرکدام از `disabled_card_ids`، `row_order`، `enable_drag_reorder`
- **Auth:** `IsAuthenticated`
## نحوه شناسایی مزرعه
- مزرعه از طریق `farm_uuid` و مالک کاربر لاگین‌شده پیدا می‌شود.
- پیاده‌سازی در `dashboard/views.py:20` و `dashboard/views.py:22` است.
- اگر `farm_uuid` ارسال نشود یا مزرعه برای آن کاربر پیدا نشود، خطای validation برمی‌گردد.
## تنظیمات داشبورد
تنظیمات داشبورد per-farm در دیتابیس ذخیره می‌شود.
### فیلدها
- `disabled_card_ids`: لیست کارت‌های غیرفعال
- `row_order`: ترتیب ردیف‌ها
- `enable_drag_reorder`: فعال/غیرفعال بودن drag reorder
### مدل ذخیره‌سازی
- مدل: `FarmDashboardConfig`
- فایل: `dashboard/models.py:6`
- جدول: `farm_dashboard_configs`
### مقادیر پیش‌فرض
- از `dashboard/defaults.py:4` و `dashboard/defaults.py:30` می‌آید.
- کارت‌های معتبر در `dashboard/defaults.py:16`
- ردیف‌های معتبر در `dashboard/defaults.py:4`
### اعتبارسنجی
- serializer اصلی: `dashboard/serializers.py:6`
- serializer patch: `dashboard/serializers.py:43`
- `disabled_card_ids` فقط باید از `VALID_CARD_IDS` باشد.
- `row_order` باید تمام `VALID_ROW_IDS` را دقیقاً یک‌بار داشته باشد.
## نقطه تجمیع اصلی داده‌ها
تمام کارت‌ها در تابع زیر assemble می‌شوند:
- `dashboard/services.py:85`
این تابع خروجی چند app مختلف را جمع می‌کند و response نهایی dashboard را می‌سازد.
## منبع داده هر کارت
### `farmOverviewKpis`
- **از کجا ساخته می‌شود:** `dashboard/services.py:41`
- **نوع:** aggregator
- **منابع ورودی:**
- `farmHealthScore` از `crop_health.services.get_crop_health_summary_data`
- `waterStressIndex` از `water.services.get_water_stress_index_data`
- `avgSoilMoisture` از `device_hub.services.get_sensor_7_in_1_summary_data`
- `disease_risk` و `pest_risk` از `pest_detection.services.get_risk_summary_data`
- `yield_prediction_card` از `yield_harvest.services.get_yield_harvest_summary_data`
- **نکته مهم:** این کارت جدول یا مدل مستقل ندارد؛ از چند سرویس ترکیب می‌شود.
### `farmWeatherCard`
- **از کجا پر می‌شود:** `water/services.py:9`
- **مدل اصلی:** `WeatherForecastLog`
- **فایل مدل:** `water/models.py:8`
- **جدول:** `weather_forecast_logs`
- **منطق:** جدیدترین رکورد هواشناسی برای همان `farm` خوانده می‌شود.
### `farmAlertsTracker`
- **از کجا پر می‌شود:** `farm_alerts/services.py:379`
- **مدل اصلی:** `FarmAlert`
- **فایل مدل:** `farm_alerts/models.py:16`
- **جدول:** `farm_alerts`
- **منطق:** هشدارهای active مزرعه خوانده می‌شوند و از روی آن‌ها summary ساخته می‌شود.
- **نکته:** با اینکه مدل `FarmAlertTrackerSnapshot` هم وجود دارد در `farm_alerts/models.py:76`، endpoint فعلی کارت tracker مستقیم از `FarmAlert` می‌سازد، نه از snapshot.
### `sensorValuesList`
- **از کجا پر می‌شود:** `device_hub/services.py:495`
- **جزء داخلی:** `device_hub/services.py:334`
- **مدل‌ها:**
- `FarmDevice` در `device_hub/models.py:45`
- `SensorExternalRequestLog` در `device_hub/models.py:94`
- **جداول:**
- `farm_sensors`
- `sensor_external_request_logs`
- **منطق:**
- اول سنسور اصلی خاک مزرعه پیدا می‌شود.
- بعد history لاگ‌های همان device خوانده می‌شود.
- از payload لاگ‌ها، مقادیر سنسورها استخراج می‌شود.
### `sensorRadarChart`
- **از کجا پر می‌شود:** `device_hub/services.py:389`
- **منبع داده:** همان `SensorExternalRequestLog` و `FarmDevice`
- **منطق:** آخرین reading سنسور 7-in-1 گرفته می‌شود و بر اساس ideal range برای هر فیلد score ساخته می‌شود.
### `sensorComparisonChart`
- **از کجا پر می‌شود:** `device_hub/services.py:412`
- **منبع داده:** `SensorExternalRequestLog`
- **منطق:** history چند reading آخر برای رطوبت خاک گرفته می‌شود و series نمودار ساخته می‌شود.
### `anomalyDetectionCard`
- **از کجا پر می‌شود:** `device_hub/services.py:451`
- **منبع داده:** `SensorExternalRequestLog`
- **منطق:** آخرین reading با بازه‌های ideal مقایسه می‌شود و anomaly های out-of-range ساخته می‌شود.
- **نکته:** در app `farm_alerts` یک مدل `AnomalyDetection` در `farm_alerts/models.py:41` هم وجود دارد، اما dashboard فعلی این کارت را از آن مدل نمی‌خواند.
### `farmAlertsTimeline`
- **از کجا پر می‌شود:** `farm_alerts/services.py:410`
- **مدل اصلی:** `FarmAlert`
- **فایل مدل:** `farm_alerts/models.py:16`
- **جدول:** `farm_alerts`
- **منطق:** حداکثر 10 alert آخر مزرعه خوانده می‌شود.
### `waterNeedPrediction`
- **از کجا پر می‌شود:** `water/services.py:58`
- **مدل اصلی:** `IrrigationRecommendationRequest`
- **فایل مدل:** `irrigation/models.py:9`
- **جدول:** `irrigation_requests`
- **منطق:**
- از `response_payload` آخرین درخواست آبیاری، بخش `water_balance.daily` استخراج می‌شود.
- سپس `gross_irrigation_mm` ها تبدیل به series نمودار می‌شوند.
- **نکته:** این کارت در app `water` assemble می‌شود ولی source واقعی‌اش داده‌ی persisted آبیاری است.
### `harvestPredictionCard`
- **از کجا پر می‌شود:** `yield_harvest/services.py:7`
- **مدل اصلی:** `YieldHarvestPredictionLog`
- **فایل مدل:** `yield_harvest/models.py:8`
- **جدول:** `yield_harvest_prediction_logs`
- **منطق:** جدیدترین لاگ برداشت/عملکرد مزرعه خوانده می‌شود.
### `yieldPredictionChart`
- **از کجا پر می‌شود:** `yield_harvest/services.py:7`
- **مدل اصلی:** `YieldHarvestPredictionLog`
- **فایل مدل:** `yield_harvest/models.py:8`
- **جدول:** `yield_harvest_prediction_logs`
- **منطق:** `chart_data` از همان لاگ برداشت/عملکرد برگردانده می‌شود.
### `soilMoistureHeatmap`
- **از کجا پر می‌شود:** `device_hub/services.py:469`
- **منبع داده:** `SensorExternalRequestLog`
- **مدل کمکی device:** `FarmDevice`
- **منطق:** چند reading آخر رطوبت خاک به فرمت heatmap/chart تبدیل می‌شود.
### `ndviHealthCard`
- **از کجا پر می‌شود:** `crop_health/services.py:6`
- **منبع داده فعلی:** mock data
- **فایل:** `crop_health/mock_data.py` از طریق `crop_health/services.py:3`
- **منطق:** فعلاً از دیتابیس یا external log خوانده نمی‌شود؛ مستقیم از mock برمی‌گردد.
### `recommendationsList`
- **از کجا ساخته می‌شود:** `dashboard/services.py:54`
- **نوع:** aggregator
- **منابع ورودی:**
- recommendationهای ذخیره‌شده در `Recommendation` از `farm_alerts/services.py:459`
- پیشنهاد آبیاری از `irrigation/services.py:289`
- پیشنهاد کوددهی از `fertilization/services.py:79`
- بازه برداشت از `yield_harvest.services.get_yield_harvest_summary_data`
- **مدل‌های اصلی:**
- `Recommendation` در `farm_alerts/models.py:59`
- `IrrigationRecommendationRequest` در `irrigation/models.py:9`
- `FertilizationRecommendationRequest` در `fertilization/models.py:9`
- `YieldHarvestPredictionLog` در `yield_harvest/models.py:8`
- **نکته:** این کارت داده چند domain مختلف را یکی می‌کند و duplicate titleها را حذف می‌کند.
### `economicOverview`
- **از کجا پر می‌شود:** `economic_overview/services.py:7`
- **مدل اصلی:** `EconomicOverviewLog`
- **فایل مدل:** `economic_overview/models.py:8`
- **جدول:** `economic_overview_logs`
- **منطق:** آخرین لاگ اقتصادی مزرعه خوانده می‌شود.
## منابعی که فعلاً mock هستند
این بخش مهم است، چون user خواسته بداند اطلاعات از کجا می‌آید:
- `ndviHealthCard` از mock می‌آید: `crop_health/services.py:6`
- `farmHealthScore` که داخل `farmOverviewKpis` استفاده می‌شود هم از mock می‌آید: `crop_health/services.py:6`
- `disease_risk` و `pest_risk` که داخل `farmOverviewKpis` استفاده می‌شوند از mock می‌آیند: `pest_detection/services.py:6`
## منابعی که از دیتابیس می‌آیند
- تنظیمات dashboard از `FarmDashboardConfig`
- weather از `WeatherForecastLog`
- alerts/timeline از `FarmAlert`
- recommendationهای ذخیره‌شده از `Recommendation`
- داده آبیاری از `IrrigationRecommendationRequest`
- داده کوددهی برای recommendation card از `FertilizationRecommendationRequest`
- برداشت/عملکرد از `YieldHarvestPredictionLog`
- overview اقتصادی از `EconomicOverviewLog`
- سنسورها از `FarmDevice` و `SensorExternalRequestLog`
## وابستگی بین app ها در dashboard
تجمیع dashboard در `dashboard/services.py:85` به این app ها وابسته است:
- `water`
- `crop_health`
- `economic_overview`
- `farm_alerts`
- `fertilization`
- `irrigation`
- `pest_detection`
- `device_hub`
- `yield_harvest`
## نمونه flow برای `GET /api/farm-dashboard/`
1. کاربر `farm_uuid` را می‌فرستد.
2. در `dashboard/views.py:127` مزرعه متعلق به user پیدا می‌شود.
3. `dashboard/services.py:85` صدا زده می‌شود.
4. این تابع به سرویس‌های appهای مختلف call می‌زند.
5. هر سرویس یا از DB می‌خواند یا از mock/template.
6. پاسخ نهایی به‌صورت یک object شامل تمام cardها برمی‌گردد.
## نکات مهم عملی
- endpoint کارت‌ها فقط config را برنمی‌گرداند؛ payload کامل تمام cardها را یکجا برمی‌گرداند.
- config dashboard از خود کارت‌ها جداست و در endpoint جداگانه مدیریت می‌شود.
- بعضی کارت‌ها production data دارند، بعضی transitional هستند، و بعضی هنوز mock دارند.
- اگر برای مزرعه داده‌ای در بعضی جدول‌ها نباشد، معمولاً fallback/template خالی برمی‌گردد.
## فایل‌های مرجع مهم
- `dashboard/views.py:67`
- `dashboard/views.py:118`
- `dashboard/services.py:85`
- `dashboard/defaults.py:4`
- `dashboard/serializers.py:6`
- `dashboard/models.py:6`
- `docs/dashboard_card_service_map.md:1`
@@ -0,0 +1,80 @@
# نقشه سرویس کارت های داشبورد
این سند مرجع فشرده `وضعیت واقعی کارت های داشبورد` است؛ نه طراحی آینده.
تمرکز آن روی منبع داده واقعی، status فعلی، و semantics پاسخ در runtime است.
## قانون runtime در برابر seed
- داده seed / bootstrap / fixture مجاز است و باید فقط از مسیرهای seeding و bootstrap در دسترس بماند.
- داده `mock/sample/demo` نباید در مسیر runtime سرویس، view یا adapter برای تولید پاسخ production-like استفاده شود.
- اگر داده واقعی وجود ندارد، سرویس باید `empty state` یا `failure contract` صریح برگرداند، نه داده ساختگی موفق.
## نقطه شروع فعلی
- تجمیع اصلی کارت‌ها در `dashboard/services.py` داخل `get_farm_dashboard_cards` انجام می‌شود.
- endpoint فعلی ارسال کارت‌ها در `dashboard/views.py` داخل `FarmDashboardCardsView` قرار دارد.
- لیست کارت‌های معتبر در `dashboard/defaults.py` داخل `VALID_CARD_IDS` نگهداری می‌شود.
## جمع‌بندی سریع
| Card ID | Status | semantics | منبع اصلی | تابع/سرویس فعلی | app داده | توضیح |
| --- | --- | --- | --- | --- | --- | --- |
| `farmOverviewKpis` | `implemented / transitional` | aggregator | تجمیع چند سرویس | `_build_overview_kpis` | `dashboard` | منبع واحد ندارد |
| `farmWeatherCard` | `partial` | provider/persisted | آب و هوا | `get_farm_weather_card_data` | `water` | نباید fallback ساختگی runtime داشته باشد |
| `farmAlertsTracker` | `implemented` | cached snapshot | snapshot persisted | `get_alert_tracker_data` | `farm_alerts` | live AI نیست |
| `sensorValuesList` | `implemented / transitional` | persisted sensor log | سنسور 7-in-1 | `get_sensor_7_in_1_summary_data` | `sensor_7_in_1` | adoption کامل facade `farm_data` هنوز کامل نشده |
| `sensorRadarChart` | `implemented / transitional` | persisted sensor log | سنسور 7-in-1 | `get_sensor_7_in_1_summary_data` | `sensor_7_in_1` | همان وضعیت |
| `sensorComparisonChart` | `implemented / transitional` | persisted sensor log | سنسور 7-in-1 | `get_sensor_7_in_1_summary_data` | `sensor_7_in_1` | همان وضعیت |
| `anomalyDetectionCard` | `implemented / transitional` | derived from sensor logs | سنسور 7-in-1 | `get_sensor_7_in_1_summary_data` | `sensor_7_in_1` | ownership نهایی anomalyها هنوز کامل یکدست نشده |
| `farmAlertsTimeline` | `partial` | persisted timeline | هشدارها | `get_alert_timeline_data` | `farm_alerts` | نباید fallback ساختگی runtime داشته باشد |
| `waterNeedPrediction` | `implemented / proxy-derived` | derived from persisted irrigation recommendation | آبیاری | `get_water_need_prediction_data` | `water` | facade در `water` است ولی business source در `irrigation` قرار دارد |
| `harvestPredictionCard` | `implemented / proxy-derived` | persisted AI-derived | برداشت/عملکرد | `get_yield_harvest_summary_data` | `yield_harvest` | از لاگ persisted می‌آید |
| `yieldPredictionChart` | `implemented / proxy-derived` | persisted AI-derived | برداشت/عملکرد | `get_yield_harvest_summary_data` | `yield_harvest` | از لاگ persisted می‌آید |
| `soilMoistureHeatmap` | `implemented / transitional` | persisted sensor log | سنسور 7-in-1 | `get_sensor_7_in_1_summary_data` | `sensor_7_in_1` | facade نهایی همه خوانش‌ها را هنوز unify نکرده |
| `ndviHealthCard` | `disabled / partial` | not runtime-ready | سلامت گیاه | `get_crop_health_summary_data` | `crop_health` | نباید به‌عنوان کارت implemented کامل معرفی شود |
| `recommendationsList` | `implemented / transitional` | aggregator | تجمیع پیشنهادها | `_build_recommendations_list` | `dashboard` | از چند app کنار هم ساخته می‌شود |
| `economicOverview` | `implemented` | persisted/log-based | نمای اقتصادی | `get_economic_overview_data` | `economic_overview` | داده اقتصادی persisted |
## نکات مهم کارت‌ها
### `farmOverviewKpis`
- aggregator است و باید در `dashboard` بماند.
### `farmWeatherCard`
- source: `water.models.WeatherForecastLog`
- قرارداد runtime: اگر داده هواشناسی موجود نباشد، باید `empty state` یا `failure contract` صریح برگردد، نه mock.
### `farmAlertsTracker`
- source: snapshot persisted
- semantics: `cached snapshot`
### `waterNeedPrediction`
- facade فعلی در `water`
- business source واقعی: `irrigation.models.IrrigationRecommendationRequest`
- semantics: `proxy-derived persisted data`
### `harvestPredictionCard` و `yieldPredictionChart`
- source: `yield_harvest.models.YieldHarvestPredictionLog`
- semantics: `persisted AI-derived`
### `ndviHealthCard`
- status: `disabled / partial`
- تا زمانی که source runtime-ready پایدار برای NDVI نهایی نشود، نباید به عنوان کارت production-ready مستند شود.
## Ownership و transitional boundaries
- plant catalog canonical در Backend شروع می‌شود.
- dashboard هنوز بعضی کارت‌ها را از facadeهای transitional می‌خواند.
- سنسور / plant / farm ownership به‌تدریج باید به facade `farm_data` نزدیک‌تر شود، ولی همه مصرف‌کننده‌ها هنوز migrate نشده‌اند.
## Response Semantics
- `farmAlertsTracker``cached snapshot`
- `waterNeedPrediction``derived from persisted irrigation recommendation`
- `harvestPredictionCard` / `yieldPredictionChart``persisted AI-derived snapshot`
- `farmOverviewKpis` / `recommendationsList``dashboard-owned aggregator`
## Known Gaps / Follow-up
- ownership نهایی خوانش سنسور بین facade `farm_data` و سرویس‌های legacy هنوز در بعضی کارت‌ها transitional است.
- `ndviHealthCard` هنوز برای runtime production-ready نیست.
@@ -0,0 +1,860 @@
# راهنمای طراحی Device Catalog داینامیک
## هدف
هدف این تغییر این است که اضافه کردن یک دیوایس جدید فقط با ثبت اطلاعات در دیتابیس یا پنل ادمین انجام شود و برای هر دیوایس جدید نیازی به اضافه کردن فایل، ویو، serializer یا service جدید در کد نباشد.
الان ساختار پروژه برای بعضی دیوایس‌ها device-specific است؛ مثلا:
- `device_hub/sensor_7_in_1_urls.py`
- `Sensor7In1SummaryView`
- `get_sensor_7_in_1_summary_data`
- `get_sensor_7_in_1_radar_chart_data`
- `get_sensor_7_in_1_comparison_chart_data`
این ساختار برای یک MVP خوب است، ولی برای scale شدن مناسب نیست. چون برای هر دیوایس جدید باید:
- route جدید بسازید
- view جدید بسازید
- serializer جدید بسازید
- service جدید بسازید
- منطق mapping payload جدید اضافه کنید
این دقیقا چیزی است که باید حذف شود.
---
## مشکل ساختار فعلی
الان backend تا حدی بر اساس `device type` یا `sensor-7-in-1` branch می‌زند، نه بر اساس یک configuration عمومی.
نمونه‌ها:
- `device_hub/views.py`
- `Sensor7In1SummaryView`
- `Sensor7In1RadarChartView`
- `Sensor7In1ComparisonChartView`
- `device_hub/services.py`
- `get_primary_soil_sensor`
- `get_sensor_7_in_1_summary_data`
- `get_sensor_7_in_1_values_list_data`
- `get_sensor_7_in_1_radar_chart_data`
- `get_sensor_7_in_1_comparison_chart_data`
- `device_hub/sensor_serializers.py`
- `Sensor7In1SummarySerializer`
- `Sensor7In1MetaSerializer`
مشکل این approach:
1. اضافه شدن هر device جدید نیاز به deploy کد دارد.
2. naming پروژه به device خاص وابسته می‌شود.
3. APIها generic نیستند.
4. frontend مجبور می‌شود endpointهای مخصوص هر device را صدا بزند.
5. منطق business به‌جای data-driven بودن، hard-coded شده است.
---
## معماری پیشنهادی
### اصل طراحی
به‌جای این‌که برای هر device endpoint جدا داشته باشیم، باید فقط یک سری endpoint عمومی داشته باشیم که بر اساس:
- `physical_device_uuid`
یا
- `device_catalog_uuid`
یا
- `device_catalog.code`
اطلاعات همان device را برگردانند.
یعنی backend باید:
1. device را پیدا کند
2. configuration آن device را از catalog بخواند
3. payload mapping آن device را بخواند
4. widgetهای قابل نمایش آن را تشخیص دهد
5. خروجی استاندارد بسازد
---
## APIهای پیشنهادی
## راهنمای `device_code`
در این معماری باید بین این سه مفهوم تفاوت روشن باشد:
- `physical_device_uuid`: شناسه خودِ دستگاه ثبت‌شده روی مزرعه
- `device_catalog.uuid`: شناسه رکورد catalog
- `device_code`: مقدار متنی فیلد `DeviceCatalog.code` مثل `soil_sensor_v2` یا `irrigation_valve_v1`
### `device_code` را از کجا می‌گیریم؟
دو راه اصلی برای پیدا کردن `device_code`های یک دستگاه وجود دارد:
#### 1) از جزئیات device
در پاسخ این endpoint:
```http
GET /api/device-hub/devices/{physical_device_uuid}/?device_code=<device_code>
```
فیلدهای زیر برمی‌گردند:
- `data.device_catalog.code`
- `data.device_catalogs[].code`
یعنی frontend می‌تواند codeهای attachشده به device را از همین پاسخ بخواند.
#### 2) از endpoint اختصاصی لیست codeها
```http
GET /api/device-hub/devices/{physical_device_uuid}/device-codes/
```
پاسخ نمونه:
```json
{
"code": 200,
"msg": "success",
"data": {
"physical_device_uuid": "device-uuid",
"device_codes": ["soil_sensor_v2", "air_sensor_v1"]
}
}
```
این endpoint برای وقتی مناسب است که frontend فقط می‌خواهد بداند این device به چه `device_code`هایی وصل است.
### `device_code` را کجا باید ارسال کنیم؟
`device_code` همیشه لازم نیست. بسته به endpoint یکی از این حالت‌ها را دارد:
#### الف) در query string
برای endpointهایی که خروجی آن‌ها باید بر اساس یکی از catalogهای attachشده انتخاب شود:
```http
GET /api/device-hub/devices/{physical_device_uuid}/?device_code=soil_sensor_v2
GET /api/device-hub/devices/{physical_device_uuid}/latest/?device_code=soil_sensor_v2
GET /api/device-hub/devices/{physical_device_uuid}/summary/?device_code=soil_sensor_v2
GET /api/device-hub/devices/{physical_device_uuid}/values-list/?device_code=soil_sensor_v2&range=7d
GET /api/device-hub/devices/{physical_device_uuid}/comparison-chart/?device_code=soil_sensor_v2&range=7d
GET /api/device-hub/devices/{physical_device_uuid}/radar-chart/?device_code=soil_sensor_v2&range=7d
GET /api/device-hub/devices/{physical_device_uuid}/logs/?device_code=soil_sensor_v2&page=1&page_size=20
```
#### ب) در body درخواست
برای endpoint command:
```http
POST /api/device-hub/devices/{physical_device_uuid}/commands/
```
نمونه body:
```json
{
"device_code": "irrigation_valve_v1",
"command": "open",
"payload": {
"duration_seconds": 120
}
}
```
#### ج) endpointهایی که اصلاً `device_code` نمی‌خواهند
این endpoint فقط با `physical_device_uuid` کار می‌کند:
```http
GET /api/device-hub/devices/{physical_device_uuid}/device-codes/
```
و endpointهای catalog-level هم معمولاً `device_code` لازم ندارند:
```http
GET /api/device-hub/catalog/
```
### چه زمانی `device_code` اجباری است؟
وقتی یک `FarmDevice` ممکن است به چند catalog وصل باشد، backend بدون `device_code` نمی‌تواند بفهمد باید:
- mapping کدام catalog را اعمال کند
- widgetهای کدام catalog را برگرداند
- لاگ را بر اساس کدام catalog فیلتر کند
- command را برای کدام نوع device validate کند
پس در endpointهای data/summary/chart/logs/commands باید `device_code` صریح ارسال شود.
### `device_code` دقیقاً باید چه مقداری باشد؟
باید مقدار فیلد `DeviceCatalog.code` ارسال شود، نه:
- `name`
- `uuid`
- `physical_device_uuid`
مثال درست:
```text
soil_sensor_v2
air_sensor_v1
irrigation_valve_v1
```
مثال اشتباه:
```text
Soil Sensor V2
11111111-1111-1111-1111-111111111111
22222222-2222-2222-2222-222222222222
```
### اگر `device_code` اشتباه باشد چه می‌شود؟
اگر `device_code` به آن device attach نشده باشد، backend باید validation error برگرداند. معمولاً چیزی شبیه این:
```json
{
"device_code": [
"Device code is not attached to this farm device."
]
}
```
### 1) لیست دیوایس‌ها
```http
GET /api/device-hub/catalog/
```
کاربرد:
- لیست همه device catalogها
- metadata هر catalog
- نوع ارتباط device
- فیلدهای قابل نمایش
---
### 2) جزئیات یک دیوایس ثبت‌شده روی مزرعه
```http
GET /api/device-hub/devices/{physical_device_uuid}/?device_code=soil_sensor_v1
```
نکته:
- در این endpoint، `device_code` باید در query string ارسال شود.
- اگر device فقط یک catalog داشته باشد، از نظر معماری باز هم بهتر است frontend آن را صریح بفرستد.
پاسخ نمونه:
```json
{
"code": 200,
"msg": "success",
"data": {
"uuid": "farm-device-uuid",
"physical_device_uuid": "device-uuid",
"name": "Soil Sensor #1",
"device_catalog": {
"uuid": "catalog-uuid",
"code": "soil_sensor_v1",
"name": "Soil Sensor V1",
"device_communication_type": "output_only"
},
"specifications": {},
"power_source": {},
"last_payload_at": "2025-01-01T10:00:00Z"
}
}
```
---
### 3) آخرین داده‌ی یک device
```http
GET /api/device-hub/devices/{physical_device_uuid}/latest/?device_code=soil_sensor_v1
```
کاربرد:
- آخرین payload خام
- آخرین payload نرمال‌شده
- آخرین readingهای قابل نمایش
---
### 4) summary داینامیک برای یک device
```http
GET /api/device-hub/devices/{physical_device_uuid}/summary/?device_code=soil_sensor_v1
```
کاربرد:
- به‌جای `sensor_7_in_1/summary`
- خروجی بر اساس config همان device
---
### 5) نمودار مقایسه‌ای داینامیک
```http
GET /api/device-hub/devices/{physical_device_uuid}/comparison-chart/?device_code=soil_sensor_v1&range=7d
```
---
### 6) نمودار رادار داینامیک
```http
GET /api/device-hub/devices/{physical_device_uuid}/radar-chart/?device_code=soil_sensor_v1&range=7d
```
---
### 7) values list داینامیک
```http
GET /api/device-hub/devices/{physical_device_uuid}/values-list/?device_code=soil_sensor_v1&range=7d
```
---
### 8) دریافت history خام
```http
GET /api/device-hub/devices/{physical_device_uuid}/logs/?device_code=soil_sensor_v1&page=1&page_size=20
```
این endpoint برای debug و audit خیلی مهم است.
---
## تغییر مهم در مدل‌ها
### 1) `DeviceCatalog`
الان این مدل شروع خوبی دارد، ولی برای dynamic شدن کافی نیست.
مدل فعلی در:
- `device_hub/models.py:6`
فیلدهای پیشنهادی جدید:
```python
display_schema = models.JSONField(default=dict, blank=True)
payload_mapping = models.JSONField(default=dict, blank=True)
supported_widgets = models.JSONField(default=list, blank=True)
commands_schema = models.JSONField(default=list, blank=True)
capabilities = models.JSONField(default=list, blank=True)
```
### توضیح هر فیلد
#### `payload_mapping`
مشخص می‌کند payload خام این device چطور به فیلدهای استاندارد سیستم map شود.
مثال:
```json
{
"soil_moisture": ["soil_moisture", "soilMoisture", "moisture"],
"soil_temperature": ["soil_temperature", "soilTemperature", "temperature"],
"soil_ph": ["soil_ph", "soilPh", "ph"]
}
```
#### `display_schema`
مشخص می‌کند کدام فیلدها در UI نمایش داده شوند و label و unit آن‌ها چیست.
مثال:
```json
{
"fields": [
{
"id": "soil_moisture",
"label": "رطوبت خاک",
"unit": "%",
"ideal_min": 45,
"ideal_max": 65
},
{
"id": "soil_temperature",
"label": "دمای خاک",
"unit": "°C",
"ideal_min": 18,
"ideal_max": 28
}
]
}
```
#### `supported_widgets`
مشخص می‌کند برای این device چه widgetهایی فعال باشند.
مثال:
```json
[
"values_list",
"comparison_chart",
"radar_chart",
"latest_payload",
"anomaly_card"
]
```
#### `commands_schema`
برای deviceهایی که `input_only` هستند.
مثال:
```json
[
{
"command": "turn_on",
"label": "روشن کردن",
"payload_schema": {
"duration_seconds": "integer"
}
},
{
"command": "turn_off",
"label": "خاموش کردن",
"payload_schema": {}
}
]
```
#### `capabilities`
فهرست capabilityهای device:
```json
["measure", "history", "alert", "command"]
```
---
## برای deviceهای ورودی‌محور
شما گفتی بعضی deviceها فقط باید دستور بگیرند و خروجی نمی‌دهند. این دقیقا باید در مدل و API مشخص باشد.
برای این نوع device:
- `device_communication_type = "input_only"`
- `returned_data_fields = []`
- `supported_widgets = []`
- `commands_schema` باید پر باشد
API پیشنهادی:
```http
POST /api/device-hub/devices/{physical_device_uuid}/commands/
```
payload نمونه:
```json
{
"device_code": "irrigation_valve_v1",
"command": "turn_on",
"payload": {
"duration_seconds": 120
}
}
```
پاسخ نمونه:
```json
{
"code": 200,
"msg": "command accepted",
"data": {
"physical_device_uuid": "device-uuid",
"command": "turn_on",
"status": "queued"
}
}
```
---
## چه جاهایی باید در پروژه تغییر کند
### 1) حذف وابستگی به `sensor_7_in_1`
#### فایل‌هایی که باید refactor شوند
- `device_hub/views.py`
- `device_hub/services.py`
- `device_hub/sensor_serializers.py`
- `device_hub/sensor_7_in_1_urls.py`
- `device_hub/comparison_urls.py`
- `device_hub/urls.py`
#### چه چیزی باید تغییر کند
- viewهای device-specific حذف شوند
- routeهای generic جایگزین شوند
- serviceهای `get_sensor_7_in_1_*` به serviceهای generic تبدیل شوند
---
### 2) ساخت service عمومی برای پیدا کردن device
در `device_hub/services.py` باید این لایه‌ها ایجاد شود:
#### الف) resolver
```python
get_farm_device_by_physical_uuid(physical_device_uuid)
get_device_catalog_for_farm_device(farm_device)
get_latest_device_log(farm_device)
get_device_logs(farm_device, range_value=None)
```
#### ب) normalizer
```python
normalize_device_payload(device_catalog, payload)
extract_device_readings(device_catalog, payload)
```
این بخش باید از `payload_mapping` استفاده کند، نه از `SENSOR_FIELDS` ثابت.
#### ج) presenter / builder
```python
build_device_summary(farm_device)
build_device_values_list(farm_device, range_value)
build_device_comparison_chart(farm_device, range_value)
build_device_radar_chart(farm_device, range_value)
```
---
### 3) ثابت‌های hard-coded باید از کد خارج شوند
الان این موارد hard-coded هستند:
- `SENSOR_FIELDS`
- `COMPARISON_CHART_FIELD_ALIASES`
- `VALUES_LIST_FIELDS`
- `RADAR_CHART_FIELDS`
این‌ها الان در:
- `device_hub/services.py:16`
هستند و باید به config وابسته به `DeviceCatalog` منتقل شوند.
یعنی:
- به‌جای constant سراسری
- از `device_catalog.display_schema`
- و `device_catalog.payload_mapping`
استفاده شود.
---
### 4) serializerهای اختصاصی باید generic شوند
الان در:
- `device_hub/sensor_serializers.py:6`
serializerها مخصوص 7-in-1 هستند.
باید این‌ها جایگزین شوند:
- `DeviceMetaSerializer`
- `DeviceFieldValueSerializer`
- `DeviceValuesListSerializer`
- `DeviceSummarySerializer`
- `DeviceComparisonChartSerializer`
- `DeviceRadarChartSerializer`
یعنی نام serializer نباید به یک device خاص گره خورده باشد.
---
### 5) endpointهای generic بسازید
در `device_hub/urls.py` بهتر است چیزی شبیه این داشته باشید:
```python
urlpatterns = [
path("catalog/", DeviceCatalogListView.as_view(), name="device-catalog-list"),
path("devices/<uuid:physical_device_uuid>/device-codes/", DeviceCodeListView.as_view(), name="device-code-list"),
path("devices/<uuid:physical_device_uuid>/", DeviceDetailView.as_view(), name="device-detail"),
path("devices/<uuid:physical_device_uuid>/latest/", DeviceLatestPayloadView.as_view(), name="device-latest-payload"),
path("devices/<uuid:physical_device_uuid>/summary/", DeviceSummaryView.as_view(), name="device-summary"),
path("devices/<uuid:physical_device_uuid>/values-list/", DeviceValuesListView.as_view(), name="device-values-list"),
path("devices/<uuid:physical_device_uuid>/comparison-chart/", DeviceComparisonChartView.as_view(), name="device-comparison-chart"),
path("devices/<uuid:physical_device_uuid>/radar-chart/", DeviceRadarChartView.as_view(), name="device-radar-chart"),
path("devices/<uuid:physical_device_uuid>/logs/", DeviceLogListView.as_view(), name="device-log-list"),
path("devices/<uuid:physical_device_uuid>/commands/", DeviceCommandView.as_view(), name="device-command"),
path("external/", SensorExternalAPIView.as_view(), name="sensor-external-api"),
]
```
---
## روند اضافه کردن device جدید بدون تغییر کد
بعد از این refactor، اضافه کردن device جدید باید این‌طوری باشد:
### مرحله 1
یک رکورد جدید در `DeviceCatalog` ایجاد شود.
### مرحله 2
این اطلاعات برایش ثبت شود:
- `code`
- `name`
- `device_communication_type`
- `payload_mapping`
- `display_schema`
- `supported_widgets`
- `commands_schema`
### مرحله 3
هنگام ثبت `FarmDevice`، آن device به همین catalog وصل شود.
### مرحله 4
از این به بعد frontend فقط با `physical_device_uuid` به endpointهای generic می‌زند.
بدون تغییر کد.
---
## نمونه config برای یک سنسور خروجی‌محور
```json
{
"code": "soil_sensor_v2",
"name": "Soil Sensor V2",
"device_communication_type": "output_only",
"returned_data_fields": [
"soil_moisture",
"soil_temperature",
"soil_ph"
],
"payload_mapping": {
"soil_moisture": ["moisture", "soil_moisture"],
"soil_temperature": ["temperature", "soil_temperature"],
"soil_ph": ["ph", "soil_ph"]
},
"display_schema": {
"fields": [
{
"id": "soil_moisture",
"label": "رطوبت خاک",
"unit": "%",
"ideal_min": 45,
"ideal_max": 65
},
{
"id": "soil_temperature",
"label": "دمای خاک",
"unit": "°C",
"ideal_min": 18,
"ideal_max": 28
},
{
"id": "soil_ph",
"label": "PH خاک",
"unit": "pH",
"ideal_min": 6,
"ideal_max": 7.5
}
]
},
"supported_widgets": [
"values_list",
"comparison_chart",
"radar_chart",
"latest_payload"
],
"commands_schema": []
}
```
---
## نمونه config برای یک device فقط ورودی
مثلا شیر برقی یا پمپ:
```json
{
"code": "irrigation_valve_v1",
"name": "Irrigation Valve V1",
"device_communication_type": "input_only",
"returned_data_fields": [],
"payload_mapping": {},
"display_schema": {
"fields": []
},
"supported_widgets": [],
"commands_schema": [
{
"command": "open",
"label": "باز کردن شیر",
"payload_schema": {
"duration_seconds": "integer"
}
},
{
"command": "close",
"label": "بستن شیر",
"payload_schema": {}
}
]
}
```
---
## پیشنهاد مرحله‌بندی پیاده‌سازی
### فاز 1: Generic read API
اول این‌ها را بسازید:
- `DeviceDetailView`
- `DeviceLatestPayloadView`
- `DeviceSummaryView`
- `DeviceValuesListView`
- `DeviceComparisonChartView`
- `DeviceRadarChartView`
و فعلا داده را با fallback از منطق فعلی بسازید.
### فاز 2: Config-driven normalization
بعد:
- `payload_mapping`
- `display_schema`
- `supported_widgets`
را به `DeviceCatalog` اضافه کنید و منطق hard-coded را حذف کنید.
### فاز 3: Command API
برای `input_only` deviceها:
- `DeviceCommandView`
- command validation
- queue / external broker integration
### فاز 4: Admin / CMS support
برای اینکه بدون کد device جدید اضافه شود، باید از طریق:
- Django Admin
یا
- پنل داخلی
بتوانید `DeviceCatalog` را مدیریت کنید.
---
## حداقل تغییر‌هایی که همین الان باید انجام بدهید
اگر بخواهی با کمترین تغییر از ساختار فعلی به ساختار بهتر برسی، این‌ها مهم‌ترین کارها هستند:
### ضروری
1. حذف endpointهای `sensor_7_in_1`-محور
2. ساخت endpointهای generic با `physical_device_uuid`
3. جدا کردن منطق extraction از device-specific code
4. انتقال field mapping از constant به دیتابیس
5. اضافه کردن schema برای commandها
### مهم ولی فاز بعدی
1. admin برای `DeviceCatalog`
2. validation قوی برای `payload_mapping`
3. caching برای summary/chartها
4. swagger dynamic docs برای command schema
---
## جمع‌بندی
اگر هدفت این است که:
- device جدید بدون تغییر کد اضافه شود
- frontend فقط با `device_uuid` کار کند
- بعضی deviceها فقط command بگیرند
- بعضی deviceها telemetry بدهند
پس باید طراحی از:
- `device-specific code`
به این مدل تغییر کند:
- `catalog-driven architecture`
یعنی:
- `DeviceCatalog` منبع حقیقت باشد
- APIها generic باشند
- parsing و rendering بر اساس config انجام شود
- commandها هم از schema خود device خوانده شوند
---
## فایل‌های کلیدی برای refactor
- `device_hub/models.py:6`
- `device_hub/views.py:19`
- `device_hub/services.py:16`
- `device_hub/sensor_serializers.py:1`
- `device_hub/urls.py:1`
- `device_hub/sensor_7_in_1_urls.py:1`
- `device_hub/comparison_urls.py:1`
- `device_hub/seeds.py:12`
---
## پیشنهاد نهایی
بهترین مسیر این است که:
1. endpointهای generic را اضافه کنی
2. endpointهای قدیمی `sensor_7_in_1` را deprecated کنی
3. config مورد نیاز را به `DeviceCatalog` اضافه کنی
4. frontend را به `physical_device_uuid`-based API منتقل کنی
اگر خواستی، در مرحله بعد من می‌توانم همین طراحی را به تسک اجرایی تبدیل کنم و دقیقا بگویم:
- چه model fieldهایی اضافه شوند
- چه serializerهایی ساخته شوند
- چه endpointهایی پیاده شوند
- و refactor را در چه ترتیب انجام بدهی
@@ -0,0 +1,330 @@
# Fertilization Recommendation History APIs
این فایل برای تیم فرانت نوشته شده تا بتواند از APIهای history توصیه های کودهی استفاده کند.
## وضعیت recommendation
هر recommendation یک status دارد.
### statusهای ممکن
- `pending_confirmation``منتظر تایید`
- `in_progress``در حال مصرف`
- `completed``پایان یافته`
### وضعیت فعلی سیستم
فعلاً همه recommendationهای جدید و recommendationهای قبلی که migrate شده اند با وضعیت زیر ذخیره می شوند:
- `pending_confirmation`
- برچسب نمایشی: `منتظر تایید`
فرانت باید `status` را برای منطق برنامه و `status_label` را برای نمایش مستقیم استفاده کند.
---
## 1) لیست توصیه های کودهی یک مزرعه
### Endpoint
`GET /api/fertilization/recommendations/?farm_uuid=<farm_uuid>`
### کاربرد
- نمایش history توصیه های کودهی یک مزرعه
- ساخت جدول یا لیست برای مشاهده توصیه های قبلی
- نمایش badge وضعیت recommendation
- ورود به صفحه جزئیات هر recommendation
### Query Params
- `farm_uuid`: شناسه مزرعه
- `crop_id`: شناسه یا نام محصول. این فیلد همان plant name است و مستقیم برای AI هم ارسال می شود
- `page`: شماره صفحه، شروع از `1`
- `page_size`: تعداد آیتم در هر صفحه، بین `1` تا `100`
### هدرها
- `Authorization: Bearer <token>`
- `Accept: application/json`
### نمونه درخواست
```bash
curl -X GET \
'http://localhost:8000/api/fertilization/recommendations/?farm_uuid=11111111-1111-1111-1111-111111111111&page=1&page_size=10' \
-H 'accept: application/json' \
-H 'Authorization: Bearer <token>'
```
### نمونه پاسخ موفق
```json
{
"code": 200,
"msg": "success",
"data": [
{
"recommendation_uuid": "4d595ee0-9dbb-4c50-a871-2b4359d0d748",
"crop_id": "گندم",
"plant_name": "گندم",
"growth_stage": "vegetative",
"fertilizer_type": "NPK",
"status": "pending_confirmation",
"status_label": "منتظر تایید",
"requested_at": "2025-01-10T08:30:00Z"
},
{
"recommendation_uuid": "bbdf0d50-0f78-4099-a4d3-b1c4aa54eeb9",
"crop_id": "ذرت",
"plant_name": "ذرت",
"growth_stage": "flowering",
"fertilizer_type": "Micronutrient",
"status": "pending_confirmation",
"status_label": "منتظر تایید",
"requested_at": "2025-01-08T09:10:00Z"
}
],
"pagination": {
"page": 1,
"page_size": 10,
"total_pages": 3,
"total_items": 25,
"has_next": true,
"has_previous": false,
"next": "http://localhost:8000/api/fertilization/recommendations/?farm_uuid=11111111-1111-1111-1111-111111111111&page=2&page_size=10",
"previous": null
}
}
```
### فیلدهای `data[]`
- `recommendation_uuid`: شناسه یکتای recommendation برای گرفتن جزئیات
- `crop_id`: شناسه یا نام محصول ثبت شده در recommendation
- `plant_name`: معادل نمایشی `crop_id` برای سازگاری با فرانت
- `growth_stage`: مرحله رشد در زمان ثبت recommendation
- `fertilizer_type`: نوع کود پیشنهادی مثل `NPK`
- `status`: کد وضعیت recommendation
- `status_label`: متن نمایشی وضعیت recommendation
- `requested_at`: زمان ثبت recommendation
### فیلدهای `pagination`
- `page`: صفحه فعلی
- `page_size`: تعداد آیتم در هر صفحه
- `total_pages`: تعداد کل صفحات
- `total_items`: تعداد کل recommendationها
- `has_next`: آیا صفحه بعدی وجود دارد یا نه
- `has_previous`: آیا صفحه قبلی وجود دارد یا نه
- `next`: لینک صفحه بعدی
- `previous`: لینک صفحه قبلی
### پیشنهاد نمایش status در UI
- `pending_confirmation` → badge زرد یا خاکستری روشن
- `in_progress` → badge آبی یا سبز
- `completed` → badge خاکستری یا سفید
### خطاهای رایج
#### مزرعه پیدا نشد
```json
{
"farm_uuid": [
"Farm not found."
]
}
```
#### پارامترهای pagination نامعتبر
```json
{
"page": [
"Ensure this value is greater than or equal to 1."
]
}
```
---
## 2) جزئیات یک recommendation
### Endpoint
`GET /api/fertilization/recommendations/<recommendation_uuid>/`
### کاربرد
- نمایش کامل جزئیات recommendation
- باز کردن صفحه detail یا modal recommendation
- replay کردن خروجی recommendation بدون نیاز به درخواست مجدد از AI
### Path Param
- `recommendation_uuid`: شناسه recommendation از API لیست
### نکته مهم برای محصول
- فیلد اصلی محصول در این ماژول `crop_id` است
- `crop_id` همان plant name است
- بک اند همان `crop_id` را مستقیم برای AI ارسال می کند
- `plant_name` در response فقط برای سازگاری فرانت نگه داشته شده و مقدارش برابر `crop_id` است
### هدرها
- `Authorization: Bearer <token>`
- `Accept: application/json`
### نمونه درخواست
```bash
curl -X GET \
'http://localhost:8000/api/fertilization/recommendations/4d595ee0-9dbb-4c50-a871-2b4359d0d748/' \
-H 'accept: application/json' \
-H 'Authorization: Bearer <token>'
```
### نمونه پاسخ موفق
```json
{
"code": 200,
"msg": "success",
"data": {
"crop_id": "گندم",
"plant_name": "گندم",
"growth_stage": "vegetative",
"status": "pending_confirmation",
"status_label": "منتظر تایید",
"primary_recommendation": {
"fertilizer_code": "npk-202020",
"fertilizer_name": "NPK 20-20-20",
"display_title": "کود کامل متعادل",
"fertilizer_type": "NPK",
"npk_ratio": {
"n": 20,
"p": 20,
"k": 20,
"label": "20-20-20"
},
"application_method": {
"id": "fertigation",
"label": "کودآبیاری"
},
"application_interval": {
"value": 14,
"unit": "day",
"label": "هر 14 روز"
},
"dosage": {
"base_amount_per_hectare": 65,
"base_amount_per_square_meter": 0.0065,
"unit": "kg",
"label": "65 کیلوگرم در هکتار",
"calculation_basis": "engine-v2"
},
"reasoning": "متعادل برای فاز رشد",
"summary": "مصرف منظم در این مرحله توصیه می شود"
},
"nutrient_analysis": {
"macro": [
{
"key": "n",
"name": "Nitrogen",
"value": 20,
"unit": "percent",
"description": "تقویت رشد رویشی"
}
],
"micro": []
},
"application_guide": {
"safety_warning": "در ساعات خنک مصرف شود",
"steps": [
{
"step_number": 1,
"title": "حل کردن",
"description": "کود را در آب حل کنید"
}
]
},
"alternative_recommendations": [
{
"fertilizer_code": "npk-121236",
"fertilizer_name": "NPK 12-12-36",
"fertilizer_type": "NPK",
"usage_method": "fertigation",
"description": "برای نیاز پتاس بالا"
}
],
"sections": [
{
"type": "recommendation",
"title": "پیشنهاد اصلی",
"icon": "leaf",
"content": "NPK 20-20-20"
}
]
}
}
```
### نکته مهم
این response دقیقا همان ساختار endpoint زیر را برمی گرداند:
`POST /api/fertilization/recommend/`
یعنی فرانت می تواند برای صفحه detail همان componentهایی را استفاده کند که برای recommendation اصلی استفاده می کند.
### خطای رایج
#### recommendation پیدا نشد
```json
{
"code": 404,
"msg": "Recommendation not found."
}
```
---
## پیشنهاد پیاده سازی در فرانت
### برای صفحه history
- ابتدا API لیست را با `farm_uuid` صدا بزنید
- `data` را در جدول یا کارت لیست نمایش دهید
- `status_label` را مستقیم در badge یا chip نشان دهید
- اگر لازم بود رفتار UI بر اساس وضعیت تغییر کند، از `status` استفاده کنید
- با `pagination.page` و `pagination.total_pages` صفحه بندی را بسازید
- روی هر آیتم با `recommendation_uuid` به صفحه detail بروید
### برای صفحه detail
- `recommendation_uuid` را از route بگیرید
- API جزئیات را صدا بزنید
- `data.primary_recommendation` را در Hero/Card اصلی نمایش دهید
- `data.nutrient_analysis` را در بخش تحلیل عناصر نمایش دهید
- `data.application_guide` را در بخش راهنمای مصرف نمایش دهید
- `data.alternative_recommendations` را برای جایگزین ها نمایش دهید
- در صورت نیاز برای سازگاری، از `data.sections` هم استفاده کنید
### فرمول محاسبه مقدار مصرف
```text
مقدار کل = base_amount_per_square_meter × مساحت مزرعه
```
---
## خلاصه مسیرها
- لیست recommendationها:
- `GET /api/fertilization/recommendations/?farm_uuid=<farm_uuid>&page=1&page_size=10`
- جزئیات recommendation:
- `GET /api/fertilization/recommendations/<recommendation_uuid>/`
@@ -0,0 +1,619 @@
# Free-Text Plan Parser APIs
این فایل برای تیم فرانت‌اند آماده شده و دو API جدید زیر را توضیح می‌دهد:
- `POST /api/irrigation/plan-from-text/`
- `POST /api/fertilization/plan-from-text/`
هدف هر دو API:
- کاربر یک متن آزاد می‌نویسد
- backend تلاش می‌کند برنامه آبیاری یا کودهی را به JSON ساختاریافته تبدیل کند
- اگر اطلاعات کامل باشد، JSON نهایی برمی‌گردد
- اگر اطلاعات ناقص باشد، API سوال‌های تکمیلی برمی‌گرداند
- فرانت‌اند سوال‌ها را از کاربر می‌پرسد و پاسخ‌ها را دوباره برای API می‌فرستد
---
## رفتار کلی هر دو API
هر دو endpoint یک flow یکسان دارند:
1. کاربر متن آزاد اولیه را می‌فرستد
2. اگر متن کامل باشد:
- `status = "completed"`
- `final_plan` برمی‌گردد
3. اگر متن ناقص باشد:
- `status = "needs_clarification"`
- `missing_fields` برمی‌گردد
- `questions` برمی‌گردد
4. فرانت‌اند پاسخ کاربر به سوال‌ها را جمع می‌کند
5. دوباره همان endpoint را با `answers` و `partial_plan` صدا می‌زند
6. این روند تا ساخته شدن `final_plan` ادامه پیدا می‌کند
---
## الگوی کلی response
هر دو API از envelope استاندارد استفاده می‌کنند:
```json
{
"code": 200,
"msg": "موفق",
"data": {}
}
```
### معنی فیلدهای envelope
| فیلد | نوع | توضیح |
|---|---|---|
| `code` | number | کد منطقی پاسخ |
| `msg` | string | پیام کوتاه پاسخ |
| `data` | object | داده اصلی API |
---
## 1) API استخراج برنامه آبیاری
### Endpoint
```http
POST /api/irrigation/plan-from-text/
```
### کاربرد
این API متن آزاد کاربر درباره برنامه آبیاری را به JSON ساختاریافته تبدیل می‌کند.
### Request Body
هر سه فیلد زیر اختیاری هستند، اما حداقل یکی از این‌ها باید ارسال شود:
- `message`
- `answers`
- `partial_plan`
#### ساختار request
```json
{
"message": "برای گوجه فرنگی با آبیاری قطره ای هر سه روز یک بار صبح زود 25 دقیقه آبیاری می کنم و حدود 18 لیتر برای هر بوته می دهم.",
"answers": {
"growth_stage": "گلدهی"
},
"partial_plan": {
"crop_name": "گوجه فرنگی",
"irrigation_method": "قطره ای"
},
"farm_uuid": "11111111-1111-1111-1111-111111111111"
}
```
### فیلدهای request
| فیلد | نوع | اجباری | توضیح |
|---|---|---:|---|
| `message` | string | خیر | متن آزاد کاربر |
| `answers` | object | خیر | پاسخ‌های تکمیلی کاربر به سوال‌هایی که قبلا API داده |
| `partial_plan` | object | خیر | خروجی مرحله قبل برای ادامه تکمیل |
| `farm_uuid` | string | خیر | برای غنی‌سازی context مزرعه در AI |
### قانون validation
اگر هیچ‌کدام از `message`، `answers` یا `partial_plan` ارسال نشوند:
```json
{
"code": 400,
"msg": "Bad Request",
"data": {
"non_field_errors": [
"حداقل یکی از message، answers یا partial_plan باید ارسال شود."
]
}
}
```
---
## پاسخ موفق - حالت تکمیل شده
وقتی همه اطلاعات لازم موجود باشد:
```json
{
"code": 200,
"msg": "موفق",
"data": {
"status": "completed",
"status_fa": "تکمیل شد",
"summary": "برنامه آبیاری برای گوجه‌فرنگی به روش قطره‌ای هر سه روز یک‌بار صبح زود به مدت 25 دقیقه اجرا می‌شود.",
"missing_fields": [],
"questions": [],
"collected_data": {
"crop_name": "گوجه‌فرنگی",
"growth_stage": "گلدهی",
"irrigation_method": "قطره‌ای",
"water_amount_per_event": "18 لیتر برای هر بوته",
"duration_minutes": 25,
"frequency_text": "هر سه روز یک‌بار",
"interval_days": 3,
"preferred_time_of_day": "صبح زود",
"start_date": "از امروز",
"target_area": "کل مزرعه",
"trigger_conditions": [],
"notes": []
},
"final_plan": {
"crop_name": "گوجه‌فرنگی",
"growth_stage": "گلدهی",
"irrigation_method": "قطره‌ای",
"water_amount_per_event": "18 لیتر برای هر بوته",
"duration_minutes": 25,
"frequency_text": "هر سه روز یک‌بار",
"interval_days": 3,
"preferred_time_of_day": "صبح زود",
"start_date": "از امروز",
"target_area": "کل مزرعه",
"trigger_conditions": [],
"notes": []
}
}
}
```
### فیلدهای `data`
| فیلد | نوع | توضیح |
|---|---|---|
| `status` | string | یکی از `completed` یا `needs_clarification` |
| `status_fa` | string | نسخه فارسی وضعیت |
| `summary` | string | خلاصه قابل نمایش برای کاربر |
| `missing_fields` | array[string] | فیلدهای ناقص |
| `questions` | array[object] | سوال‌های تکمیلی |
| `collected_data` | object | داده‌ای که تا الان از متن و جواب‌ها استخراج شده |
| `final_plan` | object/null | برنامه نهایی؛ فقط در حالت `completed` |
### فیلدهای `collected_data` و `final_plan`
| فیلد | نوع | توضیح |
|---|---|---|
| `crop_name` | string | نام محصول |
| `growth_stage` | string | مرحله رشد محصول |
| `irrigation_method` | string | روش آبیاری |
| `water_amount_per_event` | string | مقدار آب هر نوبت |
| `duration_minutes` | number | مدت هر نوبت آبیاری به دقیقه |
| `frequency_text` | string | توصیف متنی فاصله آبیاری |
| `interval_days` | number | فاصله آبیاری بر حسب روز |
| `preferred_time_of_day` | string | زمان مناسب اجرای آبیاری |
| `start_date` | string | زمان یا تاریخ شروع برنامه |
| `target_area` | string | محدوده هدف برنامه |
| `trigger_conditions` | array[string] | شرایط تریگر اختیاری |
| `notes` | array[string] | نکات تکمیلی |
---
## پاسخ موفق - حالت نیاز به سوال تکمیلی
اگر اطلاعات کامل نباشد:
```json
{
"code": 200,
"msg": "موفق",
"data": {
"status": "needs_clarification",
"status_fa": "نیازمند پرسش تکمیلی",
"summary": "اطلاعات برنامه آبیاری برای ساخت JSON نهایی کافی نیست و به چند پاسخ تکمیلی نیاز است.",
"missing_fields": [
"growth_stage",
"start_date",
"target_area"
],
"questions": [
{
"id": "growth_stage",
"field": "growth_stage",
"question": "محصول الان در چه مرحله رشدی قرار دارد؟",
"rationale": "مرحله رشد برای کامل شدن برنامه لازم است."
},
{
"id": "start_date",
"field": "start_date",
"question": "این برنامه از چه تاریخی یا از چه زمانی باید شروع شود؟",
"rationale": "زمان شروع برنامه هنوز مشخص نشده است."
},
{
"id": "target_area",
"field": "target_area",
"question": "این برنامه برای کل مزرعه است یا بخش/ناحیه خاصی از مزرعه؟",
"rationale": "محدوده اجرای برنامه باید مشخص باشد."
}
],
"collected_data": {
"crop_name": "گوجه‌فرنگی",
"growth_stage": null,
"irrigation_method": "قطره‌ای",
"water_amount_per_event": "18 لیتر برای هر بوته",
"duration_minutes": 25,
"frequency_text": "هر سه روز یک‌بار",
"interval_days": 3,
"preferred_time_of_day": "صبح زود",
"start_date": null,
"target_area": null,
"trigger_conditions": [],
"notes": []
},
"final_plan": null
}
}
```
### ساختار `questions`
| فیلد | نوع | توضیح |
|---|---|---|
| `id` | string | شناسه سوال |
| `field` | string | فیلدی که این سوال برای آن پرسیده شده |
| `question` | string | متن سوال برای نمایش به کاربر |
| `rationale` | string | توضیح کوتاه برای اینکه چرا این سوال لازم است |
---
## flow پیشنهادی فرانت‌اند برای آبیاری
### مرحله 1
کاربر متن آزاد می‌فرستد:
```json
{
"message": "برای گوجه فرنگی هر سه روز یک بار آبیاری می کنم."
}
```
### مرحله 2
اگر `status = needs_clarification` بود:
- سوال‌ها را از `data.questions` به کاربر نمایش بده
- پاسخ‌ها را جمع کن
### مرحله 3
درخواست تکمیلی بزن:
```json
{
"partial_plan": {
"crop_name": "گوجه فرنگی",
"growth_stage": null,
"irrigation_method": null,
"water_amount_per_event": null,
"duration_minutes": null,
"frequency_text": "هر سه روز یک بار",
"interval_days": 3,
"preferred_time_of_day": null,
"start_date": null,
"target_area": null,
"trigger_conditions": [],
"notes": []
},
"answers": {
"growth_stage": "گلدهی",
"irrigation_method": "قطره ای",
"water_amount_per_event": "18 لیتر برای هر بوته",
"duration_minutes": 25,
"preferred_time_of_day": "صبح زود",
"start_date": "از امروز",
"target_area": "کل مزرعه"
}
}
```
### مرحله 4
اگر `status = completed` شد:
- از `data.final_plan` به عنوان JSON نهایی استفاده کن
---
## 2) API استخراج برنامه کودهی
### Endpoint
```http
POST /api/fertilization/plan-from-text/
```
### کاربرد
این API متن آزاد کاربر درباره برنامه کودهی را به JSON ساختاریافته تبدیل می‌کند.
### Request Body
```json
{
"message": "برای گندم در مرحله پنجه زنی هر 12 روز یک بار 20-20-20 به مقدار 35 کیلوگرم در هکتار از طریق کودآبیاری می دهم.",
"answers": {
"timing": "هر 12 روز یک بار"
},
"partial_plan": {
"crop_name": "گندم"
},
"farm_uuid": "11111111-1111-1111-1111-111111111111"
}
```
### فیلدهای request
| فیلد | نوع | اجباری | توضیح |
|---|---|---:|---|
| `message` | string | خیر | متن آزاد کاربر |
| `answers` | object | خیر | پاسخ‌های تکمیلی کاربر |
| `partial_plan` | object | خیر | داده استخراج شده مرحله قبل |
| `farm_uuid` | string | خیر | برای context مزرعه |
### validation error
```json
{
"code": 400,
"msg": "Bad Request",
"data": {
"non_field_errors": [
"حداقل یکی از message، answers یا partial_plan باید ارسال شود."
]
}
}
```
---
## پاسخ موفق - حالت تکمیل شده
```json
{
"code": 200,
"msg": "موفق",
"data": {
"status": "completed",
"status_fa": "تکمیل شد",
"summary": "برنامه کودهی برای گندم در مرحله پنجه زنی با کود 20-20-20 به صورت کودآبیاری هر 12 روز یک بار اجرا می شود.",
"missing_fields": [],
"questions": [],
"collected_data": {
"crop_name": "گندم",
"growth_stage": "پنجه زنی",
"objective": "تقویت رشد رویشی",
"applications": [
{
"fertilizer_name": "کود کامل 20-20-20",
"formula": "20-20-20",
"amount": "35 کیلوگرم در هکتار",
"application_method": "کودآبیاری",
"timing": "هر 12 روز یک بار",
"interval_days": 12,
"purpose": "تقویت رشد رویشی"
}
],
"notes": []
},
"final_plan": {
"crop_name": "گندم",
"growth_stage": "پنجه زنی",
"objective": "تقویت رشد رویشی",
"applications": [
{
"fertilizer_name": "کود کامل 20-20-20",
"formula": "20-20-20",
"amount": "35 کیلوگرم در هکتار",
"application_method": "کودآبیاری",
"timing": "هر 12 روز یک بار",
"interval_days": 12,
"purpose": "تقویت رشد رویشی"
}
],
"notes": []
}
}
}
```
### فیلدهای `collected_data` و `final_plan`
| فیلد | نوع | توضیح |
|---|---|---|
| `crop_name` | string | نام محصول |
| `growth_stage` | string | مرحله رشد |
| `objective` | string/null | هدف برنامه |
| `applications` | array[object] | لیست نوبت‌ها یا اقلام کودی |
| `notes` | array[string] | نکات تکمیلی |
### ساختار هر application
| فیلد | نوع | توضیح |
|---|---|---|
| `fertilizer_name` | string | نام کود |
| `formula` | string | فرمول یا آنالیز کود |
| `amount` | string | مقدار مصرف |
| `application_method` | string | روش مصرف |
| `timing` | string | زمان‌بندی مصرف |
| `interval_days` | number | فاصله بین نوبت‌ها |
| `purpose` | string/null | هدف آن نوبت |
---
## پاسخ موفق - حالت نیاز به سوال تکمیلی
```json
{
"code": 200,
"msg": "موفق",
"data": {
"status": "needs_clarification",
"status_fa": "نیازمند پرسش تکمیلی",
"summary": "اطلاعات برنامه کودهی برای ساخت JSON نهایی کافی نیست و به چند پاسخ تکمیلی نیاز است.",
"missing_fields": [
"growth_stage",
"formula",
"interval_days"
],
"questions": [
{
"id": "growth_stage",
"field": "growth_stage",
"question": "محصول الان در چه مرحله رشدی قرار دارد؟",
"rationale": "مرحله رشد برای تکمیل برنامه لازم است."
},
{
"id": "formula",
"field": "formula",
"question": "فرمول یا آنالیز کود چیست؟ مثلا 20-20-20.",
"rationale": "ترکیب دقیق کود هنوز مشخص نشده است."
},
{
"id": "interval_days",
"field": "interval_days",
"question": "فاصله بین نوبت های مصرف کود چند روز است؟",
"rationale": "عدد فاصله بین نوبت ها برای JSON نهایی لازم است."
}
],
"collected_data": {
"crop_name": "گندم",
"growth_stage": null,
"objective": null,
"applications": [
{
"fertilizer_name": "کود کامل",
"formula": null,
"amount": "35 کیلوگرم در هکتار",
"application_method": "کودآبیاری",
"timing": "هر چند وقت یک بار",
"interval_days": null,
"purpose": null
}
],
"notes": []
},
"final_plan": null
}
}
```
---
## flow پیشنهادی فرانت‌اند برای کودهی
### درخواست اولیه
```json
{
"message": "برای گندم از کود کامل استفاده می کنم."
}
```
### اگر incomplete بود
- از `questions` سوال‌ها را بگیر
- در UI نمایش بده
- پاسخ‌ها را جمع کن
### درخواست تکمیلی
```json
{
"partial_plan": {
"crop_name": "گندم",
"growth_stage": null,
"objective": null,
"applications": [
{
"fertilizer_name": "کود کامل",
"formula": null,
"amount": null,
"application_method": null,
"timing": null,
"interval_days": null,
"purpose": null
}
],
"notes": []
},
"answers": {
"growth_stage": "پنجه زنی",
"formula": "20-20-20",
"amount": "35 کیلوگرم در هکتار",
"application_method": "کودآبیاری",
"timing": "هر 12 روز یک بار",
"interval_days": 12
}
}
```
### اگر complete شد
- از `final_plan` استفاده کن
---
## نکات مهم برای فرانت‌اند
### 1. به `status` تکیه کنید
مهم‌ترین فیلد برای کنترل flow:
- `completed`
- `needs_clarification`
### 2. اگر `needs_clarification` بود
باید:
- `questions` را به کاربر نمایش دهید
- `partial_plan` را نگه دارید
- پاسخ‌های کاربر را در `answers` ارسال کنید
### 3. اگر `completed` بود
باید:
- `final_plan` را به عنوان نسخه نهایی برنامه ذخیره یا نمایش دهید
### 4. `collected_data` همیشه مهم است
حتی اگر برنامه ناقص باشد، `collected_data` نشان می‌دهد سیستم تا این لحظه چه چیزهایی را فهمیده است.
### 5. null در حالت ناقص طبیعی است
در حالت `needs_clarification` ممکن است بعضی فیلدهای `collected_data` `null` باشند.
اما در حالت `completed` نباید فیلدهای اصلی ناقص باشند.
### 6. بهتر است سوال‌ها را step-by-step بپرسید
پیشنهاد:
- سوال اول را نشان بده
- جواب را بگیر
- همه جواب‌ها را در `answers` جمع کن
- دوباره API را صدا بزن
---
## جمع‌بندی تفاوت دو API
| API | موضوع | خروجی نهایی |
|---|---|---|
| `/api/irrigation/plan-from-text/` | استخراج برنامه آبیاری | `final_plan` با ساختار آبیاری |
| `/api/fertilization/plan-from-text/` | استخراج برنامه کودهی | `final_plan` با ساختار کودهی |
---
## مسیر فایل
این داکیومنت در این مسیر ذخیره شده:
`docs/irrigation_fertilization_plan_parser_apis.md`
@@ -0,0 +1,247 @@
# Pest Disease Risk Summary API Reference
این فایل برای فرانت آماده شده تا ساختار خروجی endpoint زیر مشخص و قابل استفاده باشد:
```http
POST /api/pest-disease/risk-summary/
```
## Purpose
این endpoint فقط `farm_uuid` می‌گیرد و در backend:
- مزرعه را پیدا می‌کند
- اولین محصول ثبت‌شده روی همان مزرعه را برمی‌دارد
- `plant_name` را از همان محصول پر می‌کند
- `growth_stage` را فعلاً به صورت ثابت `گلدهی` به سرویس AI می‌فرستد
- خروجی خلاصه ریسک آفت و بیماری را به فرانت برمی‌گرداند
---
## Request
### Body
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111"
}
```
### Request Fields
| field | type | required | description |
|---|---|---:|---|
| `farm_uuid` | `string (uuid)` | yes | UUID مزرعه |
### Important Notes
- این endpoint فقط `farm_uuid` را از کلاینت قبول می‌کند.
- `plant_name` نباید از فرانت ارسال شود.
- `growth_stage` نباید از فرانت ارسال شود.
- `plant_name` در backend از اولین محصول مزرعه استخراج می‌شود.
- اگر مزرعه هیچ محصولی نداشته باشد، `plant_name` به صورت رشته خالی به AI ارسال می‌شود.
- `growth_stage` فعلاً همیشه `گلدهی` است.
---
## Success Response
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"diseaseRisk": {
"id": "disease-risk",
"title": "ریسک بیماری",
"subtitle": "فشار بیماری در حال افزایش است",
"stats": "68%",
"avatarColor": "warning",
"avatarIcon": "tabler-biohazard",
"chipText": "متوسط",
"chipColor": "warning",
"details": {
"reason": "رطوبت بالا و تهویه ضعیف"
}
},
"pestRisk": {
"id": "pest-risk",
"title": "ریسک آفت",
"subtitle": "فعالیت آفات قابل توجه است",
"stats": "41%",
"avatarColor": "info",
"avatarIcon": "tabler-bug",
"chipText": "کم",
"chipColor": "success",
"details": {
"reason": "شرایط محیطی نسبتاً پایدار"
}
},
"drivers": {
"humidity": "high",
"temperature": "moderate"
}
}
}
```
---
## Success Response Fields
### Top Level
| field | type | description |
|---|---|---|
| `code` | `number` | در حالت موفق مقدار `200` |
| `msg` | `string` | در حالت موفق مقدار `success` |
| `data` | `object` | محتوای اصلی پاسخ |
### `data`
| field | type | description |
|---|---|---|
| `farm_uuid` | `string` | UUID مزرعه |
| `diseaseRisk` | `object` | کارت ریسک بیماری |
| `pestRisk` | `object` | کارت ریسک آفت |
| `drivers` | `object` | عوامل موثر روی ریسک |
### `diseaseRisk` / `pestRisk`
هر دو فیلد `diseaseRisk` و `pestRisk` یک ساختار مشابه دارند:
| field | type | description |
|---|---|---|
| `id` | `string` | شناسه کارت |
| `title` | `string` | عنوان کارت |
| `subtitle` | `string` | توضیح کوتاه |
| `stats` | `string` | عدد یا درصد اصلی برای نمایش |
| `avatarColor` | `string` | رنگ آیکن یا کارت |
| `avatarIcon` | `string` | نام آیکن |
| `chipText` | `string` | وضعیت متنی مثل `کم`، `متوسط`، `زیاد` |
| `chipColor` | `string` | رنگ وضعیت مثل `success`، `warning`، `error` |
| `details` | `object` | اطلاعات تکمیلی برای UI جزئی‌تر |
### `drivers`
| field | type | description |
|---|---|---|
| `drivers` | `object` | object آزاد از عوامل مؤثر مثل رطوبت، دما، باد، بارندگی و غیره |
نکته:
- ساختار داخلی `drivers` ثابت و محدود نیست و بهتر است در فرانت به صورت dynamic render شود.
---
## Error Response - Missing `farm_uuid`
```json
{
"code": 400,
"msg": "error",
"data": {
"farm_uuid": ["This field is required."]
}
}
```
### When Happens
- وقتی `farm_uuid` در body ارسال نشده باشد
---
## Error Response - Farm Not Found
```json
{
"code": 404,
"msg": "error",
"data": {
"farm_uuid": ["Farm not found."]
}
}
```
### When Happens
- وقتی `farm_uuid` معتبر باشد ولی مزرعه‌ای با آن پیدا نشود
- یا مزرعه متعلق به کاربر فعلی نباشد
---
## Error Response - Upstream / AI Error
اگر سرویس AI خطا برگرداند، backend همان status code را با این ساختار پاس می‌دهد:
```json
{
"code": 500,
"msg": "error",
"data": {
"message": "Upstream service error"
}
}
```
نکته:
- محتویات `data` در این حالت بسته به پاسخ upstream ممکن است متفاوت باشد.
---
## Frontend Notes
- فرم درخواست فقط باید `farm_uuid` بفرستد.
- `diseaseRisk` و `pestRisk` را مثل card model در UI مصرف کنید.
- `drivers` را optional و dynamic در نظر بگیرید.
- اگر یکی از `diseaseRisk` یا `pestRisk` خالی بود، UI باید بدون crash کار کند.
- متن خطا برای `400` و `404` را می‌توانید از `data.farm_uuid[0]` بخوانید.
---
## Suggested TypeScript Interfaces
```ts
export interface RiskCard {
id?: string;
title?: string;
subtitle?: string;
stats?: string;
avatarColor?: string;
avatarIcon?: string;
chipText?: string;
chipColor?: string;
details?: Record<string, unknown>;
}
export interface PestDiseaseRiskSummaryResponse {
code: number;
msg: string;
data: {
farm_uuid: string;
diseaseRisk?: RiskCard;
pestRisk?: RiskCard;
drivers?: Record<string, unknown>;
};
}
```
---
## Example Frontend Handling
```ts
const response = await api.post('/api/pest-disease/risk-summary/', {
farm_uuid,
});
const result = response.data;
if (result.code === 200) {
const diseaseRisk = result.data.diseaseRisk;
const pestRisk = result.data.pestRisk;
const drivers = result.data.drivers;
}
```
@@ -0,0 +1,267 @@
# Recommend Task Status API Guide
این فایل برای تیم فرانت‌اند توضیح می‌دهد که برای ماژول‌های `fertilization` و `irrigation` چه درخواست‌هایی باید به بک‌اند ارسال شود و چه پاسخ‌هایی باید دریافت شود.
## Fertilization Recommendation
### 1) ثبت درخواست پیشنهاد
**Endpoint**
`POST /api/fertilization-recommendation/recommend/`
**Request Body**
```json
{
"crop_id": "wheat",
"growth_stage": "tillering",
"farm_data": {
"soilType": "loam",
"organicMatter": "medium",
"waterEC": "1.2"
},
"soilType": "loam",
"organicMatter": "medium",
"waterEC": "1.2"
}
```
**Field Description**
- `crop_id`: شناسه محصول
- `growth_stage`: مرحله رشد محصول
- `farm_data.soilType`: نوع خاک
- `farm_data.organicMatter`: مقدار ماده آلی
- `farm_data.waterEC`: EC آب
- `soilType`, `organicMatter`, `waterEC`: همین داده‌ها اگر فرانت بخواهد به صورت flat هم ارسال کند
**Success Response**
اگر سرویس خارجی مستقیم نتیجه را برگرداند:
```json
{
"status": "success",
"data": {
"plan": {
"npkRatio": "20-20-20",
"amountPerHectare": "150 kg/ha",
"applicationMethod": "drip",
"applicationInterval": "every 14 days",
"reasoning": "balanced nutrition for current growth stage"
}
}
}
```
اگر سرویس خارجی async باشد، معمولاً `task_id` برمی‌گرداند:
```json
{
"status": "success",
"data": {
"task_id": "fert-task-123",
"status": "pending"
}
}
```
### 2) دریافت وضعیت تسک
**Endpoint**
`GET /api/fertilization-recommendation/recommend/status/{task_id}/`
**Path Param**
- `task_id`: شناسه تسکی که از مرحله قبل گرفته شده
**Success Response**
```json
{
"status": "success",
"data": {
"task_id": "fert-task-123",
"status": "processing",
"progress": {
"message": "analyzing farm data"
},
"result": {
"plan": {
"npkRatio": "20-20-20",
"amountPerHectare": "150 kg/ha",
"applicationMethod": "drip",
"applicationInterval": "every 14 days",
"reasoning": "balanced nutrition for current growth stage"
}
}
}
}
```
**Possible status values**
- `pending`
- `processing`
- `completed`
- `failed`
---
## Irrigation Recommendation
### 1) ثبت درخواست پیشنهاد
**Endpoint**
`POST /api/irrigation-recommendation/recommend/`
**Request Body**
```json
{
"crop_id": "wheat",
"farm_data": {
"soilType": "loam",
"waterQuality": "good",
"climateZone": "semi-arid"
},
"soilType": "loam",
"waterQuality": "good",
"climateZone": "semi-arid"
}
```
**Field Description**
- `crop_id`: شناسه محصول
- `farm_data.soilType`: نوع خاک
- `farm_data.waterQuality`: کیفیت آب
- `farm_data.climateZone`: اقلیم
- `soilType`, `waterQuality`, `climateZone`: همین داده‌ها در حالت flat
**Success Response**
حالت نتیجه مستقیم:
```json
{
"status": "success",
"data": {
"plan": {
"frequencyPerWeek": "3",
"durationMinutes": "45",
"bestTimeOfDay": "early morning",
"moistureLevel": "optimal",
"warning": "avoid irrigation during strong wind"
},
"raw_response": "...",
"water_balance": {
"daily": [
{
"forecast_date": "2025-03-28",
"et0_mm": 4.1,
"etc_mm": 3.8,
"effective_rainfall_mm": 0.0,
"gross_irrigation_mm": 3.8,
"irrigation_timing": "06:00"
}
],
"crop_profile": {
"kc_initial": 0.7,
"kc_mid": 1.05,
"kc_end": 0.85
},
"active_kc": 1.05
},
"status": "completed"
}
}
```
حالت async:
```json
{
"status": "success",
"data": {
"task_id": "irr-task-123",
"status": "pending"
}
}
```
### 2) دریافت وضعیت تسک
**Endpoint**
`GET /api/irrigation-recommendation/recommend/status/{task_id}/`
**Path Param**
- `task_id`: شناسه تسک
**Success Response**
```json
{
"status": "success",
"data": {
"task_id": "irr-task-123",
"status": "completed",
"result": {
"plan": {
"frequencyPerWeek": "3",
"durationMinutes": "45",
"bestTimeOfDay": "early morning",
"moistureLevel": "optimal",
"warning": "avoid irrigation during strong wind"
},
"raw_response": "...",
"water_balance": {
"daily": [],
"crop_profile": {
"kc_initial": 0.7,
"kc_mid": 1.05,
"kc_end": 0.85
},
"active_kc": 1.05
},
"status": "completed"
}
}
}
```
---
## پیشنهاد پیاده‌سازی در فرانت
### Fertilization
1. با `POST /recommend/` درخواست را ارسال کنید.
2. اگر `data.task_id` برگشت، polling را با `GET /recommend/status/{task_id}/` شروع کنید.
3. وقتی `data.status` به `completed` رسید، `data.result` را نمایش دهید.
4. اگر `failed` شد، پیام خطا را به کاربر نشان دهید.
### Irrigation
1. با `POST /recommend/` درخواست را ارسال کنید.
2. اگر `task_id` برگشت، هر چند ثانیه وضعیت را چک کنید.
3. وقتی `completed` شد، `result.plan` و `result.water_balance` را نمایش دهید.
## نکات
- همه پاسخ‌ها در این پروژه معمولاً با ساختار زیر برمی‌گردند:
```json
{
"status": "success",
"data": {}
}
```
- در صورت خطا ممکن است `status` مقدار دیگری داشته باشد یا سرویس خارجی خطای مستقیم برگرداند.
- فرانت باید هر دو حالت **direct result** و **task-based result** را هندل کند.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,381 @@
# Soil API Reference for Frontend
این فایل برای فرانت آماده شده تا ساختار پاسخ APIهای خاک را سریع و شفاف داشته باشید.
## Base Notes
- سه endpoint زیر `farm_uuid` را به صورت query param لازم دارند:
- `GET /api/soil/anomalies/`
- `GET /api/soil/moisture-heatmap/`
- `GET /api/soil/summary/`
- endpoint `GET /api/soil/avg-moisture/` بدون `farm_uuid` هم جواب می‌دهد، ولی اگر `farm_uuid` ارسال شود داده بر اساس همان مزرعه محاسبه می‌شود.
- در سه endpoint اول و سوم، اگر `farm_uuid` ارسال نشود یا مزرعه پیدا نشود، پاسخ با ساختار `code/msg/data` برمی‌گردد.
- پاسخ موفق `avg-moisture` با کلید `status` برمی‌گردد، ولی سه endpoint دیگر با کلیدهای `code`, `msg`, `data` برمی‌گردند.
---
## 1) Average Soil Moisture
### Endpoint
```http
GET /api/soil/avg-moisture/?farm_uuid=<farm_uuid>
```
### Query Params
| name | type | required | description |
|---|---|---:|---|
| `farm_uuid` | `string (uuid)` | no | UUID مزرعه |
### Success Response
```json
{
"status": "success",
"data": {
"id": "avg_soil_moisture",
"title": "میانگین رطوبت خاک",
"subtitle": "کل مزرعه",
"stats": "65%",
"avatarColor": "primary",
"avatarIcon": "tabler-plant-2",
"chipText": "بهینه",
"chipColor": "success"
}
}
```
### Response Fields
| field | type | description |
|---|---|---|
| `status` | `string` | در حالت موفق مقدار `success` |
| `data.id` | `string` | شناسه کارت |
| `data.title` | `string` | عنوان کارت |
| `data.subtitle` | `string` | زیرعنوان کارت |
| `data.stats` | `string` | مقدار اصلی به صورت درصد، مثل `48%` |
| `data.avatarColor` | `string` | رنگ آیکن/کارت |
| `data.avatarIcon` | `string` | نام آیکن |
| `data.chipText` | `string` | وضعیت متنی، مثل `بهینه`، `متوسط`، `کم` |
| `data.chipColor` | `string` | رنگ وضعیت، مثل `success`، `warning`، `error` |
### Frontend Notes
- این endpoint برای ساخت یک KPI card مناسب است.
- `stats` همیشه string است و بهتر است مستقیم render شود.
- `chipText` و `chipColor` برای badge یا status pill استفاده شوند.
---
## 2) Soil Anomalies
### Endpoint
```http
GET /api/soil/anomalies/?farm_uuid=<farm_uuid>
```
### Query Params
| name | type | required | description |
|---|---|---:|---|
| `farm_uuid` | `string (uuid)` | yes | UUID مزرعه |
### Success Response
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"summary": "Risk of localized soil imbalance detected.",
"explanation": "One or more soil indicators are outside the expected range.",
"likely_cause": "Uneven irrigation or nutrient distribution.",
"recommended_action": "Inspect the affected zone and verify irrigation schedule.",
"monitoring_priority": "high",
"confidence": 0.89,
"generated_at": "2025-01-01T10:30:00Z",
"anomalies": [
{
"sensor": "رطوبت خاک زون 3",
"value": "38%",
"expected": "45-65%",
"deviation": "-12%",
"severity": "warning"
}
],
"interpretation": {
"risk_level": "medium"
},
"knowledge_base": null,
"raw_response": null
}
}
```
### Response Fields
| field | type | description |
|---|---|---|
| `code` | `number` | در حالت موفق مقدار `200` |
| `msg` | `string` | در حالت موفق مقدار `success` |
| `data.farm_uuid` | `string` | UUID مزرعه |
| `data.summary` | `string` | خلاصه کوتاه نتیجه anomaly detection |
| `data.explanation` | `string` | توضیح readable برای فرانت |
| `data.likely_cause` | `string` | علت احتمالی |
| `data.recommended_action` | `string` | اقدام پیشنهادی |
| `data.monitoring_priority` | `string` | سطح اهمیت پایش؛ مثل `low`, `medium`, `high`, `urgent` |
| `data.confidence` | `number` | میزان اطمینان مدل |
| `data.generated_at` | `string` | زمان تولید تحلیل |
| `data.anomalies` | `array` | لیست anomalyها |
| `data.anomalies[].sensor` | `string` | نام سنسور یا ناحیه |
| `data.anomalies[].value` | `string` | مقدار فعلی |
| `data.anomalies[].expected` | `string` | بازه یا مقدار مورد انتظار |
| `data.anomalies[].deviation` | `string` | اختلاف با مقدار نرمال |
| `data.anomalies[].severity` | `string` | شدت anomaly، مثل `warning` یا `error` |
| `data.interpretation` | `object` | تفسیر ساختاریافته برای UI پیشرفته |
| `data.knowledge_base` | `string \| null` | مرجع دانشی در صورت وجود |
| `data.raw_response` | `string \| null` | متن خام upstream در صورت وجود |
### Error Response - Missing `farm_uuid`
```json
{
"code": 400,
"msg": "error",
"data": {
"farm_uuid": ["This field is required."]
}
}
```
### Error Response - Farm Not Found
```json
{
"code": 404,
"msg": "error",
"data": {
"farm_uuid": ["Farm not found."]
}
}
```
### Frontend Notes
- `anomalies` می‌تواند برای table، list یا alert cards استفاده شود.
- اگر `anomalies` خالی بود، UI بهتر است empty state نمایش دهد.
- `severity` را می‌توانید به color map وصل کنید.
---
## 3) Soil Moisture Heatmap
### Endpoint
```http
GET /api/soil/moisture-heatmap/?farm_uuid=<farm_uuid>
```
### Query Params
| name | type | required | description |
|---|---|---:|---|
| `farm_uuid` | `string (uuid)` | yes | UUID مزرعه |
### Success Response
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"location": {
"name": "Zone A"
},
"current_sensor": {
"name": "Sensor 7-in-1"
},
"soil_profile": [],
"timestamp": "2025-01-01T10:30:00Z",
"grid_resolution": {
"rows": 10,
"cols": 10
},
"grid_cells": [],
"sensor_points": [],
"quality_legend": {
"low": "0-30",
"medium": "31-60",
"high": "61-100"
},
"depth_layers": [],
"model_metadata": {},
"summary": {}
}
}
```
### Supported Response Shape in Current Backend
در serializer فعلی این فیلدها پشتیبانی می‌شوند:
| field | type | description |
|---|---|---|
| `data.farm_uuid` | `string` | UUID مزرعه |
| `data.location` | `object` | اطلاعات مکانی |
| `data.current_sensor` | `object` | اطلاعات سنسور فعال |
| `data.soil_profile` | `array<object>` | داده لایه‌های خاک |
| `data.timestamp` | `string \| null` | زمان تولید heatmap |
| `data.grid_resolution` | `object` | رزولوشن grid |
| `data.grid_cells` | `array<object>` | سلول‌های grid |
| `data.sensor_points` | `array<object>` | نقاط سنسور |
| `data.quality_legend` | `object` | legend مقادیر |
| `data.depth_layers` | `array<object>` | لایه‌های عمقی |
| `data.model_metadata` | `object` | متادیتای مدل |
| `data.summary` | `object` | خلاصه تفسیری |
### Legacy / Mock Shape You May Also See
در داده mock داخلی پروژه یک ساختار ساده‌تر هم وجود دارد:
```json
{
"status": "success",
"data": {
"zones": ["زون 1", "زون 2"],
"hours": ["6 ص", "8 ص"],
"series": [
{
"name": "زون 1",
"data": [
{ "x": "6 ص", "y": 52 },
{ "x": "8 ص", "y": 48 }
]
}
]
}
}
```
### Error Response - Missing `farm_uuid`
```json
{
"code": 400,
"msg": "error",
"data": {
"farm_uuid": ["This field is required."]
}
}
```
### Error Response - Farm Not Found
```json
{
"code": 404,
"msg": "error",
"data": {
"farm_uuid": ["Farm not found."]
}
}
```
### Frontend Notes
- چون upstream shape ممکن است object-based یا series-based باشد، فرانت بهتر است defensive parsing داشته باشد.
- اگر `grid_cells` وجود داشت، heatmap را از grid render کنید.
- اگر `series` وجود داشت، می‌توانید آن را به chart heatmap یا matrix chart بدهید.
---
## 4) Soil Summary
### Endpoint
```http
GET /api/soil/summary/?farm_uuid=<farm_uuid>
```
### Query Params
| name | type | required | description |
|---|---|---:|---|
| `farm_uuid` | `string (uuid)` | yes | UUID مزرعه |
### Success Response
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"healthScore": 82,
"profileSource": "Tomato",
"healthScoreDetails": {},
"healthLanguage": {},
"avgSoilMoisture": 46,
"avgSoilMoistureRaw": 46.0,
"avgSoilMoistureStatus": "بهینه"
}
}
```
### Response Fields
| field | type | description |
|---|---|---|
| `code` | `number` | در حالت موفق مقدار `200` |
| `msg` | `string` | در حالت موفق مقدار `success` |
| `data.farm_uuid` | `string` | UUID مزرعه |
| `data.healthScore` | `number` | امتیاز سلامت کلی خاک |
| `data.profileSource` | `string` | منبع پروفایل یا محصول مرجع |
| `data.healthScoreDetails` | `object` | جزئیات محاسبه health score |
| `data.healthLanguage` | `object` | متن‌ها و labelهای قابل نمایش |
| `data.avgSoilMoisture` | `number` | میانگین گرد شده رطوبت خاک |
| `data.avgSoilMoistureRaw` | `number` | میانگین خام |
| `data.avgSoilMoistureStatus` | `string` | وضعیت متنی رطوبت خاک |
### Error Response - Missing `farm_uuid`
```json
{
"code": 400,
"msg": "error",
"data": {
"farm_uuid": ["This field is required."]
}
}
```
### Error Response - Farm Not Found
```json
{
"code": 404,
"msg": "error",
"data": {
"farm_uuid": ["Farm not found."]
}
}
```
### Frontend Notes
- این endpoint برای summary card یا hero panel خیلی مناسب است.
- `healthScoreDetails` و `healthLanguage` را optional در نظر بگیرید.
- برای UI بهتر، `healthScore` را هم به صورت عدد و هم به صورت progress/gauge نمایش دهید.
---
## Suggested Frontend Handling
- برای `avg-moisture` انتظار `status/data` داشته باشید.
- برای `anomalies`, `moisture-heatmap`, `summary` انتظار `code/msg/data` داشته باشید.
- برای خطاهای 400 و 404، متن خطا را از `data.farm_uuid[0]` بخوانید.
- در heatmap، parsing را flexible بنویسید چون shape داده ممکن است بسته به upstream تغییر کند.
@@ -0,0 +1,381 @@
# Water & Weather API Reference for Frontend
این فایل برای فرانت آماده شده تا ساختار پاسخ APIهای زیر مشخص باشد:
- `GET /api/water/card/`
- `GET /api/water/need-prediction/`
- `GET /api/water/summary/`
- `POST /api/weather/farm-card/`
## Base Notes
- `GET /api/water/card/` و `GET /api/water/summary/` بدون `farm_uuid` هم جواب می‌دهند.
- `GET /api/water/need-prediction/` هم بدون `farm_uuid` جواب می‌دهد، ولی اگر `farm_uuid` وجود داشته باشد ممکن است داده مزرعه‌محور برگردد.
- `POST /api/weather/farm-card/` نیاز به `farm_uuid` در body دارد.
- response shapeها بین این endpointها یکدست نیستند:
- بعضی endpointها با `status/data`
- بعضی endpointها با `code/msg/data`
---
## 1) Water Card
### Endpoint
```http
GET /api/water/card/?farm_uuid=<farm_uuid>
```
### Query Params
| field | type | required | description |
|---|---|---:|---|
| `farm_uuid` | `string (uuid)` | no | UUID مزرعه |
### Success Response
```json
{
"status": "success",
"data": {
"condition": "صاف",
"temperature": 24,
"unit": "°C",
"humidity": 45,
"windSpeed": 12,
"windUnit": "km/h",
"chartData": {
"labels": ["۶ صبح", "۹ صبح", "۱۲ ظهر", "۳ بعدازظهر"],
"series": [[18, 22, 26, 28]]
}
}
}
```
### Response Fields
| field | type | description |
|---|---|---|
| `status` | `string` | در حالت موفق مقدار `success` |
| `data.condition` | `string` | وضعیت فعلی آب‌وهوا |
| `data.temperature` | `number` | دمای فعلی |
| `data.unit` | `string` | واحد دما |
| `data.humidity` | `number` | رطوبت نسبی |
| `data.windSpeed` | `number` | سرعت باد |
| `data.windUnit` | `string` | واحد سرعت باد |
| `data.chartData.labels` | `string[]` | برچسب‌های زمانی |
| `data.chartData.series` | `number[][]` | سری‌های نمودار |
### Frontend Notes
- این endpoint برای weather widget یا weather card مناسب است.
- `chartData.series` به شکل آرایه دوبعدی است.
- اگر `farm_uuid` ارسال شود، backend داده را از AI گرفته و log هم می‌کند.
---
## 2) Water Need Prediction
### Endpoint
```http
GET /api/water/need-prediction/?farm_uuid=<farm_uuid>
```
### Query Params
| field | type | required | description |
|---|---|---:|---|
| `farm_uuid` | `string (uuid)` | no | UUID مزرعه |
### Success Response
```json
{
"status": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"totalNext7Days": 24.6,
"unit": "mm",
"categories": ["2025-01-01", "2025-01-02", "2025-01-03"],
"series": [
{
"name": "نیاز آبی",
"data": [3.2, 4.1, 2.8]
}
],
"dailyBreakdown": [],
"insight": {},
"knowledge_base": "",
"raw_response": ""
}
}
```
### Response Fields
| field | type | description |
|---|---|---|
| `status` | `string` | در حالت موفق مقدار `success` |
| `data.farm_uuid` | `string` | UUID مزرعه در حالت farm-based |
| `data.totalNext7Days` | `number` | مجموع نیاز آبی 7 روز آینده |
| `data.unit` | `string` | واحد نیاز آبی، مثل `mm` یا `m3` |
| `data.categories` | `string[]` | روزها یا تاریخ‌ها |
| `data.series` | `Array<{name: string, data: number[]}>` | داده‌های نمودار |
| `data.dailyBreakdown` | `object[]` | breakdown روزانه در صورت وجود |
| `data.insight` | `object` | insight یا خلاصه تحلیلی |
| `data.knowledge_base` | `string` | منبع دانشی در صورت وجود |
| `data.raw_response` | `string` | پاسخ خام upstream در صورت وجود |
### Frontend Notes
- اگر `farm_uuid` معتبر باشد، backend داده را از AI می‌گیرد.
- اگر `farm_uuid` ارسال نشود، backend از داده داخلی/mock استفاده می‌کند.
- اگر `farm_uuid` ارسال شود ولی مزرعه پیدا نشود، فعلاً به fallback داخلی برمی‌گردد و خطا نمی‌دهد.
---
## 3) Water Summary
### Endpoint
```http
GET /api/water/summary/?farm_uuid=<farm_uuid>
```
### Query Params
| field | type | required | description |
|---|---|---:|---|
| `farm_uuid` | `string (uuid)` | no | UUID مزرعه |
### Success Response
```json
{
"status": "success",
"data": {
"farmWeatherCard": {
"condition": "صاف",
"temperature": 24,
"unit": "°C",
"humidity": 45,
"windSpeed": 12,
"windUnit": "km/h",
"chartData": {
"labels": ["۶ صبح", "۹ صبح", "۱۲ ظهر"],
"series": [[18, 22, 26]]
}
},
"waterNeedPrediction": {
"totalNext7Days": 3290,
"unit": "m3",
"categories": ["روز 1", "روز 2", "روز 3"],
"series": [
{
"name": "نیاز آبی",
"data": [420, 450, 480]
}
]
},
"waterStressIndex": {
"id": "water_stress_index",
"title": "شاخص تنش آبی",
"subtitle": "فعلی",
"stats": "12%",
"avatarColor": "info",
"avatarIcon": "tabler-droplet",
"chipText": "پایین",
"chipColor": "success"
}
}
}
```
### Response Fields
| field | type | description |
|---|---|---|
| `status` | `string` | در حالت موفق مقدار `success` |
| `data.farmWeatherCard` | `object` | اطلاعات کارت وضعیت آب‌وهوا |
| `data.waterNeedPrediction` | `object` | پیش‌بینی نیاز آبی |
| `data.waterStressIndex` | `object` | کارت شاخص تنش آبی |
### `waterStressIndex` Fields
| field | type | description |
|---|---|---|
| `id` | `string` | شناسه کارت |
| `title` | `string` | عنوان کارت |
| `subtitle` | `string` | زیرعنوان |
| `stats` | `string` | مقدار اصلی برای نمایش |
| `avatarColor` | `string` | رنگ کارت/آیکن |
| `avatarIcon` | `string` | نام آیکن |
| `chipText` | `string` | وضعیت متنی |
| `chipColor` | `string` | رنگ وضعیت |
### Frontend Notes
- این endpoint برای dashboard overview مناسب است.
- سه بخش اصلی summary را می‌توانید مستقیم به سه widget مختلف map کنید.
- `waterSummary` یک response ترکیبی است و برای یک صفحه dashboard خیلی کاربردی است.
---
## 4) Weather Farm Card
### Endpoint
```http
POST /api/weather/farm-card/
```
### Request Body
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111"
}
```
### Request Fields
| field | type | required | description |
|---|---|---:|---|
| `farm_uuid` | `string (uuid)` | yes | UUID مزرعه |
### Success Response
```json
{
"code": 200,
"msg": "success",
"data": {
"condition": "صاف",
"temperature": 28.0,
"unit": "°C",
"humidity": 45,
"windSpeed": 12,
"windUnit": "km/h",
"chartData": {
"labels": ["۶ صبح", "۹ صبح", "۱۲ ظهر", "۳ بعدازظهر"],
"series": [[18, 22, 26, 28]]
}
}
}
```
### Response Fields
| field | type | description |
|---|---|---|
| `code` | `number` | در حالت موفق مقدار `200` |
| `msg` | `string` | در حالت موفق مقدار `success` |
| `data.condition` | `string` | وضعیت فعلی آب‌وهوا |
| `data.temperature` | `number` | دمای فعلی |
| `data.unit` | `string` | واحد دما |
| `data.humidity` | `number` | رطوبت نسبی |
| `data.windSpeed` | `number` | سرعت باد |
| `data.windUnit` | `string` | واحد سرعت باد |
| `data.chartData.labels` | `string[]` | برچسب‌های زمانی |
| `data.chartData.series` | `number[][]` | داده‌های نمودار |
### Error Response - Missing `farm_uuid`
```json
{
"code": 400,
"msg": "error",
"data": {
"farm_uuid": ["This field is required."]
}
}
```
### Error Response - Farm Not Found
```json
{
"code": 404,
"msg": "error",
"data": {
"farm_uuid": ["Farm not found."]
}
}
```
### Error Response - Upstream Error
```json
{
"code": 500,
"msg": "error",
"data": {
"message": "Upstream service error"
}
}
```
### Frontend Notes
- این endpoint نسخه authenticated و farm-specific برای weather card است.
- اگر farm متعلق به کاربر فعلی نباشد، `404` برمی‌گردد.
- response این endpoint با `code/msg/data` است، نه `status/data`.
---
## Suggested TypeScript Interfaces
```ts
export interface WeatherChartData {
labels?: string[];
series?: number[][];
}
export interface FarmWeatherCard {
condition?: string;
temperature?: number;
unit?: string;
humidity?: number;
windSpeed?: number;
windUnit?: string;
chartData?: WeatherChartData;
}
export interface WaterNeedSeries {
name?: string;
data?: number[];
}
export interface WaterNeedPrediction {
farm_uuid?: string;
totalNext7Days?: number;
unit?: string;
categories?: string[];
series?: WaterNeedSeries[];
dailyBreakdown?: Record<string, unknown>[];
insight?: Record<string, unknown>;
knowledge_base?: string;
raw_response?: string;
}
export interface WaterStressCard {
id?: string;
title?: string;
subtitle?: string;
stats?: string;
avatarColor?: string;
avatarIcon?: string;
chipText?: string;
chipColor?: string;
}
```
---
## Suggested Frontend Handling
- برای `GET /api/water/card/`, `GET /api/water/need-prediction/`, `GET /api/water/summary/` انتظار `status/data` داشته باشید.
- برای `POST /api/weather/farm-card/` انتظار `code/msg/data` داشته باشید.
- برای `POST /api/weather/farm-card/` خطاها را از `data.farm_uuid[0]` بخوانید.
- برای chartها بهتر است `labels` و `series` را optional render کنید.
@@ -0,0 +1,941 @@
# مرجع کامل ارتباط Backend با AI در ماژول Yield & Harvest
این سند قرارداد فعلی backend برای endpointهای ماژول `yield_harvest` را توضیح می‌دهد؛ هم از دید فرانت/کاربر، هم از دید payload ارسالی به سرویس AI.
این سند این endpointها را پوشش می‌دهد:
- `POST /api/yield-harvest/current-farm-chart/`
- `POST /api/yield-harvest/growth/`
- `GET /api/yield-harvest/growth/{task_id}/status/`
- `POST /api/yield-harvest/harvest-prediction/`
- `POST /api/yield-harvest/yield-prediction/`
- `GET /api/yield-harvest/yield-harvest-summary/`
---
## هدف این سند
این ماژول باید برای endpointهای farm-based تا حد ممکن فقط `farm_uuid` را از کاربر بگیرد و بقیه context لازم را خودش از دیتابیس بخواند.
مهم‌ترین قاعده این سند:
- فرانت نباید `plant_name` را برای endpointهای farm-based ارسال کند.
- backend باید `plant_name` را از `farm_hub.models.FarmHub` استخراج کند.
- منبع استخراج `plant_name` این است:
1. اولین محصول `farm.products` بر اساس `id`
2. اگر مزرعه محصول نداشت، اولین محصول `farm.farm_type.products` بر اساس `id`
پیاده‌سازی فعلی این رفتار در فایل زیر است:
- `yield_harvest/views.py`
مدل‌های منبع داده:
- `farm_hub/models.py`
---
## احراز هویت و سطح دسترسی
همه endpointهای این سند نیاز به JWT معتبر دارند.
### هدرهای متداول
```http
Authorization: Bearer <access_token>
Content-Type: application/json
Accept: application/json
```
### اعتبارسنجی مالکیت مزرعه
برای endpointهایی که `farm_uuid` می‌گیرند، backend فقط زمانی درخواست را قبول می‌کند که:
- مزرعه وجود داشته باشد
- و مالک آن مزرعه همان `request.user` باشد
اگر مزرعه برای کاربر جاری پیدا نشود:
```json
{
"code": 404,
"msg": "error",
"data": {
"farm_uuid": ["Farm not found."]
}
}
```
---
## الگوی کلی پاسخ‌ها
تقریباً تمام endpointهای این ماژول از envelope زیر استفاده می‌کنند:
```json
{
"code": 200,
"msg": "success",
"data": {}
}
```
### معنی فیلدهای envelope
| فیلد | نوع | توضیح |
|---|---|---|
| `code` | integer | کد منطقی پاسخ؛ معمولاً با HTTP status هم‌راستا است |
| `msg` | string | پیام کوتاه پاسخ |
| `data` | object / array / null | بدنه اصلی پاسخ |
### خطاهای متداول
| HTTP Status | `code` | توضیح |
|---|---|---|
| `400` | `400` | ورودی نامعتبر است |
| `404` | `404` | مزرعه برای کاربر جاری پیدا نشد |
| `500` | `500` | AI یا لایه محاسباتی upstream خطا داده است |
| `202` | `202` | تسک async با موفقیت در صف قرار گرفته است |
---
## قرارداد ورودی از دید Frontend
### اصل طراحی
برای endpointهای farm-based این ماژول، فرانت فقط باید `farm_uuid` را ارسال کند و نباید موارد زیر را از کاربر بگیرد:
- `plant_name`
- `crop_name` برای جریان‌های farm-based prediction
- هر context دیگری که backend می‌تواند از مزرعه استخراج کند
### استثناها
- `GET /api/yield-harvest/growth/{task_id}/status/` به `farm_uuid` نیاز ندارد؛ چون بر اساس `task_id` کار می‌کند.
- `GET /api/yield-harvest/yield-harvest-summary/` علاوه بر `farm_uuid` می‌تواند queryهای اختیاری هم داشته باشد، ولی در قرارداد فرانت ساده می‌توان فقط `farm_uuid` را فرستاد.
- endpoint رشد (`growth`) در لایه AI پارامترهای پیشرفته دارد، اما در قرارداد ساده frontend این سند، ورودی کاربر باید فقط `farm_uuid` باشد و backend باید context گیاه را از مزرعه بردارد.
---
## نگاشت endpointهای Backend به AI
| Backend Route | Method | AI Route | Method |
|---|---|---|---|
| `/api/yield-harvest/current-farm-chart/` | `POST` | `/api/crop-simulation/current-farm-chart/` | `POST` |
| `/api/yield-harvest/growth/` | `POST` | `/api/crop-simulation/growth/` | `POST` |
| `/api/yield-harvest/growth/{task_id}/status/` | `GET` | `/api/crop-simulation/growth/{task_id}/status/` | `GET` |
| `/api/yield-harvest/harvest-prediction/` | `POST` | `/api/crop-simulation/harvest-prediction/` | `POST` |
| `/api/yield-harvest/yield-prediction/` | `POST` | `/api/crop-simulation/yield-prediction/` | `POST` |
| `/api/yield-harvest/yield-harvest-summary/` | `GET` | `/api/crop-simulation/yield-harvest-summary/` | `GET` |
---
## منبع `plant_name` در Backend
### منبع داده
backend نام گیاه را از مدل `FarmHub` در `farm_hub/models.py` می‌گیرد.
### ترتیب انتخاب
1. `farm.products.order_by("id").first()`
2. اگر مورد 1 خالی بود: `farm.farm_type.products.order_by("id").first()`
### مثال مفهومی
اگر مزرعه این محصولات را داشته باشد:
```text
farm.products = ["خیار", "گوجه‌فرنگی"]
```
backend این مقدار را برای AI می‌فرستد:
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "خیار"
}
```
یعنی معیار فعلی، «اولین محصول بر اساس `id`» است، نه محصول انتخاب‌شده توسط کاربر.
---
## 1) POST `/api/yield-harvest/current-farm-chart/`
### کاربرد
دریافت نمودار وضعیت فعلی مزرعه بر اساس شبیه‌سازی رشد محصول.
### ورودی از فرانت
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111"
}
```
### نکته مهم
- `plant_name` از کاربر گرفته نمی‌شود.
- backend آن را از مزرعه استخراج می‌کند.
### payload ارسالی backend به AI
نمونه:
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "خیار"
}
```
### پاسخ موفق
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"engine": "growth_projection",
"model_name": "growth_projection_v1",
"scenario_id": 12,
"simulation_warning": null,
"categories": ["2026-04-01", "2026-04-02"],
"series": [
{
"name": "تعداد برگ تخمینی",
"key": "leaf_count_estimate",
"data": [120.0, 140.0]
}
],
"summary": [
{
"title": "تعداد برگ تخمینی",
"subtitle": "وضعیت فعلی",
"amount": 140.0,
"unit": "leaf",
"avatarColor": "success",
"avatarIcon": "tabler-leaf"
}
],
"current_state": {
"date": "2026-04-02",
"leaf_count_estimate": 140.0,
"leaf_area_index": 0.0117,
"biomass_weight": 45.0,
"storage_organ_weight": 10.0,
"soil_moisture_percent": 41.2,
"development_stage": 0.35,
"gdd": 9.0
},
"metrics": {
"yield_estimate": 10.0
},
"daily_output": []
}
}
```
### توضیح فیلدهای پاسخ
| فیلد | نوع | توضیح |
|---|---|---|
| `farm_uuid` | string / null | شناسه مزرعه |
| `plant_name` | string | نام گیاهی که شبیه‌سازی برای آن انجام شده |
| `engine` | string / null | موتور شبیه‌سازی |
| `model_name` | string / null | نام مدل |
| `scenario_id` | integer / null | شناسه سناریو |
| `simulation_warning` | string / null | هشدار غیر بحرانی |
| `categories` | array[string] | محور زمانی نمودار |
| `series` | array[object] | سری‌های نمودار |
| `summary` | array[object] | کارت‌های خلاصه |
| `current_state` | object | وضعیت آخرین روز شبیه‌سازی |
| `metrics` | object | شاخص‌های محاسبه‌شده |
| `daily_output` | array[object] | خروجی خام روزانه |
### توضیح `series[]`
| فیلد | نوع | توضیح |
|---|---|---|
| `name` | string | عنوان سری |
| `key` | string | کلید فنی سری |
| `data` | array[number] | مقادیر سری |
### توضیح `summary[]`
| فیلد | نوع | توضیح |
|---|---|---|
| `title` | string | عنوان کارت |
| `subtitle` | string | زیرعنوان |
| `amount` | number | مقدار اصلی |
| `unit` | string | واحد |
| `avatarColor` | string | رنگ پیشنهادی UI |
| `avatarIcon` | string | آیکن پیشنهادی UI |
### توضیح `current_state`
| فیلد | نوع | توضیح |
|---|---|---|
| `date` | string | تاریخ آخرین رکورد |
| `leaf_count_estimate` | number | تعداد برگ تخمینی |
| `leaf_area_index` | number | شاخص سطح برگ |
| `biomass_weight` | number | وزن بیوماس |
| `storage_organ_weight` | number | وزن اندام ذخیره‌ای / محصول |
| `soil_moisture_percent` | number | درصد رطوبت خاک |
| `development_stage` | number | مرحله رشد |
| `gdd` | number | درجه-روز رشد |
---
## 2) POST `/api/yield-harvest/growth/`
### کاربرد
شروع شبیه‌سازی رشد به صورت async.
### قرارداد ساده فرانت
در قرارداد frontend این سند، فرانت فقط باید `farm_uuid` را بفرستد.
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111"
}
```
### نکته مهم
- `plant_name` نباید از کاربر گرفته شود.
- backend آن را از مزرعه استخراج می‌کند.
- `task_id` خروجی این endpoint، ورودی endpoint وضعیت است.
### payload ارسالی backend به AI
نمونه مفهومی:
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "خیار",
"dynamic_parameters": ["DVS", "LAI", "TAGP", "TWSO", "SM"]
}
```
نکته: upstream AI ممکن است پارامترهای پیشرفته بیشتری هم بپذیرد، ولی این‌ها نباید از کاربر نهایی گرفته شوند مگر این‌که قرارداد جداگانه‌ای برای expert mode تعریف شود.
### پاسخ موفق
```json
{
"code": 202,
"msg": "تسک شبیه سازی رشد در صف قرار گرفت.",
"data": {
"task_id": "growth-task-1",
"status_url": "/api/crop-simulation/growth/growth-task-1/status/",
"plant_name": "گوجه‌فرنگی"
}
}
```
### توضیح فیلدهای پاسخ
| فیلد | نوع | توضیح |
|---|---|---|
| `task_id` | string | شناسه تسک |
| `status_url` | string | آدرس بررسی وضعیت تسک |
| `plant_name` | string | نام گیاهی که شبیه‌سازی برای آن آغاز شده |
---
## 3) GET `/api/yield-harvest/growth/{task_id}/status/`
### کاربرد
بررسی وضعیت و نتیجه تسک async شبیه‌سازی رشد.
### ورودی
این endpoint از کاربر `farm_uuid` نمی‌گیرد.
### Path Parameter
| فیلد | نوع | اجباری | توضیح |
|---|---|---:|---|
| `task_id` | string | بله | شناسه تسک برگشتی از endpoint رشد |
### Query اختیاری
| فیلد | نوع | اجباری | توضیح |
|---|---|---:|---|
| `page` | integer | خیر | شماره صفحه stageها |
| `page_size` | integer | خیر | تعداد آیتم در هر صفحه |
### پاسخ در حالت `PENDING`
```json
{
"code": 200,
"msg": "success",
"data": {
"task_id": "growth-task-1",
"status": "PENDING",
"message": "تسک در صف یا یافت نشد."
}
}
```
### پاسخ در حالت `PROGRESS`
```json
{
"code": 200,
"msg": "success",
"data": {
"task_id": "growth-task-1",
"status": "PROGRESS",
"progress": {
"current": 2,
"total": 3,
"percent": 66.7
}
}
}
```
### پاسخ در حالت `SUCCESS`
```json
{
"code": 200,
"msg": "success",
"data": {
"task_id": "growth-task-1",
"status": "SUCCESS",
"result": {
"plant_name": "گوجه‌فرنگی",
"dynamic_parameters": ["DVS"],
"engine": "growth_projection",
"model_name": "growth_projection_v1",
"scenario_id": null,
"simulation_warning": null,
"summary_metrics": {},
"stage_timeline": [],
"stages_page": [],
"pagination": {
"page": 1,
"page_size": 10,
"total_items": 0,
"total_pages": 0,
"has_next": false,
"has_previous": false
},
"daily_records_count": 0,
"default_page_size": 10
}
}
}
```
### پاسخ در حالت `FAILURE`
```json
{
"code": 200,
"msg": "success",
"data": {
"task_id": "growth-task-1",
"status": "FAILURE",
"error": "task crashed"
}
}
```
### توضیح فیلدهای status response
| فیلد | نوع | توضیح |
|---|---|---|
| `task_id` | string | شناسه تسک |
| `status` | string | وضعیت تسک: `PENDING`, `PROGRESS`, `SUCCESS`, `FAILURE` |
| `message` | string | پیام کمکی در برخی وضعیت‌ها |
| `progress` | object | وضعیت پیشرفت |
| `result` | object | نتیجه نهایی در حالت موفق |
| `error` | string | خطای نهایی در حالت failure |
### توضیح `progress`
| فیلد | نوع | توضیح |
|---|---|---|
| `current` | integer | مرحله فعلی |
| `total` | integer | کل مراحل |
| `percent` | float | درصد پیشرفت |
### توضیح `result`
| فیلد | نوع | توضیح |
|---|---|---|
| `plant_name` | string | نام گیاه |
| `dynamic_parameters` | array[string] | پارامترهای دینامیک |
| `engine` | string / null | موتور شبیه‌سازی |
| `model_name` | string / null | نام مدل |
| `scenario_id` | integer / null | شناسه سناریو |
| `simulation_warning` | string / null | هشدار محاسباتی |
| `summary_metrics` | object | شاخص‌های خلاصه |
| `stage_timeline` | array[object] | timeline کامل مراحل |
| `stages_page` | array[object] | آیتم‌های همین صفحه |
| `pagination` | object | اطلاعات صفحه‌بندی |
| `daily_records_count` | integer | تعداد رکوردهای روزانه |
| `default_page_size` | integer | اندازه صفحه پیش‌فرض |
### توضیح `pagination`
| فیلد | نوع | توضیح |
|---|---|---|
| `page` | integer | صفحه فعلی |
| `page_size` | integer | اندازه صفحه |
| `total_items` | integer | تعداد کل stageها |
| `total_pages` | integer | تعداد کل صفحه‌ها |
| `has_next` | boolean | آیا صفحه بعدی وجود دارد |
| `has_previous` | boolean | آیا صفحه قبلی وجود دارد |
---
## 4) POST `/api/yield-harvest/harvest-prediction/`
### کاربرد
پیش‌بینی زمان برداشت برای مزرعه.
### ورودی از فرانت
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111"
}
```
### payload ارسالی backend به AI
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "خیار"
}
```
### پاسخ موفق
```json
{
"code": 200,
"msg": "success",
"data": {
"date": "2026-05-14",
"dateFormatted": "14 May 2026",
"daysUntil": 43,
"description": "شبيه ساز نشان مي دهد حدود 43 روز ديگر تا برداشت باقي مانده است.",
"optimalWindowStart": "2026-05-11",
"optimalWindowEnd": "2026-05-17",
"gddDetails": {
"current_cumulative_gdd": 50.0,
"required_gdd_for_maturity": 1200.0,
"remaining_gdd": 1150.0,
"simulation_engine": "growth_projection"
}
}
}
```
### توضیح فیلدهای پاسخ
| فیلد | نوع | توضیح |
|---|---|---|
| `date` | string | تاریخ تخمینی برداشت به فرمت ISO |
| `dateFormatted` | string | تاریخ قابل نمایش |
| `daysUntil` | integer | تعداد روزهای باقیمانده |
| `description` | string | توضیح متنی |
| `optimalWindowStart` | string | شروع پنجره مناسب برداشت |
| `optimalWindowEnd` | string | پایان پنجره مناسب برداشت |
| `gddDetails` | object | جزئیات محاسبات GDD |
### توضیح `gddDetails`
| فیلد | نوع | توضیح |
|---|---|---|
| `current_cumulative_gdd` | number | GDD تجمعی فعلی |
| `required_gdd_for_maturity` | number | GDD مورد نیاز برای بلوغ |
| `remaining_gdd` | number | GDD باقی‌مانده |
| `estimated_days_to_harvest` | integer | روزهای برآوردی تا برداشت |
| `predicted_harvest_date` | string | تاریخ برآوردی برداشت |
| `predicted_harvest_window` | object | بازه برداشت |
| `daily_gdd_forecast` | array[object] | پیش‌بینی روزانه GDD |
| `simulation_engine` | string | موتور شبیه‌سازی |
| `simulation_model_name` | string | نام مدل |
| `simulation_warning` | string / null | هشدار محاسباتی |
| `scenario_id` | integer / null | شناسه سناریو |
---
## 5) POST `/api/yield-harvest/yield-prediction/`
### کاربرد
پیش‌بینی عملکرد مزرعه.
### ورودی از فرانت
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111"
}
```
### payload ارسالی backend به AI
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "خیار"
}
```
### پاسخ موفق
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"predictedYieldTons": 5.4,
"predictedYieldRaw": 5400.0,
"unit": "تن",
"sourceUnit": "kg/ha",
"simulationEngine": "growth_projection",
"simulationModel": "growth_projection_v1",
"scenarioId": 12,
"simulationWarning": null,
"supportingMetrics": {
"yield_estimate": 5400.0
}
}
}
```
### توضیح فیلدهای پاسخ
| فیلد | نوع | توضیح |
|---|---|---|
| `farm_uuid` | string | شناسه مزرعه |
| `plant_name` | string / null | نام گیاه |
| `predictedYieldTons` | number | عملکرد بر حسب تن |
| `predictedYieldRaw` | number | مقدار خام عملکرد |
| `unit` | string | واحد نمایشی |
| `sourceUnit` | string | واحد منبع |
| `simulationEngine` | string / null | موتور شبیه‌سازی |
| `simulationModel` | string / null | نام مدل شبیه‌سازی |
| `scenarioId` | integer / null | شناسه سناریو |
| `simulationWarning` | string / null | هشدار محاسباتی |
| `supportingMetrics` | object | شاخص‌های پشتیبان |
### توضیح `supportingMetrics`
این object بسته به upstream می‌تواند شامل مواردی مانند این‌ها باشد:
| فیلد | نوع | توضیح |
|---|---|---|
| `yield_estimate` | number | برآورد خام عملکرد |
| `biomass` | number | بیوماس برآوردی |
| `max_lai` | number | بیشترین شاخص سطح برگ |
---
## 6) GET `/api/yield-harvest/yield-harvest-summary/`
### کاربرد
دریافت داشبورد کامل عملکرد و برداشت.
### ورودی ساده از فرانت
```http
GET /api/yield-harvest/yield-harvest-summary/?farm_uuid=11111111-1111-1111-1111-111111111111
```
### Queryهای اختیاری قابل پشتیبانی
| فیلد | نوع | اجباری | توضیح |
|---|---|---:|---|
| `farm_uuid` | UUID | بله | شناسه مزرعه |
| `season_year` | integer | خیر | سال زراعی |
| `crop_name` | string | خیر | نام محصول |
| `include_narrative` | boolean | خیر | در صورت `true` متن‌های توضیحی هم merge می‌شوند |
### نکته قرارداد فرانت
در جریان ساده frontend، ارسال فقط `farm_uuid` کافی است و backend بقیه context لازم را مدیریت می‌کند.
### پاسخ موفق
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"season_highlights_card": {},
"yield_prediction": {},
"harvest_prediction_card": {},
"harvest_readiness_zones": {},
"yield_quality_bands": {},
"harvest_operations_card": {},
"yield_prediction_chart": {}
}
}
```
### توضیح top-level response
| فیلد | نوع | توضیح |
|---|---|---|
| `farm_uuid` | string | شناسه مزرعه |
| `season_highlights_card` | object | خلاصه مهم‌ترین KPIها |
| `yield_prediction` | object | خروجی پیش‌بینی عملکرد |
| `harvest_prediction_card` | object | تاریخ و وضعیت برداشت |
| `harvest_readiness_zones` | object | آمادگی برداشت در zoneها |
| `yield_quality_bands` | object | کیفیت برآوردی محصول |
| `harvest_operations_card` | object | عملیات پیشنهادی برداشت |
| `yield_prediction_chart` | object | نمودار عملکرد و بیوماس |
### توضیح `season_highlights_card`
| فیلد | نوع | توضیح |
|---|---|---|
| `title` | string | عنوان کارت |
| `subtitle` | string | توضیح کوتاه |
| `total_predicted_yield` | number / null | عملکرد پیش‌بینی‌شده |
| `yield_unit` | string | واحد عملکرد |
| `target_harvest_date` | string / null | تاریخ هدف برداشت |
| `days_until_harvest` | integer / null | روز باقی‌مانده |
| `average_readiness` | number / null | میانگین آمادگی |
| `primary_quality_grade` | string / null | گرید کیفیت غالب |
| `estimated_revenue` | number / null | درآمد تخمینی |
| `soil_type` | string / null | نوع خاک |
### توضیح `yield_prediction`
| فیلد | نوع | توضیح |
|---|---|---|
| `predicted_yield_tons` | number | عملکرد بر حسب تن |
| `predicted_yield_raw` | number | عملکرد خام |
| `unit` | string | واحد نمایشی |
| `source_unit` | string | واحد منبع |
| `simulation_engine` | string / null | موتور شبیه‌سازی |
| `simulation_model` | string / null | نام مدل |
| `scenario_id` | integer / null | شناسه سناریو |
| `simulation_warning` | string / null | هشدار شبیه‌سازی |
| `secondary_kpis_estimated` | boolean | آیا KPIهای ثانویه تخمینی‌اند |
| `descriptionSource` | string | منبع توضیح |
| `farm_context` | object | context مزرعه |
| `supporting_metrics` | object | متریک‌های پشتیبان |
| `explanation` | string | توضیح متنی |
### توضیح `harvest_prediction_card`
| فیلد | نوع | توضیح |
|---|---|---|
| `harvest_date` | string | تاریخ ISO برداشت |
| `harvest_date_formatted` | string | تاریخ قابل نمایش |
| `days_until` | integer | روز باقی‌مانده |
| `optimal_window_start` | string | شروع بازه مناسب |
| `optimal_window_end` | string | پایان بازه مناسب |
| `description` | string | توضیح متنی |
| `descriptionSource` | string | منبع توضیح |
| `field_conditions` | object | شرایط فعلی مزرعه |
| `readiness_metrics` | object | جزئیات readiness/GDD |
### توضیح `harvest_readiness_zones`
| فیلد | نوع | توضیح |
|---|---|---|
| `observationDate` | string / null | تاریخ مشاهده |
| `vegetationHealthClass` | string / null | کلاس سلامت پوشش گیاهی |
| `meanNdvi` | number / null | NDVI میانگین |
| `ndviTrend` | number / null | روند NDVI |
| `averageReadiness` | number / null | میانگین آمادگی |
| `zones` | array[object] | فهرست zoneها |
| `source` | string | منبع داده |
| `summary` | string | توضیح خلاصه |
### توضیح هر zone در `zones[]`
| فیلد | نوع | توضیح |
|---|---|---|
| `zoneId` | string | شناسه zone |
| `zoneLabel` | string | نام نمایشی zone |
| `gridPosition` | object / null | موقعیت grid |
| `meanNdvi` | number | NDVI میانگین zone |
| `readiness` | integer | درصد آمادگی |
| `daysUntil` | integer | روز باقی‌مانده |
| `status` | string | وضعیت zone |
### توضیح `yield_quality_bands`
| فیلد | نوع | توضیح |
|---|---|---|
| `source` | string | منبع محاسبه |
| `is_estimated` | boolean | آیا مقادیر تخمینی‌اند |
| `protein_content` | object | درصد پروتئین |
| `moisture_percentage` | object | درصد رطوبت |
| `grade_distribution` | array[object] | توزیع گریدها |
| `primary_quality_grade` | string | گرید غالب |
| `quality_score` | number | امتیاز کیفیت |
| `summary` | string | خلاصه متنی |
### توضیح `harvest_operations_card`
| فیلد | نوع | توضیح |
|---|---|---|
| `stage_label` | string | برچسب مرحله عملیاتی |
| `phase_name` | string | نام فاز رشد |
| `days_until_harvest` | integer | روز باقی‌مانده |
| `current_dvs` | number | DVS فعلی |
| `summary` | string | خلاصه عملیاتی |
| `rules_source` | string | منبع قواعد |
| `field_context` | object | context مزرعه |
| `steps` | array[object] | گام‌های عملیاتی |
### توضیح هر step در `steps[]`
| فیلد | نوع | توضیح |
|---|---|---|
| `key` | string | کلید فنی step |
| `title` | string | عنوان عملیات |
| `status` | string | وضعیت step |
| `is_completed` | boolean | آیا انجام شده |
| `estimated_days` | integer | روز برآوردی |
| `note` | string | توضیح تکمیلی |
### توضیح `yield_prediction_chart`
| فیلد | نوع | توضیح |
|---|---|---|
| `series` | array[object] | سری‌های نمودار |
| `xAxis` | object | تنظیمات محور افقی |
| `meta` | object | متادیتای نمودار |
### توضیح `yield_prediction_chart.series[]`
| فیلد | نوع | توضیح |
|---|---|---|
| `name` | string | نام سری |
| `type` | string | نوع رسم مانند `line` یا `area` |
| `data` | array[[timestamp, value]] | داده‌های نمودار؛ timestamp بر حسب milliseconds |
### توضیح `yield_prediction_chart.meta`
| فیلد | نوع | توضیح |
|---|---|---|
| `unit` | string | واحد داده |
| `simulation_engine` | string | موتور شبیه‌سازی |
| `simulation_model` | string | مدل |
| `scenario_id` | integer / null | شناسه سناریو |
| `simulation_warning` | string / null | هشدار شبیه‌سازی |
| `field_context` | object | context مزرعه |
---
## خطاهای رایج با مثال
### نبودن `farm_uuid`
```json
{
"code": 400,
"msg": "error",
"data": {
"farm_uuid": ["This field is required."]
}
}
```
### پیدا نشدن مزرعه برای کاربر جاری
```json
{
"code": 404,
"msg": "error",
"data": {
"farm_uuid": ["Farm not found."]
}
}
```
### خطای upstream AI
```json
{
"code": 500,
"msg": "error",
"data": {
"code": 500,
"msg": "خطا در پیش بینی عملکرد: Plant not found.",
"data": null
}
}
```
نکته: در این وضعیت، envelope بیرونی از backend آمده و object داخلی معمولاً همان پاسخ upstream AI است.
---
## پیش‌فرض Swagger
برای endpointهای body-based این ماژول، `farm_uuid` در Swagger با مقدار پیش‌فرض زیر نمایش داده می‌شود:
```text
11111111-1111-1111-1111-111111111111
```
این رفتار برای endpointهای زیر برقرار است:
- `POST /api/yield-harvest/current-farm-chart/`
- `POST /api/yield-harvest/growth/`
- `POST /api/yield-harvest/harvest-prediction/`
- `POST /api/yield-harvest/yield-prediction/`
---
## جمع‌بندی اجرایی برای فرانت
### چیزی که فرانت باید بفرستد
- برای بیشتر endpointها فقط `farm_uuid`
- برای status فقط `task_id`
- در جریان ساده، `plant_name` هرگز از کاربر گرفته نشود
### چیزی که backend خودش مدیریت می‌کند
- پیدا کردن مزرعه متعلق به کاربر
- استخراج `plant_name` از `farm.products` یا `farm.farm_type.products`
- ارسال payload مناسب به AI
- normalize کردن پاسخ AI در envelope استاندارد backend
### چیزی که فرانت نباید به کاربر بسپارد
- انتخاب دستی `plant_name` در این flow
- ساخت payload مستقیم AI
- تفسیر business ruleهای انتخاب محصول
---
## مسیر فایل
این سند در مسیر زیر نگهداری می‌شود:
`docs/yield_harvest_ai_integration.md`
@@ -0,0 +1,269 @@
# Yield/Harvest Prediction API Changes
این فایل تغییرات 3 API زیر را توضیح می‌دهد:
- `POST /api/yield-harvest/harvest-prediction/`
- `POST /api/yield-harvest/yield-prediction/`
- `POST /api/yield-harvest/current-farm-chart/`
---
## خلاصه تغییرات
تغییر اصلی در هر 3 endpoint این است که backend حالا context موردنیاز AI را خودش از روی مزرعه و planهای انتخابی می‌سازد.
### قبل
در استفاده قدیمی، معمولاً فرض می‌شد client باید context بیشتری برای AI بفرستد.
### الآن
- `farm_uuid` ورودی اصلی و الزامی است.
- `plant_name` اگر هم توسط client ارسال شود، مبنای نهایی backend نیست و از روی مزرعه بازنویسی/resolve می‌شود.
- در صورت نیاز، `irrigation_plan_uuid` و `fertilization_plan_uuid` هم می‌توانند ارسال شوند.
- اگر plan انتخابی معتبر و متعلق به همان مزرعه کاربر باشد، backend محتوای آن را به payload ارسالی به AI اضافه می‌کند.
- خروجی backend به‌صورت یکدست با فرمت `code / msg / data` برگردانده می‌شود.
---
## Request Contract جدید
هر 3 API از این قرارداد ورودی استفاده می‌کنند:
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"irrigation_plan_uuid": "6d6a1f0d-1a9b-4f2f-8fe1-2d73d9d2d9f1",
"fertilization_plan_uuid": "7e7b2f1e-2b0c-4a3a-9fe2-3e84e0e3e0a2"
}
```
### فیلدها
- `farm_uuid` اجباری
- `irrigation_plan_uuid` اختیاری
- `fertilization_plan_uuid` اختیاری
### نکته مهم
اگر client `plant_name` بفرستد، در این APIها مبنای نهایی backend نیست؛ backend نام گیاه را از مزرعه استخراج می‌کند.
---
## 1) POST `/api/yield-harvest/current-farm-chart/`
### تغییرات
- ورودی endpoint عملاً بر پایه `farm_uuid` کار می‌کند و `plant_name` از context مزرعه تعیین می‌شود.
- backend به‌صورت خودکار `plant_name` را از مزرعه پیدا می‌کند.
- در صورت ارسال `irrigation_plan_uuid`، اطلاعات برنامه آبیاری داخل payload ارسالی به AI قرار می‌گیرد.
- در صورت ارسال `fertilization_plan_uuid`، اطلاعات برنامه کودی هم اضافه می‌شود.
### نمونه request
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111"
}
```
### payload ارسالی backend به AI
نمونه مفهومی:
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی"
}
```
### در صورت انتخاب plan
نمونه مفهومی:
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"irrigation_plan": {
"id": 12,
"plan_payload": {
"plan": {
"durationMinutes": 20
}
}
}
}
```
### پاسخ موفق
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"scenario_id": 1,
"categories": ["day1"],
"series": {
"biomass": [1.2]
}
}
}
```
---
## 2) POST `/api/yield-harvest/harvest-prediction/`
### تغییرات
- ورودی endpoint عملاً بر پایه `farm_uuid` کار می‌کند و `plant_name` توسط backend تعیین می‌شود.
- امکان ارسال `fertilization_plan_uuid` و `irrigation_plan_uuid` برای enrich کردن context اضافه/پشتیبانی شده است.
- پاسخ AI بعد از extract شدن در `data.result`، به شکل مستقیم در `data` برگردانده می‌شود.
### نمونه request
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"fertilization_plan_uuid": "7e7b2f1e-2b0c-4a3a-9fe2-3e84e0e3e0a2"
}
```
### payload ارسالی backend به AI
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"fertilization_plan": {
"id": 34,
"plan_payload": {
"primary_recommendation": {
"fertilizer_code": "npk-151515"
}
}
}
}
```
### پاسخ موفق
```json
{
"code": 200,
"msg": "success",
"data": {
"date": "2026-07-15",
"dateFormatted": "15 Jul 2026",
"daysUntil": 96,
"gddDetails": {
"current": 800
}
}
}
```
---
## 3) POST `/api/yield-harvest/yield-prediction/`
### تغییرات
- مثل دو endpoint دیگر، `plant_name` از روی مزرعه resolve می‌شود.
- در نبود محصول مستقیم روی مزرعه، backend از fallback مناسب مزرعه استفاده می‌کند.
- امکان ارسال `irrigation_plan_uuid` و `fertilization_plan_uuid` برای فرستادن context planها به AI اضافه/پشتیبانی شده است.
- پاسخ نهایی با ساختار یکنواخت `code / msg / data` برگردانده می‌شود.
### نمونه request
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"irrigation_plan_uuid": "6d6a1f0d-1a9b-4f2f-8fe1-2d73d9d2d9f1",
"fertilization_plan_uuid": "7e7b2f1e-2b0c-4a3a-9fe2-3e84e0e3e0a2"
}
```
### payload ارسالی backend به AI
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"irrigation_plan": {
"id": 12,
"plan_payload": {
"plan": {
"durationMinutes": 30
}
}
},
"fertilization_plan": {
"id": 34,
"plan_payload": {
"primary_recommendation": {
"fertilizer_code": "npk-202020"
}
}
}
}
```
### پاسخ موفق
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"predictedYieldTons": 8.4,
"scenarioId": 1
}
}
```
---
## خطاها و Validation
### 1) مزرعه نامعتبر یا متعلق به کاربر دیگر
در این حالت endpoint خطای دسترسی/یافت‌نشدن مزرعه برمی‌گرداند.
### 2) plan نامعتبر یا متعلق به مزرعه دیگر
اگر `irrigation_plan_uuid` یا `fertilization_plan_uuid` متعلق به همان مزرعه کاربر نباشد، درخواست با خطا رد می‌شود.
نمونه:
```json
{
"code": 404,
"msg": "error",
"data": {
"irrigation_plan_uuid": [
"Irrigation plan not found."
]
}
}
```
### 3) خطای validation ورودی
اگر `farm_uuid` ارسال نشود یا `plan_uuid`ها نامعتبر باشند، serializer خطای validation برمی‌گرداند.
---
## جمع‌بندی تغییرات برای فرانت
- دیگر لازم نیست `plant_name` را برای این 3 API بفرستید.
- فقط `farm_uuid` اجباری است.
- اگر کاربر plan خاصی را انتخاب کرده، `irrigation_plan_uuid` و/یا `fertilization_plan_uuid` را هم بفرستید.
- response هر 3 endpoint با ساختار یکنواخت `code`, `msg`, `data` برمی‌گردد.
- backend خودش payload مناسب AI را از context مزرعه و planهای انتخابی می‌سازد.