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