UPDATE
This commit is contained in:
@@ -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های انتخابی میسازد.
|
||||
Reference in New Issue
Block a user