Files
Frontend/docs/crop-zoning-backend-handoff.md
T
2026-04-01 17:28:05 +03:30

14 KiB

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

{
  "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 پیشنهادی

{
  "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 پیشنهادی

{
  "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 پیشنهادی

{
  "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

{
  "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

شبه‌کد بک‌اند برای الگوریتم موقت

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