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.1) لیست روشهای آبیاری
|
||||
@@ -1548,7 +1517,6 @@
|
||||
- `POST /api/plants/fetch-info/`
|
||||
- `POST /api/pest-disease/detect/`
|
||||
- `POST /api/pest-disease/risk/`
|
||||
- `POST /api/pest-disease/risk-summary/`
|
||||
- `GET /api/irrigation/`
|
||||
- `POST /api/irrigation/`
|
||||
- `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
|
||||
|
||||
### `POST /api/crop-simulation/growth/`
|
||||
@@ -521,7 +508,6 @@
|
||||
- `POST /api/irrigation/water-stress/` در حالت fallback
|
||||
- `POST /api/pest-disease/detect/` برای diagnosis قطعی
|
||||
- `POST /api/pest-disease/risk/`
|
||||
- `POST /api/pest-disease/risk-summary/`
|
||||
- `POST /api/rag/chat/` برای پاسخهای حساس و اجرایی
|
||||
|
||||
---
|
||||
|
||||
+4
-11
@@ -110,7 +110,6 @@ Base: `/api/pest-disease/`
|
||||
|---|---|---|
|
||||
| POST | `/api/pest-disease/detect/` | تشخیص آفت/بیماری از تصویر |
|
||||
| POST | `/api/pest-disease/risk/` | پیشبینی ریسک آفات و بیماری |
|
||||
| POST | `/api/pest-disease/risk-summary/` | خلاصه KPI ریسک آفات و بیماری |
|
||||
|
||||
### App: Irrigation
|
||||
|
||||
@@ -249,16 +248,11 @@ Base: `/api/crop-simulation/`
|
||||
- history واقعی، drift سنسور، عدم قطعیت، zoning مزرعه یا depth-specific map در آن لحاظ نشدهاند.
|
||||
- اثر عملی: heatmap برای visualization خوب است ولی برای تصمیم agronomy دقیق کافی نیست.
|
||||
|
||||
### 8) Pest/Disease Risk Summary یک heuristic ساده است
|
||||
- امتیاز بیماری و آفت از ترکیب چند فرمول ثابت ساخته میشود: `pest_disease/services.py:39`
|
||||
- وزنها hard-coded هستند و مدل crop-specific یا region-specific ندارند.
|
||||
- اثر عملی: `POST /api/pest-disease/risk-summary/` بیشتر KPI تقریبی است تا مدل پیشبینی دقیق.
|
||||
|
||||
### 9) توصیههای RAG در لایه نهایی deterministic merge میشوند
|
||||
### 8) توصیههای RAG در لایه نهایی deterministic merge میشوند
|
||||
- برای irrigation/fertilization، fallback همیشه ساختار نهایی را پر میکند: `rag/services/irrigation.py:153`, `rag/services/fertilization.py:135`
|
||||
- اثر عملی: خروجی از نظر UI پایدار است، اما تشخیص اینکه کدام بخش واقعا از LLM آمده سخت میشود.
|
||||
|
||||
### 10) Crop Simulation ممکن است از engine اصلی به projection تقریبی سقوط کند
|
||||
### 9) Crop Simulation ممکن است از engine اصلی به projection تقریبی سقوط کند
|
||||
- fallback projection در خطا فعال میشود: `crop_simulation/growth_simulation.py:404`
|
||||
- اگر consumer فقط status 200 ببیند و `simulation_warning` را ignore کند، ممکن است خروجی تقریبی را واقعی فرض کند.
|
||||
|
||||
@@ -274,7 +268,7 @@ Base: `/api/crop-simulation/`
|
||||
| `farm_data` | متوسط | هسته aggregation خوب است، ولی center/merge چند سنسور سادهسازی شده |
|
||||
| `location_data` | متوسط | SoilGrids واقعی است، NDVI وابسته به تنظیمات بیرونی است |
|
||||
| `soile` | متوسط | داده واقعی دارد، اما مدل تحلیلی و interpolation ساده است |
|
||||
| `pest_disease` | متوسط | fallback زیاد و بخش risk-summary heuristic است |
|
||||
| `pest_disease` | متوسط | fallback زیاد در endpointهای تشخیص و ریسک دارد |
|
||||
| `farm_alerts` | متوسط | خروجی قابل استفاده است، ولی در failure به fallback داخلی میرود |
|
||||
| `irrigation` | متوسط رو به ضعیف | recommendation خوب، اما water-stress ساده و CRUD detail معیوب |
|
||||
| `fertilization` | متوسط | recommendation موجود است ولی heavily fallback-assisted |
|
||||
@@ -315,8 +309,7 @@ Base: `/api/crop-simulation/`
|
||||
### اولویت متوسط
|
||||
1. بهبود مدل water stress
|
||||
2. بهبود IDW/heatmap و استفاده از time-series
|
||||
3. بهبود risk-summary آفت/بیماری با crop profile و weather window دقیقتر
|
||||
4. اضافهکردن flag صریح برای crop-simulation fallback
|
||||
3. اضافهکردن flag صریح برای crop-simulation fallback
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -63,7 +63,3 @@
|
||||
- در اين حالت، يک notification با `level` برابر `info` توليد کن که صريح بگويد فعلا مورد مهم جديدي شناسايي نشده است.
|
||||
- براي اين notification حداقلي، `title` کوتاه و خنثي باشد، `message` شفاف بگويد هشدار مهم جديدي وجود ندارد، و `suggested_action` فقط يک اقدام پايشي سبک و مشخص باشد.
|
||||
- اين 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(
|
||||
"/api/rag/chat/",
|
||||
@@ -357,14 +347,6 @@ class ReportingAndAiJourneyTests(IntegrationAPITestCase):
|
||||
)
|
||||
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 tracker_stub(*, farm_uuid: str, query: str | None = None):
|
||||
from farm_alerts.services import _save_notifications, _serialize_notification
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
from functools import cached_property
|
||||
|
||||
|
||||
class PestDiseaseConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
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 الزامی است."})
|
||||
attrs["farm_uuid"] = farm_uuid
|
||||
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 .views import PestDiseaseDetectionView, PestDiseaseRiskSummaryView, PestDiseaseRiskView
|
||||
from .views import PestDiseaseDetectionView, PestDiseaseRiskView
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("detect/", PestDiseaseDetectionView.as_view(), name="pest-disease-detect"),
|
||||
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 (
|
||||
PestDiseaseDetectionRequestSerializer,
|
||||
PestDiseaseRiskRequestSerializer,
|
||||
PestDiseaseRiskSummaryRequestSerializer,
|
||||
PestDiseaseRiskSummaryResponseSerializer,
|
||||
)
|
||||
|
||||
|
||||
@@ -31,12 +29,6 @@ PestDiseaseRiskResponseSerializer = build_envelope_serializer(
|
||||
"PestDiseaseRiskResponseSerializer",
|
||||
data_schema=None,
|
||||
)
|
||||
PestDiseaseRiskSummaryEnvelopeSerializer = build_envelope_serializer(
|
||||
"PestDiseaseRiskSummaryEnvelopeSerializer",
|
||||
PestDiseaseRiskSummaryResponseSerializer,
|
||||
)
|
||||
|
||||
|
||||
class _ImageMixin:
|
||||
parser_classes = [JSONParser, MultiPartParser, FormParser]
|
||||
|
||||
@@ -160,44 +152,3 @@ class PestDiseaseRiskView(APIView):
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
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