Remove unused __init__.py file and update mock data to include new endpoints for area, products, and zones, along with their respective response structures. Refactor views to implement new API endpoints and adjust Postman collection to reflect these changes.

This commit is contained in:
2026-02-26 00:38:46 +03:30
parent 2a77f90ccd
commit 4c5b1298a0
6 changed files with 1080 additions and 168 deletions
+544
View File
@@ -0,0 +1,544 @@
# مستندات APIهای زون‌بندی کشت (Crop Zoning)
این سند تمام APIهای مورد نیاز برای صفحه **Crop Zoning** را شرح می‌دهد: ورودی‌ها، خروجی‌ها، محصولات، رنگ‌ها، مساحت کلی و دیتای هر بخش زمین به صورت جداگانه.
**مسیر صفحه:** `(dashboard)/(private)/crop-zoning`
**کامپوننت اصلی:** `CropZoningWrapper`
---
## نمای کلی و جریان درخواست‌ها
```
۱. GET area → منطقهٔ ثابت (کاربر امکان رسم ندارد)
۲. GET products → لیست محصولات و رنگ‌ها
۳. POST zones/initial → ارسال محدودهٔ مربع‌ها → دیتای محصولات پیشنهادی (نقشه + tooltip)
۴. POST zones/water-need → ارسال محدودهٔ مربع‌ها → نیاز آبی هر منطقه
۵. POST zones/soil-quality → ارسال محدودهٔ مربع‌ها → کیفیت خاک هر منطقه
۶. POST zones/cultivation-risk → ارسال محدودهٔ مربع‌ها → ریسک کشت هر منطقه
۷. GET zone/:zoneId → کلیک روی مربع → دیتای تکمیلی (پنل جزئیات: reason, criteria, ...)
```
| ردیف | API | هدف |
|------|-----|------|
| ۱ | **منطقهٔ اولیه** | دریافت منطقهٔ زمین به صورت GeoJSON؛ کاربر نمی‌تواند چیزی رسم کند |
| ۲ | **لیست محصولات و رنگ‌ها** | دریافت محصولات قابل کشت به همراه رنگ نمایش و لیبل فارسی |
| ۳ | **دیتای اولیه زون‌ها (محصولات)** | ارسال محدودهٔ مربع‌ها، دریافت محصول پیشنهادی برای نقشه و tooltip |
| ۴ | **نیاز آبی** | ارسال محدودهٔ مربع‌ها، دریافت نیاز آبی هر منطقه برای لایهٔ نیاز آبی |
| ۵ | **کیفیت خاک** | ارسال محدودهٔ مربع‌ها، دریافت کیفیت خاک هر منطقه برای لایهٔ کیفیت خاک |
| ۶ | **ریسک کشت** | ارسال محدودهٔ مربع‌ها، دریافت ریسک کشت هر منطقه برای لایهٔ ریسک کشت |
| ۷ | **دیتای تکمیلی زون** | با کلیک روی هر مربع، دریافت دیتای جزئیات (دلیل، معیارها، نمودار) |
---
## ۰. API منطقهٔ اولیه (Area)
منطقهٔ ثابت زمین که از بک‌اند دریافت می‌شود. کاربر امکان رسم یا ویرایش منطقه را ندارد.
### ۰.۱ مشخصات
- **متد:** `GET`
- **آدرس پیشنهادی:** `GET /api/crop-zoning/area/`
- **هدف:** دریافت polygon منطقهٔ زمین برای نمایش روی نقشه.
### ۰.۲ ورودی (Request)
بدون پارامتر.
### ۰.۳ خروجی (Response Body)
```json
{
"status": "success",
"data": {
"area": {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.40, 35.68],
[51.40, 35.70],
[51.38, 35.70],
[51.38, 35.68]
]
]
}
}
}
}
```
- **مختصات:** `[longitude, latitude]` طبق استاندارد GeoJSON
---
## ۱. API لیست محصولات و رنگ‌ها
برای نمایش راهنمای رنگ‌ها (Legend) و dropdown انتخاب محصول در پنل جزئیات هر زون.
### ۱.۱ مشخصات
- **متد:** `GET`
- **آدرس پیشنهادی:** `GET /api/crop-zoning/products/` یا `GET /api/crops/`
- **هدف:** دریافت لیست محصولات قابل کشت با رنگ و لیبل نمایشی.
### ۱.۲ ورودی (Request)
بدون پارامتر یا با پارامترهای اختیاری:
| پارامتر | نوع | اجباری | توضیح |
|---------|-----|--------|--------|
| `locale` | string | خیر | کد زبان برای لیبل‌ها (مثلاً `fa`, `en`) |
### ۱.۳ خروجی (Response)
```json
{
"status": "success",
"data": {
"products": [
{
"id": "wheat",
"label": "گندم",
"color": "#6bcb77"
},
{
"id": "canola",
"label": "کلزا",
"color": "#ffd93d"
},
{
"id": "saffron",
"label": "زعفران",
"color": "#9b59b6"
}
]
}
}
```
**ساختار هر محصول:**
| فیلد | نوع | اجباری | توضیح |
|------|-----|--------|--------|
| `id` | string | بله | شناسهٔ یکتا (مثلاً `wheat`, `canola`, `saffron`) |
| `label` | string | بله | نام نمایشی به زبان کاربر |
| `color` | string | بله | رنگ hex برای نمایش روی نقشه و Legend |
---
## ۲. API دیتای اولیه زون‌ها
با رسم منطقهٔ زمین، فرانت **محدودهٔ همهٔ مربع‌ها** (گرید داخل polygon) را ارسال می‌کند و **دیتای اولیه** هر مربع را دریافت می‌کند — برای نمایش نقشه، رنگ‌بندی، و **هاور/tooltip**. این دیتا شامل `reason` و `criteria` **نیست**.
### ۲.۱ مشخصات
- **متد:** `POST`
- **آدرس پیشنهادی:** `POST /api/crop-zoning/zones/initial/`
- **هدف:** ارسال محدودهٔ مربع‌ها، دریافت دیتای اولیه برای نقشه، هاور و tooltip.
### ۲.۲ ورودی (Request Body)
فرانت ابتدا با Turf.js از روی polygon منطقه گرید می‌سازد، سپس `FeatureCollection` همهٔ polygonهای مربع‌ها را ارسال می‌کند.
| فیلد | نوع | اجباری | توضیح |
|------|-----|--------|--------|
| `zones` | GeoJSON FeatureCollection | بله | محدودهٔ هر مربع به صورت Polygon؛ ترتیب index با پاسخ یکسان است |
| `products` | string[] | خیر | لیست محصولات مدنظر؛ در صورت عدم ارسال از همهٔ محصولات استفاده شود |
**ساختار `zones` (محدودهٔ همهٔ مربع‌ها):**
```json
{
"zones": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.3815, 35.68],
[51.3815, 35.6815],
[51.38, 35.6815],
[51.38, 35.68]
]
]
},
"properties": { "index": 0 }
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.3815, 35.68],
[51.383, 35.68],
[51.383, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.68]
]
]
},
"properties": { "index": 1 }
}
]
},
"products": ["wheat", "canola", "saffron"]
}
```
- **مختصات:** `[longitude, latitude]` طبق استاندارد GeoJSON
- **index:** در `properties` هر feature برای تطابق با پاسخ (اختیاری؛ در صورت نبودن از ترتیب آرایه استفاده شود)
### ۲.۳ خروجی (Response Body) — دیتای اولیه
فقط فیلدهای لازم برای **نقشه**، **هاور** و **tooltip** (نمایش هنگام عبور ماوس روی هر مربع)؛ بدون `reason` و `criteria`.
```json
{
"status": "success",
"data": {
"total_area_hectares": 23.45,
"total_area_sqm": 234500,
"zone_count": 3,
"zones": [
{
"zoneId": "zone-0",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.3815, 35.68],
[51.3815, 35.6815],
[51.38, 35.6815],
[51.38, 35.68]
]
]
},
"crop": "wheat",
"matchPercent": 85,
"waterNeed": "۴۵۰۰-۵۵۰۰ m³/ha",
"estimatedProfit": "۱۵-۲۵ میلیون/هکتار"
},
{
"zoneId": "zone-1",
"geometry": { "type": "Polygon", "coordinates": [...] },
"crop": "canola",
"matchPercent": 78,
"waterNeed": "۵۰۰۰-۶۰۰۰ m³/ha",
"estimatedProfit": "۲۰-۳۵ میلیون/هکتار"
}
]
}
}
```
**ساختار دیتای اولیه هر زون (هم برای نقشه هم برای هاور/tooltip):**
| فیلد | نوع | اجباری | توضیح |
|------|-----|--------|--------|
| `zoneId` | string | بله | شناسهٔ یکتا برای درخواست دیتای تکمیلی |
| `geometry` | Polygon | بله | هندسهٔ همان مربع ارسالی |
| `crop` | string \| null | خیر | محصول پیشنهادی؛ اگر `null`/خالی/`uncultivable` باشد → زون **غیرقابل کشت** و رنگ خاکستری |
| `matchPercent` | number | خیر | درصد تطابق (هاور/tooltip) |
| `waterNeed` | string | خیر | نیاز آبی (هاور/tooltip) |
| `estimatedProfit` | string | خیر | سود تخمینی (هاور/tooltip) |
**زون غیرقابل کشت:** اگر برای مربعی اطلاعاتی نیاید یا `crop` خالی/`null`/`uncultivable` باشد، آن مربع خاکستری نمایش داده شده و در tooltip «غیر قابل کشت» نشان داده می‌شود. کلیک روی آن پنل جزئیات باز نمی‌شود.
**نکته:** این فیلدها هنگام **هاور** روی مربع در tooltip نمایش داده می‌شوند؛ نیازی به درخواست جداگانه برای tooltip نیست.
---
## ۲.۱ API نیاز آبی (Water Need)
نیاز آبی هر منطقه را بر اساس محدودهٔ مربع‌ها برمی‌گرداند. با تغییر لایه به «نیاز آبی» در LayerControl، فرانت این API را صدا می‌زند و نقشه و Legend را به‌روزرسانی می‌کند.
### مشخصات
- **متد:** `POST`
- **آدرس پیشنهادی:** `POST /api/crop-zoning/zones/water-need/`
- **هدف:** دریافت نیاز آبی هر منطقه برای نمایش روی نقشه در لایهٔ نیاز آبی.
### ورودی (Request Body)
همان ساختار `POST zones/initial/`:
| فیلد | نوع | اجباری | توضیح |
|------|-----|--------|--------|
| `zones` | GeoJSON FeatureCollection | بله | محدودهٔ هر مربع به صورت Polygon |
### خروجی (Response Body)
```json
{
"status": "success",
"data": {
"zones": [
{
"zoneId": "zone-0",
"geometry": { "type": "Polygon", "coordinates": [...] },
"level": "low",
"value": "۳۰۰۰-۴۰۰۰ m³/ha",
"color": "#7dd3fc"
},
{
"zoneId": "zone-1",
"geometry": { "type": "Polygon", "coordinates": [...] },
"level": "medium",
"value": "۵۰۰۰-۶۰۰۰ m³/ha",
"color": "#0ea5e9"
}
]
}
}
```
| فیلد | نوع | توضیح |
|------|-----|--------|
| `zoneId` | string | شناسهٔ زون |
| `geometry` | Polygon | هندسهٔ مربع |
| `level` | string | سطح: `low`, `medium`, `high` |
| `value` | string | مقدار نیاز آبی (مثلاً m³/ha) |
| `color` | string | رنگ hex برای نمایش |
---
## ۲.۲ API کیفیت خاک (Soil Quality)
کیفیت خاک هر منطقه را برمی‌گرداند. با تغییر لایه به «کیفیت خاک»، فرانت این API را صدا می‌زند.
### مشخصات
- **متد:** `POST`
- **آدرس پیشنهادی:** `POST /api/crop-zoning/zones/soil-quality/`
### ورودی (Request Body)
همان `zones` (FeatureCollection).
### خروجی (Response Body)
```json
{
"status": "success",
"data": {
"zones": [
{
"zoneId": "zone-0",
"geometry": { "type": "Polygon", "coordinates": [...] },
"level": "low",
"score": 35,
"color": "#f87171"
},
{
"zoneId": "zone-1",
"geometry": { "type": "Polygon", "coordinates": [...] },
"level": "high",
"score": 85,
"color": "#22c55e"
}
]
}
}
```
| فیلد | نوع | توضیح |
|------|-----|--------|
| `level` | string | سطح: `low`, `medium`, `high` |
| `score` | number | امتیاز ۰–۱۰۰ |
| `color` | string | رنگ hex |
---
## ۲.۳ API ریسک کشت (Cultivation Risk)
ریسک کشت هر منطقه را برمی‌گرداند. با تغییر لایه به «ریسک کشت»، فرانت این API را صدا می‌زند.
### مشخصات
- **متد:** `POST`
- **آدرس پیشنهادی:** `POST /api/crop-zoning/zones/cultivation-risk/`
### ورودی و خروجی
ورودی: همان `zones` (FeatureCollection).
خروجی نمونه:
```json
{
"status": "success",
"data": {
"zones": [
{
"zoneId": "zone-0",
"geometry": { "type": "Polygon", "coordinates": [...] },
"level": "low",
"color": "#22c55e"
},
{
"zoneId": "zone-1",
"geometry": { "type": "Polygon", "coordinates": [...] },
"level": "high",
"color": "#ef4444"
}
]
}
}
```
| فیلد | نوع | توضیح |
|------|-----|--------|
| `level` | string | سطح: `low`, `medium`, `high` |
| `color` | string | رنگ hex |
**نکته:** برای هر لایه (نیاز آبی، کیفیت خاک، ریسک کشت) فرانت یک **درخواست جداگانه** ارسال می‌کند و نقشه و Legend متناسب با همان لایه به‌روزرسانی می‌شوند.
---
## ۳. API دیتای تکمیلی زون (با کلیک روی مربع)
وقتی کاربر روی یک مربع کلیک می‌کند، فرانت با `zoneId` دیتای **تکمیلی** را درخواست می‌کند — برای نمایش پنل جزئیات: دلیل پیشنهاد، معیارها، نمودار راداری.
### ۳.۱ مشخصات
- **متد:** `GET`
- **آدرس پیشنهادی:** `GET /api/crop-zoning/zones/:zoneId/details/`
- **هدف:** دریافت دیتای تکمیلی یک زون برای پنل جزئیات.
### ۳.۲ ورودی (Request)
| پارامتر | محل | نوع | اجباری | توضیح |
|---------|------|-----|--------|--------|
| `zoneId` | path | string | بله | شناسهٔ زون (مثلاً `zone-0`) |
**مثال:** `GET /api/crop-zoning/zones/zone-0/details/`
### ۳.۳ خروجی (Response Body)
```json
{
"status": "success",
"data": {
"zoneId": "zone-0",
"crop": "wheat",
"matchPercent": 85,
"waterNeed": "۴۵۰۰-۵۵۰۰ m³/ha",
"estimatedProfit": "۱۵-۲۵ میلیون/هکتار",
"reason": "دمای مناسب، خاک حاصلخیز، دسترسی به آب کافی",
"criteria": [
{ "name": "دما", "value": 82 },
{ "name": "بارش", "value": 75 },
{ "name": "خاک", "value": 88 },
{ "name": "آب", "value": 70 }
],
"area_hectares": 2.25
}
}
```
**فیلدهای دیتای تکمیلی:**
| فیلد | نوع | اجباری | توضیح |
|------|-----|--------|--------|
| `zoneId` | string | بله | همان zoneId درخواست |
| `crop` | string | بله | محصول پیشنهادی |
| `matchPercent` | number | بله | درصد تطابق |
| `waterNeed` | string | بله | نیاز آبی |
| `estimatedProfit` | string | بله | سود تخمینی |
| `reason` | string | بله | **فقط در دیتای تکمیلی** — دلیل پیشنهاد محصول |
| `criteria` | object[] | بله | **فقط در دیتای تکمیلی** — معیارها برای نمودار راداری |
| `area_hectares` | number | خیر | مساحت این زون بر حسب هکتار |
### ۳.۴ ساختار `criteria`
| فیلد | نوع | توضیح |
|------|-----|--------|
| `name` | string | نام معیار (دما، بارش، خاک، آب) |
| `value` | number | امتیاز ۰–۱۰۰ |
---
## ۴. مساحت کلی (Total Area)
در پاسخ **API دیتای اولیه زون‌ها** برمی‌گردد:
| فیلد | نوع | توضیح |
|------|-----|--------|
| `total_area_hectares` | number | مساحت کل منطقه بر حسب هکتار |
| `total_area_sqm` | number | مساحت کل بر حسب متر مربع |
---
## ۵. خلاصهٔ ساختار‌های مورد نیاز فرانت
### دیتای اولیه زون (برای نقشه و هاور/tooltip)
```ts
interface ZoneInitialData {
zoneId: string
geometry: Polygon
crop: string
matchPercent: number
waterNeed: string
estimatedProfit: string
}
```
### دیتای تکمیلی زون (برای پنل جزئیات — پس از کلیک)
```ts
interface ZoneDetailData {
zoneId: string
crop: string
matchPercent: number
waterNeed: string
estimatedProfit: string
reason: string
criteria: { name: string; value: number }[]
area_hectares?: number
}
```
### محصولات و رنگ‌ها (پیش‌فرض فرانت)
```ts
const CROP_COLORS: Record<CropType, string> = {
wheat: '#6bcb77',
canola: '#ffd93d',
saffron: '#9b59b6'
}
```
---
## ۶. جریان فرانت با APIها
1. **لود صفحه:** `GET /api/crop-zoning/products/` → لیست محصولات و رنگ‌ها.
2. **رسم منطقه / بهینه‌سازی:** فرانت با Turf از polygon منطقه گرید می‌سازد → `POST /api/crop-zoning/zones/initial/` با `zones` (FeatureCollection) → نقشه و tooltip با دیتای محصولات رسم می‌شود.
3. **تغییر لایه در LayerControl:** برای هر لایه یک درخواست جداگانه ارسال می‌شود:
- محصولات پیشنهادی: `POST zones/initial/` (در مرحلهٔ ۲)
- نیاز آبی: `POST zones/water-need/` → نقشه و Legend به‌روزرسانی می‌شوند
- کیفیت خاک: `POST zones/soil-quality/` → نقشه و Legend به‌روزرسانی می‌شوند
- ریسک کشت: `POST zones/cultivation-risk/` → نقشه و Legend به‌روزرسانی می‌شوند
4. **کلیک روی مربع:** `GET /api/crop-zoning/zones/{zoneId}/details/` → دیتای تکمیلی → پنل جزئیات باز می‌شود (reason, criteria, نمودار راداری).
---
## ۷. وضعیت فعلی و نیازمندی‌ها
- در حال حاضر زون‌بندی با **دیتای ماک** و الگوریتم محلی (`createZonedGrid` در `cropZoningUtils.ts`) کار می‌کند.
- برای اتصال به بک‌اند، لازم است:
1. سرویس `cropZoningService` با سه endpoint: `getProducts()`, `getZonesInitial(zones)`, `getZoneDetails(zoneId)` ایجاد شود.
2. در `CropZoningMap` به جای `createZonedGrid` ابتدا گرید با Turf ساخته شود، سپس `zones` به API ارسال و پاسخ برای رسم استفاده شود.
3. در `onZoneClick` قبل از باز کردن پنل، `getZoneDetails(zoneId)` صدا زده شود و دیتای تکمیلی به `ZoneDetailPanel` پاس داده شود.
4. مساحت کلی (`total_area_hectares`) در پاسخ initial در UI نمایش داده شود.
-1
View File
@@ -1 +0,0 @@
+315 -77
View File
@@ -1,116 +1,354 @@
"""
Static mock data for Crop Zoning API.
Matches API_RESPONSE_SPEC.md. No database, no dynamic values.
Matches CROP_ZONING_APIS.md. No database, no dynamic values.
"""
# Response for POST optimize: GeoJSON FeatureCollection (API_RESPONSE_SPEC §1)
OPTIMIZE_ZONING_RESPONSE = {
"type": "FeatureCollection",
"features": [
# ---------------------------------------------------------------------------
# GET /api/crop-zoning/area/
# منطقهٔ ثابت — کاربر امکان رسم ندارد
# ---------------------------------------------------------------------------
AREA_RESPONSE_DATA = {
"area": {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.405, 35.672],
[51.41, 35.695],
[51.385, 35.71],
[51.365, 35.688],
[51.38, 35.68],
]
],
},
}
}
# ---------------------------------------------------------------------------
# GET /api/crop-zoning/products/
# ---------------------------------------------------------------------------
PRODUCTS_RESPONSE_DATA = {
"products": [
{"id": "wheat", "label": "گندم", "color": "#6bcb77"},
{"id": "canola", "label": "کلزا", "color": "#ffd93d"},
{"id": "saffron", "label": "زعفران", "color": "#9b59b6"},
]
}
# ---------------------------------------------------------------------------
# POST /api/crop-zoning/zones/initial/
# دیتای اولیه برای نقشه و هاور/tooltip — بدون reason و criteria
# ---------------------------------------------------------------------------
ZONES_INITIAL_RESPONSE_DATA = {
"total_area_hectares": 23.45,
"total_area_sqm": 234500,
"zone_count": 3,
"zones": [
{
"type": "Feature",
"zoneId": "zone-0",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.381, 35.68],
[51.381, 35.681],
[51.38, 35.681],
[51.3815, 35.68],
[51.3815, 35.6815],
[51.38, 35.6815],
[51.38, 35.68],
]
],
},
"properties": {
"zoneId": "zone-0",
"crop": "wheat",
"matchPercent": 78,
"waterNeed": "۴۵۰۰-۵۵۰۰ m³/ha",
"estimatedProfit": "۱۵-۲۵ میلیون/هکتار",
"reason": "دمای مناسب، خاک حاصلخیز، دسترسی به آب کافی",
"criteria": [
{"name": "دما", "value": 85},
{"name": "بارش", "value": 72},
{"name": "خاک", "value": 80},
{"name": "آب", "value": 65},
],
},
"crop": "wheat",
"matchPercent": 85,
"waterNeed": "۴۵۰۰-۵۵۰۰ m³/ha",
"estimatedProfit": "۱۵-۲۵ میلیون/هکتار",
},
{
"type": "Feature",
"zoneId": "zone-1",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.381, 35.68],
[51.382, 35.68],
[51.382, 35.681],
[51.381, 35.681],
[51.381, 35.68],
[51.3815, 35.68],
[51.383, 35.68],
[51.383, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.68],
]
],
},
"properties": {
"zoneId": "zone-1",
"crop": "canola",
"matchPercent": 82,
"waterNeed": "۳۵۰۰-۴۵۰۰ m³/ha",
"estimatedProfit": "۲۰-۳۰ میلیون/هکتار",
"reason": "بارش کافی، خاک با بافت مناسب",
"criteria": [
{"name": "دما", "value": 70},
{"name": "بارش", "value": 88},
{"name": "خاک", "value": 75},
{"name": "آب", "value": 90},
],
},
"crop": "canola",
"matchPercent": 78,
"waterNeed": "۵۰۰۰-۶۰۰۰ m³/ha",
"estimatedProfit": "۲۰-۳۵ میلیون/هکتار",
},
{
"type": "Feature",
"zoneId": "zone-2",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.382, 35.68],
[51.40, 35.68],
[51.40, 35.681],
[51.382, 35.681],
[51.382, 35.68],
[51.38, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.683],
[51.38, 35.683],
[51.38, 35.6815],
]
],
},
"properties": {
"zoneId": "zone-2",
"crop": "saffron",
"matchPercent": 65,
"waterNeed": "۲۵۰۰-۳۵۰۰ m³/ha",
"estimatedProfit": "۸۰-۱۲۰ میلیون/هکتار",
"reason": "آب و هوای خشک و سرد مناسب زعفران",
"criteria": [
{"name": "دما", "value": 60},
{"name": "بارش", "value": 55},
{"name": "خاک", "value": 85},
{"name": "آب", "value": 50},
],
},
"crop": "saffron",
"matchPercent": 92,
"waterNeed": "۳۰۰۰-۴۰۰۰ m³/ha",
"estimatedProfit": "۵۰-۱۵۰ میلیون/هکتار",
},
],
}
# Response for GET initial region: GeoJSON Feature with Polygon (API_RESPONSE_SPEC §2)
INITIAL_REGION_RESPONSE = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.40, 35.68],
[51.40, 35.70],
[51.38, 35.70],
[51.38, 35.68],
]
# ---------------------------------------------------------------------------
# POST /api/crop-zoning/zones/water-need/
# نیاز آبی هر منطقه برای لایهٔ نیاز آبی
# ---------------------------------------------------------------------------
ZONES_WATER_NEED_RESPONSE_DATA = {
"zones": [
{
"zoneId": "zone-0",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.3815, 35.68],
[51.3815, 35.6815],
[51.38, 35.6815],
[51.38, 35.68],
]
],
},
"level": "medium",
"value": "۴۵۰۰-۵۵۰۰ m³/ha",
"color": "#0ea5e9",
},
{
"zoneId": "zone-1",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.3815, 35.68],
[51.383, 35.68],
[51.383, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.68],
]
],
},
"level": "high",
"value": "۵۰۰۰-۶۰۰۰ m³/ha",
"color": "#0369a1",
},
{
"zoneId": "zone-2",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.683],
[51.38, 35.683],
[51.38, 35.6815],
]
],
},
"level": "low",
"value": "۳۰۰۰-۴۰۰۰ m³/ha",
"color": "#7dd3fc",
},
]
}
# ---------------------------------------------------------------------------
# POST /api/crop-zoning/zones/soil-quality/
# کیفیت خاک هر منطقه برای لایهٔ کیفیت خاک
# ---------------------------------------------------------------------------
ZONES_SOIL_QUALITY_RESPONSE_DATA = {
"zones": [
{
"zoneId": "zone-0",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.3815, 35.68],
[51.3815, 35.6815],
[51.38, 35.6815],
[51.38, 35.68],
]
],
},
"level": "high",
"score": 88,
"color": "#22c55e",
},
{
"zoneId": "zone-1",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.3815, 35.68],
[51.383, 35.68],
[51.383, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.68],
]
],
},
"level": "medium",
"score": 62,
"color": "#eab308",
},
{
"zoneId": "zone-2",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.683],
[51.38, 35.683],
[51.38, 35.6815],
]
],
},
"level": "high",
"score": 95,
"color": "#22c55e",
},
]
}
# ---------------------------------------------------------------------------
# POST /api/crop-zoning/zones/cultivation-risk/
# ریسک کشت هر منطقه برای لایهٔ ریسک کشت
# ---------------------------------------------------------------------------
ZONES_CULTIVATION_RISK_RESPONSE_DATA = {
"zones": [
{
"zoneId": "zone-0",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.3815, 35.68],
[51.3815, 35.6815],
[51.38, 35.6815],
[51.38, 35.68],
]
],
},
"level": "low",
"color": "#22c55e",
},
{
"zoneId": "zone-1",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.3815, 35.68],
[51.383, 35.68],
[51.383, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.68],
]
],
},
"level": "medium",
"color": "#f59e0b",
},
{
"zoneId": "zone-2",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.683],
[51.38, 35.683],
[51.38, 35.6815],
]
],
},
"level": "low",
"color": "#22c55e",
},
]
}
# ---------------------------------------------------------------------------
# GET /api/crop-zoning/zones/:zoneId/details/
# دیتای تکمیلی برای پنل جزئیات — شامل reason و criteria
# منطبق با createZonedGrid و MOCK_AREA_GEOJSON
# ---------------------------------------------------------------------------
ZONE_DETAILS_BY_ID = {
"zone-0": {
"zoneId": "zone-0",
"crop": "wheat",
"matchPercent": 85,
"waterNeed": "۴۵۰۰-۵۵۰۰ m³/ha",
"estimatedProfit": "۱۵-۲۵ میلیون/هکتار",
"reason": "دمای مناسب، خاک حاصلخیز، دسترسی به آب کافی",
"criteria": [
{"name": "دما", "value": 82},
{"name": "بارش", "value": 75},
{"name": "خاک", "value": 88},
{"name": "آب", "value": 70},
],
"area_hectares": 2.25,
},
"zone-1": {
"zoneId": "zone-1",
"crop": "canola",
"matchPercent": 78,
"waterNeed": "۵۰۰۰-۶۰۰۰ m³/ha",
"estimatedProfit": "۲۰-۳۵ میلیون/هکتار",
"reason": "شرایط اقلیمی مساعد، نیاز آبی قابل تأمین",
"criteria": [
{"name": "دما", "value": 75},
{"name": "بارش", "value": 72},
{"name": "خاک", "value": 80},
{"name": "آب", "value": 78},
],
"area_hectares": 2.25,
},
"zone-2": {
"zoneId": "zone-2",
"crop": "saffron",
"matchPercent": 92,
"waterNeed": "۳۰۰۰-۴۰۰۰ m³/ha",
"estimatedProfit": "۵۰-۱۵۰ میلیون/هکتار",
"reason": "ارتفاع و آب و هوای خشک مناسب، پتانسیل سود بالا",
"criteria": [
{"name": "دما", "value": 90},
{"name": "بارش", "value": 65},
{"name": "خاک", "value": 95},
{"name": "آب", "value": 85},
],
"area_hectares": 2.25,
},
}
File diff suppressed because one or more lines are too long
+32 -3
View File
@@ -1,8 +1,37 @@
from django.urls import path
from .views import InitialRegionView, OptimizeZoningView
from .views import (
AreaView,
ProductsView,
ZoneDetailsView,
ZonesCultivationRiskView,
ZonesInitialView,
ZonesSoilQualityView,
ZonesWaterNeedView,
)
urlpatterns = [
path("optimize/", OptimizeZoningView.as_view(), name="crop-zoning-optimize"),
path("initial-region/", InitialRegionView.as_view(), name="crop-zoning-initial-region"),
path("area/", AreaView.as_view(), name="crop-zoning-area"),
path("products/", ProductsView.as_view(), name="crop-zoning-products"),
path("zones/initial/", ZonesInitialView.as_view(), name="crop-zoning-zones-initial"),
path(
"zones/water-need/",
ZonesWaterNeedView.as_view(),
name="crop-zoning-zones-water-need",
),
path(
"zones/soil-quality/",
ZonesSoilQualityView.as_view(),
name="crop-zoning-zones-soil-quality",
),
path(
"zones/cultivation-risk/",
ZonesCultivationRiskView.as_view(),
name="crop-zoning-zones-cultivation-risk",
),
path(
"zones/<str:zone_id>/details/",
ZoneDetailsView.as_view(),
name="crop-zoning-zone-details",
),
]
+188 -39
View File
@@ -3,65 +3,214 @@ Crop Zoning API views.
Plain Django only; no DRF. No database. All responses are static mock data.
Response format: {"status": "success", "data": <payload>}. HTTP 200 only.
No processing, validation, or use of input parameters in responses.
CSRF exempt on POST so frontend can call without token.
"""
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from .mock_data import INITIAL_REGION_RESPONSE, OPTIMIZE_ZONING_RESPONSE
from .mock_data import (
AREA_RESPONSE_DATA,
PRODUCTS_RESPONSE_DATA,
ZONE_DETAILS_BY_ID,
ZONES_CULTIVATION_RISK_RESPONSE_DATA,
ZONES_INITIAL_RESPONSE_DATA,
ZONES_SOIL_QUALITY_RESPONSE_DATA,
ZONES_WATER_NEED_RESPONSE_DATA,
)
class OptimizeZoningView(View):
class AreaView(View):
"""
POST endpoint for zoning optimization.
GET endpoint for fixed land area (GeoJSON polygon).
Purpose:
Returns a static GeoJSON FeatureCollection of zones with crop suggestions
(API_RESPONSE_SPEC §1). Used when the user selects a region on the map
and triggers "optimize zoning". No processing is performed on the request.
Returns static land area polygon for display on map. User cannot
draw or edit the region; it is loaded from backend.
Input parameters:
- body (optional): JSON body; may contain a GeoJSON Feature with Polygon.
Data type: object. Location: body. Not read or validated; not used in response.
None.
Response structure:
- status: string, always "success".
- data: object, GeoJSON FeatureCollection with features containing
geometry (Polygon) and properties (zoneId, crop, matchPercent, waterNeed,
estimatedProfit, reason, criteria).
No processing or validation is performed on inputs.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": OPTIMIZE_ZONING_RESPONSE},
status=200,
)
class InitialRegionView(View):
"""
GET endpoint for the initial map region.
Purpose:
Returns a static GeoJSON Feature with Polygon defining the initial
map region (API_RESPONSE_SPEC §2). Optional; used when the initial
region is loaded from the server instead of a fixed client mock.
Input parameters:
None. Query parameters, if sent, are not read or used.
Response structure:
- status: string, always "success".
- data: object, GeoJSON Feature with geometry.type "Polygon" and
coordinates as [longitude, latitude]; first and last point equal.
- data: object with key "area" (GeoJSON Feature with Polygon geometry).
No processing or validation is performed on inputs.
"""
def get(self, request):
return JsonResponse(
{"status": "success", "data": INITIAL_REGION_RESPONSE},
{"status": "success", "data": AREA_RESPONSE_DATA},
status=200,
)
class ProductsView(View):
"""
GET endpoint for list of crop products and colors.
Purpose:
Returns static list of cultivable products with display color and
Persian label for the Crop Zoning page (Legend and zone detail panel).
Used when loading the crop-zoning page.
Input parameters:
- locale: string, optional. Location: query. Language code (e.g. fa, en).
Not read or used in response.
Response structure:
- status: string, always "success".
- data: object with key "products" (array of { id, label, color }).
No processing or validation is performed on inputs.
"""
def get(self, request):
return JsonResponse(
{"status": "success", "data": PRODUCTS_RESPONSE_DATA},
status=200,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesInitialView(View):
"""
POST endpoint for initial zone data (map + hover/tooltip).
Purpose:
Accepts zones (FeatureCollection of grid squares) and returns static
initial data per zone for map rendering and hover/tooltip display.
Does not include reason or criteria (those are in zone details).
Input parameters (body, JSON):
- zones: GeoJSON FeatureCollection. Location: body. Grid square polygons.
- products: array of strings, optional. Location: body. Product IDs.
Not read or used in response.
Response structure:
- status: string, always "success".
- data: object with total_area_hectares, total_area_sqm, zone_count,
zones (array of { zoneId, geometry, crop, matchPercent, waterNeed,
estimatedProfit }).
No processing or validation is performed on inputs. Input values are
not used in the response.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": ZONES_INITIAL_RESPONSE_DATA},
status=200,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesWaterNeedView(View):
"""
POST endpoint for water need per zone (water need layer).
Purpose:
Accepts zones (FeatureCollection) and returns static water need
per zone for the water need map layer (level, value, color).
Input parameters (body, JSON):
- zones: GeoJSON FeatureCollection. Location: body.
- products: array of strings, optional. Location: body. Not used.
Response structure:
- status: string, always "success".
- data: object with zones (array of { zoneId, geometry, level, value, color }).
No processing or validation is performed on inputs.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": ZONES_WATER_NEED_RESPONSE_DATA},
status=200,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesSoilQualityView(View):
"""
POST endpoint for soil quality per zone (soil quality layer).
Purpose:
Accepts zones (FeatureCollection) and returns static soil quality
per zone for the soil quality map layer (level, score, color).
Input parameters (body, JSON):
- zones: GeoJSON FeatureCollection. Location: body.
- products: array of strings, optional. Location: body. Not used.
Response structure:
- status: string, always "success".
- data: object with zones (array of { zoneId, geometry, level, score, color }).
No processing or validation is performed on inputs.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": ZONES_SOIL_QUALITY_RESPONSE_DATA},
status=200,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesCultivationRiskView(View):
"""
POST endpoint for cultivation risk per zone (cultivation risk layer).
Purpose:
Accepts zones (FeatureCollection) and returns static cultivation
risk per zone for the risk map layer (level, color).
Input parameters (body, JSON):
- zones: GeoJSON FeatureCollection. Location: body.
- products: array of strings, optional. Location: body. Not used.
Response structure:
- status: string, always "success".
- data: object with zones (array of { zoneId, geometry, level, color }).
No processing or validation is performed on inputs.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": ZONES_CULTIVATION_RISK_RESPONSE_DATA},
status=200,
)
class ZoneDetailsView(View):
"""
GET endpoint for zone detail data (detail panel after click).
Purpose:
Returns static detail data for a single zone: reason, criteria,
area_hectares for the detail panel and radar chart.
Input parameters:
- zoneId: string. Location: path. Zone identifier (e.g. zone-0).
Not read or used in response.
Response structure:
- status: string, always "success".
- data: object with zoneId, crop, matchPercent, waterNeed,
estimatedProfit, reason, criteria (array), area_hectares.
No processing or validation is performed on inputs. Input values are
not used in the response.
"""
def get(self, request, zone_id):
data = ZONE_DETAILS_BY_ID.get(zone_id, ZONE_DETAILS_BY_ID["zone-0"])
return JsonResponse(
{"status": "success", "data": data},
status=200,
)