From 88f56da5820a9b58d3137c474a423524d783059a Mon Sep 17 00:00:00 2001 From: Mohammad Sajad Pourajam Date: Wed, 29 Apr 2026 23:54:30 +0330 Subject: [PATCH] UPDATE --- API_REFERENCE_FA.md | 32 - API_RELIABILITY_AUDIT_FA.md | 14 - APPS_URLS_AUDIT.md | 15 +- config/tones/farm_alerts_tone.txt | 4 - docs/location_and_farm_data_apps_explained.md | 512 ++++++++++++ docs/yield_harvest_pcse_rag_api_plan.md | 746 ++++++++++++++++++ .../test_reporting_and_ai_api_flow.py | 18 - pest_disease/apps.py | 10 - pest_disease/serializers.py | 19 - pest_disease/services.py | 37 - pest_disease/urls.py | 3 +- pest_disease/views.py | 49 -- 12 files changed, 1263 insertions(+), 196 deletions(-) create mode 100644 docs/location_and_farm_data_apps_explained.md create mode 100644 docs/yield_harvest_pcse_rag_api_plan.md delete mode 100644 pest_disease/services.py diff --git a/API_REFERENCE_FA.md b/API_REFERENCE_FA.md index 14ca25e..b050d7c 100644 --- a/API_REFERENCE_FA.md +++ b/API_REFERENCE_FA.md @@ -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//` diff --git a/API_RELIABILITY_AUDIT_FA.md b/API_RELIABILITY_AUDIT_FA.md index 1eff5c8..57f2495 100644 --- a/API_RELIABILITY_AUDIT_FA.md +++ b/API_RELIABILITY_AUDIT_FA.md @@ -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/` برای پاسخ‌های حساس و اجرایی --- diff --git a/APPS_URLS_AUDIT.md b/APPS_URLS_AUDIT.md index 02a6d3d..852dc97 100644 --- a/APPS_URLS_AUDIT.md +++ b/APPS_URLS_AUDIT.md @@ -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 --- diff --git a/config/tones/farm_alerts_tone.txt b/config/tones/farm_alerts_tone.txt index 5d9c29e..c76c46b 100644 --- a/config/tones/farm_alerts_tone.txt +++ b/config/tones/farm_alerts_tone.txt @@ -63,7 +63,3 @@ - در اين حالت، يک notification با `level` برابر `info` توليد کن که صريح بگويد فعلا مورد مهم جديدي شناسايي نشده است. - براي اين notification حداقلي، `title` کوتاه و خنثي باشد، `message` شفاف بگويد هشدار مهم جديدي وجود ندارد، و `suggested_action` فقط يک اقدام پايشي سبک و مشخص باشد. - اين notification حداقلي فقط وقتي استفاده شود که خروجي در غير اين صورت خالي مي شد. -- [TEMP_FORCE_MIN_NOTIFICATION_END] -- `headline` و `overview` هميشه الزامي هستند. -- عنوان ها کوتاه و عملياتي باشند. -- `suggested_action` بايد يک اقدام مشخص مزرعه اي باشد، نه توصيه کلي. diff --git a/docs/location_and_farm_data_apps_explained.md b/docs/location_and_farm_data_apps_explained.md new file mode 100644 index 0000000..383a932 --- /dev/null +++ b/docs/location_and_farm_data_apps_explained.md @@ -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 نگه‌داری می‌شوند diff --git a/docs/yield_harvest_pcse_rag_api_plan.md b/docs/yield_harvest_pcse_rag_api_plan.md new file mode 100644 index 0000000..16d3a97 --- /dev/null +++ b/docs/yield_harvest_pcse_rag_api_plan.md @@ -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=` + +اگر لازم شد از نظر convention داخلی پروژه همه‌چیز داخل `crop_simulation` بماند، می‌توانید معادل زیر را هم نگه دارید: + +`GET /api/crop-simulation/yield-harvest-summary/?farm_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=&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 قابل اعتماد می‌شود، هم توسعه بعدی آن تمیز و قابل نگهداری می‌ماند. diff --git a/integration_tests/test_reporting_and_ai_api_flow.py b/integration_tests/test_reporting_and_ai_api_flow.py index 747ded6..e15e780 100644 --- a/integration_tests/test_reporting_and_ai_api_flow.py +++ b/integration_tests/test_reporting_and_ai_api_flow.py @@ -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 diff --git a/pest_disease/apps.py b/pest_disease/apps.py index edf0331..ec241dd 100644 --- a/pest_disease/apps.py +++ b/pest_disease/apps.py @@ -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 diff --git a/pest_disease/serializers.py b/pest_disease/serializers.py index 8d1e7c2..96090d8 100644 --- a/pest_disease/serializers.py +++ b/pest_disease/serializers.py @@ -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() diff --git a/pest_disease/services.py b/pest_disease/services.py deleted file mode 100644 index df0f3a6..0000000 --- a/pest_disease/services.py +++ /dev/null @@ -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", - }, - } diff --git a/pest_disease/urls.py b/pest_disease/urls.py index 6a044a1..c00c809 100644 --- a/pest_disease/urls.py +++ b/pest_disease/urls.py @@ -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"), ] diff --git a/pest_disease/views.py b/pest_disease/views.py index aedcc86..50f1874 100644 --- a/pest_disease/views.py +++ b/pest_disease/views.py @@ -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)