467 lines
14 KiB
Markdown
467 lines
14 KiB
Markdown
|
|
# 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` برگرداند تا فرانت با کمترین تغییر از آن استفاده کند
|