This commit is contained in:
2026-05-11 03:27:21 +03:30
parent cf7cbb937c
commit d0e68a1a56
854 changed files with 102985 additions and 76 deletions
File diff suppressed because it is too large Load Diff
+180
View File
@@ -0,0 +1,180 @@
# راهنمای استفاده از API هشدارهای مزرعه
این سند نحوه کار با API فعال هشدارهای مزرعه را توضیح می‌دهد.
## Endpoint فعال
- `POST /api/farm-alerts/tracker/`
نکته:
- endpoint `POST /api/farm-alerts/timeline/` حذف شده و دیگر قابل استفاده نیست.
## کاربرد API
این API با دریافت `farm_uuid` و یک لیست از `alerts`:
- وضعیت فعلی هشدارهای مزرعه را تحلیل می‌کند
- context مزرعه را همراه با alertهای ارسالی به RAG می‌فرستد
- فقط notificationهای مهم را تولید می‌کند
- notificationهای تولیدشده را در دیتابیس ذخیره می‌کند
## ساختار درخواست
فیلدهای ورودی:
- `farm_uuid`: شناسه مزرعه
- `alerts`: لیست alertهای ورودی برای تحلیل
فیلد `farm_uuid` الزامی است.
## ساختار هر alert
هر آیتم داخل `alerts` می‌تواند این فیلدها را داشته باشد:
- `alert_id`: شناسه هشدار
- `level`: سطح هشدار مثل `info` یا `warning` یا `danger`
- `title`: عنوان هشدار
- `message`: توضیح هشدار
- `suggested_action`: اقدام پیشنهادی
- `source_metric_type`: نوع شاخص مثل `moisture`
- `timestamp`: زمان هشدار با فرمت datetime
- `payload`: داده تکمیلی به صورت JSON object
همه فیلدهای داخل هر alert اختیاری هستند، ولی بهتر است برای تحلیل دقیق‌تر حداقل `title` یا `message` و در صورت امکان `level` و `source_metric_type` ارسال شوند.
## نمونه درخواست
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"alerts": [
{
"alert_id": "soil-moisture-001",
"level": "warning",
"title": "افت رطوبت خاک",
"message": "رطوبت خاک کمتر از حد مطلوب گزارش شده است.",
"suggested_action": "آبیاری اصلاحی بررسی شود.",
"source_metric_type": "moisture",
"timestamp": "2025-02-14T09:30:00Z",
"payload": {
"window": "3d",
"current_value": 38.5,
"threshold": 45
}
},
{
"alert_id": "fungal-risk-002",
"level": "danger",
"title": "ریسک قارچی بالا",
"message": "شرایط محیطی برای بیماری قارچی شدید شده است.",
"suggested_action": "بازدید و اقدام پیشگیرانه فوری انجام شود.",
"source_metric_type": "fungal_risk",
"timestamp": "2025-02-14T10:00:00Z",
"payload": {
"humidity": 89,
"duration_hours": 18
}
}
]
}
```
## نمونه درخواست با curl
```bash
curl -X POST http://localhost:8000/api/farm-alerts/tracker/ \
-H "Content-Type: application/json" \
-d '{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"alerts": [
{
"alert_id": "soil-moisture-001",
"level": "warning",
"title": "افت رطوبت خاک",
"message": "رطوبت خاک کمتر از حد مطلوب گزارش شده است.",
"suggested_action": "آبیاری اصلاحی بررسی شود.",
"source_metric_type": "moisture"
}
]
}'
```
## ساختار پاسخ موفق
پاسخ HTTP با envelope زیر برمی‌گردد:
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"service_id": "farm_alerts",
"tracker": {},
"headline": "جمع بندی کوتاه وضعیت هشدارها",
"overview": "توضیح کوتاه و اجرایی از مهم ترین وضعیت مزرعه",
"status_level": "warning",
"notifications": [
{
"id": 12,
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"endpoint": "tracker",
"level": "warning",
"title": "افت رطوبت خاک",
"message": "تنش رطوبتی در مزرعه ادامه دارد.",
"suggested_action": "آبیاری جبرانی کوتاه مدت اجرا شود.",
"source_alert_id": "soil-moisture-001",
"source_metric_type": "moisture",
"payload": {},
"created_at": "2025-02-14T10:15:00+00:00",
"updated_at": "2025-02-14T10:15:00+00:00"
}
],
"raw_llm_response": "{\"headline\":\"...\"}",
"structured_context": {}
}
}
```
## وضعیت‌های خطا
### خطای ورودی نامعتبر
اگر `farm_uuid` ارسال نشود:
```json
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"farm_uuid": [
"farm_uuid الزامی است."
]
}
}
```
### خطای داخلی
اگر در مرحله تحلیل RAG یا تولید پاسخ خطایی رخ دهد:
```json
{
"code": 500,
"msg": "خطا در تولید tracker هشدارها: ...",
"data": null
}
```
## نکات رفتاری API
- اگر `alerts` ارسال نشود، API آن را به صورت آرایه خالی در نظر می‌گیرد.
- notificationهای ساخته‌شده برای endpoint `tracker` در دیتابیس ذخیره می‌شوند.
- مدل باید notification تکراری نسازد و اگر مورد مهمی وجود نداشته باشد، خروجی notification می‌تواند خالی باشد.
- تحلیل فقط روی endpoint `tracker` فعال است.
## پیشنهاد برای مصرف‌کننده API
- برای هر alert یک `alert_id` پایدار بفرستید تا ردیابی و جلوگیری از تکرار بهتر انجام شود.
- برای alertهای حساس، `timestamp` و `source_metric_type` را حتما ارسال کنید.
- اگر داده تکمیلی دارید، آن را داخل `payload` بفرستید تا RAG context کامل‌تر شود.
@@ -0,0 +1,492 @@
# توضیح فیلدهای پاسخ API هشدارهای مزرعه
این سند فیلدهای JSON خروجی `POST /api/farm-alerts/tracker/` را توضیح می‌دهد.
## ساختار کلی پاسخ
پاسخ API به شکل envelope برمی‌گردد:
```json
{
"code": 200,
"msg": "success",
"data": {}
}
```
## فیلدهای سطح اول
### `code`
- نوع: `number`
- معنی: کد وضعیت داخلی API
- مقدار رایج:
- `200`: موفق
- `400`: ورودی نامعتبر
- `500`: خطای داخلی
### `msg`
- نوع: `string`
- معنی: پیام خلاصه وضعیت پاسخ
- نمونه:
- `success`
- `داده نامعتبر.`
- `خطا در تولید tracker هشدارها: ...`
### `data`
- نوع: `object | null`
- معنی: بدنه اصلی پاسخ
- در موفقیت شامل جزئیات تحلیل مزرعه است
- در بعضی خطاها ممکن است `null` باشد
## فیلدهای داخل `data`
### `farm_uuid`
- نوع: `string`
- معنی: شناسه مزرعه‌ای که تحلیل روی آن انجام شده
### `service_id`
- نوع: `string`
- معنی: شناسه سرویس تولیدکننده پاسخ
- مقدار فعلی: `farm_alerts`
### `tracker`
- نوع: `object`
- معنی: خروجی ساختاریافته‌ی موتور tracker قبل از خلاصه‌سازی نهایی AI
- شامل لیست alertها، آمار، خوشه‌بندی و مهم‌ترین مسئله است
### `headline`
- نوع: `string`
- معنی: تیتر کوتاه و سریع برای وضعیت فعلی مزرعه
- برای نمایش در کارت، header یا لیست اعلان مناسب است
### `overview`
- نوع: `string`
- معنی: جمع‌بندی کوتاه و اجرایی از مهم‌ترین وضعیت فعلی
- معمولا نسخه‌ی خلاصه‌شده‌ی مهم‌ترین alert یا نتیجه کلی تحلیل است
### `status_level`
- نوع: `string`
- مقادیر مجاز:
- `info`
- `warning`
- `danger`
- معنی: سطح کلی وضعیت مزرعه از دید AI
### `notifications`
- نوع: `array`
- معنی: notificationهای نهایی که پس از تحلیل AI ساخته و در دیتابیس ذخیره شده‌اند
- اگر مورد مهمی وجود نداشته باشد، می‌تواند خالی باشد
### `raw_llm_response`
- نوع: `string | null`
- معنی: پاسخ خام JSON که مدل زبانی تولید کرده است
- بیشتر برای debug، audit یا بررسی رفتار AI مفید است
### `structured_context`
- نوع: `object`
- معنی: کانتکست ساختاریافته‌ای که به مدل داده شده است
- شامل اطلاعات مزرعه، tracker، forecastها و alertهای ورودی است
## فیلدهای `tracker`
### `tracker.totalAlerts`
- نوع: `number`
- معنی: تعداد کل alertهای شناسایی‌شده توسط tracker
### `tracker.alerts`
- نوع: `array`
- معنی: لیست خام alertهای تشخیص‌داده‌شده
هر آیتم در `tracker.alerts`:
#### `metric_type`
- نوع: `string`
- معنی: نوع شاخصی که alert از آن آمده
- نمونه:
- `moisture`
- `temperature`
- `ph`
- `ec`
- `fungal_risk`
#### `title`
- نوع: `string`
- معنی: عنوان انسانی alert
#### `current_value`
- نوع: `number`
- معنی: مقدار فعلی شاخص
#### `threshold_value`
- نوع: `number | string`
- معنی: آستانه مرجع برای تشخیص alert
#### `severity`
- نوع: `string`
- مقادیر رایج:
- `low`
- `medium`
- `high`
- `critical`
- معنی: شدت alert در لایه tracker
#### `duration_hours`
- نوع: `number`
- معنی: مدت تداوم وضعیت هشدار به ساعت
#### `duration`
- نوع: `string`
- معنی: نسخه خوانای `duration_hours`
- نمونه: `3 ساعت`
#### `timestamp`
- نوع: `string`
- معنی: زمان مرجع alert با فرمت ISO datetime
#### `sensor_id`
- نوع: `string`
- معنی: شناسه مزرعه/سنسوری که alert برای آن محاسبه شده
#### `zone_id`
- نوع: `string | null`
- معنی: شناسه ناحیه، اگر alert مربوط به ناحیه خاصی باشد
#### `domain`
- نوع: `string`
- معنی: دامنه عملیاتی alert
- نمونه:
- `water_balance`
- `temperature_stress`
- `root_chemistry`
- `disease_pressure`
#### `direction`
- نوع: `string`
- معنی: جهت عبور از آستانه
- نمونه:
- `below`: مقدار از حد مجاز کمتر شده
- `above`: مقدار از حد مجاز بیشتر شده
#### `unit`
- نوع: `string`
- معنی: واحد شاخص
- نمونه:
- `%`
- `°C`
- `pH`
- `dS/m`
#### `icon`
- نوع: `string`
- معنی: نام آیکن پیشنهادی برای UI
#### `summary`
- نوع: `string`
- معنی: خلاصه انسانی و کوتاه alert
#### `recommended_action`
- نوع: `string`
- معنی: اقدام عملیاتی پیشنهادی tracker
#### `explanation`
- نوع: `string`
- معنی: توضیح قابل‌فهم درباره چرایی ایجاد alert
#### `metadata`
- نوع: `object`
- معنی: داده تکمیلی برای توسعه‌های بعدی
## فیلدهای `tracker.alertStats`
- نوع: `array`
- معنی: خلاصه آماری alertها برای نمایش سریع در UI
هر آیتم:
### `title`
- عنوان دسته alert
### `count`
- نوع: `string`
- تعداد alertها در آن دسته
### `avatarColor`
- نوع: `string`
- رنگ پیشنهادی UI
### `avatarIcon`
- نوع: `string`
- آیکن پیشنهادی UI
### `severity`
- نوع: `string`
- بالاترین شدت در آن دسته
### `topSummary`
- نوع: `string`
- مهم‌ترین خلاصه در آن دسته
## فیلدهای `tracker.alertClusters`
- نوع: `array`
- معنی: گروه‌بندی alertها بر اساس domain
هر آیتم:
### `domain`
- نوع: `string`
- نام دامنه خوشه
### `title`
- نوع: `string`
- عنوان انسانی خوشه
### `alert_count`
- نوع: `number`
- تعداد alertهای این خوشه
### `highest_severity`
- نوع: `string`
- بیشترین شدت بین alertهای این خوشه
### `primary_metric`
- نوع: `string`
- مهم‌ترین metric در آن خوشه
### `summary`
- نوع: `string`
- خلاصه وضعیت خوشه
### `alert_ids`
- نوع: `array`
- معنی: شناسه‌های alertهای عضو خوشه
## فیلد `tracker.mostCriticalIssue`
- نوع: `object | null`
- معنی: مهم‌ترین مسئله‌ای که tracker پیدا کرده
- ساختار آن مشابه هر آیتم در `tracker.alerts` است
## فیلدهای خلاصه‌ای `tracker`
### `tracker.prioritizedAlertSummaries`
- نوع: `array`
- معنی: لیستی از summaryهای مهم به ترتیب اولویت
### `tracker.recommendedOperationalActions`
- نوع: `array`
- معنی: لیستی از اقدام‌های عملیاتی پیشنهادی
### `tracker.humanReadableExplanations`
- نوع: `array`
- معنی: توضیح‌های متنی قابل‌خواندن برای نمایش یا گزارش
## فیلدهای `notifications`
- نوع: `array`
- معنی: اعلان‌های نهایی ذخیره‌شده در سیستم
هر آیتم در `notifications`:
### `id`
- نوع: `number`
- معنی: شناسه داخلی notification
### `uuid`
- نوع: `string`
- معنی: شناسه یکتای notification
### `farm_uuid`
- نوع: `string`
- معنی: شناسه مزرعه مربوط به notification
### `since_id`
- نوع: `number | null`
- معنی: شناسه مرجع برای زنجیره یا گروه‌بندی notificationها در سیستم
### `endpoint`
- نوع: `string`
- معنی: endpoint تولیدکننده notification
- مقدار فعلی: `tracker`
### `title`
- نوع: `string`
- معنی: عنوان notification
### `message`
- نوع: `string`
- معنی: متن اصلی notification
### `level`
- نوع: `string`
- مقادیر مجاز:
- `info`
- `warning`
- `danger`
- معنی: سطح notification
### `suggested_action`
- نوع: `string`
- معنی: اقدام پیشنهادی نهایی برای کاربر
### `source_alert_id`
- نوع: `string`
- معنی: شناسه alert مبنا که notification از آن ساخته شده
### `source_metric_type`
- نوع: `string`
- معنی: نوع metric مبنا
### `payload`
- نوع: `object`
- معنی: داده تکمیلی notification
### `is_read`
- نوع: `boolean`
- معنی: آیا notification توسط کاربر خوانده شده یا نه
### `metadata`
- نوع: `object`
- معنی: اطلاعات جانبی درباره منبع یا نحوه تولید notification
- نمونه:
- `source: farm_alerts_tracker_ai`
### `created_at`
- نوع: `string`
- معنی: زمان ایجاد notification
### `updated_at`
- نوع: `string`
- معنی: زمان آخرین به‌روزرسانی notification
## فیلدهای `structured_context`
این بخش برای debug و audit مفید است و نشان می‌دهد چه داده‌ای به مدل داده شده است.
### `structured_context.farm_profile`
- اطلاعات پایه مزرعه
فیلدهای مهم:
- `farm_uuid`: شناسه مزرعه
- `location.latitude`: عرض جغرافیایی
- `location.longitude`: طول جغرافیایی
- `plant_names`: لیست گیاه‌های ثبت‌شده
- `irrigation_method`: روش آبیاری یا `null`
- `last_sensor_update`: زمان آخرین داده سنسور
### `structured_context.tracker`
- همان داده tracker که به AI داده شده است
### `structured_context.forecasts`
- نوع: `array`
- معنی: پیش‌بینی‌های هواشناسی کوتاه‌مدت
هر آیتم:
- `date`: تاریخ پیش‌بینی
- `temperature_min`: کمینه دما
- `temperature_max`: بیشینه دما
- `humidity_mean`: میانگین رطوبت
- `precipitation`: بارش
- `et0`: تبخیر-تعرق مرجع
### `structured_context.incoming_alerts`
- نوع: `array`
- معنی: alertهایی که از request به API ارسال شده و در تحلیل استفاده شده‌اند
- اگر چیزی ارسال نشده باشد، آرایه خالی است
## تفاوت `severity` و `level`
این دو فیلد شبیه هم هستند ولی یکسان نیستند:
- `severity`: شدت داخلی alert در tracker با مقادیر `low/medium/high/critical`
- `level`: سطح نهایی notification یا status کلی با مقادیر `info/warning/danger`
به طور معمول:
- `low` معمولا به `info` نزدیک است
- `medium` معمولا به `warning` نزدیک است
- `high` و `critical` معمولا به `danger` نزدیک هستند
## نکته عملی
اگر می‌خواهید در frontend فقط پیام نهایی را نمایش دهید، معمولا این فیلدها کافی هستند:
- `data.headline`
- `data.overview`
- `data.status_level`
- `data.notifications`
اگر می‌خواهید صفحه تحلیلی یا داشبورد کامل بسازید، از این بخش‌ها هم استفاده کنید:
- `data.tracker.alerts`
- `data.tracker.alertStats`
- `data.tracker.alertClusters`
- `data.structured_context.forecasts`
@@ -0,0 +1,619 @@
# Free-Text Plan Parser APIs
این فایل برای تیم فرانت‌اند آماده شده و دو API جدید زیر را توضیح می‌دهد:
- `POST /api/irrigation/plan-from-text/`
- `POST /api/fertilization/plan-from-text/`
هدف هر دو API:
- کاربر یک متن آزاد می‌نویسد
- بک‌اند تلاش می‌کند برنامه آبیاری یا کودهی را به JSON ساختاریافته تبدیل کند
- اگر اطلاعات کامل باشد، JSON نهایی برمی‌گردد
- اگر اطلاعات ناقص باشد، API سوال‌های تکمیلی برمی‌گرداند
- فرانت‌اند سوال‌ها را از کاربر می‌پرسد و پاسخ‌ها را دوباره برای API می‌فرستد
---
## رفتار کلی هر دو API
هر دو endpoint یک flow یکسان دارند:
1. کاربر متن آزاد اولیه را می‌فرستد
2. اگر متن کامل باشد:
- `status = "completed"`
- `final_plan` برمی‌گردد
3. اگر متن ناقص باشد:
- `status = "needs_clarification"`
- `missing_fields` برمی‌گردد
- `questions` برمی‌گردد
4. فرانت‌اند پاسخ کاربر به سوال‌ها را جمع می‌کند
5. دوباره همان endpoint را با `answers` و `partial_plan` صدا می‌زند
6. این روند تا ساخته شدن `final_plan` ادامه پیدا می‌کند
---
## الگوی کلی response
هر دو API از envelope استاندارد استفاده می‌کنند:
```json
{
"code": 200,
"msg": "موفق",
"data": {}
}
```
### معنی فیلدهای envelope
| فیلد | نوع | توضیح |
|---|---|---|
| `code` | number | کد منطقی پاسخ |
| `msg` | string | پیام کوتاه پاسخ |
| `data` | object | داده اصلی API |
---
## 1) API استخراج برنامه آبیاری
### Endpoint
```http
POST /api/irrigation/plan-from-text/
```
### کاربرد
این API متن آزاد کاربر درباره برنامه آبیاری را به JSON ساختاریافته تبدیل می‌کند.
### Request Body
هر سه فیلد زیر اختیاری هستند، اما حداقل یکی از این‌ها باید ارسال شود:
- `message`
- `answers`
- `partial_plan`
#### ساختار request
```json
{
"message": "برای گوجه فرنگی با آبیاری قطره ای هر سه روز یک بار صبح زود 25 دقیقه آبیاری می کنم و حدود 18 لیتر برای هر بوته می دهم.",
"answers": {
"growth_stage": "گلدهی"
},
"partial_plan": {
"crop_name": "گوجه فرنگی",
"irrigation_method": "قطره ای"
},
"farm_uuid": "11111111-1111-1111-1111-111111111111"
}
```
### فیلدهای request
| فیلد | نوع | اجباری | توضیح |
|---|---|---:|---|
| `message` | string | خیر | متن آزاد کاربر |
| `answers` | object | خیر | پاسخ‌های تکمیلی کاربر به سوال‌هایی که قبلا API داده |
| `partial_plan` | object | خیر | خروجی مرحله قبل برای ادامه تکمیل |
| `farm_uuid` | string | خیر | برای غنی‌سازی context مزرعه در RAG |
### قانون validation
اگر هیچ‌کدام از `message`، `answers` یا `partial_plan` ارسال نشوند:
```json
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"non_field_errors": [
"حداقل یکی از message، answers یا partial_plan باید ارسال شود."
]
}
}
```
---
## پاسخ موفق - حالت تکمیل شده
وقتی همه اطلاعات لازم موجود باشد:
```json
{
"code": 200,
"msg": "موفق",
"data": {
"status": "completed",
"status_fa": "تکمیل شد",
"summary": "برنامه آبیاری برای گوجه‌فرنگی به روش قطره‌ای هر سه روز یک‌بار صبح زود به مدت 25 دقیقه اجرا می‌شود.",
"missing_fields": [],
"questions": [],
"collected_data": {
"crop_name": "گوجه‌فرنگی",
"growth_stage": "گلدهی",
"irrigation_method": "قطره‌ای",
"water_amount_per_event": "18 لیتر برای هر بوته",
"duration_minutes": 25,
"frequency_text": "هر سه روز یک‌بار",
"interval_days": 3,
"preferred_time_of_day": "صبح زود",
"start_date": "از امروز",
"target_area": "کل مزرعه",
"trigger_conditions": [],
"notes": []
},
"final_plan": {
"crop_name": "گوجه‌فرنگی",
"growth_stage": "گلدهی",
"irrigation_method": "قطره‌ای",
"water_amount_per_event": "18 لیتر برای هر بوته",
"duration_minutes": 25,
"frequency_text": "هر سه روز یک‌بار",
"interval_days": 3,
"preferred_time_of_day": "صبح زود",
"start_date": "از امروز",
"target_area": "کل مزرعه",
"trigger_conditions": [],
"notes": []
}
}
}
```
### فیلدهای `data`
| فیلد | نوع | توضیح |
|---|---|---|
| `status` | string | یکی از `completed` یا `needs_clarification` |
| `status_fa` | string | نسخه فارسی وضعیت |
| `summary` | string | خلاصه قابل نمایش برای کاربر |
| `missing_fields` | array[string] | فیلدهای ناقص |
| `questions` | array[object] | سوال‌های تکمیلی |
| `collected_data` | object | داده‌ای که تا الان از متن و جواب‌ها استخراج شده |
| `final_plan` | object/null | برنامه نهایی؛ فقط در حالت `completed` |
### فیلدهای `collected_data` و `final_plan`
| فیلد | نوع | توضیح |
|---|---|---|
| `crop_name` | string | نام محصول |
| `growth_stage` | string | مرحله رشد محصول |
| `irrigation_method` | string | روش آبیاری |
| `water_amount_per_event` | string | مقدار آب هر نوبت |
| `duration_minutes` | number | مدت هر نوبت آبیاری به دقیقه |
| `frequency_text` | string | توصیف متنی فاصله آبیاری |
| `interval_days` | number | فاصله آبیاری بر حسب روز |
| `preferred_time_of_day` | string | زمان مناسب اجرای آبیاری |
| `start_date` | string | زمان یا تاریخ شروع برنامه |
| `target_area` | string | محدوده هدف برنامه |
| `trigger_conditions` | array[string] | شرایط تریگر اختیاری |
| `notes` | array[string] | نکات تکمیلی |
---
## پاسخ موفق - حالت نیاز به سوال تکمیلی
اگر اطلاعات کامل نباشد:
```json
{
"code": 200,
"msg": "موفق",
"data": {
"status": "needs_clarification",
"status_fa": "نیازمند پرسش تکمیلی",
"summary": "اطلاعات برنامه آبیاری برای ساخت JSON نهایی کافی نیست و به چند پاسخ تکمیلی نیاز است.",
"missing_fields": [
"growth_stage",
"start_date",
"target_area"
],
"questions": [
{
"id": "growth_stage",
"field": "growth_stage",
"question": "محصول الان در چه مرحله رشدی قرار دارد؟",
"rationale": "مرحله رشد برای کامل شدن برنامه لازم است."
},
{
"id": "start_date",
"field": "start_date",
"question": "این برنامه از چه تاریخی یا از چه زمانی باید شروع شود؟",
"rationale": "زمان شروع برنامه هنوز مشخص نشده است."
},
{
"id": "target_area",
"field": "target_area",
"question": "این برنامه برای کل مزرعه است یا بخش/ناحیه خاصی از مزرعه؟",
"rationale": "محدوده اجرای برنامه باید مشخص باشد."
}
],
"collected_data": {
"crop_name": "گوجه‌فرنگی",
"growth_stage": null,
"irrigation_method": "قطره‌ای",
"water_amount_per_event": "18 لیتر برای هر بوته",
"duration_minutes": 25,
"frequency_text": "هر سه روز یک‌بار",
"interval_days": 3,
"preferred_time_of_day": "صبح زود",
"start_date": null,
"target_area": null,
"trigger_conditions": [],
"notes": []
},
"final_plan": null
}
}
```
### ساختار `questions`
| فیلد | نوع | توضیح |
|---|---|---|
| `id` | string | شناسه سوال |
| `field` | string | فیلدی که این سوال برای آن پرسیده شده |
| `question` | string | متن سوال برای نمایش به کاربر |
| `rationale` | string | توضیح کوتاه برای اینکه چرا این سوال لازم است |
---
## flow پیشنهادی فرانت‌اند برای آبیاری
### مرحله 1
کاربر متن آزاد می‌فرستد:
```json
{
"message": "برای گوجه فرنگی هر سه روز یک بار آبیاری می کنم."
}
```
### مرحله 2
اگر `status = needs_clarification` بود:
- سوال‌ها را از `data.questions` به کاربر نمایش بده
- پاسخ‌ها را جمع کن
### مرحله 3
درخواست تکمیلی بزن:
```json
{
"partial_plan": {
"crop_name": "گوجه فرنگی",
"growth_stage": null,
"irrigation_method": null,
"water_amount_per_event": null,
"duration_minutes": null,
"frequency_text": "هر سه روز یک بار",
"interval_days": 3,
"preferred_time_of_day": null,
"start_date": null,
"target_area": null,
"trigger_conditions": [],
"notes": []
},
"answers": {
"growth_stage": "گلدهی",
"irrigation_method": "قطره ای",
"water_amount_per_event": "18 لیتر برای هر بوته",
"duration_minutes": 25,
"preferred_time_of_day": "صبح زود",
"start_date": "از امروز",
"target_area": "کل مزرعه"
}
}
```
### مرحله 4
اگر `status = completed` شد:
- از `data.final_plan` به عنوان JSON نهایی استفاده کن
---
## 2) API استخراج برنامه کودهی
### Endpoint
```http
POST /api/fertilization/plan-from-text/
```
### کاربرد
این API متن آزاد کاربر درباره برنامه کودهی را به JSON ساختاریافته تبدیل می‌کند.
### Request Body
```json
{
"message": "برای گندم در مرحله پنجه زنی هر 12 روز یک بار 20-20-20 به مقدار 35 کیلوگرم در هکتار از طریق کودآبیاری می دهم.",
"answers": {
"timing": "هر 12 روز یک بار"
},
"partial_plan": {
"crop_name": "گندم"
},
"farm_uuid": "11111111-1111-1111-1111-111111111111"
}
```
### فیلدهای request
| فیلد | نوع | اجباری | توضیح |
|---|---|---:|---|
| `message` | string | خیر | متن آزاد کاربر |
| `answers` | object | خیر | پاسخ‌های تکمیلی کاربر |
| `partial_plan` | object | خیر | داده استخراج شده مرحله قبل |
| `farm_uuid` | string | خیر | برای context مزرعه |
### validation error
```json
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"non_field_errors": [
"حداقل یکی از message، answers یا partial_plan باید ارسال شود."
]
}
}
```
---
## پاسخ موفق - حالت تکمیل شده
```json
{
"code": 200,
"msg": "موفق",
"data": {
"status": "completed",
"status_fa": "تکمیل شد",
"summary": "برنامه کودهی برای گندم در مرحله پنجه زنی با کود 20-20-20 به صورت کودآبیاری هر 12 روز یک بار اجرا می شود.",
"missing_fields": [],
"questions": [],
"collected_data": {
"crop_name": "گندم",
"growth_stage": "پنجه زنی",
"objective": "تقویت رشد رویشی",
"applications": [
{
"fertilizer_name": "کود کامل 20-20-20",
"formula": "20-20-20",
"amount": "35 کیلوگرم در هکتار",
"application_method": "کودآبیاری",
"timing": "هر 12 روز یک بار",
"interval_days": 12,
"purpose": "تقویت رشد رویشی"
}
],
"notes": []
},
"final_plan": {
"crop_name": "گندم",
"growth_stage": "پنجه زنی",
"objective": "تقویت رشد رویشی",
"applications": [
{
"fertilizer_name": "کود کامل 20-20-20",
"formula": "20-20-20",
"amount": "35 کیلوگرم در هکتار",
"application_method": "کودآبیاری",
"timing": "هر 12 روز یک بار",
"interval_days": 12,
"purpose": "تقویت رشد رویشی"
}
],
"notes": []
}
}
}
```
### فیلدهای `collected_data` و `final_plan`
| فیلد | نوع | توضیح |
|---|---|---|
| `crop_name` | string | نام محصول |
| `growth_stage` | string | مرحله رشد |
| `objective` | string/null | هدف برنامه |
| `applications` | array[object] | لیست نوبت‌ها یا اقلام کودی |
| `notes` | array[string] | نکات تکمیلی |
### ساختار هر application
| فیلد | نوع | توضیح |
|---|---|---|
| `fertilizer_name` | string | نام کود |
| `formula` | string | فرمول یا آنالیز کود |
| `amount` | string | مقدار مصرف |
| `application_method` | string | روش مصرف |
| `timing` | string | زمان‌بندی مصرف |
| `interval_days` | number | فاصله بین نوبت‌ها |
| `purpose` | string/null | هدف آن نوبت |
---
## پاسخ موفق - حالت نیاز به سوال تکمیلی
```json
{
"code": 200,
"msg": "موفق",
"data": {
"status": "needs_clarification",
"status_fa": "نیازمند پرسش تکمیلی",
"summary": "اطلاعات برنامه کودهی برای ساخت JSON نهایی کافی نیست و به چند پاسخ تکمیلی نیاز است.",
"missing_fields": [
"growth_stage",
"formula",
"interval_days"
],
"questions": [
{
"id": "growth_stage",
"field": "growth_stage",
"question": "محصول الان در چه مرحله رشدی قرار دارد؟",
"rationale": "مرحله رشد برای تکمیل برنامه لازم است."
},
{
"id": "formula",
"field": "formula",
"question": "فرمول یا آنالیز کود چیست؟ مثلا 20-20-20.",
"rationale": "ترکیب دقیق کود هنوز مشخص نشده است."
},
{
"id": "interval_days",
"field": "interval_days",
"question": "فاصله بین نوبت های مصرف کود چند روز است؟",
"rationale": "عدد فاصله بین نوبت ها برای JSON نهایی لازم است."
}
],
"collected_data": {
"crop_name": "گندم",
"growth_stage": null,
"objective": null,
"applications": [
{
"fertilizer_name": "کود کامل",
"formula": null,
"amount": "35 کیلوگرم در هکتار",
"application_method": "کودآبیاری",
"timing": "هر چند وقت یک بار",
"interval_days": null,
"purpose": null
}
],
"notes": []
},
"final_plan": null
}
}
```
---
## flow پیشنهادی فرانت‌اند برای کودهی
### درخواست اولیه
```json
{
"message": "برای گندم از کود کامل استفاده می کنم."
}
```
### اگر incomplete بود
- از `questions` سوال‌ها را بگیر
- در UI نمایش بده
- پاسخ‌ها را جمع کن
### درخواست تکمیلی
```json
{
"partial_plan": {
"crop_name": "گندم",
"growth_stage": null,
"objective": null,
"applications": [
{
"fertilizer_name": "کود کامل",
"formula": null,
"amount": null,
"application_method": null,
"timing": null,
"interval_days": null,
"purpose": null
}
],
"notes": []
},
"answers": {
"growth_stage": "پنجه زنی",
"formula": "20-20-20",
"amount": "35 کیلوگرم در هکتار",
"application_method": "کودآبیاری",
"timing": "هر 12 روز یک بار",
"interval_days": 12
}
}
```
### اگر complete شد
- از `final_plan` استفاده کن
---
## نکات مهم برای فرانت‌اند
### 1. به `status` تکیه کنید
مهم‌ترین فیلد برای کنترل flow:
- `completed`
- `needs_clarification`
### 2. اگر `needs_clarification` بود
باید:
- `questions` را به کاربر نمایش دهید
- `partial_plan` را نگه دارید
- پاسخ‌های کاربر را در `answers` ارسال کنید
### 3. اگر `completed` بود
باید:
- `final_plan` را به عنوان نسخه نهایی برنامه ذخیره یا نمایش دهید
### 4. `collected_data` همیشه مهم است
حتی اگر برنامه ناقص باشد، `collected_data` نشان می‌دهد سیستم تا این لحظه چه چیزهایی را فهمیده است.
### 5. null در حالت ناقص طبیعی است
در حالت `needs_clarification` ممکن است بعضی فیلدهای `collected_data` `null` باشند.
اما در حالت `completed` نباید فیلدهای اصلی ناقص باشند.
### 6. فرانت‌اند بهتر است سوال‌ها را به صورت step-by-step بپرسد
پیشنهاد:
- سوال اول را نشان بده
- جواب را بگیر
- همه جواب‌ها را در `answers` جمع کن
- دوباره API را صدا بزن
---
## جمع‌بندی تفاوت دو API
| API | موضوع | خروجی نهایی |
|---|---|---|
| `/api/irrigation/plan-from-text/` | استخراج برنامه آبیاری | `final_plan` با ساختار آبیاری |
| `/api/fertilization/plan-from-text/` | استخراج برنامه کودهی | `final_plan` با ساختار کودهی |
---
## مسیر فایل
این داکیومنت در این مسیر ذخیره شده:
`docs/irrigation_fertilization_plan_parser_apis.md`
@@ -0,0 +1,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,371 @@
# ساختار فعلی `location_data`
این فایل وضعیت فعلی اپ `location_data` را توضیح می‌دهد؛ هم از نظر ساختار فایل‌ها و هم از نظر مدل‌ها، APIها و جریان داده.
## هدف فعلی اپ
اپ `location_data` فعلاً این مسئولیت‌ها را دارد:
- نگه‌داری موقعیت زمین با `lat` و `lon`
- نگه‌داری مرز مزرعه در `farm_boundary`
- نگه‌داری ساختار بلوک‌های زمین در `block_layout`
- نگه‌داری داده‌های خاک برای عمق‌های مختلف در `SoilDepthData`
- نگه‌داری مشاهدات NDVI در `NdviObservation`
- برگرداندن ساختار بلوک‌های زمین از API محلی بدون نیاز به API خارجی در فاز فعلی
نکته مهم:
- در فاز فعلی، endpoint اصلی `location_data` برای ساختار زمین، فقط داده را در دیتابیس می‌خواند/ذخیره می‌کند.
- فعلاً برای بلوک‌ها، زیر‌بلوک‌ها و داده‌های ماهواره‌ای هیچ درخواست خارجی زده نمی‌شود.
## ساختار فایل‌ها
```text
location_data/
├── admin.py
├── apps.py
├── models.py
├── serializers.py
├── views.py
├── urls.py
├── tasks.py
├── soil_adapters.py
├── remote_sensing.py
├── ndvi.py
├── test_soil_api.py
├── test_soil_adapters.py
├── test_ndvi_health_api.py
├── postman/
│ └── soil_data.json
├── management/
│ └── commands/
└── migrations/
├── 0001_initial.py
├── 0002_soildepthdata_refactor.py
├── 0002_soillocation_ideal_sensor_profile.py
├── 0003_rename_app_label.py
├── 0004_soillocation_farm_boundary.py
├── 0005_merge_20260327_0840.py
├── 0006_remove_soillocation_ideal_sensor_profile.py
├── 0007_ndviobservation.py
└── 0008_soillocation_block_layout.py
```
## مدل‌ها و جدول‌ها
### 1) `SoilLocation`
مدل اصلی اپ است و نماینده یک موقعیت یکتا برای زمین یا مرکز زمین محسوب می‌شود.
فیلدهای اصلی:
| فیلد | نوع | توضیح |
|---|---|---|
| `id` | `BigAutoField` | شناسه داخلی رکورد |
| `latitude` | `DecimalField(9,6)` | عرض جغرافیایی |
| `longitude` | `DecimalField(9,6)` | طول جغرافیایی |
| `task_id` | `CharField` | شناسه تسک Celery برای جریان قدیمی واکشی خاک |
| `farm_boundary` | `JSONField` | مرز مزرعه به شکل Polygon یا corners |
| `input_block_count` | `PositiveIntegerField` | تعداد بلوک اولیه‌ای که از ورودی کشاورز می‌آید |
| `block_layout` | `JSONField` | ساختار بلوک‌ها و زیر‌بلوک‌های زمین |
| `created_at` | `DateTimeField` | زمان ایجاد |
| `updated_at` | `DateTimeField` | زمان آخرین تغییر |
قیدها:
- روی ترکیب `latitude` و `longitude` یکتا است.
رفتار مهم:
- اگر `input_block_count` ارسال نشود، مقدار پیش‌فرض `1` است.
- اگر `block_layout` خالی باشد، به صورت خودکار با یک بلوک کامل ساخته می‌شود.
- متد `set_input_block_count()` ساختار اولیه بلوک‌ها را می‌سازد.
### 2) `SoilDepthData`
این مدل داده‌های خاک را برای هر عمق نگه می‌دارد و به `SoilLocation` وصل است.
عمق‌های فعلی:
- `0-5cm`
- `5-15cm`
- `15-30cm`
فیلدهای مهم:
| فیلد | نوع | توضیح |
|---|---|---|
| `soil_location` | `ForeignKey` | ارتباط با `SoilLocation` |
| `depth_label` | `CharField` | برچسب عمق |
| `bdod` تا `wv1500` | `FloatField` | پارامترهای مختلف خاک |
| `created_at` | `DateTimeField` | زمان ثبت رکورد |
قیدها:
- برای هر `soil_location` و هر `depth_label` فقط یک رکورد وجود دارد.
### 3) `NdviObservation`
این مدل برای ذخیره مشاهده‌های NDVI استفاده می‌شود.
فیلدهای مهم:
| فیلد | نوع | توضیح |
|---|---|---|
| `location` | `ForeignKey` | ارتباط با `SoilLocation` |
| `observation_date` | `DateField` | تاریخ مشاهده |
| `mean_ndvi` | `FloatField` | میانگین NDVI |
| `ndvi_map` | `JSONField` | داده مکانی NDVI |
| `vegetation_health_class` | `CharField` | کلاس سلامت پوشش گیاهی |
| `satellite_source` | `CharField` | منبع تصویر ماهواره‌ای |
| `cloud_cover` | `FloatField` | درصد ابر |
| `metadata` | `JSONField` | داده تکمیلی |
## ساختار `block_layout`
فیلد `block_layout` فعلاً ساختار پایه تقسیم زمین را نگه می‌دارد.
نمونه پیش‌فرض وقتی کل زمین یک بلوک باشد:
```json
{
"input_block_count": 1,
"default_full_farm": true,
"algorithm_status": "pending",
"blocks": [
{
"block_code": "block-1",
"order": 1,
"source": "default",
"needs_subdivision": null,
"sub_blocks": []
}
]
}
```
نمونه وقتی ورودی مثلاً `block_count = 3` باشد:
```json
{
"input_block_count": 3,
"default_full_farm": false,
"algorithm_status": "pending",
"blocks": [
{
"block_code": "block-1",
"order": 1,
"source": "input",
"needs_subdivision": null,
"sub_blocks": []
},
{
"block_code": "block-2",
"order": 2,
"source": "input",
"needs_subdivision": null,
"sub_blocks": []
},
{
"block_code": "block-3",
"order": 3,
"source": "input",
"needs_subdivision": null,
"sub_blocks": []
}
]
}
```
معنای فیلدها:
| فیلد | توضیح |
|---|---|
| `input_block_count` | تعداد بلوک اولیه |
| `default_full_farm` | آیا کل زمین هنوز یک بلوک کامل است یا نه |
| `algorithm_status` | وضعیت اجرای الگوریتم تقسیم‌بندی |
| `blocks` | لیست بلوک‌های فعلی |
| `block_code` | کد بلوک |
| `order` | ترتیب بلوک |
| `source` | منشأ بلوک: `default` یا `input` |
| `needs_subdivision` | آیا الگوریتم تشخیص داده که این بلوک باید خردتر شود یا نه |
| `sub_blocks` | لیست زیر‌بلوک‌ها |
## Serializerها
### `SoilDataRequestSerializer`
ورودی endpoint اصلی `location_data`:
| فیلد | اجباری | توضیح |
|---|---|---|
| `lat` | بله | عرض جغرافیایی |
| `lon` | بله | طول جغرافیایی |
| `block_count` | خیر | تعداد بلوک اولیه، پیش‌فرض `1` |
### `SoilLocationResponseSerializer`
خروجی اصلی برای یک location:
- `id`
- `lat`
- `lon`
- `input_block_count`
- `block_layout`
- `depths`
### `SoilDepthDataSerializer`
لیست پارامترهای خاک برای هر عمق را برمی‌گرداند.
### `NdviHealthRequestSerializer` و `NdviHealthResponseSerializer`
برای endpoint مربوط به NDVI استفاده می‌شوند.
## Viewها و APIها
### 1) `SoilDataView`
مسیر:
- `GET /api/soil-data/`
- `POST /api/soil-data/`
وظیفه فعلی:
- گرفتن `lat` و `lon`
- گرفتن `block_count` در صورت وجود
- ساخت یا پیدا کردن `SoilLocation`
- ذخیره `input_block_count`
- ساخت `block_layout`
- برگرداندن پاسخ با `source = local`
رفتار فعلی:
- اگر location وجود نداشته باشد، ساخته می‌شود.
- اگر `block_count` تغییر کند، ساختار `block_layout` دوباره ساخته می‌شود.
- فعلاً هیچ fetch خارجی برای اطلاعات خاک یا ماهواره‌ای انجام نمی‌شود.
### 2) `SoilDataTaskStatusView`
مسیر:
- `GET /api/soil-data/tasks/<task_id>/status/`
وضعیت فعلی:
- هنوز در کد وجود دارد.
- برای جریان قدیمی مبتنی بر Celery طراحی شده است.
- با تغییر اخیر، endpoint اصلی `location_data` دیگر به‌طور پیش‌فرض task جدیدی صف نمی‌کند.
### 3) `NdviHealthView`
مسیر:
- `POST /api/soil-data/ndvi-health/`
وظیفه:
- دریافت `farm_uuid`
- خواندن داده NDVI از سرویس داخلی NDVI
- برگرداندن اطلاعات سلامت پوشش گیاهی
## فایل `tasks.py`
این فایل هنوز منطق قدیمی واکشی داده خاک را نگه می‌دارد.
اجزای اصلی:
- `fetch_soil_data_for_coordinates()`
- `fetch_soil_data_task()`
نکته:
- این بخش هنوز برای سازگاری و جریان‌های قدیمی در پروژه باقی مانده است.
- ولی در فاز فعلی تقسیم بلوک‌ها، از این task برای endpoint اصلی `location_data` استفاده نمی‌شود.
## فایل `soil_adapters.py`
این فایل abstraction مربوط به تامین داده خاک را نگه می‌دارد.
کاربرد آن:
- mock provider
- live soil provider
- ساختار depth-based data fetch
در وضعیت فعلی:
- برای منطق بلوک‌بندی فعلی لازم نیست.
- اما برای جریان قدیمی یا مراحل بعدی می‌تواند دوباره استفاده شود.
## فایل `remote_sensing.py`
این فایل مربوط به منطق سنجش‌ازدور و داده‌های ماهواره‌ای است.
در وضعیت فعلی:
- برای block layout فعلاً استفاده فعال ندارد.
- بعداً می‌تواند برای تحلیل هر بلوک یا زیر‌بلوک استفاده شود.
## فایل `ndvi.py`
این فایل سرویس/منطق NDVI را نگه می‌دارد و برای endpoint NDVI استفاده می‌شود.
## migrationها
مهم‌ترین migrationهای فعلی:
| migration | توضیح |
|---|---|
| `0001_initial.py` | ساختار اولیه `SoilLocation` |
| `0002_soildepthdata_refactor.py` | جداسازی داده‌های عمقی در `SoilDepthData` |
| `0004_soillocation_farm_boundary.py` | اضافه شدن `farm_boundary` |
| `0007_ndviobservation.py` | اضافه شدن `NdviObservation` |
| `0008_soillocation_block_layout.py` | اضافه شدن `input_block_count` و `block_layout` |
## تست‌ها
فایل‌های تست اصلی:
- `location_data/test_soil_api.py`
- تست ساختار محلی بلوک‌ها
- تست پیش‌فرض یک بلوک
- تست تغییر `block_count`
- `location_data/test_soil_adapters.py`
- تست adapterهای خاک
- تست ذخیره depth data
- `location_data/test_ndvi_health_api.py`
- تست endpoint NDVI
## ارتباط با `farm_data`
`location_data` مستقیماً توسط `farm_data` استفاده می‌شود.
نمونه وابستگی‌ها:
- `farm_data` از `SoilLocation` به عنوان `center_location` استفاده می‌کند.
- `farm_boundary` از سمت `farm_data` می‌آید.
- `block_count` هم از ورودی `farm_data` قابل ثبت است.
- `farm_data` فعلاً فقط location و block layout را ذخیره می‌کند و برای این بخش sync خارجی انجام نمی‌دهد.
## جمع‌بندی ساختار فعلی
الان `location_data` دو لایه دارد:
1. لایه فعلی فعال برای بلوک‌بندی زمین
- محلی
- ساده
- بدون API خارجی
- با `input_block_count` و `block_layout`
2. لایه قدیمی/جانبی برای خاک و NDVI
- `SoilDepthData`
- `tasks.py`
- `soil_adapters.py`
- `NdviObservation`
- `remote_sensing.py`
یعنی از نظر معماری، اپ الان هم داده مکانی زمین را نگه می‌دارد و هم زیرساختی برای تحلیل خاک/NDVI دارد، ولی منطق جدید بلوک‌ها فعلاً مستقل و محلی پیاده شده است.
@@ -0,0 +1,685 @@
# مستند کامل عملکرد فعلی `location_data`
این فایل شرح می‌دهد که اپ `location_data` در وضعیت فعلی دقیقاً چه کاری انجام می‌دهد، چه مدل‌هایی دارد، جریان درخواست‌ها چگونه است، منطق تقسیم‌بندی بلوک‌ها چگونه اجرا می‌شود و چه بخش‌هایی فقط داده ذخیره‌شده را برمی‌گردانند.
---
## 1) هدف فعلی اپ `location_data`
اپ `location_data` در وضعیت فعلی چند مسئولیت اصلی دارد:
- نگه‌داری موقعیت جغرافیایی زمین با `lat` و `lon`
- نگه‌داری مرز زمین یا بلوک در `farm_boundary`
- نگه‌داری ساختار بلوک‌های اصلی زمین در `block_layout`
- نگه‌داری نتیجه خردسازی هوشمند هر بلوک در مدل `BlockSubdivision`
- تولید نقاط شبکه‌ای 100 متری یا هر اندازه‌ای که با `SUBDIVISION_CHUNK_SQM` تنظیم شود
- اجرای خوشه‌بندی `KMeans` روی نقاط شبکه‌ای
- پیدا کردن تعداد بهینه خوشه‌ها با روش `Elbow`
- ذخیره centroidهای نهایی هر بخش خردشده
- تولید و ذخیره تصویر نمودار `K-SSE` برای هر subdivision
- نگه‌داری داده‌های خاک در `SoilDepthData`
- نگه‌داری داده‌های NDVI در `NdviObservation`
نکته مهم:
- در فاز فعلی، `GET` هیچ پردازش جدیدی انجام نمی‌دهد.
- تمام پردازش subdivision فقط در زمان `POST` و فقط اگر subdivision آن بلوک قبلاً ساخته نشده باشد اجرا می‌شود.
---
## 2) تنظیمات محیطی
### `SUBDIVISION_CHUNK_SQM`
در `config/settings.py` یک متغیر جدید اضافه شده است:
- `SUBDIVISION_CHUNK_SQM`
- مقدار پیش‌فرض: `100`
- واحد: متر مربع
کاربرد:
- تعیین می‌کند شبکه اولیه برای subdivision با چه اندازه‌ای ساخته شود.
- اگر مقدار `100` باشد، هر chunk تقریباً یک سلول `10m x 10m` خواهد بود، چون:
```text
step = sqrt(100) = 10 meters
```
این مقدار از `.env` یا environment خوانده می‌شود:
```env
SUBDIVISION_CHUNK_SQM=100
```
---
## 3) مدل‌های اصلی اپ
## 3.1) `SoilLocation`
این مدل رکورد اصلی location را نگه می‌دارد.
### فیلدها
- `latitude`
- `longitude`
- `task_id`
- `farm_boundary`
- `input_block_count`
- `block_layout`
- `created_at`
- `updated_at`
### نقش
- هر location با ترکیب `latitude + longitude` یکتا است.
- اطلاعات کلی زمین یا مرکز زمین را نگه می‌دارد.
- اگر هنوز هیچ تقسیم‌بندی انجام نشده باشد، ساختار اولیه بلوک‌ها را در `block_layout` نگه می‌دارد.
### `block_layout`
این فیلد JSON ساختار بلوک‌ها را نگه می‌دارد. نمونه ساده:
```json
{
"input_block_count": 1,
"default_full_farm": true,
"algorithm_status": "completed",
"blocks": [
{
"block_code": "block-1",
"order": 1,
"source": "default",
"needs_subdivision": true,
"sub_blocks": [
{
"sub_block_code": "sub-block-1",
"centroid_lat": 35.689123,
"centroid_lon": 51.389456
}
],
"subdivision_summary": {
"chunk_size_sqm": 100,
"grid_point_count": 24,
"centroid_count": 3,
"optimal_k": 3
}
}
]
}
```
### رفتار مهم
- اگر `block_layout` خالی باشد، به صورت پیش‌فرض با یک بلوک کامل ساخته می‌شود.
- متد `set_input_block_count()` ساختار اولیه بلوک‌های اصلی را می‌سازد.
---
## 3.2) `BlockSubdivision`
این مدل نتیجه واقعی subdivision برای هر بلوک را ذخیره می‌کند.
### فیلدها
- `soil_location`: ارتباط با `SoilLocation`
- `block_code`: شناسه بلوکی که subdivision روی آن اجرا شده
- `source_boundary`: مرز همان بلوک
- `chunk_size_sqm`: اندازه هر chunk
- `grid_points`: نقاط اولیه شبکه
- `centroid_points`: centroidهای نهایی خوشه‌ها
- `grid_point_count`: تعداد نقاط اولیه
- `centroid_count`: تعداد centroidهای نهایی
- `elbow_plot`: تصویر نمودار elbow
- `status`: وضعیت رکورد
- `metadata`: داده تکمیلی مانند `optimal_k` و `inertia_curve`
- `created_at`
- `updated_at`
### نقش
این مدل منبع اصلی داده subdivision است.
یعنی:
- نقاط خام شبکه در این مدل ذخیره می‌شوند
- centroidهای نهایی هم در این مدل ذخیره می‌شوند
- نمودار elbow هم در همین مدل ذخیره می‌شود
### قید یکتا
برای هر location و هر `block_code` فقط یک subdivision وجود دارد:
```text
(soil_location, block_code) unique
```
بنابراین اگر برای یک بلوک قبلاً subdivision ساخته شده باشد، دوباره ایجاد نمی‌شود.
---
## 3.3) `SoilDepthData`
این مدل داده‌های خاک برای عمق‌های مختلف را نگه می‌دارد.
عمق‌های فعلی:
- `0-5cm`
- `5-15cm`
- `15-30cm`
این بخش در حال حاضر مستقل از subdivision است و هنوز برای هر sub-block جداگانه داده خاک تولید نمی‌کند.
---
## 3.4) `NdviObservation`
این مدل داده‌های NDVI و سلامت پوشش گیاهی را نگه می‌دارد.
این بخش هم فعلاً مستقل از منطق subdivision است.
---
## 4) فایل `block_subdivision.py`
فایل `location_data/block_subdivision.py` مرکز اصلی منطق هوشمند subdivision است.
### وظایف اصلی این فایل
- استخراج polygon از ورودی
- تبدیل مختصات جغرافیایی به صفحه محلی متری
- ساخت grid points با اندازه chunk مشخص
- اجرای `KMeans` برای `K=1..10`
- ذخیره `SSE` یا همان `Inertia`
- پیدا کردن elbow point
- ساخت centroidهای نهایی خوشه‌ها
- sync کردن نتیجه با `block_layout`
- تولید تصویر نمودار elbow
- ذخیره تصویر در مدل با `ContentFile`
---
## 5) روند هندسی subdivision
## 5.1) استخراج Polygon
ورودی boundary می‌تواند به چند شکل بیاید:
- GeoJSON Polygon
- `corners`
- آرایه مستقیم از نقاط
تابع `extract_polygon()` این ورودی را به لیستی از نقاط جغرافیایی تبدیل می‌کند.
نمونه ورودی معتبر:
```json
{
"type": "Polygon",
"coordinates": [
[
[51.3890, 35.6890],
[51.3902, 35.6890],
[51.3902, 35.6900],
[51.3890, 35.6900],
[51.3890, 35.6890]
]
]
}
```
---
## 5.2) تبدیل مختصات به فضای محلی متری
برای اینکه بتوانیم فاصله‌ها و گریدبندی را بر اساس متر حساب کنیم، polygon از مختصات جغرافیایی به مختصات محلی متری تبدیل می‌شود.
تابع مربوط:
- `project_polygon_to_local_meters()`
ویژگی این تبدیل:
- نقطه اول polygon به عنوان origin در نظر گرفته می‌شود
- با تقریب محلی، `lat/lon` به `x/y` در واحد متر تبدیل می‌شوند
این تبدیل برای subdivision کوچک و محلی مناسب است.
---
## 5.3) تولید grid points
تابع:
- `generate_grid_points()`
منطق:
1. ابتدا اندازه گام محاسبه می‌شود:
```text
step_m = sqrt(chunk_size_sqm)
```
2. روی bounding box polygon، نقاط مرکزی grid بررسی می‌شوند.
3. هر نقطه‌ای که داخل polygon باشد نگه داشته می‌شود.
خروجی:
- `grid_points`: مختصات جغرافیایی قابل ذخیره در JSON
- `grid_vectors`: مختصات محلی متری برای ورود به `KMeans`
نمونه هر grid point:
```json
{
"point_code": "pt-1",
"lat": 35.689123,
"lon": 51.389456
}
```
---
## 6) الگوریتم خوشه‌بندی هوشمند
## 6.1) اجرای `KMeans`
تابع:
- `cluster_grid_points()`
منطق:
- روی `grid_vectors` خوشه‌بندی انجام می‌شود
- برای `K=1` تا `K=10` اجرا می‌شود
- اگر تعداد نقاط کمتر از 10 باشد، `max_k = len(grid_vectors)` در نظر گرفته می‌شود
برای هر `K`:
- مدل `KMeans` ساخته می‌شود
- `fit()` اجرا می‌شود
- مقدار `model.inertia_` به عنوان `SSE` ذخیره می‌شود
خروجی میانی:
```json
[
{"k": 1, "sse": 1300.5},
{"k": 2, "sse": 640.2},
{"k": 3, "sse": 390.1}
]
```
---
## 6.2) پیدا کردن Elbow Point
تابع:
- `detect_elbow_point()`
منطق فعلی:
1. از روی SSEها، شیب افت بین نقاط متوالی محاسبه می‌شود.
2. سپس تغییرات شیب محاسبه می‌شود.
3. هر جایی که افت شیب ناگهان متوقف شود، همان نقطه elbow در نظر گرفته می‌شود.
یعنی در عمل:
- ابتدا `slopes` محاسبه می‌شود
- سپس اختلاف شیب‌ها بررسی می‌شود
- بیشترین تغییر شیب به عنوان elbow انتخاب می‌شود
خروجی:
- `optimal_k`
---
## 6.3) تولید centroidهای نهایی
بعد از پیدا شدن `optimal_k`:
- مدل `KMeans` همان `K` نهایی انتخاب می‌شود
- مختصات مراکز خوشه‌ها (`cluster_centers_`) گرفته می‌شود
- از فضای متری به `lat/lon` تبدیل می‌شود
- در `centroid_points` ذخیره می‌شود
نمونه centroid:
```json
{
"sub_block_code": "sub-block-1",
"centroid_lat": 35.689321,
"centroid_lon": 51.389789
}
```
این centroidها در عمل همان مراکز بخش‌های کوچکتر زمین هستند.
---
## 7) تولید و ذخیره نمودار Elbow
### تابع
- `render_elbow_plot()`
### منطق
پس از محاسبه `inertia_curve` و `optimal_k`:
1. نمودار `K` در برابر `SSE` رسم می‌شود
2. نقطه elbow با رنگ قرمز مشخص می‌شود
3. تصویر به صورت PNG در `BytesIO` ذخیره می‌شود
4. با `ContentFile` به `ImageField` مدل `BlockSubdivision` داده می‌شود
### نکته مهم حافظه
برای جلوگیری از memory leak:
- از backend غیرتعاملی `Agg` استفاده می‌شود
- بعد از ذخیره تصویر، `plt.close(fig)` اجرا می‌شود
- buffer هم بسته می‌شود
این برای پردازش‌های همزمان سرور ضروری است.
---
## 8) جریان کامل `POST /api/soil-data/`
این endpoint الان مهم‌ترین ورودی subdivision است.
### ورودی‌های قابل پشتیبانی
- `lat`
- `lon`
- `block_count`
- `block_code`
- `farm_boundary`
### سناریوی اجرا
#### مرحله 1: اعتبارسنجی ورودی
سریالایزر `SoilDataRequestSerializer` داده را validate می‌کند.
#### مرحله 2: پیدا کردن یا ساخت location
بر اساس `lat/lon`:
- اگر location وجود نداشته باشد ساخته می‌شود
- اگر وجود داشته باشد از همان رکورد استفاده می‌شود
#### مرحله 3: آپدیت ساختار اولیه بلوک‌ها
اگر `block_count` فرق کرده باشد:
- `block_layout` دوباره با `set_input_block_count()` ساخته می‌شود
#### مرحله 4: انتخاب boundary برای subdivision
اولویت:
1. `farm_boundary` ارسالی در request
2. اگر نبود، `location.farm_boundary` ذخیره‌شده
#### مرحله 5: اجرای subdivision فقط در صورت نیاز
تابع:
- `create_or_get_block_subdivision()`
اگر رکورد `(location, block_code)` از قبل وجود داشته باشد:
- هیچ پردازش جدیدی اجرا نمی‌شود
- همان رکورد قبلی برگردانده می‌شود
اگر وجود نداشته باشد:
- grid ساخته می‌شود
- KMeans اجرا می‌شود
- elbow پیدا می‌شود
- centroidها ساخته می‌شوند
- نمودار elbow ساخته می‌شود
- همه چیز در `BlockSubdivision` ذخیره می‌شود
- `block_layout` با `sub_blocks` sync می‌شود
#### مرحله 6: response
خروجی شامل این‌هاست:
- اطلاعات `SoilLocation`
- `farm_boundary`
- `block_layout`
- `block_subdivisions`
- `depths`
فیلد `source` در response:
- `created` اگر location یا subdivision جدید ساخته شده باشد
- `database` اگر قبلاً وجود داشته باشد
---
## 9) جریان کامل `GET /api/soil-data/`
این endpoint الان فقط برای read استفاده می‌شود.
### ورودی
- `lat`
- `lon`
- `block_code` اختیاری
### رفتار
- location را از دیتابیس پیدا می‌کند
- subdivisionهای ذخیره‌شده را می‌خواند
- هیچ الگوریتمی را اجرا نمی‌کند
- هیچ `KMeans` یا پردازش هندسی انجام نمی‌دهد
### پاسخ
داده ذخیره‌شده را با `source = database` برمی‌گرداند.
اگر location پیدا نشود:
- `404`
---
## 10) نقش `serializers.py`
### `SoilDataRequestSerializer`
ورودی endpoint اصلی را مدیریت می‌کند:
- `lat`
- `lon`
- `block_count`
- `block_code`
- `farm_boundary`
### `SoilLocationResponseSerializer`
خروجی location را برمی‌گرداند:
- `id`
- `lat`
- `lon`
- `input_block_count`
- `farm_boundary`
- `block_layout`
- `block_subdivisions`
- `depths`
### `BlockSubdivisionSerializer`
خروجی subdivision را برمی‌گرداند:
- `block_code`
- `chunk_size_sqm`
- `grid_points`
- `centroid_points`
- `grid_point_count`
- `centroid_count`
- `elbow_plot`
- `status`
- `metadata`
- `created_at`
- `updated_at`
---
## 11) نقش `block_layout` در کنار `BlockSubdivision`
در معماری فعلی دو سطح ذخیره‌سازی داریم:
### 11.1) `BlockSubdivision`
منبع اصلی و canonical برای subdivision
### 11.2) `block_layout`
خلاصه‌ای از نتیجه subdivision برای مصرف سریع‌تر در response و ساختار کلی location
یعنی:
- داده دقیق در `BlockSubdivision` است
- خلاصه آن در `block_layout.blocks[].sub_blocks` قرار می‌گیرد
---
## 12) وضعیت فعلی بخش‌های قدیمی‌تر اپ
## 12.1) `tasks.py`
این فایل هنوز وجود دارد و برای fetch داده خاک به صورت قدیمی استفاده می‌شود، اما در مسیر subdivision فعلی نقشی ندارد.
## 12.2) `soil_adapters.py`
این فایل adapterهای داده خاک را نگه می‌دارد و فعلاً برای subdivision استفاده نمی‌شود.
## 12.3) `remote_sensing.py`
منطق سنجش‌ازدور را نگه می‌دارد و هنوز مستقیماً به subdivision وصل نشده است.
## 12.4) `ndvi.py`
برای endpoint مربوط به NDVI استفاده می‌شود و فعلاً از centroidهای subdivision استفاده نمی‌کند.
---
## 13) وابستگی‌های جدید
برای عملکرد فعلی subdivision این dependencyها لازم هستند:
- `scikit-learn`
- `matplotlib`
- `Pillow`
- `numpy`
### دلیل هرکدام
- `scikit-learn`: اجرای `KMeans`
- `matplotlib`: رسم elbow plot
- `Pillow`: پشتیبانی از `ImageField`
- `numpy`: وابستگی پایه `scikit-learn`
---
## 14) migrationهای مهم مرتبط با ساختار فعلی
- `0008_soillocation_block_layout.py`
- اضافه شدن `input_block_count`
- اضافه شدن `block_layout`
- `0009_blocksubdivision.py`
- اضافه شدن مدل `BlockSubdivision`
- `0010_blocksubdivision_elbow_plot.py`
- اضافه شدن فیلد `elbow_plot`
---
## 15) محدودیت‌های فعلی
چند محدودیت مهم در پیاده‌سازی فعلی وجود دارد:
- subdivision فعلاً بر اساس هندسه و خوشه‌بندی نقاط انجام می‌شود، نه بر اساس داده واقعی خاک یا NDVI
- برای هر `block_code` فرض می‌شود یک مرز مستقل از بیرون داده می‌شود
- هنوز برای هر `sub_block` رکورد location مستقل ساخته نمی‌شود
- هنوز داده خاک، هوا و NDVI برای centroidهای جدید به صورت جداگانه fetch نمی‌شود
- elbow detection فعلی heuristic-based است و هنوز نسخه پیشرفته‌تر آماری ندارد
---
## 16) تست‌های مرتبط
### `location_data/test_block_subdivision.py`
این تست‌ها بررسی می‌کنند:
- elbow detection کار می‌کند
- payload subdivision ساخته می‌شود
- grid points و centroid points خروجی دارند
### `location_data/test_soil_api.py`
این تست‌ها بررسی می‌کنند:
- `POST` subdivision جدید می‌سازد
- `GET` فقط داده ذخیره‌شده را برمی‌گرداند
- الگوریتم در `GET` دوباره اجرا نمی‌شود
---
## 17) جمع‌بندی معماری فعلی
در وضعیت فعلی، `location_data` این معماری را دارد:
### لایه 1: Location پایه
- `SoilLocation`
- `farm_boundary`
- `block_layout`
### لایه 2: Subdivision هوشمند
- `BlockSubdivision`
- grid generation
- KMeans
- elbow detection
- centroid generation
- elbow plot generation
### لایه 3: داده‌های مکمل
- `SoilDepthData`
- `NdviObservation`
- بخش‌های legacy مثل `tasks.py`
در نتیجه، اپ الان می‌تواند:
- یک بلوک با مرز مشخص بگیرد
- آن را به نقاط شبکه‌ای خرد کند
- تعداد بهینه بخش‌ها را با KMeans + Elbow پیدا کند
- centroidهای نهایی را ذخیره کند
- نمودار elbow را ذخیره کند
- و در درخواست‌های بعدی فقط همان نتیجه ذخیره‌شده را بدون پردازش مجدد برگرداند
---
## 18) پیشنهاد برای مراحل بعدی
اگر در مرحله بعد بخواهی این ساختار را توسعه بدهی، منطقی‌ترین قدم‌ها این‌ها هستند:
1. ساخت endpoint مستقل برای subdivision هر block
2. اتصال هر centroid به fetch داده خاک و هوا
3. ساخت رکورد مستقل برای هر `sub_block`
4. استفاده از NDVI یا داده سنسور برای تعیین `K` یا وزن‌دهی خوشه‌ها
5. نمایش مستقیم `elbow_plot` با URL کامل media
@@ -0,0 +1,972 @@
# مستند کامل اپ `location_data`
این سند، وضعیت فعلی اپ `location_data` را به صورت کامل توضیح می‌دهد:
- مدل‌های داده
- منطق business
- جریان ساخت location و block
- subdivision و خوشه‌بندی
- تولید analysis grid
- سنجش‌ازدور با openEO
- تسک‌های Celery
- APIهای فعلی
- ساختار responseها
- محدودیت‌ها و فرضیات فعلی
این فایل بر اساس کد فعلی پروژه نوشته شده است و هدفش این است که یک مرجع فنی برای توسعه‌دهنده‌های بعدی باشد.
---
## 1) هدف اپ `location_data`
اپ `location_data` در وضعیت فعلی چند نقش اصلی دارد:
1. نگه‌داری موقعیت جغرافیایی زمین با `lat/lon`
2. نگه‌داری مرز زمین یا مرز blockها
3. ساخت ساختار blockهای مزرعه
4. اجرای subdivision برای blockها
5. تولید grid analysis با ابعاد 30x30 متر
6. نگه‌داری نتایج سنجش‌ازدور روی هر grid cell
7. نگه‌داری داده‌های خاک و NDVI سنتی
8. فراهم کردن API برای:
- location data
- subdivision
- remote sensing trigger/result
- NDVI health
به صورت خلاصه، `location_data` الان فقط یک جدول مختصات نیست؛ بلکه هاب مکانی پروژه است.
---
## 2) مفاهیم اصلی دامنه
### 2.1) SoilLocation
`SoilLocation` نماینده یک location اصلی برای یک مزرعه یا مرکز زمین است.
این مدل:
- مختصات `latitude` و `longitude` را نگه می‌دارد
- `farm_boundary` را ذخیره می‌کند
- تعداد blockهای اولیه را نگه می‌دارد
- `block_layout` را نگه می‌دارد
- مبنای ارتباط با:
- `SoilDepthData`
- `BlockSubdivision`
- `AnalysisGridCell`
- `RemoteSensingRun`
- `NdviObservation`
---
### 2.2) BlockSubdivision
`BlockSubdivision` نتیجه خردسازی یک block است.
این مدل نگه می‌دارد:
- block code
- مرز همان block
- chunk size برای subdivision
- grid points اولیه
- centroid points نهایی
- elbow plot
- metadata الگوریتم
این مدل برای مرحله‌ای است که یک block را به بخش‌های کوچک‌تر تقسیم می‌کنیم.
---
### 2.3) AnalysisGridCell
`AnalysisGridCell` سلول‌های 30x30 متری تحلیل سنجش‌ازدور را نگه می‌دارد.
هر cell:
- به یک `SoilLocation` وصل است
- در صورت نیاز به یک `BlockSubdivision` وصل است
- یک `cell_code` یکتا دارد
- geometry خودش را به صورت Polygon نگه می‌دارد
- centroid خودش را نگه می‌دارد
این مدل واحد اصلی تحلیل remote sensing است.
---
### 2.4) AnalysisGridObservation
`AnalysisGridObservation` داده زمانی هر سلول را نگه می‌دارد.
برای هر cell و بازه زمانی:
- `ndvi`
- `ndwi`
- `lst_c`
- `soil_vv`
- `soil_vv_db`
- `dem_m`
- `slope_deg`
ذخیره می‌شود.
این مدل cache دیتابیسی اصلی برای نتایج openEO است.
---
### 2.5) RemoteSensingRun
`RemoteSensingRun` وضعیت یک اجرای async سنجش‌ازدور را نگه می‌دارد.
این مدل:
- به `SoilLocation` وصل است
- optionally به `BlockSubdivision` وصل است
- `block_code` و بازه زمانی را نگه می‌دارد
- status execution را نگه می‌دارد
- metadata مربوط به task/backend/result summary را نگه می‌دارد
این مدل برای tracking jobها در Celery استفاده می‌شود.
---
### 2.6) SoilDepthData
این مدل داده‌های خاک را در عمق‌های مختلف نگه می‌دارد:
- `0-5cm`
- `5-15cm`
- `15-30cm`
---
### 2.7) NdviObservation
این مدل نگه‌دارنده NDVI سنتی است که جدا از workflow جدید openEO هم هنوز وجود دارد.
---
## 3) ساختار فایل‌های مهم اپ
```text
location_data/
├── admin.py
├── apps.py
├── block_subdivision.py
├── grid_analysis.py
├── models.py
├── ndvi.py
├── openeo_service.py
├── remote_sensing.py
├── serializers.py
├── soil_adapters.py
├── tasks.py
├── urls.py
├── views.py
├── migrations/
└── tests...
```
### نقش فایل‌ها
- `models.py`: مدل‌های اصلی
- `serializers.py`: serializerهای API
- `views.py`: endpointهای DRF
- `urls.py`: routeها
- `tasks.py`: تسک‌های Celery
- `block_subdivision.py`: subdivision و elbow/kmeans
- `grid_analysis.py`: ساخت analysis grid cells
- `openeo_service.py`: لایه سرویس openEO
- `remote_sensing.py`: منطق قدیمی‌تر سنجش‌ازدور/NDVI ساده
- `soil_adapters.py`: adapterهای داده خاک
---
## 4) تنظیمات مهم
### `SUBDIVISION_CHUNK_SQM`
در `config/settings.py`:
```python
SUBDIVISION_CHUNK_SQM = int(os.environ.get("SUBDIVISION_CHUNK_SQM", "900"))
```
مقدار پیش‌فرض فعلی:
- `900`
معنا:
- grid analysis با سلول‌های `30m x 30m`
چون:
```text
step_m = sqrt(900) = 30m
```
---
## 5) مدل‌های دیتابیس و منطق آن‌ها
## 5.1) SoilLocation
فیلدهای مهم:
- `latitude`
- `longitude`
- `task_id`
- `farm_boundary`
- `input_block_count`
- `block_layout`
- `created_at`
- `updated_at`
### قید مهم
- `latitude + longitude` یکتا هستند
### block_layout
`block_layout` JSON summary کلی blockها را نگه می‌دارد.
نمونه:
```json
{
"input_block_count": 1,
"default_full_farm": true,
"algorithm_status": "completed",
"blocks": [
{
"block_code": "block-1",
"order": 1,
"source": "default",
"needs_subdivision": true,
"sub_blocks": [
{
"sub_block_code": "sub-block-1",
"centroid_lat": 35.689123,
"centroid_lon": 51.389456
}
],
"subdivision_summary": {
"chunk_size_sqm": 900,
"grid_point_count": 12,
"centroid_count": 3,
"optimal_k": 3
},
"analysis_grid_summary": {
"chunk_size_sqm": 900,
"cell_count": 18
}
}
]
}
```
`block_layout` canonical source نیست؛ بیشتر یک summary سریع برای API است.
---
## 5.2) BlockSubdivision
فیلدهای مهم:
- `soil_location`
- `block_code`
- `source_boundary`
- `chunk_size_sqm`
- `grid_points`
- `centroid_points`
- `grid_point_count`
- `centroid_count`
- `elbow_plot`
- `status`
- `metadata`
### نقش
برای هر `block_code` در هر location، نتیجه subdivision در این مدل ذخیره می‌شود.
### metadata
شامل مواردی مثل:
- `estimated_area_sqm`
- `optimal_k`
- `inertia_curve`
- `analysis_grid`
---
## 5.3) RemoteSensingRun
فیلدهای مهم:
- `soil_location`
- `block_subdivision`
- `block_code`
- `provider`
- `chunk_size_sqm`
- `temporal_start`
- `temporal_end`
- `status`
- `metadata`
- `error_message`
- `started_at`
- `finished_at`
### statusها
- `pending`
- `running`
- `success`
- `failure`
### نقش
این جدول وضعیت اجرای async را نگه می‌دارد.
---
## 5.4) AnalysisGridCell
فیلدهای مهم:
- `soil_location`
- `block_subdivision`
- `block_code`
- `cell_code`
- `chunk_size_sqm`
- `geometry`
- `centroid_lat`
- `centroid_lon`
### نقش
واحد spatial اصلی برای تحلیل remote sensing است.
### idempotency
سطح سرویس با این شرط enforce می‌شود:
- اگر برای یک `SoilLocation + block_code + chunk_size_sqm` cellها قبلاً ساخته شده باشند، دوباره ساخته نمی‌شوند.
### geometry
به صورت GeoJSON-like polygon ذخیره می‌شود.
---
## 5.5) AnalysisGridObservation
فیلدهای مهم:
- `cell`
- `run`
- `temporal_start`
- `temporal_end`
- `ndvi`
- `ndwi`
- `lst_c`
- `soil_vv`
- `soil_vv_db`
- `dem_m`
- `slope_deg`
- `metadata`
### uniqueness
برای جلوگیری از duplicate:
- روی `cell + temporal_start + temporal_end` constraint داریم.
این باعث می‌شود cache دیتابیسی پایدار باشد.
---
## 5.6) SoilDepthData
این مدل داده‌های خاک را در عمق‌های مختلف نگه می‌دارد.
هنوز به صورت مستقیم برای هر analysis grid cell جداگانه استفاده نشده است.
---
## 5.7) NdviObservation
این مدل legacy / parallel NDVI store است.
workflow جدید openEO جایگزین آن نشده، بلکه کنار آن وجود دارد.
---
## 6) منطق subdivision در `block_subdivision.py`
این فایل مسئول خردسازی blockها است.
### کارهایی که انجام می‌دهد
- استخراج polygon از boundary
- تبدیل مختصات جغرافیایی به مختصات محلی متری
- تولید grid points اولیه
- اجرای KMeans برای `K=1..10`
- محاسبه SSE/Inertia
- پیدا کردن elbow point
- انتخاب centroidها
- رسم elbow plot با matplotlib
- ذخیره plot در `ImageField`
- sync کردن نتیجه با `block_layout`
### input
ممکن است boundary به شکل‌های زیر برسد:
- GeoJSON Polygon
- corners
- list مستقیم از points
### خروجی
- centroidهای نهایی block
- metadata الگوریتم
- elbow plot
---
## 7) منطق ساخت analysis grid در `grid_analysis.py`
این فایل مسئول تولید سلول‌های 30x30 متری برای تحلیل remote sensing است.
### تابع اصلی
- `create_or_get_analysis_grid_cells(...)`
### ورودی‌ها
- `location`
- optional `boundary`
- optional `block_code`
- optional `block_subdivision`
- optional `chunk_size_sqm`
### رفتار
1. chunk size را تعیین می‌کند
2. boundary را resolve می‌کند
3. polygon را extract می‌کند
4. اگر قبلاً برای همان `location + block_code + chunk_size` cell ساخته شده باشد، خروجی existing برمی‌گرداند
5. اگر نه، grid cellها ساخته می‌شوند و `AnalysisGridCell` ذخیره می‌شود
### نحوه ساخت شبکه
- polygon به دستگاه محلی متری تبدیل می‌شود
- `step_m = sqrt(chunk_size_sqm)` محاسبه می‌شود
- یک grid مستطیلی روی bounding box ساخته می‌شود
- هر cell که با polygon intersect داشته باشد نگه داشته می‌شود
### cell_code
فرمت فعلی deterministic است:
```text
loc-{location_id}__block-{block_code}__chunk-{chunk_size_sqm}__rXXXXcYYYY
```
### metadata summary
پس از ساخت grid:
- روی `BlockSubdivision.metadata["analysis_grid"]`
- و روی `SoilLocation.block_layout`
summary ذخیره می‌شود.
---
## 8) منطق openEO در `openeo_service.py`
این فایل لایه service اصلی برای تحلیل openEO است.
### backend
```text
https://openeofed.dataspace.copernicus.eu
```
### هدف
گرفتن batch metricها برای مجموعه‌ای از `AnalysisGridCell`ها.
### جریان کلی
1. اتصال و auth به openEO
2. ساخت `FeatureCollection` از cellها
3. ساخت `spatial_extent`
4. اجرای یک job per metric روی همه cellها
5. parse کردن نتیجه aggregate_spatial
6. merge کردن metricها روی map keyed by `cell_code`
### metricهای فعلی
- `ndvi` از `SENTINEL2_L2A`
- `ndwi` از `SENTINEL2_L2A`
- `lst_c` از `SENTINEL3_SLSTR_L2_LST`
- `soil_vv` از `SENTINEL1_GRD`
- `soil_vv_db` در Python از `soil_vv`
- `dem_m` از `COPERNICUS_30`
- `slope_deg` از DEM اگر backend پشتیبانی کند
### cloud mask Sentinel-2
کلاس‌های معتبر SCL:
- `4`
- `5`
- `6`
نکته مهم:
- از `isin()` استفاده نمی‌شود
- فقط logical comparison استفاده می‌شود
### aggregate_spatial
فقط از:
```python
aggregate_spatial(geometries=feature_collection, reducer="mean")
```
استفاده می‌شود.
### slope support
اگر backend `slope()` را پشتیبانی نکند:
- `slope_deg = null`
- و metadata می‌گوید `slope_supported=False`
### normalized output
خروجی نهایی به این شکل است:
```python
{
"results": {
"cell-1": {
"ndvi": ...,
"ndwi": ...,
"lst_c": ...,
"soil_vv": ...,
"soil_vv_db": ...,
"dem_m": ...,
"slope_deg": ...,
}
},
"metadata": {
"backend": "openeo",
"collections_used": [...],
"slope_supported": True,
"job_refs": {},
"failed_metrics": []
}
}
```
---
## 9) Celery workflow در `tasks.py`
### تسک قدیمی
- `fetch_soil_data_task`
برای خاک legacy است.
### workflow جدید remote sensing
تابع/تسک‌های اصلی:
- `run_remote_sensing_analysis(...)`
- `run_remote_sensing_analysis_task.delay(...)`
### ورودی task
- `soil_location_id`
- optional `block_code`
- `temporal_start`
- `temporal_end`
- optional `force_refresh`
- optional `run_id`
### رفتار task
1. `SoilLocation` را پیدا می‌کند
2. `BlockSubdivision` را اگر لازم باشد resolve می‌کند
3. `RemoteSensingRun` را create/update می‌کند
4. `AnalysisGridCell`ها را ensure می‌کند
5. اگر observation برای همان range قبلاً باشد و `force_refresh=False`، دوباره process نمی‌کند
6. در غیر این صورت، `compute_remote_sensing_metrics()` را صدا می‌زند
7. `AnalysisGridObservation`ها را upsert می‌کند
8. status run را success/failure می‌کند
### idempotency
اگر observation قبلاً برای همان:
- cell
- temporal_start
- temporal_end
وجود داشته باشد، duplicate ساخته نمی‌شود.
### retry behavior
task روی خطاهای transient مثل:
- `OpenEOExecutionError`
- `OpenEOServiceError`
- request-level failures
retry می‌کند.
روی auth failure retry نمی‌کند.
---
## 10) APIهای فعلی `location_data`
## 10.1) `GET /api/soil-data/`
کاربرد:
- فقط اطلاعات ذخیره‌شده location را برمی‌گرداند
- subdivision را rerun نمی‌کند
ورودی:
- `lat`
- `lon`
- optional `block_code`
خروجی:
- location data
- block layout
- block subdivisions
- depths
---
## 10.2) `POST /api/soil-data/`
کاربرد:
- `SoilLocation` را create/get می‌کند
- در صورت نیاز `BlockSubdivision` می‌سازد
ورودی:
- `lat`
- `lon`
- `block_count`
- `block_code`
- `farm_boundary`
خروجی:
- location کامل
- `source` = `created` یا `database`
---
## 10.3) `GET /api/soil-data/tasks/<task_id>/status/`
کاربرد:
- status task قدیمی fetch خاک
---
## 10.4) `POST /api/soil-data/ndvi-health/`
کاربرد:
- NDVI health مستقل برای farm
---
## 10.5) `POST /api/soil-data/remote-sensing/`
کاربرد:
- remote sensing analysis را queue می‌کند
- heavy work را sync اجرا نمی‌کند
ورودی:
- `lat`
- `lon`
- optional `block_code`
- `start_date`
- `end_date`
- optional `force_refresh`
رفتار:
1. location را پیدا می‌کند
2. run می‌سازد
3. Celery task را enqueue می‌کند
4. `202 Accepted` برمی‌گرداند
خروجی شامل:
- `status=processing`
- `source=processing`
- `location`
- `block_code`
- `chunk_size_sqm`
- `temporal_extent`
- `summary` خالی
- `cells=[]`
- `run`
- `task_id`
---
## 10.6) `GET /api/soil-data/remote-sensing/`
کاربرد:
- فقط cache دیتابیسی remote sensing را می‌خواند
- هیچ openEO یا subdivision sync اجرا نمی‌کند
ورودی:
- `lat`
- `lon`
- optional `block_code`
- `start_date`
- `end_date`
خروجی حالت‌ها:
### حالت 1: result موجود است
- `status=success`
- `source=database`
- summary metrics
- cells list
- run info
### حالت 2: result هنوز نیست ولی job در حال اجراست
- `status=processing`
- `source=processing`
- summary خالی
- cells empty
- run info
### حالت 3: location نیست
- `404`
---
## 11) serializerهای مهم
### `SoilDataRequestSerializer`
برای endpoint اصلی location.
### `SoilLocationResponseSerializer`
برای بازگشت location + blocks + depths.
### `BlockSubdivisionSerializer`
برای بازگشت subdivision data.
### `RemoteSensingTriggerSerializer`
برای trigger API remote sensing.
### `RemoteSensingCellObservationSerializer`
برای بازگشت per-cell remote sensing metrics.
### `RemoteSensingSummarySerializer`
برای بازگشت summary statisticها.
### `RemoteSensingRunSerializer`
برای بازگشت status run.
### `RemoteSensingResponseSerializer`
برای payload کامل remote sensing GET.
---
## 12) منطق summary statistics در remote sensing GET
در response مربوط به `GET /remote-sensing/` این فیلدها برمی‌گردند:
- `cell_count`
- `ndvi_mean`
- `ndwi_mean`
- `lst_c_mean`
- `soil_vv_db_mean`
- `dem_m_mean`
- `slope_deg_mean`
این‌ها از روی observationهای موجود در DB محاسبه می‌شوند، نه از openEO live.
---
## 13) admin
در `admin.py` الان موارد زیر رجیستر شده‌اند:
- `SoilLocation`
- `SoilDepthData`
- `BlockSubdivision`
- `RemoteSensingRun`
- `AnalysisGridCell`
- `AnalysisGridObservation`
این باعث می‌شود debugging و inspection از طریق admin ممکن باشد.
---
## 14) تست‌های فعلی
### `test_soil_api.py`
- ساخت location
- ساخت subdivision
- رفتار GET/POST location
### `test_block_subdivision.py`
- elbow detection
- payload subdivision
### `test_grid_analysis.py`
- ساخت analysis grid 30x30
- idempotency grid cells
- استفاده از boundary location
### `test_openeo_service.py`
- parse نتیجه aggregate_spatial
- merge metricها
- conversion به dB
### `test_remote_sensing_api.py`
- queue شدن remote sensing task
- processing response
- cache read response
- not found behavior
### `test_ndvi_health_api.py`
- NDVI health API
---
## 15) وابستگی‌های مهم
در `requirements.txt` dependencyهای مهم این بخش‌ها شامل این‌ها هستند:
- `scikit-learn`
- `matplotlib`
- `Pillow`
- `numpy`
- `openeo`
### نقش آن‌ها
- `scikit-learn`: KMeans
- `matplotlib`: elbow plot
- `Pillow`: ImageField support
- `numpy`: وابستگی عددی
- `openeo`: ارتباط با backend سنجش‌ازدور
---
## 16) migrationهای مهم
- `0008_soillocation_block_layout.py`
- `0009_blocksubdivision.py`
- `0010_blocksubdivision_elbow_plot.py`
- `0011_remote_sensing_models.py`
این migrationها ساختار فعلی location/subdivision/remote sensing را ساخته‌اند.
---
## 17) محدودیت‌ها و فرضیات فعلی
### محدودیت‌های فعلی
1. `block_layout` canonical source نیست و summary است.
2. subdivision و analysis grid دو لایه جدا هستند.
3. slope ممکن است روی backend همیشه پشتیبانی نشود.
4. API GET remote-sensing فقط cache می‌خواند.
5. هنوز endpoint مجزای status run نداریم.
6. grid generation از projection محلی استفاده می‌کند، نه GIS stack سنگین.
7. openEO calls فعلاً برای batch metric processing طراحی شده‌اند، نه orchestration پیچیده job lifecycle.
### فرضیات
1. مزرعه‌ها آن‌قدر کوچک هستند که local projected approximation مناسب باشد.
2. `SUBDIVISION_CHUNK_SQM=900` برای workflow فعلی درست است.
3. `cell_code` deterministic بودن برای idempotency کافی است.
4. `AnalysisGridObservation` cache اصلی remote sensing است.
---
## 18) جریان کامل داده از ابتدا تا نتیجه remote sensing
### مرحله 1: ایجاد location
کاربر `POST /api/soil-data/` را صدا می‌زند.
نتیجه:
- `SoilLocation` ساخته می‌شود
- `farm_boundary` ذخیره می‌شود
- block layout ساخته می‌شود
- در صورت نیاز `BlockSubdivision` ساخته می‌شود
### مرحله 2: تولید analysis grid
وقتی task remote sensing اجرا می‌شود:
- اگر cellها قبلاً نباشند، `AnalysisGridCell`ها ساخته می‌شوند
### مرحله 3: اجرای openEO
Celery task:
- FeatureCollection از cellها می‌سازد
- metricها را batch اجرا می‌کند
- نتیجه را parse می‌کند
### مرحله 4: ذخیره observation
برای هر cell:
- یک `AnalysisGridObservation` برای بازه زمانی موردنظر ذخیره/آپدیت می‌شود
### مرحله 5: بازگشت نتیجه از API
کاربر `GET /api/soil-data/remote-sensing/` را صدا می‌زند.
سیستم:
- فقط DB را می‌خواند
- summary می‌سازد
- cells را برمی‌گرداند
---
## 19) پیشنهاد توسعه بعدی
برای ادامه توسعه، این‌ها منطقی‌ترین قدم‌ها هستند:
1. ساخت endpoint مستقل status برای `RemoteSensingRun`
2. اضافه کردن pagination برای cell responseها
3. اضافه کردن job reference واقعی openEO در metadata
4. پشتیبانی از چند resolution دیگر غیر از 30x30
5. ساخت serializer/model جدا برای summaryهای precomputed
6. اضافه کردن نمودارها یا aggregationهای block-level
7. اتصال remote sensing resultها به recommendation engine
---
## 20) جمع‌بندی نهایی
اپ `location_data` الان یک سیستم چندلایه است:
### لایه مکانی پایه
- `SoilLocation`
- `farm_boundary`
- `block_layout`
### لایه subdivision
- `BlockSubdivision`
- KMeans
- elbow plot
### لایه grid analysis
- `AnalysisGridCell`
### لایه observation
- `AnalysisGridObservation`
- `RemoteSensingRun`
### لایه سرویس
- `block_subdivision.py`
- `grid_analysis.py`
- `openeo_service.py`
- `tasks.py`
### لایه API
- `SoilDataView`
- `RemoteSensingAnalysisView`
- `NdviHealthView`
در نتیجه، `location_data` الان از یک app ساده location عبور کرده و به یک زیرسیستم کامل spatial + remote sensing تبدیل شده است.
+290
View File
@@ -0,0 +1,290 @@
# لیست APIهایی که با استفاده از PCSE تحلیل انجام می‌دهند
این فایل APIهایی را فهرست می‌کند که در این پروژه یا مستقیماً از `PCSE` استفاده می‌کنند، یا بخشی از تحلیلشان به خروجی‌های شبیه‌سازی `PCSE` وابسته است.
همچنین مشخص می‌کند:
- از چه مدل `PCSE` استفاده می‌شود
- ورودی‌های لازم `PCSE` در این پروژه از کجا تأمین می‌شوند
- برنامه آبیاری، برنامه کودهی و داده آب‌وهوا چطور به مدل تزریق می‌شوند
## مدل PCSE مورد استفاده در پروژه
مدل پیش‌فرضی که در سرویس شبیه‌سازی استفاده می‌شود این است:
- `Wofost81_NWLP_CWB_CNB`
توضیح کوتاه:
- `Wofost81`: نسخه 8.1 از خانواده مدل‌های WOFOST
- `NWLP`: شبیه‌سازی با محدودیت نیتروژن
- `CWB`: water balance
- `CNB`: carbon/nitrogen balance
بنابراین APIهایی که واقعاً از موتور اصلی شبیه‌سازی استفاده می‌کنند، عملاً روی این مدل اجرا می‌شوند مگر اینکه بعداً در کد override شده باشد.
## PCSE در این پروژه چه ورودی‌هایی می‌خواهد؟
در این پروژه لایه شبیه‌سازی برای اجرای `PCSE` این ورودی‌ها را می‌سازد:
### 1. `weather`
رکوردهای آب‌وهوا با فیلدهای زیر:
- `DAY`
- `LAT`
- `LON`
- `ELEV`
- `IRRAD`
- `TMIN`
- `TMAX`
- `VAP`
- `WIND`
- `RAIN`
- `E0`
- `ES0`
- `ET0`
### 2. `soil`
پارامترهای خاک، از جمله:
- `SMFCF`
- `SMW`
- `SM0`
- `RDMSOL`
- `CRAIRC`
- `SOPE`
- `KSUB`
و در این پروژه بعضی شاخص‌های کمکی هم کنار آن نگهداری می‌شوند، مثل:
- `nitrogen`
- `phosphorus`
- `potassium`
- `soil_ph`
- `electrical_conductivity`
### 3. `site_parameters`
پارامترهای سایت/شرایط اولیه، از جمله:
- `WAV`
- `SMLIM`
- `IFUNRN`
- `NOTINF`
- `SSI`
- `SSMAX`
- `NAVAILI`
### 4. `crop_parameters`
پارامترهای محصول. این‌ها یا از پروفایل شبیه‌سازی گیاه می‌آیند، یا اگر موجود نباشند به‌صورت default ساخته می‌شوند. مهم‌ترین defaultها:
- `crop_name`
- `TSUM1`
- `TSUM2`
- `YIELD_SCALE`
- `MAX_LAI`
- `MAX_BIOMASS`
### 5. `agromanagement`
تقویم و رویدادهای زراعی، شامل:
- `CropCalendar`
- `TimedEvents`
- `StateEvents`
همین بخش جایی است که برنامه آبیاری و برنامه کودهی به شبیه‌سازی تزریق می‌شود.
## ورودی‌ها از کجا می‌آیند؟
### آب‌وهوا
منبع اصلی:
- جدول `WeatherForecast`
مسیر تولید:
- با `farm_uuid` مزرعه پیدا می‌شود
- از `center_location` مزرعه، forecastها خوانده می‌شوند
- معمولاً تا `14` روز آینده برداشته می‌شوند
- داده‌های `precipitation` و `et0` که در دیتابیس به `mm/day` هستند، برای `PCSE` به `cm/day` تبدیل می‌شوند
فیلدهایی که از forecast استفاده می‌شوند:
- `forecast_date``DAY`
- `temperature_min``TMIN`
- `temperature_max` یا `temperature_mean``TMAX`
- `humidity_mean``VAP`
- `wind_speed_max``WIND`
- `precipitation``RAIN`
- `et0``E0`, `ES0`, `ET0`
اگر کاربر خودش `weather` را مستقیم بدهد، همان ورودی مستقیم استفاده می‌شود.
### خاک و وضعیت سایت
منبع اصلی:
- جدول `SensorData`
- رابطه `center_location.depths`
- بخشی از `sensor_payload`
نحوه ساخت:
- از لایه سطحی خاک (`top_depth`) پارامترهایی مثل `wv0033`, `wv1500`, `porosity` خوانده می‌شوند
- از روی آن‌ها `SMFCF`, `SMW`, `SM0` ساخته می‌شود
- از `sensor_payload`، شاخص‌هایی مثل `soil_moisture`, `nitrogen`, `phosphorus`, `potassium`, `soil_ph`, `electrical_conductivity` استخراج می‌شود
- سپس از این‌ها `soil` و `site_parameters` نهایی ساخته می‌شود
### پارامترهای محصول
منبع اصلی:
- مدل `Plant`
اولویت تأمین:
1. `simulation profile` داخل یکی از این profileها:
- `growth_profile.simulation`
- `irrigation_profile.simulation`
- `health_profile.simulation`
2. اگر profile آماده وجود نداشته باشد، پارامترهای پیش‌فرض از روی اطلاعات رشد گیاه ساخته می‌شوند
### تقویم زراعی `agromanagement`
منبع اصلی:
- اگر در `simulation profile` گیاه موجود باشد، از همان استفاده می‌شود
- وگرنه به‌صورت پیش‌فرض از بازه زمانی آب‌وهوای موجود ساخته می‌شود
ساختار پیش‌فرض:
- `crop_start_date` از اولین روز forecast
- `crop_end_date` از آخرین روز forecast یا کمی بعد از آن
- `TimedEvents` و `StateEvents` به‌صورت اولیه خالی هستند
## 1) APIهای مستقیم و قطعی مبتنی بر PCSE
### 1. `POST /api/crop-simulation/growth/`
- کاربرد: اجرای شبیه‌سازی رشد گیاه.
- نقش PCSE: هسته اصلی این API اجرای مدل شبیه‌سازی `PCSE/WOFOST` است.
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
- ورودی‌ها:
- آب‌وهوا: از `WeatherForecast` یا ورودی مستقیم `weather`
- خاک: از `SensorData` و `center_location.depths`
- crop parameters: از `Plant` و `simulation profile` یا default
- agromanagement: از `simulation profile` یا default
- نوع استفاده: مستقیم.
### 2. `GET /api/crop-simulation/growth/<task_id>/status/`
- کاربرد: دریافت وضعیت و نتیجه شبیه‌سازی رشد.
- نقش PCSE: نتیجه‌ای که برمی‌گرداند خروجی همان شبیه‌سازی مبتنی بر `PCSE` است.
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
- نوع استفاده: مستقیم.
### 3. `POST /api/crop-simulation/current-farm-chart/`
- کاربرد: تولید chart وضعیت فعلی مزرعه.
- نقش PCSE: داده‌های chart مثل `LAI`، `TAGP`، `TWSO`، `SM` و `daily_output` از شبیه‌سازی `PCSE` ساخته می‌شوند.
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
- ورودی‌ها:
- `farm_uuid`
- آب‌وهوا از `WeatherForecast`
- خاک/سایت از `SensorData` و داده‌های خاک location
- پارامتر گیاه از `Plant`
- نوع استفاده: مستقیم.
### 4. `POST /api/crop-simulation/yield-prediction/`
- کاربرد: پیش‌بینی عملکرد مزرعه.
- نقش PCSE: عملکرد پیش‌بینی‌شده از خروجی شبیه‌سازی رشد/خروجی‌های `PCSE` استخراج می‌شود.
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
- ورودی‌ها: همان ورودی‌های `current-farm-chart`
- نوع استفاده: مستقیم.
### 5. `POST /api/crop-simulation/harvest-prediction/`
- کاربرد: پیش‌بینی زمان تقریبی برداشت.
- نقش PCSE: با استفاده از `daily_output`، `DVS` و سایر خروجی‌های شبیه‌سازی، زمان رسیدن به برداشت برآورد می‌شود.
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
- ورودی‌ها: همان ورودی‌های شبیه‌سازی رشد مزرعه
- نوع استفاده: مستقیم.
### 6. `GET /api/crop-simulation/yield-harvest-summary/`
- کاربرد: خلاصه عملکرد و برداشت.
- نقش PCSE: چند بخش این API مثل `yield_prediction`، `harvest_prediction_card` و `yield_prediction_chart` از خروجی‌های شبیه‌سازی `PCSE` تغذیه می‌شوند.
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
- ورودی‌ها:
- خروجی `yield_prediction`
- خروجی `harvest_prediction`
- خروجی `current-farm-chart`
- همگی در نهایت متکی به همان ورودی‌های farm/weather/soil/plant هستند
- نوع استفاده: مستقیم/ترکیبی.
### 7. `POST /api/irrigation/water-stress/`
- کاربرد: محاسبه شاخص تنش آبی مزرعه.
- نقش PCSE: این API از شبیه‌سازی `crop_simulation` استفاده می‌کند و شاخص تنش آبی را با تکیه بر خروجی‌هایی مثل `SM`، `DVS`، `ET0` و `RAIN` می‌سازد.
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
- ورودی‌ها:
- آب‌وهوا از `WeatherForecast`
- خاک و رطوبت خاک از `SensorData`
- پارامتر گیاه از `Plant`
- نوع استفاده: مستقیم، ولی شاخص نهایی یک فرمول داخلی روی خروجی شبیه‌سازی است.
## 2) APIهایی که بخشی از تحلیلشان ممکن است با PCSE انجام شود
### 8. `POST /api/irrigation/recommend/`
- کاربرد: تولید توصیه آبیاری.
- نقش PCSE: در صورت موجود بودن `simulation profile`، داده مزرعه و forecast مناسب، optimizer ابتدا سناریوهای آبیاری را با `PCSE` ارزیابی می‌کند.
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
- برنامه آبیاری از کجا می‌آید؟
- ابتدا در خود سیستم چند strategy ساخته می‌شود
- بر اساس `daily_water_needs` و تعداد eventها، برای هر سناریو `irrigation_events` ساخته می‌شود
- این eventها به شکل `TimedEvents` با سیگنال `irrigate` وارد `agromanagement` می‌شوند
- آب‌وهوا از کجا می‌آید؟
- از forecastهای جدول `WeatherForecast`
- سایر ورودی‌ها از کجا می‌آیند؟
- خاک و رطوبت و مواد غذایی: از `SensorData`
- گیاه و simulation profile: از `Plant`
- نکته: اگر شرایط کافی نباشد، به مسیر heuristic برمی‌گردد.
- نوع استفاده: مشروط/جزئی.
### 9. `POST /api/fertilization/recommend/`
- کاربرد: تولید توصیه کودهی.
- نقش PCSE: در صورت موجود بودن `simulation profile` و forecast، سناریوهای کودهی با `PCSE` شبیه‌سازی و امتیازدهی می‌شوند.
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
- برنامه کودهی از کجا می‌آید؟
- optimizer چند سناریوی کودهی می‌سازد
- برای هر سناریو event کودهی به شکل `TimedEvents`
- با سیگنال `apply_n`
- و payload شامل `N_amount` و `N_recovery`
- وارد `agromanagement` می‌شود
- آب‌وهوا از کجا می‌آید؟
- از forecastهای جدول `WeatherForecast`
- سایر ورودی‌ها از کجا می‌آیند؟
- خاک و وضعیت عناصر از `SensorData`
- پروفایل گیاه از `Plant`
- نکته: اگر `PCSE` یا داده کافی در دسترس نباشد، fallback heuristic استفاده می‌شود.
- نوع استفاده: مشروط/جزئی.
## 3) APIهایی که از PCSE استفاده نمی‌کنند
این endpointها در همین حوزه هستند اما خودشان تحلیل مبتنی بر `PCSE` انجام نمی‌دهند:
- `POST /api/irrigation/plan-from-text/`
- `POST /api/fertilization/plan-from-text/`
این دو بیشتر parser متن آزاد هستند و برنامه را از متن به JSON ساختاریافته تبدیل می‌کنند.
## جمع‌بندی کوتاه
اگر بخواهیم فقط APIهایی را نام ببریم که واقعاً تحلیل یا شبیه‌سازی وابسته به `PCSE` دارند، مهم‌ترین‌ها این‌ها هستند:
- `POST /api/crop-simulation/growth/`
- `GET /api/crop-simulation/growth/<task_id>/status/`
- `POST /api/crop-simulation/current-farm-chart/`
- `POST /api/crop-simulation/yield-prediction/`
- `POST /api/crop-simulation/harvest-prediction/`
- `GET /api/crop-simulation/yield-harvest-summary/`
- `POST /api/irrigation/water-stress/`
- `POST /api/irrigation/recommend/` (مشروط)
- `POST /api/fertilization/recommend/` (مشروط)
## جمع‌بندی فنی خیلی کوتاه
- مدل اصلی `PCSE` در این پروژه: `Wofost81_NWLP_CWB_CNB`
- آب‌وهوا عمدتاً از `WeatherForecast` می‌آید
- خاک و رطوبت و بخشی از وضعیت تغذیه از `SensorData` و داده‌های خاک location می‌آید
- پارامترهای گیاه و setup شبیه‌سازی از `Plant` و `simulation profile` می‌آید
- برنامه آبیاری و کودهی در optimizer ساخته می‌شوند و از طریق `TimedEvents` داخل `agromanagement` به `PCSE` تزریق می‌شوند
@@ -0,0 +1,743 @@
# مستند کامل APIهای آپدیت‌شده مرتبط با PCSE
این فایل APIهایی را توضیح می‌دهد که اخیراً آپدیت شده‌اند تا بتوانند `برنامه آبیاری` و `برنامه کودهی` را از ورودی بگیرند و به شبیه‌سازی `PCSE` پاس بدهند.
APIهای این سند:
- `POST /api/crop-simulation/growth/`
- `GET /api/crop-simulation/growth/<task_id>/status/`
- `POST /api/crop-simulation/current-farm-chart/`
- `POST /api/crop-simulation/yield-prediction/`
- `POST /api/crop-simulation/harvest-prediction/`
- `GET /api/crop-simulation/yield-harvest-summary/`
- `POST /api/irrigation/water-stress/`
---
## فرمت مشترک `irrigation_recommendation`
این فیلد در APIهای این سند می‌تواند ارسال شود:
```json
{
"events": [
{
"date": "2026-04-25",
"amount": 2.5,
"efficiency": 0.8
}
]
}
```
### توضیح فیلدها
- `events`: آرایه‌ای از رویدادهای آبیاری
- `date`: تاریخ اجرای آبیاری
- `amount`: مقدار آبیاری برای event
- `efficiency`: راندمان آبیاری، اختیاری
### رفتار در PCSE
این داده‌ها به `TimedEvents` با سیگنال `irrigate` تبدیل می‌شوند.
---
## فرمت مشترک `fertilization_recommendation`
```json
{
"events": [
{
"date": "2026-04-20",
"N_amount": 45,
"N_recovery": 0.7
}
]
}
```
### توضیح فیلدها
- `events`: آرایه‌ای از رویدادهای کودهی
- `date`: تاریخ اجرای کودهی
- `N_amount`: مقدار نیتروژن
- `N_recovery`: ضریب بازیافت/جذب نیتروژن
### رفتار در PCSE
این داده‌ها به `TimedEvents` با سیگنال `apply_n` تبدیل می‌شوند.
---
## 1) `POST /api/crop-simulation/growth/`
### کاربرد
شروع شبیه‌سازی رشد گیاه به‌صورت async و برگرداندن `task_id`.
### ورودی
```json
{
"plant_name": "گوجه‌فرنگی",
"dynamic_parameters": ["DVS", "LAI", "TAGP", "TWSO", "SM"],
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"weather": [
{
"DAY": "2026-04-01",
"LAT": 35.7,
"LON": 51.4,
"TMIN": 12,
"TMAX": 24,
"RAIN": 0.0,
"ET0": 0.32
}
],
"soil_parameters": {
"SMFCF": 0.34,
"SMW": 0.14,
"RDMSOL": 120.0
},
"site_parameters": {
"WAV": 40.0
},
"crop_parameters": {},
"agromanagement": {},
"irrigation_recommendation": {
"events": [
{
"date": "2026-04-02",
"amount": 2.5
}
]
},
"fertilization_recommendation": {
"events": [
{
"date": "2026-04-02",
"N_amount": 45,
"N_recovery": 0.7
}
]
},
"page_size": 2
}
```
### توضیح پارامترهای ورودی
- `plant_name`: نام گیاه
- `dynamic_parameters`: لیست پارامترهای دینامیک موردنیاز در خروجی
- `farm_uuid`: شناسه مزرعه؛ اگر باشد، داده مزرعه و forecast از سیستم خوانده می‌شود
- `weather`: آب‌وهوا به‌صورت مستقیم؛ اگر `farm_uuid` نباشد لازم است
- `soil_parameters`: override برای پارامترهای خاک
- `site_parameters`: override برای پارامترهای site
- `crop_parameters`: override برای پارامترهای crop
- `agromanagement`: override برای تقویم زراعی
- `irrigation_recommendation`: برنامه آبیاری برای تزریق به PCSE
- `fertilization_recommendation`: برنامه کودهی برای تزریق به PCSE
- `page_size`: اندازه صفحه مراحل رشد در endpoint وضعیت task
### اعتبارسنجی
- حداقل یکی از `farm_uuid` یا `weather` باید ارسال شود
- `dynamic_parameters` نباید خالی باشد
- `page_size` باید بین `1` تا `50` باشد
### پاسخ موفق
```json
{
"code": 202,
"msg": "تسک شبیه سازی رشد در صف قرار گرفت.",
"data": {
"task_id": "growth-task-1",
"status_url": "/api/crop-simulation/growth/growth-task-1/status/",
"plant_name": "گوجه‌فرنگی"
}
}
```
### توضیح فیلدهای پاسخ
- `code`: کد داخلی پاسخ
- `msg`: پیام پاسخ
- `data.task_id`: شناسه task
- `data.status_url`: آدرس پیگیری وضعیت task
- `data.plant_name`: نام گیاه
---
## 2) `GET /api/crop-simulation/growth/<task_id>/status/`
### کاربرد
بررسی وضعیت اجرای شبیه‌سازی رشد و دریافت نتیجه نهایی.
### پارامترهای Query
- `page`: شماره صفحه
- `page_size`: اندازه صفحه
### پاسخ موفق
```json
{
"code": 200,
"msg": "موفق",
"data": {
"task_id": "growth-task-1",
"status": "SUCCESS",
"status_fa": "موفق",
"result": {
"plant_name": "گوجه‌فرنگی",
"dynamic_parameters": ["DVS", "LAI", "TAGP"],
"engine": "موتور شبیه سازی PCSE",
"model_name": "مدل ووفوست",
"scenario_id": 12,
"simulation_warning": null,
"summary_metrics": {
"yield_estimate": 5400.0,
"biomass": 9800.0,
"max_lai": 4.2
},
"stage_timeline": [
{
"order": 1,
"stage_code": "vegetative",
"stage_name": "رشد رویشی",
"start_date": "2026-04-01",
"end_date": "2026-04-05",
"days_count": 5,
"metrics": {
"DVS": {
"start": 0.1,
"end": 0.8,
"min": 0.1,
"max": 0.8,
"avg": 0.45
}
}
}
],
"stages_page": [],
"pagination": {
"page": 1,
"page_size": 10,
"total_items": 1,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"daily_records_count": 14,
"default_page_size": 10
}
}
}
```
### توضیح کامل فیلدهای `result`
- `plant_name`: نام گیاه
- `dynamic_parameters`: پارامترهای پویا
- `engine`: نام فارسی موتور اجرا
- `model_name`: نام فارسی مدل
- `scenario_id`: شناسه سناریوی ذخیره‌شده
- `simulation_warning`: هشدار fallback یا خطای غیرکشنده
- `summary_metrics.yield_estimate`: برآورد عملکرد
- `summary_metrics.biomass`: بیوماس
- `summary_metrics.max_lai`: بیشینه شاخص سطح برگ
- `stage_timeline`: خلاصه مراحل رشد
- `stages_page`: همان `stage_timeline` به‌صورت صفحه‌بندی‌شده
- `pagination`: متادیتای صفحه‌بندی
- `daily_records_count`: تعداد رکوردهای روزانه
- `default_page_size`: اندازه صفحه پیش‌فرض
---
## 3) `POST /api/crop-simulation/current-farm-chart/`
### کاربرد
ساخت chart وضعیت فعلی مزرعه بر اساس شبیه‌سازی.
### ورودی
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"irrigation_recommendation": {
"events": [
{
"date": "2026-04-25",
"amount": 2.5
}
]
},
"fertilization_recommendation": {
"events": [
{
"date": "2026-04-20",
"N_amount": 45,
"N_recovery": 0.7
}
]
}
}
```
### توضیح ورودی
- `farm_uuid`: شناسه مزرعه
- `plant_name`: نام گیاه، اختیاری
- `irrigation_recommendation`: برنامه آبیاری
- `fertilization_recommendation`: برنامه کودهی
### پاسخ موفق
```json
{
"code": 200,
"msg": "موفق",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"engine": "موتور شبیه سازی PCSE",
"model_name": "مدل ووفوست",
"scenario_id": 15,
"simulation_warning": null,
"categories": ["2026-04-01", "2026-04-02"],
"series": [
{
"name": "تعداد برگ تخمینی",
"key": "leaf_count_estimate",
"data": [2400, 3600]
}
],
"summary": [
{
"title": "تعداد برگ تخمینی",
"subtitle": "وضعیت فعلی",
"amount": 3600,
"unit": "برگ",
"avatarColor": "success",
"avatarIcon": "tabler-leaf"
}
],
"current_state": {
"date": "2026-04-02",
"leaf_count_estimate": 3600,
"leaf_area_index": 0.3,
"biomass_weight": 120.5,
"storage_organ_weight": 0.0,
"soil_moisture_percent": 41.2,
"development_stage": 0.15,
"gdd": 12.5
},
"metrics": {
"yield_estimate": 5400,
"biomass": 9800,
"max_lai": 4.2
},
"daily_output": []
}
}
```
### توضیح کامل پاسخ
- `categories`: محور تاریخ chart
- `series`: سری‌های نمودار
- `summary`: کارت‌های summary
- `current_state`: وضعیت آخرین روز شبیه‌سازی
- `metrics`: متریک‌های خلاصه شبیه‌سازی
- `daily_output`: خروجی روزانه کامل PCSE
---
## 4) `POST /api/crop-simulation/yield-prediction/`
### کاربرد
تبدیل خروجی شبیه‌سازی به پیش‌بینی عملکرد.
### ورودی
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"irrigation_recommendation": {
"events": [
{
"date": "2026-04-25",
"amount": 2.5
}
]
},
"fertilization_recommendation": {
"events": [
{
"date": "2026-04-20",
"N_amount": 45,
"N_recovery": 0.7
}
]
}
}
```
### پاسخ موفق
```json
{
"code": 200,
"msg": "موفق",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"predictedYieldTons": 5.4,
"predictedYieldRaw": 5400.0,
"unit": "تن",
"sourceUnit": "کیلوگرم در هکتار",
"simulationEngine": "موتور شبیه سازی PCSE",
"simulationModel": "مدل ووفوست",
"scenarioId": 15,
"simulationWarning": null,
"supportingMetrics": {
"yield_estimate": 5400.0,
"biomass": 9800.0,
"max_lai": 4.2
}
}
}
```
### توضیح فیلدها
- `predictedYieldTons`: عملکرد پیش‌بینی‌شده بر حسب تن
- `predictedYieldRaw`: عملکرد خام بر حسب کیلوگرم در هکتار
- `unit`: واحد نهایی
- `sourceUnit`: واحد منبع
- `simulationEngine`: نام موتور
- `simulationModel`: نام مدل
- `scenarioId`: شناسه سناریو
- `simulationWarning`: هشدار احتمالی
- `supportingMetrics`: متریک‌های پشتیبان شبیه‌سازی
---
## 5) `POST /api/crop-simulation/harvest-prediction/`
### کاربرد
پیش‌بینی زمان تقریبی برداشت.
### ورودی
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"irrigation_recommendation": {
"events": [
{
"date": "2026-04-25",
"amount": 2.5
}
]
},
"fertilization_recommendation": {
"events": [
{
"date": "2026-04-20",
"N_amount": 45,
"N_recovery": 0.7
}
]
}
}
```
### پاسخ موفق
```json
{
"code": 200,
"msg": "موفق",
"data": {
"date": "2026-05-14",
"dateFormatted": "14 May 2026",
"daysUntil": 23,
"description": "توضیح زمان برداشت",
"optimalWindowStart": "2026-05-11",
"optimalWindowEnd": "2026-05-17",
"gddDetails": {
"current_cumulative_gdd": 50,
"required_gdd_for_maturity": 1200,
"remaining_gdd": 1150,
"estimated_days_to_harvest": 23,
"predicted_harvest_date": "2026-05-14",
"predicted_harvest_window": {
"start": "2026-05-11",
"end": "2026-05-17"
},
"daily_gdd_forecast": [],
"simulation_engine": "pcse",
"simulation_model_name": "Wofost81_NWLP_CWB_CNB",
"simulation_warning": null,
"scenario_id": 18
}
}
}
```
### توضیح فیلدها
- `date`: تاریخ برداشت پیش‌بینی‌شده
- `dateFormatted`: تاریخ فرمت‌شده برای UI
- `daysUntil`: تعداد روز تا برداشت
- `description`: توضیح خلاصه
- `optimalWindowStart`: شروع بازه بهینه برداشت
- `optimalWindowEnd`: پایان بازه بهینه برداشت
- `gddDetails`: جزئیات مبتنی بر GDD و شبیه‌سازی
---
## 6) `GET /api/crop-simulation/yield-harvest-summary/`
### کاربرد
ساخت داشبورد خلاصه عملکرد و برداشت.
### ورودی
این API از query string استفاده می‌کند.
### پارامترهای query
- `farm_uuid`: شناسه مزرعه
- `season_year`: سال زراعی
- `crop_name`: نام محصول
- `include_narrative`: اگر `true` باشد، تلاش برای تولید narrative می‌شود
- `irrigation_recommendation`: JSON string برنامه آبیاری
- `fertilization_recommendation`: JSON string برنامه کودهی
### نمونه درخواست
```text
GET /api/crop-simulation/yield-harvest-summary/?farm_uuid=11111111-1111-1111-1111-111111111111&season_year=1404&crop_name=wheat&include_narrative=true&irrigation_recommendation={"events":[{"date":"2026-04-25","amount":2.5}]}&fertilization_recommendation={"events":[{"date":"2026-04-20","N_amount":45,"N_recovery":0.7}]}
```
### پاسخ موفق
```json
{
"code": 200,
"msg": "موفق",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"season_highlights_card": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"crop_name": "wheat",
"season_year": "1404",
"title": "خلاصه فصل",
"subtitle": "",
"total_predicted_yield": 5.4,
"yield_unit": "تن",
"target_harvest_date": "2026-05-14",
"days_until_harvest": 23,
"average_readiness": 74,
"primary_quality_grade": "A",
"estimated_revenue": null,
"soil_type": "loam"
},
"yield_prediction": {},
"harvest_prediction_card": {},
"harvest_readiness_zones": {},
"yield_quality_bands": {},
"harvest_operations_card": {},
"yield_prediction_chart": {}
}
}
```
### ساختار response
- `season_highlights_card`: خلاصه فصل
- `yield_prediction`: بلوک پیش‌بینی عملکرد
- `harvest_prediction_card`: بلوک پیش‌بینی برداشت
- `harvest_readiness_zones`: آمادگی برداشت نواحی
- `yield_quality_bands`: باندهای کیفیت محصول
- `harvest_operations_card`: پیشنهاد عملیات برداشت
- `yield_prediction_chart`: نمودار عملکرد و بیوماس
### توضیح مهم
این API بخش‌هایی از response را از چند سرویس مختلف می‌سازد، بنابراین بعضی بلوک‌ها ساختار nested و بزرگ دارند. مخصوصاً:
- `yield_prediction`
- `harvest_prediction_card`
- `yield_prediction_chart`
این سه بخش مستقیماً تحت تأثیر `irrigation_recommendation` و `fertilization_recommendation` قرار می‌گیرند چون از اجرای PCSE استفاده می‌کنند.
---
## 7) `POST /api/irrigation/water-stress/`
### کاربرد
محاسبه شاخص تنش آبی با استفاده از خروجی شبیه‌سازی.
### ورودی
```json
{
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"irrigation_recommendation": {
"events": [
{
"date": "2026-04-25",
"amount": 2.5
}
]
},
"fertilization_recommendation": {
"events": [
{
"date": "2026-04-20",
"N_amount": 45,
"N_recovery": 0.7
}
]
}
}
```
### توضیح ورودی
- `farm_uuid`: شناسه مزرعه
- `sensor_uuid`: نام قدیمی برای `farm_uuid`
- `plant_name`: نام گیاه، اختیاری
- `irrigation_recommendation`: برنامه آبیاری
- `fertilization_recommendation`: برنامه کودهی
### پاسخ موفق
```json
{
"code": 200,
"msg": "success",
"data": {
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"plant_name": "گوجه‌فرنگی",
"waterStressIndex": 37,
"level": "متوسط",
"sourceMetric": {
"soilMoisturePercent": 46.0,
"availableWaterRatio": 0.72,
"fieldCapacity": 0.34,
"wiltingPoint": 0.14,
"rootDepthCm": 120.0,
"recentEt0": 0.33,
"recentRain": 1.0,
"soilMoistureDrop": 4.2,
"developmentStage": 1.02,
"stageCode": "flowering",
"stageSensitivity": 1.2,
"engine": "crop_simulation",
"formula": "stress = clamp(((moisture_deficit + et0_pressure + trend_pressure - rainfall_relief - root_depth_relief) * stage_sensitivity), 0, 100)"
}
}
}
```
### توضیح فیلدهای response
- `farm_uuid`: شناسه مزرعه
- `plant_name`: نام گیاه resolved شده
- `waterStressIndex`: شاخص نهایی تنش آبی بین `0` تا `100`
- `level`: سطح تنش آبی (`پایین`، `متوسط`، `بالا`)
- `sourceMetric`: جزئیات محاسبه
### توضیح `sourceMetric`
- `soilMoisturePercent`: درصد رطوبت خاک
- `availableWaterRatio`: نسبت آب قابل استفاده
- `fieldCapacity`: ظرفیت مزرعه
- `wiltingPoint`: نقطه پژمردگی
- `rootDepthCm`: عمق ریشه
- `recentEt0`: تبخیر و تعرق مرجع اخیر
- `recentRain`: بارش اخیر
- `soilMoistureDrop`: افت رطوبت خاک
- `developmentStage`: مرحله رشد عددی
- `stageCode`: کد مرحله رشد
- `stageSensitivity`: حساسیت مرحله رشد
- `engine`: منبع محاسبه
- `formula`: فرمول محاسبه شاخص
---
## خطاهای رایج
### خطای 400
وقتی ورودی نامعتبر باشد:
```json
{
"code": 400,
"msg": "داده نامعتبر.",
"data": {
"farm_uuid": ["farm_uuid الزامی است."]
}
}
```
### خطای 404
بیشتر در `water-stress` وقتی مزرعه پیدا نشود:
```json
{
"code": 404,
"msg": "Farm not found.",
"data": null
}
```
### خطای 500
وقتی اجرای شبیه‌سازی یا ساخت response شکست بخورد:
```json
{
"code": 500,
"msg": "خطا در پیش بینی عملکرد: ...",
"data": null
}
```
---
## جمع‌بندی
APIهایی که در این سند توضیح داده شدند، حالا می‌توانند از ورودی کاربر:
- `irrigation_recommendation`
- `fertilization_recommendation`
را دریافت کنند و آن‌ها را به لایه شبیه‌سازی `PCSE` پاس بدهند. این یعنی خروجی‌های:
- رشد
- chart مزرعه
- پیش‌بینی عملکرد
- پیش‌بینی برداشت
- خلاصه Yield/Harvest
- شاخص تنش آبی
می‌توانند تحت تأثیر برنامه آبیاری و برنامه کودهی ارسالی کاربر قرار بگیرند.
@@ -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 قابل اعتماد می‌شود، هم توسعه بعدی آن تمیز و قابل نگهداری می‌ماند.