This commit is contained in:
2026-05-09 16:55:06 +03:30
parent 1679825ae2
commit cead7dafe2
51 changed files with 7514 additions and 1221 deletions
+371
View File
@@ -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 دارد، ولی منطق جدید بلوک‌ها فعلاً مستقل و محلی پیاده شده است.
+685
View File
@@ -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
+972
View File
@@ -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 تبدیل شده است.