# Crop Zoning Backend Handoff این سند برای انتقال منطق موجود در `src/views/dashboards/farm/cropZoning/cropZoningUtils.ts` از فرانت‌اند به بک‌اند تهیه شده است تا تیم بک‌اند بتواند APIهای لازم را به‌صورت پایدار و قابل توسعه پیاده‌سازی کند. ## هدف منطق فعلی در فرانت‌اند دو کار اصلی انجام می‌دهد: 1. تولید گرید مربعی از روی یک پلیگان مزرعه 2. اختصاص محصول پیشنهادی به هر زون بر اساس یک الگوریتم Rule-based موقت هدف انتقال به بک‌اند این است که: - تولید زون‌ها در یک نقطه مرکزی و قابل اعتماد انجام شود - منطق پیشنهاد محصول از فرانت حذف شود - APIها قابل استفاده برای ذخیره، بازخوانی، و توسعه مدل تصمیم‌گیری واقعی باشند - در آینده بتوان الگوریتم Rule-based را با مدل داده‌محور یا ML جایگزین کرد بدون تغییر زیاد در فرانت --- ## منطق فعلی فرانت فایل فعلی شامل دو تابع اصلی است: ### 1) `createGridFromPolygon` ورودی: - یک `GeoJSON Feature` - مقدار `cellSideKm` با مقدار پیش‌فرض `0.15` خروجی: - یک `FeatureCollection` شامل سلول‌های مربعی داخل پلیگان - برای هر سلول فقط `properties.index` ثبت می‌شود رفتار: - با استفاده از bounding box پلیگان، گرید مربعی ساخته می‌شود - فقط بخش‌های داخل پلیگان نگه داشته می‌شوند (`mask`) - هر سلول یک اندیس ترتیبی می‌گیرد ### 2) `createZonedGrid` ورودی: - یک `GeoJSON Feature` - مقدار `cellSideKm` با مقدار پیش‌فرض `0.15` خروجی: - یک `FeatureCollection` برای هر زون این فیلدها تولید می‌شوند: - `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` برگرداند تا فرانت با کمترین تغییر از آن استفاده کند