UPDATE
This commit is contained in:
@@ -1006,37 +1006,6 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 9.3) خلاصه ریسک بیماری و آفات
|
|
||||||
|
|
||||||
- مسیر: `POST /api/pest-disease/risk-summary/`
|
|
||||||
|
|
||||||
#### ورودی
|
|
||||||
|
|
||||||
- `farm_uuid` — `string` — الزامی
|
|
||||||
- `sensor_uuid` — `string` — اختیاری
|
|
||||||
|
|
||||||
#### خروجی موفق
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "success",
|
|
||||||
"data": {
|
|
||||||
"farm_uuid": "string",
|
|
||||||
"diseaseRisk": {},
|
|
||||||
"pestRisk": {},
|
|
||||||
"drivers": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### خطاها
|
|
||||||
|
|
||||||
- `400`: نبودن `farm_uuid`
|
|
||||||
- `404`: مزرعه پیدا نشد
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10) Irrigation
|
## 10) Irrigation
|
||||||
|
|
||||||
### 10.1) لیست روشهای آبیاری
|
### 10.1) لیست روشهای آبیاری
|
||||||
@@ -1548,7 +1517,6 @@
|
|||||||
- `POST /api/plants/fetch-info/`
|
- `POST /api/plants/fetch-info/`
|
||||||
- `POST /api/pest-disease/detect/`
|
- `POST /api/pest-disease/detect/`
|
||||||
- `POST /api/pest-disease/risk/`
|
- `POST /api/pest-disease/risk/`
|
||||||
- `POST /api/pest-disease/risk-summary/`
|
|
||||||
- `GET /api/irrigation/`
|
- `GET /api/irrigation/`
|
||||||
- `POST /api/irrigation/`
|
- `POST /api/irrigation/`
|
||||||
- `GET /api/irrigation/<pk>/`
|
- `GET /api/irrigation/<pk>/`
|
||||||
|
|||||||
@@ -359,19 +359,6 @@
|
|||||||
- `متوسط` برای آگاهی از ریسک کلی.
|
- `متوسط` برای آگاهی از ریسک کلی.
|
||||||
- `کم` برای عملیات خودکار یا قطعی.
|
- `کم` برای عملیات خودکار یا قطعی.
|
||||||
|
|
||||||
### `POST /api/pest-disease/risk-summary/`
|
|
||||||
|
|
||||||
- منبع داده: خلاصه سبکتر از همان سرویس RAG.
|
|
||||||
- نقاط قوت فعلی:
|
|
||||||
- دیگر صرفا heuristic ثابت قبلی نیست و از RAG تغذیه میشود.
|
|
||||||
- ضعفها:
|
|
||||||
- چون summary است، جزئیات uncertainty کمتر دیده میشود.
|
|
||||||
- اگر RAG fallback یا retrieval ضعیف داشته باشد، summary هم ضعیف میشود.
|
|
||||||
- سطح اعتماد:
|
|
||||||
- `متوسط رو به کم`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10) Crop Simulation API
|
## 10) Crop Simulation API
|
||||||
|
|
||||||
### `POST /api/crop-simulation/growth/`
|
### `POST /api/crop-simulation/growth/`
|
||||||
@@ -521,7 +508,6 @@
|
|||||||
- `POST /api/irrigation/water-stress/` در حالت fallback
|
- `POST /api/irrigation/water-stress/` در حالت fallback
|
||||||
- `POST /api/pest-disease/detect/` برای diagnosis قطعی
|
- `POST /api/pest-disease/detect/` برای diagnosis قطعی
|
||||||
- `POST /api/pest-disease/risk/`
|
- `POST /api/pest-disease/risk/`
|
||||||
- `POST /api/pest-disease/risk-summary/`
|
|
||||||
- `POST /api/rag/chat/` برای پاسخهای حساس و اجرایی
|
- `POST /api/rag/chat/` برای پاسخهای حساس و اجرایی
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+4
-11
@@ -110,7 +110,6 @@ Base: `/api/pest-disease/`
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| POST | `/api/pest-disease/detect/` | تشخیص آفت/بیماری از تصویر |
|
| POST | `/api/pest-disease/detect/` | تشخیص آفت/بیماری از تصویر |
|
||||||
| POST | `/api/pest-disease/risk/` | پیشبینی ریسک آفات و بیماری |
|
| POST | `/api/pest-disease/risk/` | پیشبینی ریسک آفات و بیماری |
|
||||||
| POST | `/api/pest-disease/risk-summary/` | خلاصه KPI ریسک آفات و بیماری |
|
|
||||||
|
|
||||||
### App: Irrigation
|
### App: Irrigation
|
||||||
|
|
||||||
@@ -249,16 +248,11 @@ Base: `/api/crop-simulation/`
|
|||||||
- history واقعی، drift سنسور، عدم قطعیت، zoning مزرعه یا depth-specific map در آن لحاظ نشدهاند.
|
- history واقعی، drift سنسور، عدم قطعیت، zoning مزرعه یا depth-specific map در آن لحاظ نشدهاند.
|
||||||
- اثر عملی: heatmap برای visualization خوب است ولی برای تصمیم agronomy دقیق کافی نیست.
|
- اثر عملی: heatmap برای visualization خوب است ولی برای تصمیم agronomy دقیق کافی نیست.
|
||||||
|
|
||||||
### 8) Pest/Disease Risk Summary یک heuristic ساده است
|
### 8) توصیههای RAG در لایه نهایی deterministic merge میشوند
|
||||||
- امتیاز بیماری و آفت از ترکیب چند فرمول ثابت ساخته میشود: `pest_disease/services.py:39`
|
|
||||||
- وزنها hard-coded هستند و مدل crop-specific یا region-specific ندارند.
|
|
||||||
- اثر عملی: `POST /api/pest-disease/risk-summary/` بیشتر KPI تقریبی است تا مدل پیشبینی دقیق.
|
|
||||||
|
|
||||||
### 9) توصیههای RAG در لایه نهایی deterministic merge میشوند
|
|
||||||
- برای irrigation/fertilization، fallback همیشه ساختار نهایی را پر میکند: `rag/services/irrigation.py:153`, `rag/services/fertilization.py:135`
|
- برای irrigation/fertilization، fallback همیشه ساختار نهایی را پر میکند: `rag/services/irrigation.py:153`, `rag/services/fertilization.py:135`
|
||||||
- اثر عملی: خروجی از نظر UI پایدار است، اما تشخیص اینکه کدام بخش واقعا از LLM آمده سخت میشود.
|
- اثر عملی: خروجی از نظر UI پایدار است، اما تشخیص اینکه کدام بخش واقعا از LLM آمده سخت میشود.
|
||||||
|
|
||||||
### 10) Crop Simulation ممکن است از engine اصلی به projection تقریبی سقوط کند
|
### 9) Crop Simulation ممکن است از engine اصلی به projection تقریبی سقوط کند
|
||||||
- fallback projection در خطا فعال میشود: `crop_simulation/growth_simulation.py:404`
|
- fallback projection در خطا فعال میشود: `crop_simulation/growth_simulation.py:404`
|
||||||
- اگر consumer فقط status 200 ببیند و `simulation_warning` را ignore کند، ممکن است خروجی تقریبی را واقعی فرض کند.
|
- اگر consumer فقط status 200 ببیند و `simulation_warning` را ignore کند، ممکن است خروجی تقریبی را واقعی فرض کند.
|
||||||
|
|
||||||
@@ -274,7 +268,7 @@ Base: `/api/crop-simulation/`
|
|||||||
| `farm_data` | متوسط | هسته aggregation خوب است، ولی center/merge چند سنسور سادهسازی شده |
|
| `farm_data` | متوسط | هسته aggregation خوب است، ولی center/merge چند سنسور سادهسازی شده |
|
||||||
| `location_data` | متوسط | SoilGrids واقعی است، NDVI وابسته به تنظیمات بیرونی است |
|
| `location_data` | متوسط | SoilGrids واقعی است، NDVI وابسته به تنظیمات بیرونی است |
|
||||||
| `soile` | متوسط | داده واقعی دارد، اما مدل تحلیلی و interpolation ساده است |
|
| `soile` | متوسط | داده واقعی دارد، اما مدل تحلیلی و interpolation ساده است |
|
||||||
| `pest_disease` | متوسط | fallback زیاد و بخش risk-summary heuristic است |
|
| `pest_disease` | متوسط | fallback زیاد در endpointهای تشخیص و ریسک دارد |
|
||||||
| `farm_alerts` | متوسط | خروجی قابل استفاده است، ولی در failure به fallback داخلی میرود |
|
| `farm_alerts` | متوسط | خروجی قابل استفاده است، ولی در failure به fallback داخلی میرود |
|
||||||
| `irrigation` | متوسط رو به ضعیف | recommendation خوب، اما water-stress ساده و CRUD detail معیوب |
|
| `irrigation` | متوسط رو به ضعیف | recommendation خوب، اما water-stress ساده و CRUD detail معیوب |
|
||||||
| `fertilization` | متوسط | recommendation موجود است ولی heavily fallback-assisted |
|
| `fertilization` | متوسط | recommendation موجود است ولی heavily fallback-assisted |
|
||||||
@@ -315,8 +309,7 @@ Base: `/api/crop-simulation/`
|
|||||||
### اولویت متوسط
|
### اولویت متوسط
|
||||||
1. بهبود مدل water stress
|
1. بهبود مدل water stress
|
||||||
2. بهبود IDW/heatmap و استفاده از time-series
|
2. بهبود IDW/heatmap و استفاده از time-series
|
||||||
3. بهبود risk-summary آفت/بیماری با crop profile و weather window دقیقتر
|
3. اضافهکردن flag صریح برای crop-simulation fallback
|
||||||
4. اضافهکردن flag صریح برای crop-simulation fallback
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,3 @@
|
|||||||
- در اين حالت، يک notification با `level` برابر `info` توليد کن که صريح بگويد فعلا مورد مهم جديدي شناسايي نشده است.
|
- در اين حالت، يک notification با `level` برابر `info` توليد کن که صريح بگويد فعلا مورد مهم جديدي شناسايي نشده است.
|
||||||
- براي اين notification حداقلي، `title` کوتاه و خنثي باشد، `message` شفاف بگويد هشدار مهم جديدي وجود ندارد، و `suggested_action` فقط يک اقدام پايشي سبک و مشخص باشد.
|
- براي اين notification حداقلي، `title` کوتاه و خنثي باشد، `message` شفاف بگويد هشدار مهم جديدي وجود ندارد، و `suggested_action` فقط يک اقدام پايشي سبک و مشخص باشد.
|
||||||
- اين notification حداقلي فقط وقتي استفاده شود که خروجي در غير اين صورت خالي مي شد.
|
- اين notification حداقلي فقط وقتي استفاده شود که خروجي در غير اين صورت خالي مي شد.
|
||||||
- [TEMP_FORCE_MIN_NOTIFICATION_END]
|
|
||||||
- `headline` و `overview` هميشه الزامي هستند.
|
|
||||||
- عنوان ها کوتاه و عملياتي باشند.
|
|
||||||
- `suggested_action` بايد يک اقدام مشخص مزرعه اي باشد، نه توصيه کلي.
|
|
||||||
|
|||||||
@@ -0,0 +1,512 @@
|
|||||||
|
# توضیح `location_data/apps.py` و `farm_data/apps.py`
|
||||||
|
|
||||||
|
این فایل یک توضیح کوتاه ولی کاربردی از دو فایل تنظیمات اپ Django در پروژه میدهد:
|
||||||
|
|
||||||
|
- `location_data/apps.py`
|
||||||
|
- `farm_data/apps.py`
|
||||||
|
|
||||||
|
همچنین برای فهم بهتر، به فیلدهای مهم مدلهای مرتبط هم اشاره میکند تا معلوم شود این دو app در عمل چه دادههایی را مدیریت میکنند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1) فایل `location_data/apps.py`
|
||||||
|
|
||||||
|
این فایل AppConfig مربوط به اپ `location_data` را تعریف میکند.
|
||||||
|
|
||||||
|
کلاس اصلی:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SoilDataConfig(AppConfig):
|
||||||
|
```
|
||||||
|
|
||||||
|
### فیلدها و بخشها
|
||||||
|
|
||||||
|
#### `default_auto_field = "django.db.models.BigAutoField"`
|
||||||
|
|
||||||
|
- مشخص میکند اگر در مدلهای این اپ برای primary key چیزی تعریف نشده باشد، Django بهصورت پیشفرض از `BigAutoField` استفاده کند.
|
||||||
|
- `BigAutoField` یک شناسه عددی auto-increment بزرگ است.
|
||||||
|
- این گزینه بیشتر برای مدلهایی مفید است که قرار است رکوردهای زیادی داشته باشند.
|
||||||
|
|
||||||
|
#### `name = "location_data"`
|
||||||
|
|
||||||
|
- نام کامل اپ Django است.
|
||||||
|
- Django با این مقدار اپ را register میکند.
|
||||||
|
- این مقدار باید با مسیر ماژول اپ یکی باشد.
|
||||||
|
|
||||||
|
#### `verbose_name = "Soil Data (SoilGrids)"`
|
||||||
|
|
||||||
|
- نام نمایشی اپ در Django admin یا جاهایی است که Django نام انسانی اپ را نشان میدهد.
|
||||||
|
- این مقدار بیشتر جنبه نمایشی دارد و روی منطق برنامه اثر مستقیم ندارد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## propertyها و سرویسها در `location_data/apps.py`
|
||||||
|
|
||||||
|
این فایل فقط metadata اپ را نگه نمیدارد؛ دو سرویس reusable هم از طریق AppConfig در اختیار بقیه پروژه میگذارد.
|
||||||
|
|
||||||
|
### `@cached_property def ndvi_health_service(self)`
|
||||||
|
|
||||||
|
- این property یک نمونه از `NdviHealthService` میسازد.
|
||||||
|
- import آن از فایل `.ndvi` انجام میشود.
|
||||||
|
- به دلیل `cached_property` فقط یک بار ساخته میشود و بعد همان instance دوباره استفاده میشود.
|
||||||
|
|
||||||
|
کاربرد:
|
||||||
|
|
||||||
|
- برای تحلیل یا سرویسهای مرتبط با NDVI
|
||||||
|
- جلوگیری از ساخت مکرر object
|
||||||
|
|
||||||
|
### `@cached_property def soil_data_adapter(self)`
|
||||||
|
|
||||||
|
این property adapter مناسب برای داده خاک را بر اساس تنظیمات پروژه انتخاب میکند.
|
||||||
|
|
||||||
|
دو adapter پشتیبانی میشوند:
|
||||||
|
|
||||||
|
- `SoilGridsAdapter`
|
||||||
|
- `MockSoilDataAdapter`
|
||||||
|
|
||||||
|
#### منطق انتخاب provider
|
||||||
|
|
||||||
|
مقدار provider از این setting خوانده میشود:
|
||||||
|
|
||||||
|
```python
|
||||||
|
settings.SOIL_DATA_PROVIDER
|
||||||
|
```
|
||||||
|
|
||||||
|
اگر وجود نداشته باشد، مقدار پیشفرض:
|
||||||
|
|
||||||
|
```python
|
||||||
|
"mock"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### حالت اول: `provider == "soilgrids"`
|
||||||
|
|
||||||
|
در این حالت:
|
||||||
|
|
||||||
|
- از `SoilGridsAdapter` استفاده میشود
|
||||||
|
- timeout آن از این setting میآید:
|
||||||
|
|
||||||
|
```python
|
||||||
|
settings.SOILGRIDS_TIMEOUT_SECONDS
|
||||||
|
```
|
||||||
|
|
||||||
|
اگر این setting هم نباشد، مقدار پیشفرض:
|
||||||
|
|
||||||
|
```python
|
||||||
|
60
|
||||||
|
```
|
||||||
|
|
||||||
|
یعنی درخواست به provider واقعی SoilGrids حداکثر 60 ثانیه صبر میکند.
|
||||||
|
|
||||||
|
#### حالت دوم: `provider == "mock"`
|
||||||
|
|
||||||
|
در این حالت:
|
||||||
|
|
||||||
|
- از `MockSoilDataAdapter` استفاده میشود
|
||||||
|
- delay آن از این setting میآید:
|
||||||
|
|
||||||
|
```python
|
||||||
|
settings.SOIL_MOCK_DELAY_SECONDS
|
||||||
|
```
|
||||||
|
|
||||||
|
اگر این setting هم نباشد، مقدار پیشفرض:
|
||||||
|
|
||||||
|
```python
|
||||||
|
0.8
|
||||||
|
```
|
||||||
|
|
||||||
|
یعنی adapter تستی/نمایشی با تاخیر مصنوعی 0.8 ثانیه کار میکند.
|
||||||
|
|
||||||
|
#### حالت نامعتبر
|
||||||
|
|
||||||
|
اگر `SOIL_DATA_PROVIDER` چیزی غیر از `soilgrids` یا `mock` باشد:
|
||||||
|
|
||||||
|
- `ValueError` رخ میدهد
|
||||||
|
- یعنی config پروژه اشتباه است و provider شناخته نشده
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ارتباط `location_data/apps.py` با فیلدهای واقعی داده
|
||||||
|
|
||||||
|
این فایل خودش مدل تعریف نمیکند، اما بهصورت مستقیم برای کار با مدلهای اپ `location_data` استفاده میشود؛ مهمترین آنها اینها هستند:
|
||||||
|
|
||||||
|
- `location_data.models.SoilLocation`
|
||||||
|
- `location_data.models.SoilDepthData`
|
||||||
|
- `location_data.models.NdviObservation`
|
||||||
|
|
||||||
|
### فیلدهای مهم `SoilLocation`
|
||||||
|
|
||||||
|
#### `latitude`
|
||||||
|
|
||||||
|
- عرض جغرافیایی مرکز زمین
|
||||||
|
- نوع آن `DecimalField` است
|
||||||
|
- روی آن index وجود دارد
|
||||||
|
- این نقطه معمولاً مرکز هندسی مزرعه است، نه لزوماً یکی از گوشههای مرز
|
||||||
|
|
||||||
|
#### `longitude`
|
||||||
|
|
||||||
|
- طول جغرافیایی مرکز زمین
|
||||||
|
- مثل `latitude` برای lookup و resolve کردن دادههای خاک استفاده میشود
|
||||||
|
|
||||||
|
#### `task_id`
|
||||||
|
|
||||||
|
- شناسه تسک Celery برای پردازشهای async
|
||||||
|
- وقتی fetch داده خاک یا پردازش مرتبط در صف باشد، میتوان با این فیلد وضعیت را track کرد
|
||||||
|
|
||||||
|
#### `farm_boundary`
|
||||||
|
|
||||||
|
- مرز مزرعه را بهصورت JSON نگه میدارد
|
||||||
|
- معمولاً بهشکل `Polygon` یا ساختار corner-based ذخیره میشود
|
||||||
|
- این فیلد خیلی مهم است چون فقط یک نقطه center نگه نمیدارید، بلکه شکل کلی زمین هم ثبت میشود
|
||||||
|
|
||||||
|
#### `created_at` / `updated_at`
|
||||||
|
|
||||||
|
- زمان ایجاد و آخرین بهروزرسانی رکورد
|
||||||
|
|
||||||
|
### propertyهای مهم `SoilLocation`
|
||||||
|
|
||||||
|
#### `center_latitude`
|
||||||
|
|
||||||
|
- فقط alias برای `latitude` است
|
||||||
|
|
||||||
|
#### `center_longitude`
|
||||||
|
|
||||||
|
- فقط alias برای `longitude` است
|
||||||
|
|
||||||
|
#### `is_complete`
|
||||||
|
|
||||||
|
- بررسی میکند آیا هر سه لایه خاک برای این location ثبت شدهاند یا نه
|
||||||
|
- شرط آن این است که تعداد `depths` دقیقاً 3 باشد
|
||||||
|
|
||||||
|
### فیلدهای مهم `SoilDepthData`
|
||||||
|
|
||||||
|
این مدل برای هر location سه رکورد عمق خاک نگه میدارد:
|
||||||
|
|
||||||
|
- `0-5cm`
|
||||||
|
- `5-15cm`
|
||||||
|
- `15-30cm`
|
||||||
|
|
||||||
|
فیلدهای اصلی:
|
||||||
|
|
||||||
|
- `soil_location`: ارتباط به `SoilLocation`
|
||||||
|
- `depth_label`: مشخص میکند داده برای کدام عمق است
|
||||||
|
- `bdod`: چگالی ظاهری خاک
|
||||||
|
- `cec`: ظرفیت تبادل کاتیونی
|
||||||
|
- `cfvo`: حجم قطعات درشت خاک
|
||||||
|
- `clay`: درصد رس
|
||||||
|
- `nitrogen`: مقدار نیتروژن
|
||||||
|
- `ocd` و `ocs`: شاخصهای کربن آلی
|
||||||
|
- `phh2o`: pH خاک
|
||||||
|
- `sand`: درصد شن
|
||||||
|
- `silt`: درصد سیلت
|
||||||
|
- `soc`: کربن آلی خاک
|
||||||
|
- `wv0010`: رطوبت حجمی در فشار 10 kPa
|
||||||
|
- `wv0033`: رطوبت در حدود ظرفیت زراعی
|
||||||
|
- `wv1500`: رطوبت در نقطه پژمردگی دائم
|
||||||
|
|
||||||
|
این فیلدها برای شبیهسازی، آبیاری، و تخمین وضعیت واقعی خاک مهم هستند.
|
||||||
|
|
||||||
|
### فیلدهای مهم `NdviObservation`
|
||||||
|
|
||||||
|
- `location`: ارتباط با `SoilLocation`
|
||||||
|
- `observation_date`: تاریخ مشاهده
|
||||||
|
- `mean_ndvi`: میانگین NDVI
|
||||||
|
- `ndvi_map`: داده مکانی NDVI
|
||||||
|
- `vegetation_health_class`: کلاس سلامت پوشش گیاهی
|
||||||
|
- `satellite_source`: منبع تصویر مثل `sentinel-2`
|
||||||
|
- `cloud_cover`: درصد پوشش ابر
|
||||||
|
- `metadata`: داده تکمیلی
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## نکته مهم: grid بندی زمین انجام میشود
|
||||||
|
|
||||||
|
بله، در لایه داده و سنجش از دور، مفهوم grid بندی وجود دارد.
|
||||||
|
|
||||||
|
اما این grid بندی در پروژه بیشتر در این دو سطح دیده میشود:
|
||||||
|
|
||||||
|
### 1) grid در NDVI map
|
||||||
|
|
||||||
|
در `location_data/remote_sensing.py` داده NDVI بهصورت grid محاسبه و ذخیره میشود.
|
||||||
|
|
||||||
|
یعنی:
|
||||||
|
|
||||||
|
- تصویر ماهوارهای به خانههای کوچکتر تقسیم میشود
|
||||||
|
- برای هر خانه مقدار NDVI محاسبه میشود
|
||||||
|
- خروجی در `ndvi_map` معمولاً بهشکل grid نگهداری میشود
|
||||||
|
|
||||||
|
این یعنی وضعیت سلامت گیاه فقط بهصورت یک عدد کلی نیست، بلکه میتواند روی بخشهای مختلف زمین map شود.
|
||||||
|
|
||||||
|
### 2) grid/cell در adapter خاک
|
||||||
|
|
||||||
|
در `location_data/soil_adapters.py` هم منطق cell/grid دیده میشود، مخصوصاً در adapterهای mock یا interpolation-based.
|
||||||
|
|
||||||
|
یعنی:
|
||||||
|
|
||||||
|
- مختصات lat/lon به cellهای شبکهای نگاشت میشود
|
||||||
|
- در بعضی محاسبات از `grid_x` و `grid_y` استفاده میشود
|
||||||
|
- این کمک میکند داده خاک برای ناحیههای نزدیک، رفتار مکانی منطقیتری داشته باشد
|
||||||
|
|
||||||
|
### نتیجه مهم
|
||||||
|
|
||||||
|
خود مدل `SoilLocation` یک مرکز زمین را نگه میدارد، ولی مرز مزرعه و NDVI grid باعث میشوند سیستم فقط point-based نباشد.
|
||||||
|
|
||||||
|
یعنی:
|
||||||
|
|
||||||
|
- مرکز زمین برای lookup سریع و اتصال به داده خاک/هوا استفاده میشود
|
||||||
|
- مرز مزرعه برای شکل واقعی زمین ذخیره میشود
|
||||||
|
- grid بندی برای تحلیل مکانی، مخصوصاً در NDVI، انجام میشود
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مرکز زمین چطور از مرز مزرعه بهدست میآید
|
||||||
|
|
||||||
|
در `farm_data/services.py` از روی `farm_boundary`، مرکز هندسی polygon محاسبه میشود.
|
||||||
|
|
||||||
|
پس flow کلی اینطور است:
|
||||||
|
|
||||||
|
1. مرز مزرعه ارسال میشود
|
||||||
|
2. polygon نرمال میشود
|
||||||
|
3. centroid هندسی آن محاسبه میشود
|
||||||
|
4. یک `SoilLocation` برای center ساخته یا پیدا میشود
|
||||||
|
5. بعد داده خاک، NDVI و هوا به این location متصل میشوند
|
||||||
|
|
||||||
|
پس زمین فقط با یک نقطه خام ثبت نمیشود؛ اول مرز دارد، بعد center از روی آن بهدست میآید.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## متدهای کمکی `location_data/apps.py`
|
||||||
|
|
||||||
|
### `get_ndvi_health_service()`
|
||||||
|
|
||||||
|
- خروجی `self.ndvi_health_service` را برمیگرداند
|
||||||
|
- یک accessor ساده برای گرفتن سرویس NDVI است
|
||||||
|
|
||||||
|
### `get_soil_data_adapter()`
|
||||||
|
|
||||||
|
- خروجی `self.soil_data_adapter` را برمیگرداند
|
||||||
|
- بقیه بخشهای پروژه از این متد برای گرفتن adapter فعال استفاده میکنند
|
||||||
|
/
|
||||||
|
---
|
||||||
|
|
||||||
|
## فیلدها و settingهای مهم مرتبط با `location_data/apps.py`
|
||||||
|
|
||||||
|
### فیلدهای AppConfig
|
||||||
|
|
||||||
|
- `default_auto_field`: نوع primary key پیشفرض مدلها
|
||||||
|
- `name`: نام داخلی اپ
|
||||||
|
- `verbose_name`: نام نمایشی اپ
|
||||||
|
|
||||||
|
### settingهای استفادهشده
|
||||||
|
|
||||||
|
- `SOIL_DATA_PROVIDER`: انتخاب provider فعال خاک
|
||||||
|
- `SOILGRIDS_TIMEOUT_SECONDS`: timeout برای provider واقعی SoilGrids
|
||||||
|
- `SOIL_MOCK_DELAY_SECONDS`: تاخیر مصنوعی برای provider mock
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2) فایل `farm_data/apps.py`
|
||||||
|
|
||||||
|
این فایل AppConfig مربوط به اپ `farm_data` را تعریف میکند.
|
||||||
|
|
||||||
|
کلاس اصلی:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class FarmDataConfig(AppConfig):
|
||||||
|
```
|
||||||
|
|
||||||
|
### فیلدها
|
||||||
|
|
||||||
|
#### `default_auto_field = "django.db.models.BigAutoField"`
|
||||||
|
|
||||||
|
- مثل اپ قبلی، تعیین میکند primary key پیشفرض مدلهای این اپ از نوع `BigAutoField` باشد.
|
||||||
|
|
||||||
|
#### `name = "farm_data"`
|
||||||
|
|
||||||
|
- نام داخلی و ماژول اپ Django است.
|
||||||
|
- برای شناسایی اپ در `INSTALLED_APPS` و registry داخلی Django استفاده میشود.
|
||||||
|
|
||||||
|
#### `label = "sensor_data"`
|
||||||
|
|
||||||
|
- label داخلی اپ در registry Django است.
|
||||||
|
- این فیلد زمانی مهم میشود که:
|
||||||
|
- بخواهید نام registry اپ با `name` فرق داشته باشد
|
||||||
|
- یا از تداخل نام اپها جلوگیری کنید
|
||||||
|
- در این پروژه، اپ `farm_data` با label داخلی `sensor_data` شناخته میشود.
|
||||||
|
|
||||||
|
نکته:
|
||||||
|
|
||||||
|
- `label` باید در کل پروژه یکتا باشد.
|
||||||
|
- این مقدار ممکن است در migrationها، relationها یا lookupهای app registry اثر داشته باشد.
|
||||||
|
|
||||||
|
#### `verbose_name = "farm-data"`
|
||||||
|
|
||||||
|
- نام نمایشی اپ است.
|
||||||
|
- بیشتر برای admin و نمایش انسانی استفاده میشود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## نکته مهم درباره `farm_data/apps.py`
|
||||||
|
|
||||||
|
برخلاف `location_data/apps.py`، این فایل:
|
||||||
|
|
||||||
|
- `cached_property` ندارد
|
||||||
|
- service locator ندارد
|
||||||
|
- adapter یا provider انتخاب نمیکند
|
||||||
|
|
||||||
|
یعنی فعلاً فقط نقش config پایه اپ را دارد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ارتباط `farm_data/apps.py` با فیلدهای واقعی داده
|
||||||
|
|
||||||
|
این app بیشتر دادههای farm-level و sensor-level را نگه میدارد. مهمترین مدلهایش:
|
||||||
|
|
||||||
|
- `farm_data.models.SensorData`
|
||||||
|
- `farm_data.models.SensorParameter`
|
||||||
|
- `farm_data.models.ParameterUpdateLog`
|
||||||
|
|
||||||
|
### فیلدهای مهم `SensorData`
|
||||||
|
|
||||||
|
#### `farm_uuid`
|
||||||
|
|
||||||
|
- شناسه یکتای مزرعه
|
||||||
|
- primary key این مدل است
|
||||||
|
- هر رکورد `SensorData` نماینده یک مزرعه است
|
||||||
|
|
||||||
|
#### `center_location`
|
||||||
|
|
||||||
|
- ارتباط به `location_data.SoilLocation`
|
||||||
|
- یعنی این مزرعه به یک location مرکزی وصل است
|
||||||
|
- از همین نقطه مرکزی برای weather/soil/simulation استفاده میشود
|
||||||
|
|
||||||
|
#### `weather_forecast`
|
||||||
|
|
||||||
|
- ارتباط اختیاری با `weather.WeatherForecast`
|
||||||
|
- اگر موجود باشد، forecast منتخب یا آخرین forecast به مزرعه وصل میشود
|
||||||
|
|
||||||
|
#### `sensor_payload`
|
||||||
|
|
||||||
|
- مهمترین فیلد این مدل است
|
||||||
|
- داده سنسورها بهصورت JSON نگهداری میشود
|
||||||
|
- ساختار معمول آن شبیه این است:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sensor-7-1": {
|
||||||
|
"soil_moisture": 25.5,
|
||||||
|
"soil_temperature": 22.3,
|
||||||
|
"soil_ph": 7.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
مزیت این ساختار:
|
||||||
|
|
||||||
|
- چند سنسور در یک مزرعه پشتیبانی میشود
|
||||||
|
- هر سنسور میتواند فیلدهای خاص خودش را داشته باشد
|
||||||
|
- schema سنسورها rigid نیست
|
||||||
|
|
||||||
|
#### `plants`
|
||||||
|
|
||||||
|
- رابطه چندبهچند با `plant.Plant`
|
||||||
|
- یعنی یک farm میتواند چند گیاه مرتبط داشته باشد
|
||||||
|
|
||||||
|
#### `irrigation_method`
|
||||||
|
|
||||||
|
- روش آبیاری انتخابشده برای مزرعه
|
||||||
|
- برای recommendation و planning مهم است
|
||||||
|
|
||||||
|
#### `created_at` / `updated_at`
|
||||||
|
|
||||||
|
- زمان ایجاد و آخرین ویرایش رکورد
|
||||||
|
|
||||||
|
### propertyهای مهم `SensorPayloadMixin`
|
||||||
|
|
||||||
|
مدل `SensorData` از `SensorPayloadMixin` ارث میگیرد و این helperها را دارد:
|
||||||
|
|
||||||
|
#### `_payload()`
|
||||||
|
|
||||||
|
- payload را فقط وقتی dict معتبر باشد برمیگرداند
|
||||||
|
|
||||||
|
#### `get_sensor_block(sensor_key=None)`
|
||||||
|
|
||||||
|
- اگر `sensor_key` بدهید، همان بلوک سنسور را برمیگرداند
|
||||||
|
- اگر ندهید، اولین بلوک معتبر را برمیگرداند
|
||||||
|
|
||||||
|
#### `get_metric(metric_name, sensor_key=None)`
|
||||||
|
|
||||||
|
- یک metric خاص را از payload پیدا میکند
|
||||||
|
- اول در sensor مشخصشده میگردد
|
||||||
|
- اگر پیدا نشد، در بقیه blockها جستجو میکند
|
||||||
|
|
||||||
|
#### propertyهای آماده
|
||||||
|
|
||||||
|
این propertyها shortcut هستند:
|
||||||
|
|
||||||
|
- `soil_moisture`
|
||||||
|
- `soil_temperature`
|
||||||
|
- `soil_ph`
|
||||||
|
- `electrical_conductivity`
|
||||||
|
- `nitrogen`
|
||||||
|
- `phosphorus`
|
||||||
|
- `potassium`
|
||||||
|
|
||||||
|
یعنی بهجای parse دستی JSON، مستقیم میتوان این متریکها را خواند.
|
||||||
|
|
||||||
|
### فیلدهای مهم `SensorParameter`
|
||||||
|
|
||||||
|
این مدل dictionary پارامترهای سنسور را نگه میدارد.
|
||||||
|
|
||||||
|
#### `sensor_key`
|
||||||
|
|
||||||
|
- کلید سنسور مثل `sensor-7-1`
|
||||||
|
|
||||||
|
#### `code`
|
||||||
|
|
||||||
|
- کد پارامتر مثل `soil_moisture`
|
||||||
|
|
||||||
|
#### `name_fa`
|
||||||
|
|
||||||
|
- نام فارسی پارامتر
|
||||||
|
|
||||||
|
#### `unit`
|
||||||
|
|
||||||
|
- واحد پارامتر مثل `%` یا `dS/m`
|
||||||
|
|
||||||
|
#### `data_type`
|
||||||
|
|
||||||
|
- نوع داده مثل `float`, `int`, `string`, `bool`
|
||||||
|
|
||||||
|
#### `metadata`
|
||||||
|
|
||||||
|
- داده تکمیلی برای UI یا validation
|
||||||
|
- مثلاً:
|
||||||
|
- بازه مجاز
|
||||||
|
- توضیح
|
||||||
|
- تنظیمات نمایش
|
||||||
|
|
||||||
|
### فیلدهای مهم `ParameterUpdateLog`
|
||||||
|
|
||||||
|
- `parameter`: ارتباط به `SensorParameter`
|
||||||
|
- `action`: نوع عملیات مثل `added` یا `modified`
|
||||||
|
- `payload`: خلاصه تغییرات
|
||||||
|
- `updated_at`: زمان ثبت لاگ
|
||||||
|
|
||||||
|
این مدل برای audit و پیگیری تغییرات پارامترها مفید است.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## جمعبندی
|
||||||
|
|
||||||
|
### `location_data/apps.py`
|
||||||
|
|
||||||
|
- هم metadata اپ را نگه میدارد
|
||||||
|
- هم سرویس و adapter در اختیار پروژه میگذارد
|
||||||
|
- هم از settingها برای انتخاب provider واقعی یا mock استفاده میکند
|
||||||
|
- و در عمل با location center، مرز مزرعه، داده لایههای خاک و gridهای NDVI کار میکند
|
||||||
|
|
||||||
|
### `farm_data/apps.py`
|
||||||
|
|
||||||
|
- فقط config پایه AppConfig را تعریف میکند
|
||||||
|
- نقش آن بیشتر register کردن اپ با نام و label مشخص است
|
||||||
|
- اما دادههای اصلی مزرعه مثل `farm_uuid`، `sensor_payload`، گیاه، روش آبیاری و اتصال به center location در مدلهای همین app نگهداری میشوند
|
||||||
@@ -0,0 +1,746 @@
|
|||||||
|
# راهنمای پیادهسازی API صفحه Yield & Harvest با PCSE و RAG
|
||||||
|
|
||||||
|
این فایل برای تیم بکاند نوشته شده تا صفحه `yield-harvest` را با تکیه بر:
|
||||||
|
|
||||||
|
- مستند `psce_doc.txt` پروژه
|
||||||
|
- سرویسهای موجود در `crop_simulation/`
|
||||||
|
- معماری فعلی RAG در `rag/`
|
||||||
|
|
||||||
|
به شکل قابل اتکا پیادهسازی کند.
|
||||||
|
|
||||||
|
نکته: فایل `psce_doc.txt` در عمل مستند PCSE است و در این سند هم با عنوان PCSE به آن اشاره میشود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## جمعبندی سریع
|
||||||
|
|
||||||
|
برای این صفحه، بهترین معماری این است:
|
||||||
|
|
||||||
|
1. عددها، تاریخها، درصدها و سریهای نمودار از PCSE و دادههای مزرعه ساخته شوند.
|
||||||
|
2. RAG فقط برای متنهای توضیحی، خلاصهسازی، و wording کاربرپسند استفاده شود.
|
||||||
|
3. RAG اجازه نداشته باشد عدد جدید، تاریخ جدید، یا KPI جدید اختراع کند.
|
||||||
|
4. یک endpoint خلاصه برای فرانت برگردانده شود:
|
||||||
|
|
||||||
|
`GET /api/yield-harvest/summary/?farm_uuid=<uuid>`
|
||||||
|
|
||||||
|
اگر لازم شد از نظر convention داخلی پروژه همهچیز داخل `crop_simulation` بماند، میتوانید معادل زیر را هم نگه دارید:
|
||||||
|
|
||||||
|
`GET /api/crop-simulation/yield-harvest-summary/?farm_uuid=<uuid>`
|
||||||
|
|
||||||
|
برای فرانت فعلی، مدل summary بهتر از چند endpoint پراکنده است.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## چیزی که همین الان در پروژه دارید
|
||||||
|
|
||||||
|
### لایه PCSE / شبیهسازی
|
||||||
|
|
||||||
|
الان پروژه این اجزا را دارد:
|
||||||
|
|
||||||
|
- `crop_simulation/services.py`
|
||||||
|
- `crop_simulation/growth_simulation.py`
|
||||||
|
- `crop_simulation/harvest_prediction.py`
|
||||||
|
- `crop_simulation/yield_prediction.py`
|
||||||
|
|
||||||
|
و همین حالا از PCSE این خروجیها را میسازید:
|
||||||
|
|
||||||
|
- `yield` از `TWSO`
|
||||||
|
- `biomass` از `TAGP`
|
||||||
|
- `leaf_area_index` از `LAI`
|
||||||
|
- `development_stage` از `DVS`
|
||||||
|
- `soil_moisture` از `SM`
|
||||||
|
|
||||||
|
این دقیقاً با چیزی که در `psce_doc.txt` آمده همراستا است: PCSE برای state/rate variables و خروجی روزانه مناسب است و برای ساخت KPI، trend و harvest timing گزینه خوبی است.
|
||||||
|
|
||||||
|
### لایه RAG
|
||||||
|
|
||||||
|
الان پروژه الگوی خوبی برای سرویسهای ترکیبی deterministic + RAG دارد:
|
||||||
|
|
||||||
|
- `rag/services/irrigation.py`
|
||||||
|
- `rag/services/fertilization.py`
|
||||||
|
- `rag/chat.py`
|
||||||
|
- `config/rag_config.yaml`
|
||||||
|
|
||||||
|
الگوی درست در این پروژه این است:
|
||||||
|
|
||||||
|
- اول عددها و تصمیمهای پایه از منطق deterministic ساخته میشوند
|
||||||
|
- بعد RAG فقط متن نهایی را polish میکند
|
||||||
|
- در صورت خراب شدن خروجی LLM، fallback ساختاریافته دارید
|
||||||
|
|
||||||
|
برای `yield-harvest` هم باید دقیقاً همین الگو تکرار شود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## اصل طراحی مهم
|
||||||
|
|
||||||
|
### چه چیزی باید deterministic باشد
|
||||||
|
|
||||||
|
این فیلدها نباید از RAG بیایند:
|
||||||
|
|
||||||
|
- `daysUntil`
|
||||||
|
- `readiness`
|
||||||
|
- `share`
|
||||||
|
- `series[].data`
|
||||||
|
- `averageReadiness`
|
||||||
|
- `predicted yield`
|
||||||
|
- `harvest date`
|
||||||
|
- هر عددی که در progress bar، chart، KPI یا sorting استفاده میشود
|
||||||
|
|
||||||
|
### چه چیزی میتواند از RAG بیاید
|
||||||
|
|
||||||
|
این فیلدها مناسب RAG هستند:
|
||||||
|
|
||||||
|
- `season_highlights_card.subtitle`
|
||||||
|
- `season_highlights_card.spotlight.caption`
|
||||||
|
- `harvest_prediction_card.description`
|
||||||
|
- `harvest_operations_card.summary`
|
||||||
|
- `steps[].note`
|
||||||
|
- badgeها و labelهای توصیفی کوتاه
|
||||||
|
|
||||||
|
### قانون طلایی
|
||||||
|
|
||||||
|
RAG باید فقط روی context قطعی زیر کار کند:
|
||||||
|
|
||||||
|
- farm details
|
||||||
|
- خروجیهای PCSE
|
||||||
|
- دادههای آبوهوا
|
||||||
|
- دادههای سنسور
|
||||||
|
- در صورت وجود: داده کیفیت، آفات، اقتصاد، بازار
|
||||||
|
|
||||||
|
و prompt آن باید صریح بگوید:
|
||||||
|
|
||||||
|
- عدد جدید نساز
|
||||||
|
- فقط از مقادیر دادهشده استفاده کن
|
||||||
|
- اگر داده کافی نیست، کمبود را بگو
|
||||||
|
- خروجی را در JSON معتبر برگردان
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## پیشنهاد معماری برای این صفحه
|
||||||
|
|
||||||
|
بهترین راه این است که یک orchestrator service جدید بسازید:
|
||||||
|
|
||||||
|
- `crop_simulation/yield_harvest_summary.py`
|
||||||
|
|
||||||
|
و داخل آن چند builder کوچک داشته باشید:
|
||||||
|
|
||||||
|
- `_build_yield_prediction_block()`
|
||||||
|
- `_build_harvest_prediction_block()`
|
||||||
|
- `_build_yield_prediction_chart_block()`
|
||||||
|
- `_build_harvest_readiness_zones_block()`
|
||||||
|
- `_build_yield_quality_bands_block()`
|
||||||
|
- `_build_harvest_operations_block()`
|
||||||
|
- `_build_season_highlights_block()`
|
||||||
|
|
||||||
|
و یک facade نهایی:
|
||||||
|
|
||||||
|
- `YieldHarvestSummaryService.get_summary(farm_uuid, season_year=None, crop_name=None)`
|
||||||
|
|
||||||
|
این service باید:
|
||||||
|
|
||||||
|
1. داده مزرعه را از `farm_data.services.get_farm_details()` بگیرد.
|
||||||
|
2. خروجی شبیهسازی را از `CurrentFarmChartSimulator` و سرویسهای فعلی بگیرد.
|
||||||
|
3. بلوکهای deterministic را بسازد.
|
||||||
|
4. اگر RAG فعال بود، متنهای narrative را enrich کند.
|
||||||
|
5. یک payload نهایی برای فرانت برگرداند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mapping پیشنهادی هر کارت به منبع داده
|
||||||
|
|
||||||
|
| بلوک | منبع اصلی | وضعیت امکان پیادهسازی |
|
||||||
|
|---|---|---|
|
||||||
|
| `yield_prediction` | `crop_simulation/yield_prediction.py` + chart metrics | قابل پیادهسازی همین الآن |
|
||||||
|
| `harvest_prediction_card` | `crop_simulation/harvest_prediction.py` | قابل پیادهسازی همین الآن |
|
||||||
|
| `yield_prediction_chart` | `CurrentFarmChartSimulator` + historical baseline | نیمهآماده، نیاز به تصمیم درباره سری مقایسهای |
|
||||||
|
| `season_highlights_card` | aggregate از بقیه بلوکها + RAG | قابل پیادهسازی اگر بقیه بلوکها حاضر باشند |
|
||||||
|
| `harvest_readiness_zones` | داده قطعه/زون + PCSE per zone | فعلاً gap دادهای دارد |
|
||||||
|
| `yield_quality_bands` | quality model / grading rules / lab data | فعلاً gap دادهای دارد |
|
||||||
|
| `harvest_operations_card` | rules + optimizer + RAG | قابل پیادهسازی بهصورت اولیه |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## نکته مهم: همه بلوکها را PCSE بهتنهایی تولید نمیکند
|
||||||
|
|
||||||
|
PCSE برای این بخشها خیلی مناسب است:
|
||||||
|
|
||||||
|
- زمان برداشت
|
||||||
|
- روند رشد
|
||||||
|
- عملکرد پیشبینیشده
|
||||||
|
- stage / maturity / readiness proxy
|
||||||
|
- trend chart
|
||||||
|
|
||||||
|
اما PCSE بهصورت پیشفرض برای اینها کافی نیست:
|
||||||
|
|
||||||
|
- `بریکس`
|
||||||
|
- `گرید ممتاز / درجه یک / فرآوری`
|
||||||
|
- `پتانسیل صادرات`
|
||||||
|
- `قیمت فروش`
|
||||||
|
- `premium`
|
||||||
|
- readiness per block وقتی مدل block-level ندارید
|
||||||
|
|
||||||
|
پس برای صفحه کامل، باید سه لایه را از هم جدا ببینید:
|
||||||
|
|
||||||
|
1. `PCSE layer`: عددهای زیستی و رشد
|
||||||
|
2. `Rules/Aggregation layer`: تبدیل خروجی PCSE به KPIهای محصولی
|
||||||
|
3. `RAG layer`: توضیح، جمعبندی، و متن قابل نمایش
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## طراحی endpoint پیشنهادی
|
||||||
|
|
||||||
|
### Route
|
||||||
|
|
||||||
|
پیشنهاد اصلی:
|
||||||
|
|
||||||
|
- `GET /api/yield-harvest/summary/?farm_uuid=<uuid>&season_year=1404&crop_name=wheat`
|
||||||
|
|
||||||
|
### Query params
|
||||||
|
|
||||||
|
- `farm_uuid`: اجباری
|
||||||
|
- `season_year`: اختیاری
|
||||||
|
- `crop_name`: اختیاری
|
||||||
|
- `include_narrative`: اختیاری، پیشفرض `true`
|
||||||
|
|
||||||
|
### چرا `include_narrative` خوب است
|
||||||
|
|
||||||
|
چون اگر RAG کند یا موقتاً غیرفعال باشد، باز هم فرانت میتواند با داده deterministic رندر شود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ساختار پیشنهادی response
|
||||||
|
|
||||||
|
همان envelope فعلی پروژه مناسب است:
|
||||||
|
|
||||||
|
```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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## پیشنهاد فنی برای ساخت هر بلوک
|
||||||
|
|
||||||
|
## 1) `yield_prediction`
|
||||||
|
|
||||||
|
### منبع
|
||||||
|
|
||||||
|
- `YieldPredictionService`
|
||||||
|
- یا مستقیم `CurrentFarmChartSimulator.simulate()`
|
||||||
|
|
||||||
|
### منطق پیشنهادی
|
||||||
|
|
||||||
|
- `predicted yield` از `TWSO`
|
||||||
|
- `harvest readiness` از `DVS` و فاصله تا maturity
|
||||||
|
- `quality score` فعلاً rule-based، نه RAG-based
|
||||||
|
- `loss risk` از ترکیب moisture/weather/pest signals اگر دارید، وگرنه با fallback ساده
|
||||||
|
|
||||||
|
### توصیه
|
||||||
|
|
||||||
|
اگر الآن فقط داده قطعی میخواهید:
|
||||||
|
|
||||||
|
- KPI اول را دقیق بسازید
|
||||||
|
- KPIهای دوم تا چهارم را با rules ساده و شفاف بسازید
|
||||||
|
- آنها را به عنوان `estimated` در کد داخلی mark کنید
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2) `harvest_prediction_card`
|
||||||
|
|
||||||
|
### منبع
|
||||||
|
|
||||||
|
- `crop_simulation/harvest_prediction.py`
|
||||||
|
|
||||||
|
### وضعیت
|
||||||
|
|
||||||
|
این بلوک تقریباً آماده است.
|
||||||
|
|
||||||
|
### توصیه
|
||||||
|
|
||||||
|
- `dateFormatted` را از serializer-ready formatter بگیرید
|
||||||
|
- `daysUntil` حتماً `number` بماند
|
||||||
|
- متن `description` اگر خواستید با RAG بهتر شود، version deterministic را هم نگه دارید
|
||||||
|
|
||||||
|
یعنی بهتر است این دو فیلد را داشته باشید:
|
||||||
|
|
||||||
|
- `description`
|
||||||
|
- `descriptionSource = "deterministic" | "rag"`
|
||||||
|
|
||||||
|
این source flag برای debugging خیلی کمک میکند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3) `yield_prediction_chart`
|
||||||
|
|
||||||
|
### واقعیت فعلی
|
||||||
|
|
||||||
|
خروجی chart فعلی شما در `CurrentFarmChartSimulator` بیشتر اینها را میدهد:
|
||||||
|
|
||||||
|
- leaf count estimate
|
||||||
|
- biomass
|
||||||
|
- storage organ weight
|
||||||
|
- lai
|
||||||
|
- soil moisture
|
||||||
|
|
||||||
|
اما payload فرانت `yield-harvest` در سند شما نمودار `سال قبل / سال جاری` میخواهد.
|
||||||
|
|
||||||
|
### نتیجه
|
||||||
|
|
||||||
|
این بخش هنوز one-to-one با کد فعلی آماده نیست.
|
||||||
|
|
||||||
|
### راه درست
|
||||||
|
|
||||||
|
یکی از این دو مسیر را انتخاب کنید:
|
||||||
|
|
||||||
|
#### مسیر A - سریعتر
|
||||||
|
|
||||||
|
همان chart فعلی را به contract فرانت نزدیک کنید و فعلاً بهجای `سال قبل/سال جاری` از:
|
||||||
|
|
||||||
|
- `عملکرد تجمعی پیشبینیشده`
|
||||||
|
- `بیوماس`
|
||||||
|
|
||||||
|
استفاده کنید.
|
||||||
|
|
||||||
|
#### مسیر B - محصولیتر
|
||||||
|
|
||||||
|
دو سری واقعی تولید کنید:
|
||||||
|
|
||||||
|
- `سال جاری`: از simulation جاری
|
||||||
|
- `سال قبل`: از historical scenario یا historical harvest data
|
||||||
|
|
||||||
|
اگر داده سال قبل ندارید، این سری را fake نکنید.
|
||||||
|
|
||||||
|
### توصیه نهایی
|
||||||
|
|
||||||
|
برای production بهتر است تا وقتی historical source ندارید، label سریها را صادقانه تغییر دهید.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4) `harvest_readiness_zones`
|
||||||
|
|
||||||
|
### بزرگترین gap فعلی
|
||||||
|
|
||||||
|
مدل داده فعلی شما عمدتاً farm-level است:
|
||||||
|
|
||||||
|
- `SensorData`
|
||||||
|
- `center_location`
|
||||||
|
- `farm_details`
|
||||||
|
|
||||||
|
اما این کارت block-level data میخواهد:
|
||||||
|
|
||||||
|
- `A1`
|
||||||
|
- `A2`
|
||||||
|
- cultivar per block
|
||||||
|
- readiness per block
|
||||||
|
|
||||||
|
### نتیجه
|
||||||
|
|
||||||
|
بدون model یا source جدید برای block/zone، این کارت را نباید fake کنید.
|
||||||
|
|
||||||
|
### راه درست
|
||||||
|
|
||||||
|
یا باید یکی از اینها را اضافه کنید:
|
||||||
|
|
||||||
|
1. مدل `FarmBlock` یا `FarmZone`
|
||||||
|
2. چند sensor/polygon برای هر مزرعه
|
||||||
|
3. block metadata در JSON مزرعه
|
||||||
|
|
||||||
|
### پیادهسازی پیشنهادی
|
||||||
|
|
||||||
|
برای هر block:
|
||||||
|
|
||||||
|
1. weather/location را resolve کنید
|
||||||
|
2. crop parameters block-specific بسازید
|
||||||
|
3. یک simulation جدا اجرا کنید
|
||||||
|
4. `DVS`, `TWSO`, `SM`, `maturity date` را بگیرید
|
||||||
|
5. readiness را از rule زیر بسازید
|
||||||
|
|
||||||
|
مثال rule:
|
||||||
|
|
||||||
|
```text
|
||||||
|
readiness = clamp((current_dvs / 2.0) * 100, 0, 100)
|
||||||
|
```
|
||||||
|
|
||||||
|
یا:
|
||||||
|
|
||||||
|
```text
|
||||||
|
readiness = clamp(100 - days_until_harvest * 4, 0, 100)
|
||||||
|
```
|
||||||
|
|
||||||
|
بهتر است این rule داخل code comment شفاف توضیح داده شود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5) `yield_quality_bands`
|
||||||
|
|
||||||
|
### نکته مهم
|
||||||
|
|
||||||
|
PCSE بهصورت پیشفرض grade یا brix تولید نمیکند.
|
||||||
|
|
||||||
|
پس این کارت را باید از یکی از این منابع ساخت:
|
||||||
|
|
||||||
|
1. داده آزمایشگاهی
|
||||||
|
2. rule engine بر اساس moisture + disease risk + maturity + crop profile
|
||||||
|
3. مدل quality جدا
|
||||||
|
|
||||||
|
### چیزی که نباید انجام شود
|
||||||
|
|
||||||
|
اینکه RAG خودش بگوید:
|
||||||
|
|
||||||
|
- 46% گرید ممتاز
|
||||||
|
- 34% گرید یک
|
||||||
|
|
||||||
|
بدون منطق deterministic
|
||||||
|
|
||||||
|
این کار برای dashboard اشتباه است.
|
||||||
|
|
||||||
|
### پیشنهاد عملی
|
||||||
|
|
||||||
|
فاز اول:
|
||||||
|
|
||||||
|
- اگر quality data ندارید، این کارت را با flag `unavailable` برگردانید
|
||||||
|
- یا bands را از rule-based scoring بسازید و در backend صریح mark کنید که estimated هستند
|
||||||
|
|
||||||
|
مثلاً:
|
||||||
|
|
||||||
|
- `quality_score >= 85 -> premium`
|
||||||
|
- `70..84 -> grade_1`
|
||||||
|
- `< 70 -> processing`
|
||||||
|
|
||||||
|
اما این rule باید crop-specific باشد، نه universal.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6) `harvest_operations_card`
|
||||||
|
|
||||||
|
### بهترین جا برای ترکیب deterministic + RAG
|
||||||
|
|
||||||
|
این کارت candidate خیلی خوبی برای RAG است، چون:
|
||||||
|
|
||||||
|
- order و timing میتواند از rules و simulation بیاید
|
||||||
|
- متن summary و noteها میتواند از RAG بیاید
|
||||||
|
|
||||||
|
### طراحی پیشنهادی
|
||||||
|
|
||||||
|
ابتدا backend یک payload قطعی بسازد:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"recommended_shift_count": 2,
|
||||||
|
"sorting_capacity_ton_per_day": 15,
|
||||||
|
"required_workers": 12,
|
||||||
|
"max_transfer_hours": 6,
|
||||||
|
"priority_blocks": ["A1", "A2"],
|
||||||
|
"reasoning": [
|
||||||
|
"A1 readiness بالاتر دارد",
|
||||||
|
"moisture در محدوده مناسب است"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
بعد این payload را به RAG بدهید تا فقط اینها را تولید کند:
|
||||||
|
|
||||||
|
- `summary`
|
||||||
|
- `steps[].note`
|
||||||
|
- `steps[].status`
|
||||||
|
|
||||||
|
و حتیالمقدور:
|
||||||
|
|
||||||
|
- `outputs[]` را deterministic نگه دارید
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7) `season_highlights_card`
|
||||||
|
|
||||||
|
### بهترین روش
|
||||||
|
|
||||||
|
این کارت را از بقیه بلوکها derive کنید، نه از یک منبع مستقل.
|
||||||
|
|
||||||
|
یعنی:
|
||||||
|
|
||||||
|
- `seasonLabel` از ورودی season
|
||||||
|
- `badges` از readiness/quality/risk
|
||||||
|
- `spotlight.value` از harvest window یا best sale window
|
||||||
|
- `metrics` از yield, grade, revenue estimate
|
||||||
|
|
||||||
|
### نکته
|
||||||
|
|
||||||
|
اگر هنوز economy data ندارید، فیلدهایی مثل:
|
||||||
|
|
||||||
|
- `درآمد هدف`
|
||||||
|
- `پنجره طلایی فروش`
|
||||||
|
|
||||||
|
نباید با RAG اختراع شوند.
|
||||||
|
|
||||||
|
یا باید:
|
||||||
|
|
||||||
|
- حذف شوند
|
||||||
|
- یا با `null`
|
||||||
|
- یا با fallback business rule
|
||||||
|
|
||||||
|
برگردند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## پیشنهاد برای RAG service جدید
|
||||||
|
|
||||||
|
مثل `irrigation` و `fertilization` یک سرویس جدید اضافه کنید:
|
||||||
|
|
||||||
|
- `rag/services/yield_harvest.py`
|
||||||
|
|
||||||
|
و در `config/rag_config.yaml` هم service جدید تعریف کنید:
|
||||||
|
|
||||||
|
- `yield_harvest`
|
||||||
|
|
||||||
|
### نقش این سرویس
|
||||||
|
|
||||||
|
این سرویس نباید خودش KPI بسازد.
|
||||||
|
|
||||||
|
فقط باید:
|
||||||
|
|
||||||
|
- summary بنویسد
|
||||||
|
- subtitle بسازد
|
||||||
|
- operation notes تولید کند
|
||||||
|
- badge text را human-friendly کند
|
||||||
|
|
||||||
|
### ورودی پیشنهادی به RAG
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"farm_uuid": "...",
|
||||||
|
"plant_name": "...",
|
||||||
|
"deterministic_metrics": {
|
||||||
|
"predicted_yield_tons": 42.8,
|
||||||
|
"days_until_harvest": 18,
|
||||||
|
"readiness_avg": 84,
|
||||||
|
"quality_score": 91,
|
||||||
|
"loss_risk_percent": 6.5
|
||||||
|
},
|
||||||
|
"operations": {
|
||||||
|
"shift_count": 2,
|
||||||
|
"required_workers": 12,
|
||||||
|
"max_transfer_hours": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### خروجی پیشنهادی از RAG
|
||||||
|
|
||||||
|
فقط این بخشها:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"season_highlights_card": {
|
||||||
|
"subtitle": "...",
|
||||||
|
"spotlight": {
|
||||||
|
"caption": "..."
|
||||||
|
},
|
||||||
|
"badges": ["...", "..."]
|
||||||
|
},
|
||||||
|
"harvest_prediction_card": {
|
||||||
|
"description": "..."
|
||||||
|
},
|
||||||
|
"harvest_operations_card": {
|
||||||
|
"summary": "...",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "...",
|
||||||
|
"note": "...",
|
||||||
|
"status": "..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rule مهم
|
||||||
|
|
||||||
|
اگر JSON RAG خراب بود:
|
||||||
|
|
||||||
|
- fallback deterministic برگردان
|
||||||
|
- endpoint fail نکند
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فایلهایی که پیشنهاد میشود تغییر کنند
|
||||||
|
|
||||||
|
### در `crop_simulation/`
|
||||||
|
|
||||||
|
- `crop_simulation/apps.py`
|
||||||
|
- اضافه کردن getter برای summary service
|
||||||
|
|
||||||
|
- `crop_simulation/serializers.py`
|
||||||
|
- serializer درخواست و پاسخ summary
|
||||||
|
|
||||||
|
- `crop_simulation/views.py`
|
||||||
|
- view جدید برای summary
|
||||||
|
|
||||||
|
- `crop_simulation/urls.py`
|
||||||
|
- route جدید
|
||||||
|
|
||||||
|
- `crop_simulation/yield_harvest_summary.py`
|
||||||
|
- orchestrator اصلی
|
||||||
|
|
||||||
|
### در `Schemas/`
|
||||||
|
|
||||||
|
- `Schemas/crop_simulation_yield_harvest_summary.py`
|
||||||
|
- contract برای مستندسازی route
|
||||||
|
|
||||||
|
- `Schemas/__init__.py`
|
||||||
|
- register کردن contract جدید
|
||||||
|
|
||||||
|
### در `rag/`
|
||||||
|
|
||||||
|
- `rag/services/yield_harvest.py`
|
||||||
|
- narrative enrichment
|
||||||
|
|
||||||
|
### در config
|
||||||
|
|
||||||
|
- `config/rag_config.yaml`
|
||||||
|
- service جدید `yield_harvest`
|
||||||
|
|
||||||
|
- `config/tones/yield_harvest_tone.txt`
|
||||||
|
- tone مخصوص summary صفحه
|
||||||
|
|
||||||
|
- در صورت نیاز:
|
||||||
|
- `config/knowledge_base/yield_harvest/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ترتیب پیشنهادی پیادهسازی
|
||||||
|
|
||||||
|
### فاز 1 - سریع و قابل اتکا
|
||||||
|
|
||||||
|
فقط این بلوکها را واقعی کنید:
|
||||||
|
|
||||||
|
- `yield_prediction`
|
||||||
|
- `harvest_prediction_card`
|
||||||
|
- `yield_prediction_chart`
|
||||||
|
- `harvest_operations_card` با rules ساده
|
||||||
|
|
||||||
|
و برای بقیه اگر داده ندارید:
|
||||||
|
|
||||||
|
- `null`
|
||||||
|
- یا `available: false`
|
||||||
|
|
||||||
|
برگردانید.
|
||||||
|
|
||||||
|
### فاز 2 - RAG narrative
|
||||||
|
|
||||||
|
به summary و operation notes متن کاربرپسند اضافه کنید.
|
||||||
|
|
||||||
|
### فاز 3 - block-level readiness
|
||||||
|
|
||||||
|
بعد از اضافه شدن مدل zone/block، `harvest_readiness_zones` را واقعی کنید.
|
||||||
|
|
||||||
|
### فاز 4 - quality/economy
|
||||||
|
|
||||||
|
بعد از اضافه شدن منبع quality یا market:
|
||||||
|
|
||||||
|
- `yield_quality_bands`
|
||||||
|
- revenue
|
||||||
|
- sale window
|
||||||
|
|
||||||
|
را کامل کنید.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## توصیه مهم درباره contract
|
||||||
|
|
||||||
|
اگر بعضی بلوکها هنوز data source واقعی ندارند، بهتر است از همین حالا response را اینطور طراحی کنید:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"harvest_readiness_zones": {
|
||||||
|
"available": false,
|
||||||
|
"reason": "block_level_data_missing",
|
||||||
|
"averageReadiness": null,
|
||||||
|
"blocks": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
این بهتر از mock پنهان است، چون:
|
||||||
|
|
||||||
|
- فرانت میفهمد چرا کارت کامل نیست
|
||||||
|
- QA راحتتر تست میکند
|
||||||
|
- بعداً migration سادهتر میشود
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ریسکهای اصلی
|
||||||
|
|
||||||
|
### 1. استفاده از RAG برای عددسازی
|
||||||
|
|
||||||
|
بزرگترین خطر این صفحه همین است. RAG فقط برای narrative مناسب است.
|
||||||
|
|
||||||
|
### 2. نبود داده block-level
|
||||||
|
|
||||||
|
`harvest_readiness_zones` بدون مدل block عملاً دقیق نمیشود.
|
||||||
|
|
||||||
|
### 3. نبود quality source
|
||||||
|
|
||||||
|
`yield_quality_bands` بدون مدل کیفیت یا rule engine crop-specific قابل اتکا نیست.
|
||||||
|
|
||||||
|
### 4. ناهماهنگی chart contract
|
||||||
|
|
||||||
|
chart فعلی backend دقیقاً همان chart مورد انتظار فرانت نیست.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## پیشنهاد نهایی من
|
||||||
|
|
||||||
|
اگر بخواهم برای همین repo کمریسکترین مسیر را انتخاب کنم:
|
||||||
|
|
||||||
|
1. یک summary service در `crop_simulation` بسازید.
|
||||||
|
2. `yield_prediction` و `harvest_prediction_card` را از سرویسهای فعلی reuse کنید.
|
||||||
|
3. `yield_prediction_chart` را فعلاً با داده واقعی current simulation برگردانید، نه مقایسه fake سال قبل.
|
||||||
|
4. `harvest_operations_card` را با rules deterministic + optional RAG summary بسازید.
|
||||||
|
5. `season_highlights_card` را از همین بلوکها aggregate کنید.
|
||||||
|
6. `harvest_readiness_zones` و `yield_quality_bands` را فقط وقتی source واقعی دارید production کنید.
|
||||||
|
|
||||||
|
این مسیر با معماری فعلی پروژه، با `psce_doc.txt`، و با pattern موجود RAG بیشترین سازگاری را دارد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## یک pseudo flow پیشنهادی
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/yield-harvest/summary/?farm_uuid=...
|
||||||
|
->
|
||||||
|
load farm details
|
||||||
|
->
|
||||||
|
run/reuse PCSE simulation outputs
|
||||||
|
->
|
||||||
|
build deterministic blocks
|
||||||
|
->
|
||||||
|
optionally call RAG for narrative fields only
|
||||||
|
->
|
||||||
|
merge response
|
||||||
|
->
|
||||||
|
return code/msg/data
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## نتیجه
|
||||||
|
|
||||||
|
برای این صفحه:
|
||||||
|
|
||||||
|
- PCSE باید موتور عددها باشد
|
||||||
|
- rules باید موتور KPIهای محصولی باشد
|
||||||
|
- RAG باید موتور متن و explanation باشد
|
||||||
|
|
||||||
|
اگر این مرزبندی رعایت شود، هم dashboard قابل اعتماد میشود، هم توسعه بعدی آن تمیز و قابل نگهداری میماند.
|
||||||
@@ -278,16 +278,6 @@ class ReportingAndAiJourneyTests(IntegrationAPITestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
stack.enter_context(
|
|
||||||
patch(
|
|
||||||
"pest_disease.services.build_pest_disease_risk_summary",
|
|
||||||
return_value={
|
|
||||||
"riskLevel": "medium",
|
|
||||||
"headline": "Watch humidity trend",
|
|
||||||
"items": [{"title": "Powdery mildew", "severity": "medium"}],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
chat_response = self.client.post(
|
chat_response = self.client.post(
|
||||||
"/api/rag/chat/",
|
"/api/rag/chat/",
|
||||||
@@ -357,14 +347,6 @@ class ReportingAndAiJourneyTests(IntegrationAPITestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(pest_risk_response.status_code, 200)
|
self.assertEqual(pest_risk_response.status_code, 200)
|
||||||
|
|
||||||
pest_summary_response = self.client.post(
|
|
||||||
"/api/pest-disease/risk-summary/",
|
|
||||||
data={"farm_uuid": str(self.farm_uuid)},
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
self.assertEqual(pest_summary_response.status_code, 200)
|
|
||||||
self.assertEqual(pest_summary_response.json()["data"]["riskLevel"], "medium")
|
|
||||||
|
|
||||||
def test_alert_and_crop_simulation_endpoints_persist_records(self) -> None:
|
def test_alert_and_crop_simulation_endpoints_persist_records(self) -> None:
|
||||||
def tracker_stub(*, farm_uuid: str, query: str | None = None):
|
def tracker_stub(*, farm_uuid: str, query: str | None = None):
|
||||||
from farm_alerts.services import _save_notifications, _serialize_notification
|
from farm_alerts.services import _save_notifications, _serialize_notification
|
||||||
|
|||||||
@@ -1,17 +1,7 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from functools import cached_property
|
|
||||||
|
|
||||||
|
|
||||||
class PestDiseaseConfig(AppConfig):
|
class PestDiseaseConfig(AppConfig):
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
name = "pest_disease"
|
name = "pest_disease"
|
||||||
verbose_name = "Pest & Disease"
|
verbose_name = "Pest & Disease"
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def risk_summary_service(self):
|
|
||||||
from .services import build_pest_disease_risk_summary
|
|
||||||
|
|
||||||
return build_pest_disease_risk_summary
|
|
||||||
|
|
||||||
def get_risk_summary_service(self):
|
|
||||||
return self.risk_summary_service
|
|
||||||
|
|||||||
@@ -29,22 +29,3 @@ class PestDiseaseRiskRequestSerializer(serializers.Serializer):
|
|||||||
raise serializers.ValidationError({"farm_uuid": "farm_uuid الزامی است."})
|
raise serializers.ValidationError({"farm_uuid": "farm_uuid الزامی است."})
|
||||||
attrs["farm_uuid"] = farm_uuid
|
attrs["farm_uuid"] = farm_uuid
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class PestDiseaseRiskSummaryRequestSerializer(serializers.Serializer):
|
|
||||||
farm_uuid = serializers.CharField(required=False, help_text="شناسه یکتای مزرعه")
|
|
||||||
sensor_uuid = serializers.CharField(required=False, help_text="نام قدیمی برای farm_uuid")
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
farm_uuid = attrs.get("farm_uuid") or attrs.get("sensor_uuid")
|
|
||||||
if not farm_uuid:
|
|
||||||
raise serializers.ValidationError({"farm_uuid": "farm_uuid الزامی است."})
|
|
||||||
attrs["farm_uuid"] = farm_uuid
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
class PestDiseaseRiskSummaryResponseSerializer(serializers.Serializer):
|
|
||||||
farm_uuid = serializers.CharField()
|
|
||||||
diseaseRisk = serializers.JSONField()
|
|
||||||
pestRisk = serializers.JSONField()
|
|
||||||
drivers = serializers.JSONField()
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from rag.services import get_pest_disease_risk
|
|
||||||
|
|
||||||
|
|
||||||
def _stats_label(level: str | None) -> str:
|
|
||||||
if level == "high":
|
|
||||||
return "بالا"
|
|
||||||
if level == "medium":
|
|
||||||
return "متوسط"
|
|
||||||
return "پایین"
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_risk_block(block: dict[str, Any] | None) -> dict[str, Any]:
|
|
||||||
payload = dict(block or {})
|
|
||||||
payload.setdefault("score", 0.0)
|
|
||||||
payload.setdefault("level", "low")
|
|
||||||
payload["statsLabel"] = _stats_label(payload.get("level"))
|
|
||||||
return payload
|
|
||||||
|
|
||||||
|
|
||||||
def build_pest_disease_risk_summary(*, farm_uuid: str) -> dict[str, Any]:
|
|
||||||
rag_result = get_pest_disease_risk(farm_uuid=farm_uuid)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"farm_uuid": farm_uuid,
|
|
||||||
"diseaseRisk": _normalize_risk_block(rag_result.get("disease_risk")),
|
|
||||||
"pestRisk": _normalize_risk_block(rag_result.get("pest_risk")),
|
|
||||||
"drivers": {
|
|
||||||
"keyDrivers": rag_result.get("key_drivers") or [],
|
|
||||||
"summary": rag_result.get("summary"),
|
|
||||||
"forecastWindow": rag_result.get("forecast_window"),
|
|
||||||
"source": "rag",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import PestDiseaseDetectionView, PestDiseaseRiskSummaryView, PestDiseaseRiskView
|
from .views import PestDiseaseDetectionView, PestDiseaseRiskView
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("detect/", PestDiseaseDetectionView.as_view(), name="pest-disease-detect"),
|
path("detect/", PestDiseaseDetectionView.as_view(), name="pest-disease-detect"),
|
||||||
path("risk/", PestDiseaseRiskView.as_view(), name="pest-disease-risk"),
|
path("risk/", PestDiseaseRiskView.as_view(), name="pest-disease-risk"),
|
||||||
path("risk-summary/", PestDiseaseRiskSummaryView.as_view(), name="pest-disease-risk-summary"),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ from rag.services import get_pest_disease_detection, get_pest_disease_risk
|
|||||||
from .serializers import (
|
from .serializers import (
|
||||||
PestDiseaseDetectionRequestSerializer,
|
PestDiseaseDetectionRequestSerializer,
|
||||||
PestDiseaseRiskRequestSerializer,
|
PestDiseaseRiskRequestSerializer,
|
||||||
PestDiseaseRiskSummaryRequestSerializer,
|
|
||||||
PestDiseaseRiskSummaryResponseSerializer,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -31,12 +29,6 @@ PestDiseaseRiskResponseSerializer = build_envelope_serializer(
|
|||||||
"PestDiseaseRiskResponseSerializer",
|
"PestDiseaseRiskResponseSerializer",
|
||||||
data_schema=None,
|
data_schema=None,
|
||||||
)
|
)
|
||||||
PestDiseaseRiskSummaryEnvelopeSerializer = build_envelope_serializer(
|
|
||||||
"PestDiseaseRiskSummaryEnvelopeSerializer",
|
|
||||||
PestDiseaseRiskSummaryResponseSerializer,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class _ImageMixin:
|
class _ImageMixin:
|
||||||
parser_classes = [JSONParser, MultiPartParser, FormParser]
|
parser_classes = [JSONParser, MultiPartParser, FormParser]
|
||||||
|
|
||||||
@@ -160,44 +152,3 @@ class PestDiseaseRiskView(APIView):
|
|||||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
)
|
)
|
||||||
return Response({"code": 200, "msg": "success", "data": result}, status=status.HTTP_200_OK)
|
return Response({"code": 200, "msg": "success", "data": result}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class PestDiseaseRiskSummaryView(APIView):
|
|
||||||
@extend_schema(
|
|
||||||
tags=["Pest & Disease"],
|
|
||||||
summary="خلاصه ریسک بیماری و آفات",
|
|
||||||
description=(
|
|
||||||
"با دریافت farm_uuid، خلاصه ریسک بیماری و آفات را از سرویس RAG "
|
|
||||||
"گرفته و در قالب سبک تر برای KPI بازمی گرداند."
|
|
||||||
),
|
|
||||||
request=PestDiseaseRiskSummaryRequestSerializer,
|
|
||||||
responses={
|
|
||||||
200: build_response(PestDiseaseRiskSummaryEnvelopeSerializer, "خلاصه ریسک بیماری و آفات."),
|
|
||||||
400: build_response(PestDiseaseValidationErrorSerializer, "پارامتر ورودی نامعتبر است."),
|
|
||||||
404: build_response(PestDiseaseValidationErrorSerializer, "مزرعه یافت نشد."),
|
|
||||||
},
|
|
||||||
examples=[
|
|
||||||
OpenApiExample(
|
|
||||||
"نمونه درخواست risk summary",
|
|
||||||
value={"farm_uuid": "11111111-1111-1111-1111-111111111111"},
|
|
||||||
request_only=True,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def post(self, request):
|
|
||||||
from .services import build_pest_disease_risk_summary
|
|
||||||
|
|
||||||
serializer = PestDiseaseRiskSummaryRequestSerializer(data=request.data)
|
|
||||||
if not serializer.is_valid():
|
|
||||||
return Response(
|
|
||||||
{"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors},
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
result = build_pest_disease_risk_summary(farm_uuid=serializer.validated_data["farm_uuid"])
|
|
||||||
except ValueError as exc:
|
|
||||||
return Response(
|
|
||||||
{"code": 404, "msg": str(exc), "data": None},
|
|
||||||
status=status.HTTP_404_NOT_FOUND,
|
|
||||||
)
|
|
||||||
return Response({"code": 200, "msg": "success", "data": result}, status=status.HTTP_200_OK)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user