Files

467 lines
14 KiB
Markdown
Raw Permalink Normal View History

2026-04-01 17:28:05 +03:30
# Crop Zoning Backend Handoff
این سند برای انتقال منطق موجود در `src/views/dashboards/farm/cropZoning/cropZoningUtils.ts` از فرانت‌اند به بک‌اند تهیه شده است تا تیم بک‌اند بتواند APIهای لازم را به‌صورت پایدار و قابل توسعه پیاده‌سازی کند.
## هدف
منطق فعلی در فرانت‌اند دو کار اصلی انجام می‌دهد:
1. تولید گرید مربعی از روی یک پلیگان مزرعه
2. اختصاص محصول پیشنهادی به هر زون بر اساس یک الگوریتم Rule-based موقت
هدف انتقال به بک‌اند این است که:
- تولید زون‌ها در یک نقطه مرکزی و قابل اعتماد انجام شود
- منطق پیشنهاد محصول از فرانت حذف شود
- APIها قابل استفاده برای ذخیره، بازخوانی، و توسعه مدل تصمیم‌گیری واقعی باشند
- در آینده بتوان الگوریتم Rule-based را با مدل داده‌محور یا ML جایگزین کرد بدون تغییر زیاد در فرانت
---
## منطق فعلی فرانت
فایل فعلی شامل دو تابع اصلی است:
### 1) `createGridFromPolygon`
ورودی:
- یک `GeoJSON Feature<Polygon>`
- مقدار `cellSideKm` با مقدار پیش‌فرض `0.15`
خروجی:
- یک `FeatureCollection<Polygon>` شامل سلول‌های مربعی داخل پلیگان
- برای هر سلول فقط `properties.index` ثبت می‌شود
رفتار:
- با استفاده از bounding box پلیگان، گرید مربعی ساخته می‌شود
- فقط بخش‌های داخل پلیگان نگه داشته می‌شوند (`mask`)
- هر سلول یک اندیس ترتیبی می‌گیرد
### 2) `createZonedGrid`
ورودی:
- یک `GeoJSON Feature<Polygon>`
- مقدار `cellSideKm` با مقدار پیش‌فرض `0.15`
خروجی:
- یک `FeatureCollection<Polygon, ZoneFeatureProperties>`
برای هر زون این فیلدها تولید می‌شوند:
- `zoneId`
- `crop`
- `matchPercent`
- `waterNeed`
- `estimatedProfit`
- `reason`
- `criteria`
منطق تخصیص محصول فعلی از تابع `ruleBasedCropAssignment` می‌آید.
---
## جزئیات الگوریتم فعلی تخصیص محصول
الگوریتم فعلی موقت و نمایشی است و به داده واقعی مزرعه متصل نیست.
### ورودی الگوریتم
- `index`: شماره زون
- `coords`: مختصات پلیگان سلول
### نحوه محاسبه
یک `seed` مصنوعی از این داده‌ها ساخته می‌شود:
- اندیس زون
- اولین latitude
- اولین longitude
فرمول فعلی:
- `seed = index * 7 + floor(lat * 100) + floor(lng * 100)`
### محصولات ممکن
در وضعیت فعلی فقط این محصولات استفاده می‌شوند:
- `wheat`
- `canola`
- `saffron`
### منطق انتخاب
- محصول با `seed % numberOfCrops` انتخاب می‌شود
- درصد تطابق (`matchPercent`) بین 60 تا 94 تولید می‌شود
- `waterNeed`، `estimatedProfit` و `reason` از روی محصول انتخابی از یک map ثابت خوانده می‌شوند
- `criteria` نیز به‌صورت ساختگی از seed تولید می‌شود
### نکته مهم
این الگوریتم:
- deterministic است
- اما علمی/عملیاتی نیست
- صرفاً برای نمایش UI مناسب بوده
- نباید به همان شکل نهایی در production باقی بماند مگر موقت و با برچسب mock/demo
---
## پیشنهاد معماری بک‌اند
بهتر است بک‌اند این منطق را در دو لایه جدا کند:
### لایه 1: Zone Generation
مسئول تولید گرید از روی پلیگان مزرعه
### لایه 2: Crop Recommendation / Zone Evaluation
مسئول تخصیص محصول و ویژگی‌های تحلیلی به هر زون
این جداسازی مهم است چون در آینده ممکن است:
- گرید ثابت بماند اما مدل پیشنهاد محصول عوض شود
- یا زون‌ها ذخیره شوند ولی recommendation دوباره محاسبه شود
---
## APIهای پیشنهادی
### API 1: Generate Initial Zones
**Purpose**
تولید گرید اولیه از روی محدوده مزرعه
**Method**
`POST /api/crop-zoning/zones/initial`
**Request Body**
```json
{
"farm_id": 123,
"cell_side_km": 0.15,
"boundary": {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[51.0,35.0],[51.1,35.0],[51.1,35.1],[51.0,35.1],[51.0,35.0]]]
},
"properties": {}
}
}
```
**Behavior**
- اعتبارسنجی GeoJSON انجام شود
- Polygon معتبر بودن بررسی شود
- گرید مربعی با اندازه `cell_side_km` ساخته شود
- فقط سلول‌های داخل boundary برگردانده شوند
- برای هر زون یک شناسه یکتا تولید شود
- در صورت نیاز زون‌ها در DB ذخیره شوند
**Response پیشنهادی**
```json
{
"farm_id": 123,
"cell_side_km": 0.15,
"zones": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[51.0,35.0],[51.01,35.0],[51.01,35.01],[51.0,35.01],[51.0,35.0]]]
},
"properties": {
"zone_id": "zone-0",
"index": 0
}
}
]
}
}
```
---
### API 2: Run Crop Recommendation For Zones
**Purpose**
اختصاص محصول پیشنهادی و شاخص‌ها به زون‌های تولید شده
**Method**
`POST /api/crop-zoning/zones/recommend`
**Request Body پیشنهادی**
```json
{
"farm_id": 123,
"algorithm": "rule_based_v1",
"zones": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[51.0,35.0],[51.01,35.0],[51.01,35.01],[51.0,35.01],[51.0,35.0]]]
},
"properties": {
"zone_id": "zone-0",
"index": 0
}
}
]
}
}
```
**Behavior**
- اگر فعلاً داده واقعی وجود ندارد، همان الگوریتم موقت `rule_based_v1` پیاده‌سازی شود
- خروجی برای هر زون شامل crop recommendation و تحلیل‌ها باشد
- اگر زون‌ها از قبل در DB هستند، امکان ارسال فقط `farm_id` هم می‌تواند اضافه شود
**Response پیشنهادی**
```json
{
"farm_id": 123,
"algorithm": "rule_based_v1",
"zones": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[51.0,35.0],[51.01,35.0],[51.01,35.01],[51.0,35.01],[51.0,35.0]]]
},
"properties": {
"zone_id": "zone-0",
"index": 0,
"crop": "wheat",
"match_percent": 82,
"water_need": "۴۵۰۰-۵۵۰۰ m³/ha",
"estimated_profit": "۱۵-۲۵ میلیون/هکتار",
"reason": "دمای مناسب، خاک حاصلخیز، دسترسی به آب کافی",
"criteria": [
{ "name": "دما", "value": 78 },
{ "name": "بارش", "value": 66 },
{ "name": "خاک", "value": 72 },
{ "name": "آب", "value": 81 }
]
}
}
]
}
}
```
---
### API 3: Get Saved Zoning Result
**Purpose**
دریافت آخرین نتیجه زون‌بندی ذخیره شده برای مزرعه
**Method**
`GET /api/crop-zoning/farms/:farmId/zones`
**Use case**
- هنگام ورود مجدد کاربر به صفحه
- جلوگیری از محاسبه مجدد غیرضروری
- نمایش نسخه ذخیره‌شده recommendation
**Response**
- همان ساختار `FeatureCollection` enriched شده با properties کامل هر زون
---
## ساختار داده پیشنهادی
### Zone entity
حداقل فیلدهای پیشنهادی برای هر زون:
- `id`
- `farm_id`
- `zone_index`
- `geometry` (GeoJSON Polygon یا نوع spatial معادل)
- `cell_side_km`
- `created_at`
- `updated_at`
### Zone recommendation entity
اگر recommendation جدا ذخیره شود:
- `id`
- `zone_id`
- `algorithm_version`
- `crop`
- `match_percent`
- `water_need`
- `estimated_profit`
- `reason`
- `criteria` (JSON)
- `created_at`
اگر simplicity مهم‌تر است، می‌توان recommendation را مستقیماً روی جدول zone نیز ذخیره کرد.
---
## قرارداد خروجی پیشنهادی برای فرانت
برای کم کردن تغییرات فرانت، بهتر است response نهایی نزدیک به ساختار فعلی باشد.
### ساختار هر feature
```json
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": []
},
"properties": {
"zone_id": "zone-0",
"index": 0,
"crop": "wheat",
"match_percent": 82,
"water_need": "۴۵۰۰-۵۵۰۰ m³/ha",
"estimated_profit": "۱۵-۲۵ میلیون/هکتار",
"reason": "...",
"criteria": [
{ "name": "دما", "value": 78 }
]
}
}
```
### نکته naming
در فرانت فعلی بعضی فیلدها camelCase هستند:
- `zoneId`
- `matchPercent`
- `waterNeed`
- `estimatedProfit`
اگر بک‌اند snake_case برمی‌گرداند، فرانت باید mapper داشته باشد.
برای ساده‌تر شدن integration یکی از این دو رویکرد انتخاب شود:
1. بک‌اند موقتاً همان naming فرانت را برگرداند
2. یا فرانت یک adapter ثابت برای map کردن response داشته باشد
پیشنهاد بهتر:
- بک‌اند از قرارداد استاندارد خودش مثل `snake_case` استفاده کند
- فرانت mapper داشته باشد
---
## پیشنهاد برای نسخه اول پیاده‌سازی
برای فاز اول، این scope کافی است:
### فاز 1
- تولید گرید از polygon در بک‌اند
- پیاده‌سازی الگوریتم mock با نام `rule_based_v1`
- برگرداندن FeatureCollection کامل
- امکان ذخیره نتیجه برای هر farm
### فاز 2
- استفاده از داده‌های واقعی مثل:
- نوع خاک
- منابع آب
- اقلیم
- شیب زمین
- سوابق کشت
- نسخه‌بندی الگوریتم recommendation
- بازمحاسبه recommendation بدون بازتولید geometry
---
## شبه‌کد بک‌اند برای الگوریتم موقت
```text
for each zone in zones:
coords = zone.geometry.coordinates
lat = coords[0][0][1] or 35
lng = coords[0][0][0] or 51
seed = zone.index * 7 + floor(lat * 100) + floor(lng * 100)
crops = [wheat, canola, saffron]
crop = crops[abs(seed) % len(crops)]
match_percent = 60 + (abs(seed) % 35)
water_need = map by crop
estimated_profit = map by crop
reason = map by crop
criteria = generated from seed
```
---
## Validation requirements
بک‌اند بهتر است این موارد را validate کند:
- `boundary.type == Feature`
- `geometry.type == Polygon`
- polygon ring بسته باشد
- مختصات معتبر longitude/latitude باشند
- `cell_side_km > 0`
- تعداد زون‌ها از یک سقف منطقی بیشتر نشود
### پیشنهاد محدودیت
برای جلوگیری از payload سنگین:
- حداقل `cell_side_km` تعریف شود
- یا حداکثر تعداد زون مجاز تعریف شود
- اگر مزرعه خیلی بزرگ باشد response مناسب خطا داده شود
---
## Error responses پیشنهادی
### 400 Bad Request
برای:
- GeoJSON نامعتبر
- polygon نامعتبر
- cell size نامعتبر
### 404 Not Found
برای:
- farm پیدا نشد
### 422 Unprocessable Entity
برای:
- boundary معتبر است ولی برای zoning قابل استفاده نیست
---
## چیزی که باید از فرانت حذف یا ساده شود
بعد از آماده شدن API بک‌اند، این بخش‌ها از فرانت باید حذف/کم شوند:
- استفاده مستقیم از `createZonedGrid`
- استفاده مستقیم از `ruleBasedCropAssignment`
- تولید mock recommendation در کلاینت
و فرانت فقط این مسئولیت‌ها را نگه می‌دارد:
- ارسال boundary
- دریافت FeatureCollection از API
- نمایش زون‌ها روی نقشه
- نمایش جزئیات recommendation
---
## پیشنهاد نهایی برای تیم بک‌اند
اگر بخواهیم کم‌ریسک و سریع جلو برویم:
1. ابتدا فقط endpoint تولید zone + recommendation را با الگوریتم mock فعلی بسازید
2. response را GeoJSON-based نگه دارید
3. شناسه و نسخه الگوریتم را در خروجی قرار دهید
4. بعداً recommendation engine را از geometry generation جدا کنید
---
## خلاصه اجرایی
منطق فعلی فرانت یک implementation موقت برای:
- ساخت grid از polygon مزرعه
- اختصاص crop recommendation نمایشی به هر zone
پیشنهاد می‌شود بک‌اند:
- این منطق را به API منتقل کند
- geometry generation و recommendation را از هم جدا نگه دارد
- در فاز اول همان رفتار deterministic فعلی را با نام `rule_based_v1` پیاده‌سازی کند
- خروجی را به‌صورت `GeoJSON FeatureCollection` برگرداند تا فرانت با کمترین تغییر از آن استفاده کند