diff --git a/.env.example b/.env.example index 2609aeb..87dfb09 100644 --- a/.env.example +++ b/.env.example @@ -26,3 +26,5 @@ AI_SERVICE_API_KEY= SENSOR_HUB_SERVICE_BASE_URL=https://sensor-hub.example.com SENSOR_HUB_SERVICE_API_KEY= + +CROP_ZONE_CHUNK_AREA_SQM=10000 diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md deleted file mode 100644 index e47319e..0000000 --- a/API_DOCUMENTATION.md +++ /dev/null @@ -1,1836 +0,0 @@ -# مستندات API های صفحات منوی عمودی - -این مستندات شامل تمام API های مورد نیاز برای صفحات موجود در VerticalMenu است. - -**توجهات مهم:** -- صفحات User Management و Roles & Permissions فقط برای ادمین قابل دسترسی است -- صفحات Calendar، Kanban، و Todo باید به یکدیگر متصل باشند و تسک‌های غیرروتین در هر سه قابل نمایش باشند -- AI Chat باید به تسک‌های Calendar، Kanban، و Todo دسترسی داشته باشد -- AI Chat باید نسبت به برخی پیام‌ها حساس باشد (sensitive message handling) -- سیستم Authentication جزو این مستندات نیست - ---- - -## فهرست مطالب - -1. [Chat](#1-chat) -2. [AI Chat](#2-ai-chat) -3. [Calendar](#3-calendar) -4. [Kanban](#4-kanban) -5. [Todo](#5-todo) -6. [User Management](#6-user-management) -7. [Roles & Permissions](#7-roles--permissions) -8. [Sensor Hub](#8-sensor-hub) - ---- - -## 1. Chat - -**Route:** `/apps/chat` - -**دسترسی:** همه کاربران - -### API های مورد نیاز - -#### 1.1. دریافت لیست مخاطبین - -**Endpoint:** `GET /api/chat/contacts` - -**Response:** -```json -{ - "contacts": [ - { - "id": number, - "fullName": "string", - "role": "string", - "about": "string", - "avatar": "string", - "avatarColor": "primary" | "success" | "error" | "warning" | "info", - "status": "busy" | "away" | "online" | "offline" - } - ] -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `contacts[].id` | number | شناسه یکتا مخاطب | -| `contacts[].fullName` | string | نام کامل | -| `contacts[].role` | string | نقش کاربر | -| `contacts[].about` | string | درباره کاربر | -| `contacts[].avatar` | string | آواتار (URL) | -| `contacts[].avatarColor` | ThemeColor | رنگ آواتار (در صورت نبودن تصویر) | -| `contacts[].status` | StatusType | وضعیت آنلاین/آفلاین | - -#### 1.2. دریافت چت‌های کاربر - -**Endpoint:** `GET /api/chat/conversations` - -**Response:** -```json -{ - "chats": [ - { - "id": number, - "userId": number, - "unseenMsgs": number, - "lastMessage": { - "message": "string", - "time": "string", - "senderId": number, - "msgStatus": { - "isSent": boolean, - "isDelivered": boolean, - "isSeen": boolean - } - } - } - ] -} -``` - -#### 1.3. دریافت پیام‌های چت - -**Endpoint:** `GET /api/chat/conversations/:conversationId/messages` - -**Path Parameters:** -- `conversationId` (number, required): شناسه مکالمه - -**Query Parameters:** -- `page?` (number, optional): شماره صفحه -- `limit?` (number, optional): تعداد پیام در هر صفحه - -**Response:** -```json -{ - "messages": [ - { - "message": "string", - "time": "string", - "senderId": number, - "msgStatus": { - "isSent": boolean, - "isDelivered": boolean, - "isSeen": boolean - } - } - ], - "pagination": { - "page": number, - "limit": number, - "total": number, - "totalPages": number - } -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `messages[].message` | string | متن پیام | -| `messages[].time` | string | زمان ارسال (ISO 8601) | -| `messages[].senderId` | number | شناسه فرستنده | -| `messages[].msgStatus.isSent` | boolean | وضعیت ارسال | -| `messages[].msgStatus.isDelivered` | boolean | وضعیت تحویل | -| `messages[].msgStatus.isSeen` | boolean | وضعیت مشاهده | - -#### 1.4. ارسال پیام جدید - -**Endpoint:** `POST /api/chat/conversations/:conversationId/messages` - -**Path Parameters:** -- `conversationId` (number, required): شناسه مکالمه - -**Request Body:** -```json -{ - "message": "string" -} -``` - -**Response:** -```json -{ - "message": { - "id": number, - "message": "string", - "time": "string", - "senderId": number, - "msgStatus": { - "isSent": boolean, - "isDelivered": boolean, - "isSeen": boolean - } - } -} -``` - -#### 1.5. به‌روزرسانی وضعیت مشاهده پیام - -**Endpoint:** `PUT /api/chat/messages/:messageId/read` - -**Path Parameters:** -- `messageId` (number, required): شناسه پیام - -**Response:** -```json -{ - "success": boolean -} -``` - -#### 1.6. دریافت اطلاعات پروفایل کاربر - -**Endpoint:** `GET /api/chat/profile` - -**Response:** -```json -{ - "profileUser": { - "id": number, - "role": "string", - "about": "string", - "avatar": "string", - "fullName": "string", - "status": "busy" | "away" | "online" | "offline", - "settings": { - "isNotificationsOn": boolean, - "isTwoStepAuthVerificationEnabled": boolean - } - } -} -``` - ---- - -## 2. AI Chat - -**Route:** `/apps/ai-chat` - -**دسترسی:** همه کاربران - -**نکات مهم:** -- AI Chat باید به تسک‌های Calendar، Kanban، و Todo دسترسی داشته باشد -- AI Chat باید نسبت به برخی پیام‌ها حساس باشد و پردازش ویژه انجام دهد - -### API های مورد نیاز - -#### 2.1. ارسال پیام به AI - -**Endpoint:** `POST /api/ai-chat/messages` - -**Request Body:** -```json -{ - "content": "string", - "images": ["string"], - "files": [ - { - "name": "string", - "data": "string", - "type": "string", - "size": number - } - ], - "model": "gpt-4" | "gpt-3.5" | "claude" | "gemini", - "conversationId": "string" -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `content` | string | متن پیام | -| `images` | string[] | آرایه URL تصاویر (base64 data URLs) | -| `files[].name` | string | نام فایل | -| `files[].data` | string | داده فایل (base64 data URL) | -| `files[].type` | string | نوع MIME فایل | -| `files[].size` | number | حجم فایل (بایت) | -| `model` | string | مدل AI مورد استفاده | -| `conversationId` | string | شناسه مکالمه (اختیاری) | - -**Response:** -```json -{ - "message": { - "id": "string", - "role": "user" | "assistant", - "content": "string", - "images": ["string"], - "files": [ - { - "name": "string", - "data": "string", - "type": "string", - "size": number - } - ], - "timestamp": "string", - "isSensitive": boolean, - "sensitiveReason": "string" - }, - "assistantResponse": { - "id": "string", - "role": "assistant", - "content": "string", - "timestamp": "string", - "referencedTasks": [ - { - "taskId": "string", - "source": "calendar" | "kanban" | "todo", - "title": "string" - } - ] - } -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `message.isSensitive` | boolean | آیا پیام حساس است | -| `message.sensitiveReason` | string | دلیل حساس بودن پیام | -| `assistantResponse.referencedTasks[].taskId` | string | شناسه تسک | -| `assistantResponse.referencedTasks[].source` | string | منبع تسک (calendar/kanban/todo) | -| `assistantResponse.referencedTasks[].title` | string | عنوان تسک | - -#### 2.2. دریافت تاریخچه مکالمه - -**Endpoint:** `GET /api/ai-chat/conversations/:conversationId/messages` - -**Path Parameters:** -- `conversationId` (string, required): شناسه مکالمه - -**Response:** -```json -{ - "messages": [ - { - "id": "string", - "role": "user" | "assistant", - "content": "string", - "images": ["string"], - "files": [ - { - "name": "string", - "data": "string", - "type": "string", - "size": number - } - ], - "timestamp": "string" - } - ] -} -``` - -#### 2.3. دریافت لیست مکالمات - -**Endpoint:** `GET /api/ai-chat/conversations` - -**Response:** -```json -{ - "conversations": [ - { - "id": "string", - "title": "string", - "lastMessage": "string", - "timestamp": "string", - "messageCount": number - } - ] -} -``` - -#### 2.4. دریافت تسک‌های مرتبط (برای AI Chat) - -**Endpoint:** `GET /api/ai-chat/tasks` - -**Query Parameters:** -- `source?` (string, optional): منبع تسک (calendar, kanban, todo) -- `routine?` (number, optional): نوع روتین (0: NONE, 1: DAILY, 2: WEEKLY, 3: MONTHLY, 4: YEARLY) - -**Response:** -```json -{ - "tasks": [ - { - "id": "string", - "title": "string", - "description": "string", - "deadline": number, - "routine": 0 | 1 | 2 | 3 | 4, - "tags": ["string"], - "source": "calendar" | "kanban" | "todo", - "status": "string", - "priority": "high" | "medium" | "low" - } - ] -} -``` - -**نکته:** این API تسک‌هایی که routine = 0 دارند را برمی‌گرداند تا در Calendar، Kanban، و Todo مشترک باشند. - ---- - -## 3. Calendar - -**Route:** `/apps/calendar` - -**دسترسی:** همه کاربران - -**نکات مهم:** -- Calendar باید با Kanban و Todo متصل باشد -- تسک‌هایی که routine = 0 دارند باید در هر سه قابل نمایش باشند - -### API های مورد نیاز - -#### 3.1. دریافت Event ها - -**Endpoint:** `GET /api/events` - -**Query Parameters:** -- `start?` (string, optional): تاریخ شروع (ISO 8601) -- `end?` (string, optional): تاریخ پایان (ISO 8601) -- `calendar?` (string, optional): فیلتر بر اساس تقویم (Personal, Business, Family, Holiday, ETC) - -**Response:** -```json -{ - "events": [ - { - "id": "string", - "title": "string", - "description": "string", - "deadline": number, - "tags": ["string"], - "author": { - "name": "string", - "image": "string" - }, - "calendar": "Personal" | "Business" | "Family" | "Holiday" | "ETC", - "start": "string", - "end": "string", - "allDay": boolean, - "extendedProps": {} - } - ] -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `events[].id` | string | شناسه یکتا رویداد | -| `events[].title` | string | عنوان رویداد | -| `events[].description` | string | توضیحات رویداد | -| `events[].deadline` | number | مهلت (Unix timestamp) | -| `events[].tags` | string[] | برچسب‌ها | -| `events[].author.name` | string | نام نویسنده | -| `events[].author.image` | string | تصویر نویسنده (URL) | -| `events[].calendar` | string | نوع تقویم | -| `events[].start` | string | زمان شروع (ISO 8601) | -| `events[].end` | string | زمان پایان (ISO 8601) | -| `events[].allDay` | boolean | رویداد تمام روز | - -#### 3.2. ایجاد Event جدید - -**Endpoint:** `POST /api/events` - -**Request Body:** -```json -{ - "title": "string", - "description": "string", - "deadline": number, - "tags": ["string"], - "calendar": "Personal" | "Business" | "Family" | "Holiday" | "ETC", - "start": "string", - "end": "string", - "allDay": boolean, - "extendedProps": {} -} -``` - -**Response:** -```json -{ - "event": { - "id": "string", - "title": "string", - "description": "string", - "deadline": number, - "tags": ["string"], - "author": { - "name": "string", - "image": "string" - }, - "calendar": "string", - "start": "string", - "end": "string", - "allDay": boolean - } -} -``` - -#### 3.3. به‌روزرسانی Event - -**Endpoint:** `PUT /api/events/:eventId` - -**Path Parameters:** -- `eventId` (string, required): شناسه رویداد - -**Request Body:** -```json -{ - "title": "string", - "description": "string", - "deadline": number, - "tags": ["string"], - "calendar": "string", - "start": "string", - "end": "string", - "allDay": boolean -} -``` - -**Response:** -```json -{ - "event": { - "id": "string", - "title": "string", - "description": "string", - "deadline": number, - "tags": ["string"], - "author": { - "name": "string", - "image": "string" - }, - "calendar": "string", - "start": "string", - "end": "string", - "allDay": boolean - } -} -``` - -#### 3.4. حذف Event - -**Endpoint:** `DELETE /api/events/:eventId` - -**Path Parameters:** -- `eventId` (string, required): شناسه رویداد - -**Response:** -```json -{ - "success": boolean -} -``` - -#### 3.5. دریافت تسک‌های غیرروتین (مشترک با Kanban و Todo) - -**Endpoint:** `GET /api/tasks/non-routine` - -**Response:** -```json -{ - "tasks": [ - { - "id": "string", - "title": "string", - "description": "string", - "deadline": number, - "routine": 0, - "tags": ["string"], - "author": { - "name": "string", - "image": "string" - }, - "source": "calendar" | "kanban" | "todo", - "status": "string", - "priority": "high" | "medium" | "low" - } - ] -} -``` - -**نکته:** این API فقط تسک‌هایی با `routine = 0` (NONE) را برمی‌گرداند که باید در Calendar، Kanban، و Todo نمایش داده شوند. - ---- - -## 4. Kanban - -**Route:** `/apps/kanban` - -**دسترسی:** همه کاربران - -**نکات مهم:** -- Kanban باید با Calendar و Todo متصل باشد -- تسک‌هایی که routine = 0 دارند باید در هر سه قابل نمایش باشند - -### API های مورد نیاز - -#### 4.1. دریافت Board Kanban - -**Endpoint:** `GET /api/kanban/board` - -**Response:** -```json -{ - "columns": [ - { - "id": number, - "title": "string", - "taskIds": [number] - } - ], - "tasks": [ - { - "id": number, - "title": "string", - "badgeText": ["string"], - "attachments": number, - "comments": number, - "assigned": [ - { - "src": "string", - "name": "string" - } - ], - "image": "string", - "dueDate": "string", - "routine": 0 | 1 | 2 | 3 | 4, - "description": "string", - "tags": ["string"], - "source": "kanban" | "calendar" | "todo" - } - ] -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `columns[].id` | number | شناسه یکتا ستون | -| `columns[].title` | string | عنوان ستون | -| `columns[].taskIds` | number[] | شناسه‌های تسک‌های موجود در ستون | -| `tasks[].id` | number | شناسه یکتا تسک | -| `tasks[].title` | string | عنوان تسک | -| `tasks[].badgeText` | string[] | برچسب‌های متن | -| `tasks[].attachments` | number | تعداد پیوست‌ها | -| `tasks[].comments` | number | تعداد کامنت‌ها | -| `tasks[].assigned[].src` | string | تصویر اختصاص یافته (URL) | -| `tasks[].assigned[].name` | string | نام اختصاص یافته | -| `tasks[].image` | string | تصویر تسک (URL) | -| `tasks[].dueDate` | string | تاریخ مهلت (ISO 8601) | -| `tasks[].routine` | number | نوع روتین (0: NONE, 1: DAILY, ...) | -| `tasks[].description` | string | توضیحات تسک | -| `tasks[].tags` | string[] | برچسب‌ها | -| `tasks[].source` | string | منبع تسک | - -#### 4.2. ایجاد ستون جدید - -**Endpoint:** `POST /api/kanban/columns` - -**Request Body:** -```json -{ - "title": "string" -} -``` - -**Response:** -```json -{ - "column": { - "id": number, - "title": "string", - "taskIds": [] - } -} -``` - -#### 4.3. به‌روزرسانی ستون - -**Endpoint:** `PUT /api/kanban/columns/:columnId` - -**Path Parameters:** -- `columnId` (number, required): شناسه ستون - -**Request Body:** -```json -{ - "title": "string", - "taskIds": [number] -} -``` - -**Response:** -```json -{ - "column": { - "id": number, - "title": "string", - "taskIds": [number] - } -} -``` - -#### 4.4. حذف ستون - -**Endpoint:** `DELETE /api/kanban/columns/:columnId` - -**Path Parameters:** -- `columnId` (number, required): شناسه ستون - -**Response:** -```json -{ - "success": boolean -} -``` - -#### 4.5. ایجاد تسک جدید - -**Endpoint:** `POST /api/kanban/tasks` - -**Request Body:** -```json -{ - "columnId": number, - "title": "string", - "description": "string", - "badgeText": ["string"], - "attachments": number, - "comments": number, - "assigned": [ - { - "src": "string", - "name": "string" - } - ], - "image": "string", - "dueDate": "string", - "routine": 0 | 1 | 2 | 3 | 4, - "tags": ["string"] -} -``` - -**Response:** -```json -{ - "task": { - "id": number, - "title": "string", - "badgeText": ["string"], - "attachments": number, - "comments": number, - "assigned": [ - { - "src": "string", - "name": "string" - } - ], - "image": "string", - "dueDate": "string", - "routine": number, - "description": "string", - "tags": ["string"], - "source": "kanban" - } -} -``` - -#### 4.6. به‌روزرسانی تسک - -**Endpoint:** `PUT /api/kanban/tasks/:taskId` - -**Path Parameters:** -- `taskId` (number, required): شناسه تسک - -**Request Body:** -```json -{ - "title": "string", - "description": "string", - "badgeText": ["string"], - "attachments": number, - "comments": number, - "assigned": [ - { - "src": "string", - "name": "string" - } - ], - "image": "string", - "dueDate": "string", - "routine": number, - "tags": ["string"], - "columnId": number -} -``` - -**Response:** -```json -{ - "task": { - "id": number, - "title": "string", - "badgeText": ["string"], - "attachments": number, - "comments": number, - "assigned": [ - { - "src": "string", - "name": "string" - } - ], - "image": "string", - "dueDate": "string", - "routine": number, - "description": "string", - "tags": ["string"], - "source": "string" - } -} -``` - -#### 4.7. حذف تسک - -**Endpoint:** `DELETE /api/kanban/tasks/:taskId` - -**Path Parameters:** -- `taskId` (number, required): شناسه تسک - -**Response:** -```json -{ - "success": boolean -} -``` - -#### 4.8. جابجایی تسک بین ستون‌ها - -**Endpoint:** `PUT /api/kanban/tasks/:taskId/move` - -**Path Parameters:** -- `taskId` (number, required): شناسه تسک - -**Request Body:** -```json -{ - "fromColumnId": number, - "toColumnId": number, - "position": number -} -``` - -**Response:** -```json -{ - "success": boolean, - "columns": [ - { - "id": number, - "title": "string", - "taskIds": [number] - } - ] -} -``` - ---- - -## 5. Todo - -**Route:** `/todo/all` - -**دسترسی:** همه کاربران - -**نکات مهم:** -- Todo باید با Calendar و Kanban متصل باشد -- تسک‌هایی که routine = 0 دارند باید در هر سه قابل نمایش باشند - -### API های مورد نیاز - -#### 5.1. دریافت لیست Todo - -**Endpoint:** `GET /api/todos` - -**Query Parameters:** -- `status?` (string, optional): وضعیت (pending, in-progress, completed) -- `priority?` (string, optional): اولویت (high, medium, low) -- `label?` (string, optional): برچسب -- `filter?` (string, optional): فیلتر (all, starred, important, completed, trashed) -- `search?` (string, optional): جستجو در عنوان و توضیحات - -**Response:** -```json -{ - "todos": [ - { - "id": number, - "title": "string", - "description": "string", - "status": "pending" | "in-progress" | "completed", - "priority": "high" | "medium" | "low", - "startDate": "string", - "dueDate": "string", - "createdDate": "string", - "labels": ["string"], - "isStarred": boolean, - "isImportant": boolean, - "isTrashed": boolean, - "routine": 0 | 1 | 2 | 3 | 4, - "source": "todo" | "calendar" | "kanban" - } - ], - "total": number -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `todos[].id` | number | شناسه یکتا todo | -| `todos[].title` | string | عنوان todo | -| `todos[].description` | string | توضیحات todo | -| `todos[].status` | string | وضعیت (pending, in-progress, completed) | -| `todos[].priority` | string | اولویت (high, medium, low) | -| `todos[].startDate` | string | تاریخ شروع (ISO 8601) | -| `todos[].dueDate` | string | تاریخ مهلت (ISO 8601) | -| `todos[].createdDate` | string | تاریخ ایجاد (ISO 8601) | -| `todos[].labels` | string[] | برچسب‌ها | -| `todos[].isStarred` | boolean | نشان شده | -| `todos[].isImportant` | boolean | مهم | -| `todos[].isTrashed` | boolean | حذف شده | -| `todos[].routine` | number | نوع روتین (0: NONE, 1: DAILY, ...) | -| `todos[].source` | string | منبع todo | - -#### 5.2. ایجاد Todo جدید - -**Endpoint:** `POST /api/todos` - -**Request Body:** -```json -{ - "title": "string", - "description": "string", - "status": "pending" | "in-progress" | "completed", - "priority": "high" | "medium" | "low", - "startDate": "string", - "dueDate": "string", - "labels": ["string"], - "isStarred": boolean, - "isImportant": boolean, - "routine": 0 | 1 | 2 | 3 | 4 -} -``` - -**Response:** -```json -{ - "todo": { - "id": number, - "title": "string", - "description": "string", - "status": "string", - "priority": "string", - "startDate": "string", - "dueDate": "string", - "createdDate": "string", - "labels": ["string"], - "isStarred": boolean, - "isImportant": boolean, - "isTrashed": boolean, - "routine": number, - "source": "todo" - } -} -``` - -#### 5.3. به‌روزرسانی Todo - -**Endpoint:** `PUT /api/todos/:todoId` - -**Path Parameters:** -- `todoId` (number, required): شناسه todo - -**Request Body:** -```json -{ - "title": "string", - "description": "string", - "status": "pending" | "in-progress" | "completed", - "priority": "high" | "medium" | "low", - "startDate": "string", - "dueDate": "string", - "labels": ["string"], - "isStarred": boolean, - "isImportant": boolean, - "isTrashed": boolean, - "routine": number -} -``` - -**Response:** -```json -{ - "todo": { - "id": number, - "title": "string", - "description": "string", - "status": "string", - "priority": "string", - "startDate": "string", - "dueDate": "string", - "createdDate": "string", - "labels": ["string"], - "isStarred": boolean, - "isImportant": boolean, - "isTrashed": boolean, - "routine": number, - "source": "string" - } -} -``` - -#### 5.4. حذف Todo - -**Endpoint:** `DELETE /api/todos/:todoId` - -**Path Parameters:** -- `todoId` (number, required): شناسه todo - -**Response:** -```json -{ - "success": boolean -} -``` - -#### 5.5. دریافت لیست برچسب‌های موجود - -**Endpoint:** `GET /api/todos/labels` - -**Response:** -```json -{ - "labels": ["string"] -} -``` - ---- - -## 6. User Management - -**Route:** `/apps/user/list`, `/apps/user/view` - -**دسترسی:** فقط برای ادمین (Admin) - -### 6.1. User List - -**Route:** `/apps/user/list` - -#### API های مورد نیاز - -##### 6.1.1. دریافت لیست کاربران - -**Endpoint:** `GET /api/admin/users` - -**Query Parameters:** -- `role?` (string, optional): فیلتر بر اساس نقش (admin, author, editor, maintainer, subscriber) -- `status?` (string, optional): فیلتر بر اساس وضعیت (active, pending, inactive) -- `search?` (string, optional): جستجو در نام، ایمیل، نام کاربری -- `page?` (number, optional): شماره صفحه -- `limit?` (number, optional): تعداد آیتم در هر صفحه - -**Response:** -```json -{ - "users": [ - { - "id": number, - "role": "string", - "email": "string", - "status": "active" | "pending" | "inactive", - "avatar": "string", - "company": "string", - "country": "string", - "contact": "string", - "fullName": "string", - "username": "string", - "currentPlan": "string", - "avatarColor": "primary" | "success" | "error" | "warning" | "info", - "billing": "string" - } - ], - "pagination": { - "page": number, - "limit": number, - "total": number, - "totalPages": number - }, - "stats": { - "total": number, - "active": number, - "pending": number, - "inactive": number - } -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `users[].id` | number | شناسه یکتا کاربر | -| `users[].role` | string | نقش کاربر | -| `users[].email` | string | ایمیل کاربر | -| `users[].status` | string | وضعیت کاربر | -| `users[].avatar` | string | آواتار کاربر (URL) | -| `users[].company` | string | نام شرکت | -| `users[].country` | string | کشور | -| `users[].contact` | string | شماره تماس | -| `users[].fullName` | string | نام کامل | -| `users[].username` | string | نام کاربری | -| `users[].currentPlan` | string | پلن فعلی | -| `users[].avatarColor` | ThemeColor | رنگ آواتار (در صورت نبودن تصویر) | -| `users[].billing` | string | روش پرداخت | - -##### 6.1.2. ایجاد کاربر جدید - -**Endpoint:** `POST /api/admin/users` - -**Request Body:** -```json -{ - "role": "string", - "email": "string", - "fullName": "string", - "username": "string", - "contact": "string", - "company": "string", - "country": "string", - "status": "active" | "pending" | "inactive", - "currentPlan": "string", - "avatar": "string" -} -``` - -**Response:** -```json -{ - "user": { - "id": number, - "role": "string", - "email": "string", - "status": "string", - "avatar": "string", - "company": "string", - "country": "string", - "contact": "string", - "fullName": "string", - "username": "string", - "currentPlan": "string", - "avatarColor": "string", - "billing": "string" - } -} -``` - -##### 6.1.3. به‌روزرسانی کاربر - -**Endpoint:** `PUT /api/admin/users/:userId` - -**Path Parameters:** -- `userId` (number, required): شناسه کاربر - -**Request Body:** -```json -{ - "role": "string", - "email": "string", - "fullName": "string", - "username": "string", - "contact": "string", - "company": "string", - "country": "string", - "status": "active" | "pending" | "inactive", - "currentPlan": "string", - "avatar": "string" -} -``` - -**Response:** -```json -{ - "user": { - "id": number, - "role": "string", - "email": "string", - "status": "string", - "avatar": "string", - "company": "string", - "country": "string", - "contact": "string", - "fullName": "string", - "username": "string", - "currentPlan": "string", - "avatarColor": "string", - "billing": "string" - } -} -``` - -##### 6.1.4. حذف کاربر - -**Endpoint:** `DELETE /api/admin/users/:userId` - -**Path Parameters:** -- `userId` (number, required): شناسه کاربر - -**Response:** -```json -{ - "success": boolean -} -``` - -### 6.2. User View - -**Route:** `/apps/user/view` - -#### API های مورد نیاز - -##### 6.2.1. دریافت جزئیات کاربر - -**Endpoint:** `GET /api/admin/users/:userId` - -**Path Parameters:** -- `userId` (number, required): شناسه کاربر - -**Response:** -```json -{ - "user": { - "id": number, - "role": "string", - "email": "string", - "status": "string", - "avatar": "string", - "company": "string", - "country": "string", - "contact": "string", - "fullName": "string", - "username": "string", - "currentPlan": "string", - "avatarColor": "string", - "billing": "string", - "joinDate": "string", - "lastLogin": "string", - "twoStepVerification": boolean, - "recentDevices": [ - { - "id": "string", - "device": "string", - "browser": "string", - "location": "string", - "lastActive": "string", - "ip": "string" - } - ], - "activityTimeline": [ - { - "id": "string", - "title": "string", - "description": "string", - "time": "string", - "icon": "string", - "color": "string" - } - ], - "projects": [ - { - "id": "string", - "name": "string", - "startDate": "string", - "deadline": "string", - "status": "string", - "budget": number, - "spent": number - } - ], - "invoices": [ - { - "id": "string", - "total": number, - "issuedDate": "string", - "status": "string", - "balance": number - } - ], - "connections": [ - { - "id": "string", - "app": "string", - "username": "string", - "avatar": "string", - "connected": boolean, - "connectedAt": "string" - } - ], - "notifications": { - "email": { - "newComment": boolean, - "newAnswer": boolean, - "followMe": boolean, - "answerOnForm": boolean, - "productUpdate": boolean, - "productNewFeature": boolean, - "productAnnouncement": boolean - }, - "phone": { - "newComment": boolean, - "newAnswer": boolean, - "followMe": boolean, - "answerOnForm": boolean - } - } - } -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `user.joinDate` | string | تاریخ عضویت (ISO 8601) | -| `user.lastLogin` | string | آخرین ورود (ISO 8601) | -| `user.twoStepVerification` | boolean | فعال‌سازی احراز هویت دو مرحله‌ای | -| `user.recentDevices[].id` | string | شناسه دستگاه | -| `user.recentDevices[].device` | string | نام دستگاه | -| `user.recentDevices[].browser` | string | مرورگر | -| `user.recentDevices[].location` | string | موقعیت جغرافیایی | -| `user.recentDevices[].lastActive` | string | آخرین فعالیت (ISO 8601) | -| `user.recentDevices[].ip` | string | آدرس IP | -| `user.activityTimeline[].id` | string | شناسه فعالیت | -| `user.activityTimeline[].title` | string | عنوان فعالیت | -| `user.activityTimeline[].description` | string | توضیحات فعالیت | -| `user.activityTimeline[].time` | string | زمان فعالیت (ISO 8601) | -| `user.activityTimeline[].icon` | string | آیکون | -| `user.activityTimeline[].color` | string | رنگ | -| `user.projects[].id` | string | شناسه پروژه | -| `user.projects[].name` | string | نام پروژه | -| `user.projects[].startDate` | string | تاریخ شروع (ISO 8601) | -| `user.projects[].deadline` | string | تاریخ مهلت (ISO 8601) | -| `user.projects[].status` | string | وضعیت پروژه | -| `user.projects[].budget` | number | بودجه | -| `user.projects[].spent` | number | هزینه شده | -| `user.invoices[].id` | string | شناسه فاکتور | -| `user.invoices[].total` | number | مبلغ کل | -| `user.invoices[].issuedDate` | string | تاریخ صدور (ISO 8601) | -| `user.invoices[].status` | string | وضعیت فاکتور | -| `user.invoices[].balance` | number | مانده حساب | -| `user.connections[].id` | string | شناسه اتصال | -| `user.connections[].app` | string | نام اپلیکیشن | -| `user.connections[].username` | string | نام کاربری | -| `user.connections[].avatar` | string | آواتار (URL) | -| `user.connections[].connected` | boolean | وضعیت اتصال | -| `user.connections[].connectedAt` | string | تاریخ اتصال (ISO 8601) | -| `user.notifications.email.*` | boolean | تنظیمات اطلاع‌رسانی ایمیل | -| `user.notifications.phone.*` | boolean | تنظیمات اطلاع‌رسانی تلفن | - -##### 6.2.2. به‌روزرسانی تنظیمات امنیتی کاربر - -**Endpoint:** `PUT /api/admin/users/:userId/security` - -**Path Parameters:** -- `userId` (number, required): شناسه کاربر - -**Request Body:** -```json -{ - "twoStepVerification": boolean, - "currentPassword": "string", - "newPassword": "string" -} -``` - -**Response:** -```json -{ - "success": boolean -} -``` - -##### 6.2.3. به‌روزرسانی تنظیمات اطلاع‌رسانی - -**Endpoint:** `PUT /api/admin/users/:userId/notifications` - -**Path Parameters:** -- `userId` (number, required): شناسه کاربر - -**Request Body:** -```json -{ - "email": { - "newComment": boolean, - "newAnswer": boolean, - "followMe": boolean, - "answerOnForm": boolean, - "productUpdate": boolean, - "productNewFeature": boolean, - "productAnnouncement": boolean - }, - "phone": { - "newComment": boolean, - "newAnswer": boolean, - "followMe": boolean, - "answerOnForm": boolean - } -} -``` - -**Response:** -```json -{ - "success": boolean -} -``` - ---- - -## 7. Roles & Permissions - -**Route:** `/apps/roles`, `/apps/permissions` - -**دسترسی:** فقط برای ادمین (Admin) - -### 7.1. Roles - -**Route:** `/apps/roles` - -#### API های مورد نیاز - -##### 7.1.1. دریافت لیست نقش‌ها - -**Endpoint:** `GET /api/admin/roles` - -**Response:** -```json -{ - "roles": [ - { - "id": "string", - "name": "string", - "totalUsers": number, - "avatars": ["string"], - "description": "string" - } - ], - "roleStats": { - "administrator": number, - "author": number, - "editor": number, - "maintainer": number, - "subscriber": number - } -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `roles[].id` | string | شناسه یکتا نقش | -| `roles[].name` | string | نام نقش | -| `roles[].totalUsers` | number | تعداد کاربران دارای این نقش | -| `roles[].avatars` | string[] | آواتارهای کاربران (URL) | -| `roles[].description` | string | توضیحات نقش | -| `roleStats.*` | number | آمار کاربران هر نقش | - -##### 7.1.2. دریافت کاربران بر اساس نقش - -**Endpoint:** `GET /api/admin/roles/:roleName/users` - -**Path Parameters:** -- `roleName` (string, required): نام نقش - -**Response:** -```json -{ - "users": [ - { - "id": number, - "fullName": "string", - "username": "string", - "email": "string", - "avatar": "string", - "role": "string", - "status": "string" - } - ] -} -``` - -##### 7.1.3. ایجاد نقش جدید - -**Endpoint:** `POST /api/admin/roles` - -**Request Body:** -```json -{ - "name": "string", - "description": "string", - "permissions": ["string"] -} -``` - -**Response:** -```json -{ - "role": { - "id": "string", - "name": "string", - "description": "string", - "totalUsers": number, - "avatars": [], - "permissions": ["string"] - } -} -``` - -##### 7.1.4. به‌روزرسانی نقش - -**Endpoint:** `PUT /api/admin/roles/:roleId` - -**Path Parameters:** -- `roleId` (string, required): شناسه نقش - -**Request Body:** -```json -{ - "name": "string", - "description": "string", - "permissions": ["string"] -} -``` - -**Response:** -```json -{ - "role": { - "id": "string", - "name": "string", - "description": "string", - "totalUsers": number, - "avatars": ["string"], - "permissions": ["string"] - } -} -``` - -##### 7.1.5. حذف نقش - -**Endpoint:** `DELETE /api/admin/roles/:roleId` - -**Path Parameters:** -- `roleId` (string, required): شناسه نقش - -**Response:** -```json -{ - "success": boolean -} -``` - -### 7.2. Permissions - -**Route:** `/apps/permissions` - -#### API های مورد نیاز - -##### 7.2.1. دریافت لیست Permission ها - -**Endpoint:** `GET /api/admin/permissions` - -**Query Parameters:** -- `search?` (string, optional): جستجو در نام permission - -**Response:** -```json -{ - "permissions": [ - { - "id": number, - "name": "string", - "createdDate": "string", - "assignedTo": "string" | ["string"] - } - ], - "total": number -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `permissions[].id` | number | شناسه یکتا permission | -| `permissions[].name` | string | نام permission | -| `permissions[].createdDate` | string | تاریخ ایجاد (ISO 8601) | -| `permissions[].assignedTo` | string \| string[] | نقش یا نقش‌های اختصاص یافته | - -##### 7.2.2. ایجاد Permission جدید - -**Endpoint:** `POST /api/admin/permissions` - -**Request Body:** -```json -{ - "name": "string", - "assignedTo": "string" | ["string"] -} -``` - -**Response:** -```json -{ - "permission": { - "id": number, - "name": "string", - "createdDate": "string", - "assignedTo": "string" | ["string"] - } -} -``` - -##### 7.2.3. به‌روزرسانی Permission - -**Endpoint:** `PUT /api/admin/permissions/:permissionId` - -**Path Parameters:** -- `permissionId` (number, required): شناسه permission - -**Request Body:** -```json -{ - "name": "string", - "assignedTo": "string" | ["string"] -} -``` - -**Response:** -```json -{ - "permission": { - "id": number, - "name": "string", - "createdDate": "string", - "assignedTo": "string" | ["string"] - } -} -``` - -##### 7.2.4. حذف Permission - -**Endpoint:** `DELETE /api/admin/permissions/:permissionId` - -**Path Parameters:** -- `permissionId` (number, required): شناسه permission - -**Response:** -```json -{ - "success": boolean -} -``` - ---- - -## 8. Sensor Hub - -**Route:** تب مرکز سنسور در Account Settings - -**دسترسی:** کاربران احراز هویت شده - -### API های مورد نیاز - -#### 8.1. دریافت لیست سنسورها - -**Endpoint:** `GET /api/sensor-hub/` - -**Response:** -```json -{ - "data": [ - { - "name": "string", - "uuid_sensor": "string", - "last_updated": "string" - } - ] -} -``` - -یا در صورت برگرداندن تک سنسور: -```json -{ - "data": { - "name": "string", - "uuid_sensor": "string", - "last_updated": "string" - } -} -``` - -#### 8.2. افزودن سنسور جدید - -**Endpoint:** `POST /api/sensor-hub/` - -**Request Body:** -```json -{ - "name": "string", - "uuid_sensor": "string", - "plant_type": "string", - "plant_name": "string", - "area_geojson": {}, - "area_m2": number -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | الزامی | توضیحات | -|------|-----|--------|---------| -| `name` | string | بله | نام سنسور | -| `uuid_sensor` | string | بله | شناسه یکتای سنسور | -| `plant_type` | string | خیر | نوع محصول (مثل گندم، جو، ذرت) | -| `plant_name` | string | خیر | رقم/نام محصول (وابسته به plant_type) | -| `area_geojson` | object | خیر | محدوده GeoJSON رسم‌شده روی نقشه | -| `area_m2` | number | خیر | مساحت به متر مربع | - -#### 8.3. دریافت انواع محصول و ارقام (Plant Types & Varieties) - -**Endpoint:** `GET /api/sensor-hub/plant-types` - -**توضیح:** این API لیست انواع محصول و ارقام/رقم‌های هر نوع را برمی‌گرداند. فرانت‌اند در فرم افزودن سنسور از این داده برای Autocomplete نوع محصول و رقم استفاده می‌کند. - -**Response:** -```json -{ - "plant_types": [ - "گندم", - "جو", - "ذرت", - "برنج", - "پنبه", - "چغندر قند", - "سیب‌زمینی", - "گوجه‌فرنگی", - "پیاز", - "سبزیجات" - ], - "plant_names_by_type": { - "گندم": ["رقم آذر", "رقم شریف", "رقم مروارید", "رقم بهار", "رقم الوند"], - "جو": ["رقم سرداری", "رقم زاگرس", "رقم کرج", "رقم ریجاب"], - "ذرت": ["رقم سینگل کراس ۷۰۴", "رقم سینگل کراس ۷۰۷", "رقم ماکزیما"], - "برنج": ["رقم فجر", "رقم خزر", "رقم طارم"], - "پنبه": ["رقم ورامین", "رقم ساحل", "رقم سپید"], - "چغندر قند": ["رقم اکباتان", "رقم شیرین", "رقم پاییزه"], - "سیب‌زمینی": ["رقم آگریا", "رقم مارفونا", "رقم ساوالان"], - "گوجه‌فرنگی": ["رقم چری", "رقم روتگرز", "رقم پامیس"], - "پیاز": ["رقم قرمز آذرشهر", "رقم سفید قم", "رقم زرد"], - "سبزیجات": ["کاهو", "جعفری", "شوید", "تره", "ریحان"] - } -} -``` - -**توضیحات فیلدها:** - -| فیلد | نوع | توضیحات | -|------|-----|---------| -| `plant_types` | string[] | آرایه انواع محصول برای انتخاب در Autocomplete | -| `plant_names_by_type` | Record | نگاشت نوع محصول به لیست ارقام/نام‌های آن | - -**نکته:** داده‌های نمونه بالا باید از بک‌اند قابل ویرایش و مدیریت باشند. فرانت‌اند این مقادیر را به‌طور ثابت ندارد و از این API دریافت می‌کند. - ---- - -## خلاصه نکات مهم - -1. **Sensor Hub**: فرم افزودن سنسور شامل نوع محصول و رقم می‌شود. لیست `plant_types` و `plant_names_by_type` باید از API دریافت شود، نه به‌صورت ثابت در فرانت. -2. **User Management & Roles & Permissions**: فقط برای ادمین قابل دسترسی هستند -3. **Calendar, Kanban, Todo**: این سه صفحه باید به یکدیگر متصل باشند و تسک‌هایی با `routine = 0` (NONE) در هر سه قابل نمایش باشند -4. **AI Chat**: باید به تسک‌های Calendar، Kanban، و Todo دسترسی داشته باشد و نسبت به برخی پیام‌ها حساس باشد -5. **Authentication**: سیستم Authentication جزو این مستندات نیست - ---- - -## Enum ها و Type های مشترک - -### Routine Enum -```typescript -enum Routine { - NONE = 0, - DAILY = 1, - WEEKLY = 2, - MONTHLY = 3, - YEARLY = 4 -} -``` - -### ThemeColor Type -```typescript -type ThemeColor = "primary" | "success" | "error" | "warning" | "info" -``` - -### Status Type (Chat) -```typescript -type StatusType = "busy" | "away" | "online" | "offline" -``` - -### Todo Status Type -```typescript -type TodoStatusType = "pending" | "in-progress" | "completed" -``` - -### Todo Priority Type -```typescript -type TodoPriorityType = "high" | "medium" | "low" -``` - -### Calendar Type -```typescript -type CalendarFiltersType = "Personal" | "Business" | "Family" | "Holiday" | "ETC" -``` - -### User Role Type -```typescript -type UserRoleType = "admin" | "author" | "editor" | "maintainer" | "subscriber" -``` - -### User Status Type -```typescript -type UserStatusType = "active" | "pending" | "inactive" -``` - ---- - -**تاریخ ایجاد مستندات:** 2024 -**نسخه:** 1.0.0 - diff --git a/API_RESPONSE_SPEC.md b/API_RESPONSE_SPEC.md deleted file mode 100644 index 42e8c4d..0000000 --- a/API_RESPONSE_SPEC.md +++ /dev/null @@ -1,152 +0,0 @@ -# مشخصات Response های API بخش Crop Zoning - -این سند فقط **فرمت و ساختار response**هایی را که فرانت‌اند از API انتظار دارد شرح می‌دهد. - ---- - -## ۱. API بهینه‌سازی زون‌بندی (Optimize Zoning) - -وقتی کاربر منطقه را روی نقشه انتخاب می‌کند و دکمه «بهینه‌سازی مجدد» را می‌زند، فرانت‌اند یک **GeoJSON Polygon** (مختصات منطقه) به API می‌فرستد و انتظار دارد سرور یک **GeoJSON FeatureCollection** برگرداند که هر feature آن یک زون با geometry (چندضلعی) و properties (داده‌های پیشنهاد محصول) دارد. - -### Request (خلاصه) - -- **ورودی:** یک GeoJSON به صورت `Feature` با `geometry.type: "Polygon"` (مختصات به صورت `[lng, lat]`). - -### Response مورد انتظار - -یک **GeoJSON FeatureCollection** با این ساختار: - -```json -{ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "geometry": { - "type": "Polygon", - "coordinates": [[[lng, lat], [lng, lat], ...]] - }, - "properties": { - "zoneId": "string", - "crop": "wheat" | "canola" | "saffron", - "matchPercent": number, - "waterNeed": "string", - "estimatedProfit": "string", - "reason": "string", - "criteria": [ - { "name": "string", "value": number }, - ... - ] - } - } - ] -} -``` - -### توضیح فیلدهای `properties` هر زون - -| فیلد | نوع | توضیح | -|------|-----|--------| -| `zoneId` | `string` | شناسه یکتا برای زون (مثلاً `"zone-0"`, `"zone-1"`) | -| `crop` | `"wheat" \| "canola" \| "saffron"` | نوع محصول پیشنهادی برای این زون | -| `matchPercent` | `number` | درصد تطابق (۰–۱۰۰) برای پیشنهاد محصول | -| `waterNeed` | `string` | نیاز آبی (مثلاً `"۴۵۰۰-۵۵۰۰ m³/ha"`) | -| `estimatedProfit` | `string` | سود تخمینی (مثلاً `"۱۵-۲۵ میلیون/هکتار"`) | -| `reason` | `string` | توضیح کوتاه دلیل پیشنهاد این محصول | -| `criteria` | `Array<{ name: string, value: number }>` | معیارهای امتیازدهی برای نمودار راداری؛ `value` بین ۰ تا ۱۰۰ (مثلاً دما، بارش، خاک، آب) | - -### نمونه response (یک feature) - -```json -{ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [51.38, 35.68], - [51.381, 35.68], - [51.381, 35.681], - [51.38, 35.681], - [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 } - ] - } - } - ] -} -``` - ---- - -## ۲. API منطقه اولیه (اختیاری) - -اگر بخواهید منطقه اولیه نقشه از سرور بیاید (به‌جای ماک ثابت)، response باید یک **GeoJSON Feature** با Polygon باشد: - -```json -{ - "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]` (lng, lat). -- آرایه اول `coordinates` حلقه بیرونی چندضلعی است؛ نقطه اول و آخر باید یکسان باشند. - ---- - -## ۳. خلاصه نوع‌های TypeScript (برای تطبیق با بک‌اند) - -```ts -type CropType = 'wheat' | 'canola' | 'saffron' - -interface ZoneFeatureProperties { - zoneId: string - crop: CropType - matchPercent: number - waterNeed: string - estimatedProfit: string - reason: string - criteria: { name: string; value: number }[] -} - -// Response بهینه‌سازی = GeoJSON FeatureCollection -// با Feature -``` - ---- - -## ۴. نکات - -- **Layer فعلی:** فرانت‌اند لایه‌های مختلف (`crops`, `waterNeed`, `soilQuality`, `cultivationRisk`) دارد؛ در صورت نیاز می‌توان برای هر لایه response جدا یا فیلدهای اضافه در `properties` تعریف کرد. -- **دکمه «تغییر محصول»:** در پنل جزئیات زون، کاربر می‌تواند محصول را بین `wheat`, `canola`, `saffron` عوض کند؛ در صورت نیاز می‌توان API جدا برای ذخیره این تغییر تعریف کرد. -- **بخش آب و هوا:** داده‌های آب و هوا از سرویس جدا (`farmDashboardService.getAllCards()` → `farmWeatherCard`) گرفته می‌شوند و در این سند پوشش داده نشده‌اند. diff --git a/CROP_ZONING_APIS.md b/CROP_ZONING_APIS.md deleted file mode 100644 index 63b994e..0000000 --- a/CROP_ZONING_APIS.md +++ /dev/null @@ -1,544 +0,0 @@ -# مستندات 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 = { - 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 نمایش داده شود. diff --git a/DASHBOARD_API_DOCUMENTATION.md b/DASHBOARD_API_DOCUMENTATION.md deleted file mode 100644 index af063ad..0000000 --- a/DASHBOARD_API_DOCUMENTATION.md +++ /dev/null @@ -1,653 +0,0 @@ -# مستندات API داشبورد Farm (Farm Dashboard) - -این سند شامل توضیحات کل داشبورد، APIهای تنظیمات (disable/enable/move کارت‌ها) و ساختار پیشنهادی ریسپانس برای محتوای کارت‌ها است. - ---- - -## ۱. نمای کلی داشبورد - -داشبورد Farm از کامپوننت `FarmDashboardWrapper` استفاده می‌کند و شامل ردیف‌ها (rows) و کارت‌های (cards) زیر است: - -| Row ID | Row Label | کارت‌ها | -|--------|-----------|---------| -| `overviewKpis` | Overview KPIs | `farmOverviewKpis` | -| `weatherAlerts` | Weather & Alerts | `farmWeatherCard`, `farmAlertsTracker` | -| `sensorMonitoring` | Sensor Monitoring | `sensorValuesList`, `sensorRadarChart` | -| `sensorCharts` | Sensor Charts | `sensorComparisonChart`, `anomalyDetectionCard` | -| `alertsWater` | Alerts & Water Prediction | `farmAlertsTimeline`, `waterNeedPrediction` | -| `predictions` | Predictions | `harvestPredictionCard`, `yieldPredictionChart` | -| `soilHeatmap` | Soil Moisture Heatmap | `soilMoistureHeatmap` | -| `ndviRecommendations` | NDVI & Recommendations | `ndviHealthCard`, `recommendationsList` | -| `economic` | Economic Overview | `economicOverview` | - ---- - -## ۲. APIهای تنظیمات داشبورد - -### ۲.۱ دریافت تنظیمات داشبورد (Get Config) - -``` -GET /api/farm-dashboard-config -``` - -**توضیح:** تنظیمات شخصی‌سازی داشبورد کاربر لاگین‌شده را برمی‌گرداند. - -**Response:** -```json -{ - "code": 200, - "msg": "OK", - "data": { - "disabled_card_ids": ["farmWeatherCard", "sensorRadarChart"], - "row_order": [ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "sensorCharts", - "alertsWater", - "predictions", - "soilHeatmap", - "ndviRecommendations", - "economic" - ], - "enable_drag_reorder": true - } -} -``` - -**فیلدها:** -| فیلد | نوع | توضیح | -|------|-----|-------| -| `disabled_card_ids` | `string[]` | لیست شناسه کارت‌های غیرفعال (hidden) | -| `row_order` | `string[]` | ترتیب نمایش ردیف‌ها | -| `enable_drag_reorder` | `boolean` | امکان جابجایی ردیف‌ها با drag | - ---- - -### ۲.۲ غیرفعال کردن کارت (Disable Card) - -``` -PATCH /api/farm-dashboard-config -``` - -**Request Body:** -```json -{ - "disabled_card_ids": ["farmWeatherCard", "sensorRadarChart"] -} -``` - -کارت با شناسه `cardId` به لیست `disabled_card_ids` اضافه می‌شود و در داشبورد نمایش داده نمی‌شود. - ---- - -### ۲.۳ فعال کردن کارت (Enable Card) - -``` -PATCH /api/farm-dashboard-config -``` - -**Request Body:** -```json -{ - "disabled_card_ids": ["farmWeatherCard"] -} -``` - -شناسه کارت از لیست `disabled_card_ids` حذف می‌شود و کارت دوباره نمایش داده می‌شود. - -**نکته:** کل لیست `disabled_card_ids` جدید ارسال می‌شود؛ برای enable باید آرایه بدون آن کارت فرستاده شود. - ---- - -### ۲.۴ جابجایی ردیف‌ها (Move Rows) - -``` -PATCH /api/farm-dashboard-config -``` - -**Request Body:** -```json -{ - "row_order": [ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "predictions", - "sensorCharts", - "alertsWater", - "soilHeatmap", - "ndviRecommendations", - "economic" - ] -} -``` - -ترتیب ردیف‌ها طبق آرایه `row_order` ذخیره می‌شود. - ---- - -### ۲.۵ تغییر وضعیت Drag Reorder - -``` -PATCH /api/farm-dashboard-config -``` - -**Request Body:** -```json -{ - "enable_drag_reorder": false -} -``` - ---- - -## ۳. API دریافت همه دیتای کارت‌ها - -### Endpoint پیشنهادی - -``` -GET /api/farm-dashboard -``` - -یا به تفکیک کارت: -``` -GET /api/farm-dashboard/cards -``` - ---- - -## ۴. لیست کامل ریسپانس هر کارت - -ساختار پیشنهادی response برای محتوای هر کارت (بر اساس داده‌های mock فعلی در فرانت): - -### ۴.۱ farmOverviewKpis - -```json -{ - "kpis": [ - { - "id": "farm_health_score", - "title": "Farm Health Score", - "subtitle": "AI Analysis", - "stats": "87%", - "avatarColor": "success", - "avatarIcon": "tabler-heartbeat", - "chipText": "Good", - "chipColor": "success" - }, - { - "id": "water_stress_index", - "title": "Water Stress Index", - "subtitle": "Current", - "stats": "12%", - "avatarColor": "info", - "avatarIcon": "tabler-droplet", - "chipText": "Low", - "chipColor": "success" - }, - { - "id": "disease_risk", - "title": "Disease Risk", - "subtitle": "Last 7 Days", - "stats": "Low", - "avatarColor": "success", - "avatarIcon": "tabler-bug", - "chipText": "5%", - "chipColor": "success" - }, - { - "id": "avg_soil_moisture", - "title": "Avg Soil Moisture", - "subtitle": "Field-wide", - "stats": "65%", - "avatarColor": "primary", - "avatarIcon": "tabler-plant-2", - "chipText": "Optimal", - "chipColor": "success" - }, - { - "id": "yield_prediction", - "title": "Yield Prediction", - "subtitle": "This Season", - "stats": "42 ton", - "avatarColor": "secondary", - "avatarIcon": "tabler-chart-bar", - "chipText": "+8%", - "chipColor": "success" - }, - { - "id": "pest_risk", - "title": "Pest Risk", - "subtitle": "AI Forecast", - "stats": "15%", - "avatarColor": "warning", - "avatarIcon": "tabler-bug-off", - "chipText": "Monitor", - "chipColor": "warning" - } - ] -} -``` - ---- - -### ۴.۲ farmWeatherCard - -```json -{ - "condition": "Clear", - "temperature": 24, - "unit": "°C", - "humidity": 45, - "windSpeed": 12, - "windUnit": "km/h", - "chartData": { - "labels": ["6am", "9am", "12pm", "3pm", "6pm", "9pm", "12am"], - "series": [[18, 22, 26, 28, 25, 20, 18]] - } -} -``` - ---- - -### ۴.۳ farmAlertsTracker - -```json -{ - "totalAlerts": 3, - "radialBarValue": 30, - "alertStats": [ - { - "title": "Water Shortage", - "count": "2", - "avatarColor": "error", - "avatarIcon": "tabler-droplet-half-2" - }, - { - "title": "Fungal Risk", - "count": "1", - "avatarColor": "warning", - "avatarIcon": "tabler-mushroom" - }, - { - "title": "Frost Alert", - "count": "0", - "avatarColor": "info", - "avatarIcon": "tabler-snowflake" - } - ] -} -``` - ---- - -### ۴.۴ sensorValuesList - -```json -{ - "sensors": [ - { - "title": "28°C", - "subtitle": "Air Temperature", - "trendNumber": 2.1, - "trend": "positive", - "unit": "°C" - }, - { - "title": "24°C", - "subtitle": "Soil Temperature", - "trendNumber": -0.5, - "trend": "negative", - "unit": "°C" - }, - { - "title": "65%", - "subtitle": "Air Humidity", - "trendNumber": 3.2, - "trend": "positive", - "unit": "%" - }, - { - "title": "42%", - "subtitle": "Soil Moisture (10cm)", - "trendNumber": -1.8, - "trend": "negative", - "unit": "%" - }, - { - "title": "6.8", - "subtitle": "Soil pH", - "trendNumber": 0.2, - "trend": "positive", - "unit": "pH" - }, - { - "title": "1.2", - "subtitle": "EC (dS/m)", - "trendNumber": 0.1, - "trend": "positive", - "unit": "dS/m" - }, - { - "title": "850", - "subtitle": "Light Intensity (lux)", - "trendNumber": 15.3, - "trend": "positive", - "unit": "lux" - }, - { - "title": "12", - "subtitle": "Wind Speed (km/h)", - "trendNumber": -2.4, - "trend": "negative", - "unit": "km/h" - } - ] -} -``` - ---- - -### ۴.۵ sensorRadarChart - -```json -{ - "labels": ["Temp", "Humidity", "pH", "EC", "Light", "Wind"], - "series": [ - { "name": "Today", "data": [75, 65, 80, 70, 85, 60] }, - { "name": "Ideal", "data": [80, 70, 75, 75, 90, 50] } - ] -} -``` - ---- - -### ۴.۶ sensorComparisonChart - -```json -{ - "currentValue": 48, - "vsLastWeek": "+5%", - "vsLastWeekValue": 5, - "categories": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], - "series": [ - { "name": "Today", "data": [42, 45, 48, 52, 50, 48, 46] }, - { "name": "Last Week", "data": [38, 40, 42, 45, 43, 40, 38] } - ] -} -``` - ---- - -### ۴.۷ anomalyDetectionCard - -```json -{ - "anomalies": [ - { - "sensor": "Soil Moisture Z3", - "value": "38%", - "expected": "45-65%", - "deviation": "-12%", - "severity": "warning" - }, - { - "sensor": "pH Sector 2", - "value": "5.2", - "expected": "6.0-7.0", - "deviation": "-0.8", - "severity": "error" - } - ] -} -``` - ---- - -### ۴.۸ farmAlertsTimeline - -```json -{ - "alerts": [ - { - "title": "Water Shortage Risk", - "description": "Soil moisture at 10cm depth (42%) is below optimal. AI predicts stress in 2-3 days if no irrigation. Recommended: irrigate within 24h.", - "time": "15 min ago", - "color": "warning" - }, - { - "title": "Fungal Disease Risk", - "description": "High humidity (65%) + temp 24°C creates favorable conditions for fungal growth. Consider preventive fungicide or reduce irrigation.", - "time": "1 hour ago", - "color": "error" - }, - { - "title": "Irrigation Suggestion", - "description": "Optimal watering window: 6:00-8:00 AM. Suggested amount: 450 m³ for Zone A. Expected efficiency gain: 12%.", - "time": "2 hours ago", - "color": "info" - }, - { - "title": "Soil Salinity Check", - "description": "EC reading 1.2 dS/m is within range. No action needed. Next check recommended in 5 days.", - "time": "4 hours ago", - "color": "success" - } - ] -} -``` - ---- - -### ۴.۹ waterNeedPrediction - -```json -{ - "totalNext7Days": 3290, - "unit": "m³", - "categories": ["Day 1", "Day 2", "Day 3", "Day 4", "Day 5", "Day 6", "Day 7"], - "series": [{ "name": "Water Need", "data": [420, 450, 480, 460, 490, 510, 480] }] -} -``` - ---- - -### ۴.۱۰ harvestPredictionCard - -```json -{ - "date": "2025-10-15", - "dateFormatted": "Oct 15, 2025", - "daysUntil": 58, - "description": "Based on current GDD accumulation and weather forecast. Optimal harvest window: Oct 12-18.", - "optimalWindowStart": "2025-10-12", - "optimalWindowEnd": "2025-10-18" -} -``` - ---- - -### ۴.۱۱ yieldPredictionChart - -```json -{ - "categories": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - "series": [ - { "name": "This Year", "data": [35, 38, 40, 42, 45, 48, 50, 48, 46, 44, 42, 42] }, - { "name": "Last Year", "data": [32, 34, 36, 38, 40, 42, 44, 42, 40, 38, 36, 38] } - ], - "summary": [ - { "title": "Predicted Yield", "subtitle": "This Season", "amount": "42 ton", "avatarColor": "primary", "avatarIcon": "tabler-chart-bar" }, - { "title": "Harvest Date", "subtitle": "Est. Oct 15", "amount": "+8%", "avatarColor": "success", "avatarIcon": "tabler-calendar" } - ] -} -``` - ---- - -### ۴.۱۲ soilMoistureHeatmap - -```json -{ - "zones": ["Z1", "Z2", "Z3", "Z4", "Z5", "Z6", "Z7"], - "hours": ["6h", "8h", "10h", "12h", "14h", "16h", "18h"], - "series": [ - { "name": "Z1", "data": [{"x": "6h", "y": 52}, {"x": "8h", "y": 48}, {"x": "10h", "y": 55}, {"x": "12h", "y": 60}, {"x": "14h", "y": 58}, {"x": "16h", "y": 54}, {"x": "18h", "y": 50}] }, - { "name": "Z2", "data": [{"x": "6h", "y": 45}, {"x": "8h", "y": 42}, {"x": "10h", "y": 48}, {"x": "12h", "y": 52}, {"x": "14h", "y": 50}, {"x": "16h", "y": 47}, {"x": "18h", "y": 44}] } - ] -} -``` - ---- - -### ۴.۱۳ ndviHealthCard - -```json -{ - "ndviIndex": 0.78, - "healthData": [ - { "title": "Nitrogen Stress", "value": "Low", "color": "success", "icon": "tabler-leaf" }, - { "title": "Crop Health", "value": "Good", "color": "success", "icon": "tabler-plant" } - ] -} -``` - ---- - -### ۴.۱۴ recommendationsList - -```json -{ - "recommendations": [ - { - "title": "Irrigation: 6:00-8:00 AM", - "subtitle": "450 m³ for Zone A. Without irrigation, yield may drop ~8%.", - "avatarIcon": "tabler-droplet", - "avatarColor": "primary" - }, - { - "title": "Fertilizer: NPK 20-20-20", - "subtitle": "Apply 25 kg/ha in 7 days. Current N deficiency in sector 2.", - "avatarIcon": "tabler-leaf", - "avatarColor": "success" - }, - { - "title": "Fungicide: Preventive", - "subtitle": "Humidity + temp favor fungi. Consider copper-based spray.", - "avatarIcon": "tabler-mushroom", - "avatarColor": "warning" - }, - { - "title": "Harvest Window: Oct 12-18", - "subtitle": "Peak ripeness expected Oct 15. Plan labor accordingly.", - "avatarIcon": "tabler-calendar-event", - "avatarColor": "info" - } - ] -} -``` - ---- - -### ۴.۱۵ economicOverview - -```json -{ - "economicData": [ - { "title": "Water Cost", "value": "€720", "subtitle": "This month", "avatarIcon": "tabler-droplet", "avatarColor": "primary" }, - { "title": "AI Water Savings", "value": "€156", "subtitle": "18% saved", "avatarIcon": "tabler-bulb", "avatarColor": "success" }, - { "title": "Platform ROI", "value": "127%", "subtitle": "vs last year", "avatarIcon": "tabler-chart-line", "avatarColor": "info" }, - { "title": "Income Forecast", "value": "€42k", "subtitle": "This season", "avatarIcon": "tabler-currency-euro", "avatarColor": "success" } - ], - "chartSeries": [ - { "name": "Water Cost", "data": [120, 115, 110, 125, 118, 122] }, - { "name": "Fertilizer", "data": [80, 85, 90, 75, 82, 78] } - ], - "chartCategories": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"] -} -``` - ---- - -## ۵. Response یکپارچه همه کارت‌ها - -اگر یک endpoint برای کل دیتای داشبورد داشته باشید: - -``` -GET /api/farm-dashboard -``` - -**Response پیشنهادی:** -```json -{ - "code": 200, - "msg": "OK", - "data": { - "farmOverviewKpis": { ... }, - "farmWeatherCard": { ... }, - "farmAlertsTracker": { ... }, - "sensorValuesList": { ... }, - "sensorRadarChart": { ... }, - "sensorComparisonChart": { ... }, - "anomalyDetectionCard": { ... }, - "farmAlertsTimeline": { ... }, - "waterNeedPrediction": { ... }, - "harvestPredictionCard": { ... }, - "yieldPredictionChart": { ... }, - "soilMoistureHeatmap": { ... }, - "ndviHealthCard": { ... }, - "recommendationsList": { ... }, - "economicOverview": { ... } - } -} -``` - ---- - -## ۶. خلاصه Endpoints - -| عملیات | Method | Endpoint | Body | -|--------|--------|----------|------| -| دریافت تنظیمات | GET | `/api/farm-dashboard-config` | - | -| غیرفعال کردن کارت | PATCH | `/api/farm-dashboard-config` | `{ "disabled_card_ids": [...] }` | -| فعال کردن کارت | PATCH | `/api/farm-dashboard-config` | `{ "disabled_card_ids": [...] }` | -| جابجایی ردیف‌ها | PATCH | `/api/farm-dashboard-config` | `{ "row_order": [...] }` | -| Enable/Disable Drag | PATCH | `/api/farm-dashboard-config` | `{ "enable_drag_reorder": boolean }` | -| دیتای همه کارت‌ها | GET | `/api/farm-dashboard` یا `/api/farm-dashboard/cards` | - | - ---- - -## ۷. Card IDs معتبر - -``` -farmOverviewKpis -farmWeatherCard -farmAlertsTracker -sensorValuesList -sensorRadarChart -sensorComparisonChart -anomalyDetectionCard -farmAlertsTimeline -waterNeedPrediction -harvestPredictionCard -yieldPredictionChart -soilMoistureHeatmap -ndviHealthCard -recommendationsList -economicOverview -``` - -## ۸. Row IDs معتبر - -``` -overviewKpis -weatherAlerts -sensorMonitoring -sensorCharts -alertsWater -predictions -soilHeatmap -ndviRecommendations -economic -``` diff --git a/FARM_AI_ASSISTANT_API.md b/FARM_AI_ASSISTANT_API.md deleted file mode 100644 index 58dcace..0000000 --- a/FARM_AI_ASSISTANT_API.md +++ /dev/null @@ -1,192 +0,0 @@ -# مستندات APIهای دستیار هوشمند مزرعه (Farm AI Assistant) - -این سند تمام APIهای مورد نیاز برای صفحه **Farm AI Assistant** را شرح می‌دهد: ورودی‌ها، خروجی‌ها و استفاده در UI. - -**مسیر صفحه:** `(dashboard)/(private)/farm-ai-assistant` -**کامپوننت اصلی:** `FarmAiAssistantChat` - ---- - -## نمای کلی - -دستیار هوشمند مزرعه برای کار به موارد زیر نیاز دارد: - -| ردیف | API | هدف | -|------|-----|------| -| ۱ | **ارسال پیام به دستیار (Chat/Complete)** | دریافت پاسخ ساخت‌یافته (توصیه، لیست، هشدار) بر اساس پیام کاربر و زمینه مزرعه | -| ۲ | **دریافت زمینه مزرعه (Farm Context)** | پر کردن نوار «زمینه مزرعه» (نوع خاک، EC آب، محصول، مرحله رشد، آخرین آبیاری) | -| ۳ | **توصیه آبیاری** | در صورت درخواست کاربر یا تصمیم دستیار برای توصیه آبیاری | -| ۴ | **توصیه کوددهی** | در صورت درخواست کاربر یا توصیه کود | -| ۵ | **تشخیص آفت از تصویر** | وقتی کاربر تصویر گیاه را ارسال می‌کند | - ---- - -## ۱. API ارسال پیام به دستیار (Farm AI Chat) - -این API هسته اصلی دستیار است و در حال حاضر در فرانت با پاسخ دمو شبیه‌سازی شده است؛ باید با API واقعی جایگزین شود. - -### ۱.۱ مشخصات - -- **متد:** `POST` -- **آدرس پیشنهادی:** `POST /api/farm-ai-assistant/chat/` یا `POST /api/farm-ai-assistant/messages/` -- **هدف:** ارسال پیام کاربر (و در صورت وجود تصویر) به همراه زمینه مزرعه و دریافت پاسخ ساخت‌یافته دستیار. - -### ۱.۲ ورودی (Request Body) - -| فیلد | نوع | اجباری | توضیح | -|------|-----|--------|--------| -| `content` | string | بله | متن پیام کاربر | -| `images` | string[] یا base64[] | خیر | آرایه آدرس تصاویر یا داده base64 (در صورت استفاده از آپلود تصویر دوربین در چت) | -| `conversation_id` | string | خیر | شناسه مکالمه برای ادامه گفتگو؛ در اولین پیام ارسال نشود | -| `farm_context` | object | توصیه | زمینه مزرعه برای پاسخ شخصی‌سازی‌شده (در صورت نبودن، بک‌اند می‌تواند از پیش‌فرض استفاده کند) | - -ساختار پیشنهادی `farm_context` (هم‌خوان با `FarmContext` در فرانت): - -```json -{ - "content": "برنامه آبیاری برای گوجه در مرحله گلدهی چطور باشد؟", - "farm_context": { - "soilType": "Loamy", - "waterEC": "1.2 dS/m", - "selectedCrop": "Tomato", - "growthStage": "Flowering", - "lastIrrigationStatus": "2 days ago" - } -} -``` - -اگر از **تصویر** استفاده شود (دکمه دوربین در input): - -```json -{ - "content": "این برگ زرد شده، چه مشکلی داره؟", - "images": ["data:image/jpeg;base64,..."], - "farm_context": { ... } -} -``` - -### ۱.۳ خروجی (Response Body) - -پاسخ باید شامل **بخش‌های ساخت‌یافته** (sections) باشد تا در UI به صورت کارت (توصیه، لیست، هشدار) رندر شود. - -**قالب پیشنهادی:** - -```json -{ - "status": "success", - "data": { - "message_id": "a-1739123456789", - "conversation_id": "conv-abc123", - "content": "", - "sections": [ - { - "type": "recommendation", - "title": "Irrigation recommendation", - "icon": "droplet", - "frequency": "3 times per week", - "amount": "15–20 L per plant", - "timing": "Early morning (05:00–07:00)", - "expandableExplanation": "Your loamy soil holds moisture well..." - }, - { - "type": "list", - "title": "Key points", - "icon": "leaf", - "items": [ - "Avoid midday watering to reduce evaporation", - "Drip irrigation preferred for root zone targeting" - ] - }, - { - "type": "warning", - "title": "Weather advisory", - "icon": "warning", - "content": "High temps forecasted next week. Consider increasing frequency." - } - ] - } -} -``` - -**ساختار هر بخش (Section) مطابق `AIResponseSection` در فرانت:** - -| فیلد | نوع | اجباری | توضیح | -|------|-----|--------|--------| -| `type` | string | بله | یکی از: `text` \| `list` \| `recommendation` \| `warning` | -| `title` | string | خیر | عنوان بخش | -| `content` | string | خیر | برای `type: "text"` یا `type: "warning"` | -| `items` | string[] | خیر | برای `type: "list"` | -| `icon` | string | خیر | یکی از: `droplet` \| `leaf` \| `warning` \| `fertilizer` \| `calendar` | -| `frequency` | string | خیر | فقط برای `recommendation`: تعداد دفعات (مثلاً در هفته) | -| `amount` | string | خیر | فقط برای `recommendation`: مقدار (مثلاً لیتر یا کیلوگرم) | -| `timing` | string | خیر | فقط برای `recommendation`: زمان پیشنهادی | -| `expandableExplanation` | string | خیر | فقط برای `recommendation`: توضیح قابل گسترش «چرا این توصیه» | - -- اگر `content` خالی باشد و فقط `sections` برگردد، در UI فقط کارت‌ها نمایش داده می‌شوند (مطابق پیاده‌سازی فعلی). -- در صورت خطا انتظار می‌رود پاسخ با `status: "error"` و پیام مناسب برگردد. - ---- - -## ۲. API دریافت زمینه مزرعه (Farm Context) - -برای پر کردن نوار «زمینه مزرعه» در بالای چت (نوع خاک، EC آب، محصول انتخاب‌شده، مرحله رشد، آخرین آبیاری). - -- **وضعیت:** در بک‌اند فعلی endpoint اختصاصی برای «یک جا گرفتن» زمینه مزرعه وجود ندارد. -- **راه‌حل فعلی:** فرانت می‌تواند داده را از منابع موجود جمع کند: - - **توصیه آبیاری:** `GET /api/irrigation-recommendation/config/` → `farmInfo` (soilType, waterQuality, climateZone) و `cropOptions` - - **توصیه کوددهی:** `GET /api/fertilization-recommendation/config/` → `farmData` (soilType, organicMatter, waterEC)، `growthStages`، `cropOptions` -- **پیشنهاد برای آینده:** یک endpoint مثل `GET /api/farm-ai-assistant/context/` یا `GET /api/farm-dashboard-config/...` که یک آبجکت هم‌خوان با `FarmContext` فرانت برگرداند (مثلاً `soilType`, `waterEC`, `selectedCrop`, `growthStage`, `lastIrrigationStatus`). - ---- - -## ۳. API توصیه آبیاری - -وقتی کاربر در چت درخواست توصیه آبیاری می‌کند (یا دستیار تصمیم می‌گیرد توصیه آبیاری بدهد)، می‌توان از API موجود استفاده کرد. - -- **Config (برای فرم/گزینه‌ها):** `GET /api/irrigation-recommendation/config/` -- **توصیه (برنامه آبیاری):** `POST /api/irrigation-recommendation/recommend/` - -ورودی پیشنهادی برای `recommend`: `crop_id`, `soilType`, `waterQuality`, `climateZone` (در نسخه فعلی mock است و در پاسخ استفاده نمی‌شود). - -جزئیات و نمونه پاسخ: [RECOMMENDATION_APIS.md](./RECOMMENDATION_APIS.md#۱-توصیه-آبیاری-irrigation-recommendation). - ---- - -## ۴. API توصیه کوددهی - -در صورت درخواست کاربر برای توصیه کود یا تصمیم دستیار برای دادن توصیه کود. - -- **Config:** `GET /api/fertilization-recommendation/config/` -- **توصیه:** `POST /api/fertilization-recommendation/recommend/` - -ورودی پیشنهادی برای `recommend`: `crop_id`, `growth_stage`, `soilType`, `organicMatter`, `waterEC` (در نسخه فعلی mock است). - -جزئیات و نمونه پاسخ: [RECOMMENDATION_APIS.md](./RECOMMENDATION_APIS.md#۳-توصیه-کوددهی-fertilization-recommendation). - ---- - -## ۵. API تشخیص آفت از تصویر - -وقتی کاربر در چت تصویر گیاه را ارسال می‌کند (دکمه دوربین یا آپلود). - -- **تحلیل تصویر:** `POST /api/pest-detection/analyze/` -- **ورودی:** بدن درخواست می‌تواند شامل تصویر (مثلاً form-data با فایل یا JSON با base64) باشد. در نسخه فعلی پاسخ ثابت (mock) است. -- **خروجی:** `pest`, `confidence`, `description`, `treatment` - -جزئیات و نمونه پاسخ: [RECOMMENDATION_APIS.md](./RECOMMENDATION_APIS.md#۲-تشخیص-آفت-pest-detection). - ---- - -## خلاصه Endpointها برای Farm AI Assistant - -| ردیف | API | متد | Endpoint | وضعیت | -|------|-----|------|----------|--------| -| ۱ | Farm AI Chat | POST | `/api/farm-ai-assistant/chat/` | موجود | -| ۱.۱ | Farm AI Chat Task Create | POST | `/api/farm-ai-assistant/chat/task/` | موجود | -| ۱.۲ | Farm AI Chat Task Status | GET | `/api/farm-ai-assistant/chat/task/{task_id}/status/` | موجود | -| ۲ | Farm Context | GET | `/api/farm-ai-assistant/context/` (پیشنهادی) | **پیاده‌سازی نشده**؛ استفاده از configهای آبیاری/کوددهی | -| ۳ | توصیه آبیاری | GET | `/api/irrigation-recommendation/config/` | موجود (mock) | -| ۳ | توصیه آبیاری | POST | `/api/irrigation-recommendation/recommend/` | موجود (mock) | -| ۴ | توصیه کوددهی | GET | `/api/fertilization-recommendation/config/` | موجود (mock) | -| ۴ | توصیه کوددهی | POST | `/api/fertilization-recommendation/recommend/` | موجود (mock) | -| ۵ | تشخیص آفت | POST | `/api/pest-detection/analyze/` | موجود (mock) | diff --git a/FARM_AI_ASSISTANT_URLS_INTEGRATION.md b/FARM_AI_ASSISTANT_URLS_INTEGRATION.md new file mode 100644 index 0000000..35b3f63 --- /dev/null +++ b/FARM_AI_ASSISTANT_URLS_INTEGRATION.md @@ -0,0 +1,546 @@ +# مستند نحوه ارتباط با سرویس `farm_ai_assistant` + +این فایل برای تیم بک‌اند تهیه شده و صرفاً بر اساس مسیرهای فعال در `farm_ai_assistant/urls.py` و رفتار فعلی ویوها نوشته شده است. + +## Base URL + +تمام endpointهای این سرویس با prefix زیر در دسترس هستند: + +```text +/api/farm-ai-assistant/ +``` + +نمونه کامل: + +```text +https:///api/farm-ai-assistant/ +``` + +## احراز هویت + +تمام endpointهای عملیاتی این ماژول نیاز به احراز هویت دارند: + +- نوع دسترسی: `IsAuthenticated` +- هدر موردنیاز: + +```http +Authorization: Bearer +``` + +> اگر کاربر لاگین نباشد، درخواست با خطای احراز هویت رد می‌شود. + +## وضعیت endpointها + +### endpointهای فعال در `farm_ai_assistant/urls.py` + +| Method | Endpoint | کاربرد | +|---|---|---| +| `GET` | `/context/` | دریافت context نمایشی/پیش‌فرض برای UI | +| `POST` | `/chat/task/` | ثبت پیام کاربر و ساخت task در سرویس AI | +| `GET` | `/chat/task//status/` | بررسی وضعیت task و دریافت نتیجه نهایی | +| `GET` | `/chats/` | لیست گفتگوهای کاربر | +| `POST` | `/chats/` | ایجاد conversation خالی | +| `DELETE` | `/chats//` | حذف conversation | +| `GET` | `/chats//messages/` | دریافت پیام‌های یک conversation | + +### endpoint غیرفعال + +endpoint زیر در فایل `urls.py` کامنت شده و در حال حاضر قابل استفاده نیست: + +```text +POST /api/farm-ai-assistant/chat/ +``` + +یعنی در وضعیت فعلی، جریان اصلی ارتباط با AI به شکل **asynchronous task-based** پیاده‌سازی شده است. + +--- + +## 1) دریافت context + +### Request + +```http +GET /api/farm-ai-assistant/context/ +Authorization: Bearer +``` + +### Response + +```json +{ + "status": "success", + "data": { + "...": "context mock/initial data" + } +} +``` + +### توضیح + +- این endpoint فعلاً داده context را از `mock_data` برمی‌گرداند. +- برای preload کردن اطلاعات اولیه UI مناسب است. +- این endpoint به سرویس خارجی AI کال نمی‌زند. + +--- + +## 2) ایجاد task برای ارسال پیام به AI + +این endpoint مهم‌ترین مسیر ارتباطی با سرویس AI است. + +### Request + +```http +POST /api/farm-ai-assistant/chat/task/ +Authorization: Bearer +Content-Type: application/json +``` + +### Body + +```json +{ + "content": "برای گوجه در مرحله گلدهی برنامه آبیاری بده", + "images": [], + "conversation_id": "optional-uuid", + "title": "optional title", + "farm_context": { + "soilType": "Loamy", + "waterEC": "1.2 dS/m", + "selectedCrop": "Tomato", + "growthStage": "Flowering" + } +} +``` + +### فیلدها + +| فیلد | نوع | اجباری | توضیح | +|---|---|---|---| +| `content` | `string` | اختیاری* | متن پیام کاربر | +| `images` | `string[]` | اختیاری | لیست URL/base64 تصویر | +| `conversation_id` | `uuid` | اختیاری | اگر گفتگو از قبل وجود دارد | +| `title` | `string` | اختیاری | عنوان گفتگو | +| `farm_context` | `object` | اختیاری | context مزرعه | + +> حداقل یکی از `content` یا `images` باید ارسال شود. در غیر این صورت validation error برمی‌گردد. + +### رفتار بک‌اند داخلی + +در این endpoint این اتفاق‌ها می‌افتد: + +1. اگر `conversation_id` ارسال شده باشد، همان conversation متعلق به همان کاربر لود می‌شود. +2. اگر `conversation_id` ارسال نشده باشد، یک conversation جدید ساخته می‌شود. +3. یک message با `role=user` در دیتابیس ذخیره می‌شود. +4. سپس بک‌اند به سرویس خارجی AI درخواست می‌زند. + +### payload ارسالی به سرویس خارجی AI + +بک‌اند این payload را به سرویس خارجی ارسال می‌کند: + +```json +{ + "content": "...", + "query": "...", + "images": [], + "conversation_id": "conversation-uuid", + "user_id": 123, + "farm_context": {}, + "title": "..." +} +``` + +### مسیر سرویس خارجی AI + +```text +POST /rag/chat/generate +``` + +### Response موفق + +```json +{ + "status": "success", + "data": { + "task_id": "abc123", + "status": "PENDING", + "status_url": "/tasks/abc123/status", + "conversation_id": "4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1", + "message_id": "5d3f7a8c-9f2e-4d0a-b56f-1f2c2f9c1a22" + } +} +``` + +### کدهای وضعیت محتمل + +| Status Code | معنی | +|---|---| +| `202` | task ساخته شده و باید polling انجام شود | +| `4xx/5xx` | خطای دریافتی از سرویس خارجی | +| `503` | سرویس خارجی AI در دسترس نیست | + +--- + +## 3) بررسی وضعیت task + +فرانت یا سرویس مصرف‌کننده باید با `task_id` وضعیت را poll کند. + +### Request + +```http +GET /api/farm-ai-assistant/chat/task//status/ +Authorization: Bearer +``` + +### مسیر سرویس خارجی AI + +بک‌اند این درخواست را به سرویس خارجی پاس می‌دهد: + +```text +GET /tasks//status +``` + +### Response در حالت در حال پردازش + +```json +{ + "status": "success", + "data": { + "task_id": "abc123", + "status": "PENDING", + "conversation_id": "4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1", + "progress": { + "message": "Processing request" + } + } +} +``` + +### Response در حالت موفق + +```json +{ + "status": "success", + "data": { + "task_id": "abc123", + "status": "SUCCESS", + "conversation_id": "4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1", + "result": { + "message_id": "9f3f8f61-cc71-4f70-a650-2f4dc6f4e5c2", + "conversation_id": "4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1", + "content": "پیشنهاد آبیاری شما آماده است", + "sections": [ + { + "type": "recommendation", + "title": "Irrigation recommendation", + "icon": "droplet", + "frequency": "3 times per week", + "amount": "15–20 L per plant", + "timing": "Early morning", + "expandableExplanation": "..." + } + ] + } + } +} +``` + +### Response در حالت خطا + +```json +{ + "status": "success", + "data": { + "task_id": "abc123", + "status": "FAILURE", + "conversation_id": "4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1", + "error": "something went wrong" + } +} +``` + +### نکته مهم + +اگر `status=SUCCESS` باشد و `result` از سرویس خارجی دریافت شود: + +- نتیجه نهایی به message دستیار تبدیل و در دیتابیس ذخیره می‌شود. +- یک `assistant message` داخل همان conversation ساخته یا به‌روزرسانی می‌شود. +- خروجی `result` در پاسخ API به فرم نهایی UI normalize می‌شود. + +--- + +## 4) لیست گفتگوها + +### Request + +```http +GET /api/farm-ai-assistant/chats/ +Authorization: Bearer +``` + +### Response + +```json +{ + "status": "success", + "data": [ + { + "id": "4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1", + "message_count": 4 + } + ] +} +``` + +### توضیح + +- فقط conversationهای متعلق به همان کاربر لاگین‌شده برگردانده می‌شود. +- مرتب‌سازی بر اساس `updated_at desc` و سپس `created_at desc` است. + +--- + +## 5) ایجاد conversation خالی + +### Request + +```http +POST /api/farm-ai-assistant/chats/ +Authorization: Bearer +Content-Type: application/json +``` + +### Body + +```json +{ + "title": "مشاوره آبیاری", + "farm_context": { + "soilType": "Loamy" + } +} +``` + +### Response + +```json +{ + "status": "success", + "data": { + "id": "4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1", + "message_count": 0 + } +} +``` + +### کد وضعیت + +```text +201 Created +``` + +--- + +## 6) دریافت پیام‌های یک conversation + +### Request + +```http +GET /api/farm-ai-assistant/chats//messages/ +Authorization: Bearer +``` + +### Response + +```json +{ + "status": "success", + "data": { + "conversation_id": "4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1", + "messages": [ + { + "message_id": "11111111-1111-1111-1111-111111111111", + "conversation_id": "4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1", + "role": "user", + "content": "برای گوجه آبیاری پیشنهاد بده", + "sections": [], + "images": [], + "created_at": "2025-03-27T12:00:00Z" + }, + { + "message_id": "22222222-2222-2222-2222-222222222222", + "conversation_id": "4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1", + "role": "assistant", + "content": "", + "sections": [ + { + "type": "list", + "title": "Key points", + "items": [ + "Avoid midday watering", + "Use drip irrigation" + ] + } + ], + "images": [], + "created_at": "2025-03-27T12:00:05Z" + } + ] + } +} +``` + +### توضیح + +- `role` یکی از دو مقدار `user` یا `assistant` است. +- برای messageهای `assistant`، فیلد `sections` می‌تواند پر باشد. +- برای messageهای `user`، معمولاً `sections` خالی است. + +--- + +## 7) حذف conversation + +### Request + +```http +DELETE /api/farm-ai-assistant/chats// +Authorization: Bearer +``` + +### Response + +```json +{ + "status": "success", + "data": { + "conversation_id": "4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1" + } +} +``` + +--- + +## فرمت `sections` در پاسخ نهایی AI + +بخش `sections` برای render شدن خروجی دستیار در UI استفاده می‌شود. + +### ساختار هر section + +```json +{ + "type": "recommendation", + "title": "Irrigation recommendation", + "content": "", + "items": [], + "icon": "droplet", + "frequency": "3 times per week", + "amount": "15–20 L per plant", + "timing": "Early morning", + "expandableExplanation": "..." +} +``` + +### مقادیر مجاز `type` + +- `text` +- `list` +- `recommendation` +- `warning` + +### نکات نرمال‌سازی + +بک‌اند فقط این کلیدها را از پاسخ AI نگه می‌دارد: + +- `type` +- `title` +- `content` +- `items` +- `icon` +- `frequency` +- `amount` +- `timing` +- `expandableExplanation` + +اگر سرویس خارجی فیلد اضافی برگرداند، در response نهایی حذف می‌شود. + +--- + +## قواعد مهم کسب‌وکاری/فنی + +### مالکیت conversation + +- هر conversation فقط برای owner خودش قابل مشاهده/حذف/دریافت پیام است. +- اگر `conversation_id` مربوط به کاربر دیگری باشد، `404 Conversation not found` برمی‌گردد. + +### title conversation + +- اگر conversation جدید بدون title ساخته شود: + - در endpoint `POST /chats/` عنوان پیش‌فرض `New chat` است. + - در endpoint `POST /chat/task/` اگر title داده نشود، از ابتدای `content` یا fallback پیش‌فرض استفاده می‌شود. + +### ذخیره‌سازی پیام‌ها + +- پیام کاربر قبل از تماس با AI ذخیره می‌شود. +- پاسخ دستیار بعد از اتمام task ذخیره می‌شود. +- داده خام/کمکی در `raw_response` نگهداری می‌شود. + +### تصاویر + +- فیلد `images` آرایه‌ای از string است. +- این string می‌تواند URL، path یا base64 باشد؛ validation فعلی فقط نوع string را چک می‌کند. + +--- + +## فلو پیشنهادی برای استفاده از API + +### سناریوی استاندارد چت + +1. فرانت در صورت نیاز `GET /context/` را صدا می‌زند. +2. فرانت پیام کاربر را با `POST /chat/task/` ارسال می‌کند. +3. از پاسخ، `task_id` و `conversation_id` دریافت می‌شود. +4. فرانت هر چند ثانیه `GET /chat/task//status/` را poll می‌کند. +5. وقتی `status=SUCCESS` شد، نتیجه نهایی از `result` خوانده می‌شود. +6. برای history کامل، `GET /chats//messages/` فراخوانی می‌شود. + +--- + +## نمونه cURL + +### ارسال پیام و ساخت task + +```bash +curl -X POST 'https:///api/farm-ai-assistant/chat/task/' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "content": "برای خیار گلخانه‌ای برنامه آبیاری بده", + "farm_context": { + "soilType": "Sandy Loam", + "selectedCrop": "Cucumber", + "growthStage": "Flowering" + } + }' +``` + +### بررسی وضعیت task + +```bash +curl -X GET 'https:///api/farm-ai-assistant/chat/task/abc123/status/' \ + -H 'Authorization: Bearer ' +``` + +### دریافت پیام‌های گفتگو + +```bash +curl -X GET 'https:///api/farm-ai-assistant/chats/4b9f4d2f-2e5f-4f7a-ae24-6e7fd9c3e0f1/messages/' \ + -H 'Authorization: Bearer ' +``` + +--- + +## جمع‌بندی سریع برای تیم backend + +- endpoint sync chat فعلاً فعال نیست. +- endpoint اصلی برای ارتباط با AI برابر `POST /chat/task/` است. +- نتیجه از طریق `GET /chat/task//status/` گرفته می‌شود. +- conversation و messageها داخل دیتابیس داخلی ذخیره می‌شوند. +- همه endpointها نیازمند authentication هستند. +- ساختار خروجی نهایی باید با `sections` برای UI سازگار باشد. diff --git a/FARM_DASHBOARD_CONFIG_API.md b/FARM_DASHBOARD_CONFIG_API.md deleted file mode 100644 index 0095c21..0000000 --- a/FARM_DASHBOARD_CONFIG_API.md +++ /dev/null @@ -1,357 +0,0 @@ -# مستند ارتباط فرانت با API تنظیمات داشبورد فارم - -این فایل مشخص می‌کند فرانت‌اند برای endpoint زیر چه request و responseی انتظار دارد: - -```text -http://localhost:8000/api/farm-dashboard-config -``` - -این endpoint در فرانت از طریق فایل `src/libs/api/services/farmDashboardService.ts` مصرف می‌شود. - ---- - -## خلاصه رفتار فرانت - -- فرانت برای دریافت تنظیمات از `GET /api/farm-dashboard-config` استفاده می‌کند. -- فرانت برای ذخیره تغییرات از `PATCH /api/farm-dashboard-config` استفاده می‌کند. -- در `PATCH` فقط فیلدهای تغییرکرده ارسال می‌شوند. -- اما در response بهتر است همیشه **کل تنظیمات نهایی** برگردانده شود. -- فرانت response را هم در حالت wrapper شده و هم بدون wrapper می‌پذیرد. - ---- - -## فرمت response قابل قبول - -فرانت هر دو فرمت زیر را می‌پذیرد. - -### فرمت پیشنهادی - -```json -{ - "code": 200, - "msg": "OK", - "data": { - "disabled_card_ids": ["farmWeatherCard", "sensorRadarChart"], - "row_order": [ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "sensorCharts", - "alertsWater", - "predictions", - "soilHeatmap", - "ndviRecommendations", - "economic" - ], - "enable_drag_reorder": true - } -} -``` - -### فرمت قابل قبول بدون wrapper - -```json -{ - "disabled_card_ids": ["farmWeatherCard", "sensorRadarChart"], - "row_order": [ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "sensorCharts", - "alertsWater", - "predictions", - "soilHeatmap", - "ndviRecommendations", - "economic" - ], - "enable_drag_reorder": true -} -``` - ---- - -## GET - -### Request - -```http -GET /api/farm-dashboard-config -Content-Type: application/json -Authorization: Bearer -``` - -> هدر `Authorization` فقط وقتی ارسال می‌شود که توکن در `localStorage` موجود باشد. - -### Response مورد انتظار - -فرانت در خروجی GET این ساختار را انتظار دارد: - -| فیلد | نوع | توضیح | -|---|---|---| -| `disabled_card_ids` | `string[]` | لیست کارت‌های مخفی‌شده | -| `row_order` | `string[]` | ترتیب ردیف‌های داشبورد | -| `enable_drag_reorder` | `boolean` | فعال/غیرفعال بودن drag reorder | - -### مثال response - -```json -{ - "code": 200, - "msg": "OK", - "data": { - "disabled_card_ids": ["farmWeatherCard", "sensorRadarChart"], - "row_order": [ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "sensorCharts", - "alertsWater", - "predictions", - "soilHeatmap", - "ndviRecommendations", - "economic" - ], - "enable_drag_reorder": true - } -} -``` - -### نکات مهم GET - -- اگر `enable_drag_reorder` برنگردد، فرانت مقدار پیش‌فرض `true` در نظر می‌گیرد. -- اگر `row_order` نامعتبر یا خالی باشد، فرانت از ترتیب پیش‌فرض خودش استفاده می‌کند. -- اگر request خطا بخورد، فرانت اول `localStorage` را چک می‌کند؛ اگر چیزی نبود، config پیش‌فرض را می‌سازد. - ---- - -## PATCH - -### رفتار request - -فرانت فقط فیلدهای تغییرکرده را می‌فرستد. یعنی body می‌تواند یکی از حالت‌های زیر باشد یا ترکیبی از آن‌ها: - -#### 1) تغییر کارت‌های غیرفعال - -```json -{ - "disabled_card_ids": ["farmWeatherCard", "sensorRadarChart"] -} -``` - -#### 2) تغییر ترتیب ردیف‌ها - -```json -{ - "row_order": [ - "overviewKpis", - "weatherAlerts", - "predictions", - "sensorMonitoring", - "sensorCharts", - "alertsWater", - "soilHeatmap", - "ndviRecommendations", - "economic" - ] -} -``` - -#### 3) فعال/غیرفعال کردن drag reorder - -```json -{ - "enable_drag_reorder": false -} -``` - -#### 4) ترکیبی - -```json -{ - "disabled_card_ids": ["farmWeatherCard"], - "row_order": [ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "sensorCharts", - "alertsWater", - "predictions", - "soilHeatmap", - "ndviRecommendations", - "economic" - ], - "enable_drag_reorder": true -} -``` - -### Response مورد انتظار برای PATCH - -هرچند request می‌تواند partial باشد، ولی response بهتر است همیشه **کل state نهایی config** را برگرداند: - -```json -{ - "code": 200, - "msg": "OK", - "data": { - "disabled_card_ids": ["farmWeatherCard"], - "row_order": [ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "sensorCharts", - "alertsWater", - "predictions", - "soilHeatmap", - "ndviRecommendations", - "economic" - ], - "enable_drag_reorder": true - } -} -``` - -### نکته خیلی مهم برای PATCH - -در پیاده‌سازی فعلی فرانت، response باید حداقل یکی از این دو فیلد را داشته باشد: - -- `disabled_card_ids` -- `row_order` - -اگر backend فقط این را برگرداند: - -```json -{ - "enable_drag_reorder": false -} -``` - -ممکن است فرانت این response را نامعتبر تشخیص دهد. -بنابراین برای جلوگیری از مشکل، **همیشه کل object نهایی config را برگردانید**. - ---- - -## mapping بین فرانت و API - -فرانت state داخلی را با نام‌های camelCase نگه می‌دارد، اما در request به snake_case تبدیل می‌کند: - -| Frontend field | API field | -|---|---| -| `disabledCardIds` | `disabled_card_ids` | -| `rowOrder` | `row_order` | -| `enableDragReorder` | `enable_drag_reorder` | - ---- - -## مقادیر معتبر پیشنهادی - -### Row ID های معتبر - -```json -[ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "sensorCharts", - "alertsWater", - "predictions", - "soilHeatmap", - "ndviRecommendations", - "economic" -] -``` - -### Card ID های معتبر - -```json -[ - "farmOverviewKpis", - "farmWeatherCard", - "farmAlertsTracker", - "sensorValuesList", - "sensorRadarChart", - "sensorComparisonChart", - "anomalyDetectionCard", - "farmAlertsTimeline", - "waterNeedPrediction", - "harvestPredictionCard", - "yieldPredictionChart", - "soilMoistureHeatmap", - "ndviHealthCard", - "recommendationsList", - "economicOverview" -] -``` - ---- - -## فرمت canonical پیشنهادی برای backend - -اگر بخواهی backend کاملاً سازگار و بدون ambiguity باشد، بهترین قرارداد این است: - -- `disabled_card_ids` فقط شامل `Card ID` باشد -- `row_order` فقط شامل `Row ID` باشد -- response همیشه full object برگرداند -- status موفق `200` باشد -- `Content-Type` برابر `application/json` باشد - ---- - -## نمونه نهایی پیشنهادی - -### GET success - -```json -{ - "code": 200, - "msg": "OK", - "data": { - "disabled_card_ids": [], - "row_order": [ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "sensorCharts", - "alertsWater", - "predictions", - "soilHeatmap", - "ndviRecommendations", - "economic" - ], - "enable_drag_reorder": true - } -} -``` - -### PATCH success - -```json -{ - "code": 200, - "msg": "OK", - "data": { - "disabled_card_ids": ["farmWeatherCard"], - "row_order": [ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "sensorCharts", - "alertsWater", - "predictions", - "soilHeatmap", - "ndviRecommendations", - "economic" - ], - "enable_drag_reorder": false - } -} -``` - ---- - -## منبع این مستند - -این مستند بر اساس رفتار واقعی فرانت در فایل‌های زیر نوشته شده است: - -- `src/libs/api/services/farmDashboardService.ts` -- `src/views/dashboards/farm/farmDashboardConfig.ts` -- `src/views/dashboards/farm/FarmDashboardWrapper.tsx` diff --git a/FARM_FEATURE_API_RESPONSES.md b/FARM_FEATURE_API_RESPONSES.md deleted file mode 100644 index 5598fa5..0000000 --- a/FARM_FEATURE_API_RESPONSES.md +++ /dev/null @@ -1,439 +0,0 @@ -# مستند پاسخ API برای فیچرهای Farm AI - -این فایل، پاسخ‌های API موردنیاز برای سه فیچر زیر را یک‌جا جمع می‌کند: - -- `src/app/(dashboard)/(private)/farm-ai-assistant/page.tsx` -- `src/app/(dashboard)/(private)/fertilization-recommendation/page.tsx` -- `src/app/(dashboard)/(private)/irrigation-recommendation/page.tsx` - -> مبنا، پیاده‌سازی فعلی فرانت در `src/views/dashboards/farm/...` و سرویس‌های `src/libs/api/services/...` است. - ---- - -## قرارداد کلی پاسخ‌ها - -در هر سه سرویس، فرانت انتظار این wrapper را دارد: - -```json -{ - "status": "success", - "data": {} -} -``` - -- `status`: معمولاً `success` یا `error` -- `data`: payload اصلی که فرانت unwrap می‌کند - ---- - -## 1) Farm AI Assistant - -### صفحه - -- `src/app/(dashboard)/(private)/farm-ai-assistant/page.tsx` -- کامپوننت اصلی: `src/views/dashboards/farm/farmAiAssistant/FarmAiAssistantChat.tsx` - -### APIهای موردنیاز - -1. `GET /api/farm-ai-assistant/context/` -2. `POST /api/farm-ai-assistant/chat/` - ---- - -### 1.1) دریافت context مزرعه - -#### Endpoint - -`GET /api/farm-ai-assistant/context/` - -#### Response - -```json -{ - "status": "success", - "data": { - "soilType": "Loamy", - "waterEC": "1.2 dS/m", - "selectedCrop": "Tomato", - "growthStage": "Flowering", - "lastIrrigationStatus": "2 days ago" - } -} -``` - -#### فیلدهای لازم - -| فیلد | نوع | مصرف در UI | -|------|-----|------------| -| `soilType` | `string` | badge نوع خاک | -| `waterEC` | `string` | badge EC آب | -| `selectedCrop` | `string` | badge محصول انتخاب‌شده | -| `growthStage` | `string` | badge مرحله رشد | -| `lastIrrigationStatus` | `string` | badge آخرین وضعیت آبیاری | - -#### نکته - -اگر این API خطا بدهد، فرانت fallback داخلی دارد و toast خطا نمایش می‌دهد. - ---- - -### 1.2) ارسال پیام به دستیار - -#### Endpoint - -`POST /api/farm-ai-assistant/chat/` - -#### Request body - -```json -{ - "content": "What is the best irrigation plan for tomato?", - "farm_context": { - "soilType": "Loamy", - "waterEC": "1.2 dS/m", - "selectedCrop": "Tomato", - "growthStage": "Flowering", - "lastIrrigationStatus": "2 days ago" - }, - "conversation_id": "conv-123" -} -``` - -#### Response - -```json -{ - "status": "success", - "data": { - "message_id": "msg-001", - "conversation_id": "conv-123", - "content": "Here is the recommended plan.", - "sections": [ - { - "type": "recommendation", - "title": "Irrigation Plan", - "icon": "droplet", - "frequency": "3 times per week", - "amount": "15 liters per plant", - "timing": "Early morning", - "expandableExplanation": "Loamy soil holds moisture well, so moderate frequency is enough." - }, - { - "type": "list", - "title": "Important Notes", - "icon": "leaf", - "items": [ - "Avoid watering at noon", - "Check leaf stress every two days" - ] - }, - { - "type": "warning", - "title": "Heat Alert", - "icon": "warning", - "content": "Increase irrigation if temperature rises above 35°C." - } - ] - } -} -``` - -#### فیلدهای لازم در response - -| فیلد | نوع | توضیح | -|------|-----|-------| -| `message_id` | `string` | id پیام assistant | -| `conversation_id` | `string` | برای ادامه چت در پیام‌های بعدی | -| `content` | `string` | متن ساده پاسخ | -| `sections` | `ChatSection[]` | خروجی ساخت‌یافته برای رندر کارت‌ها | - -#### ساختار `ChatSection` - -| فیلد | نوع | اجباری | توضیح | -|------|-----|--------|-------| -| `type` | `'text' \| 'list' \| 'recommendation' \| 'warning'` | بله | نوع سکشن | -| `title` | `string` | خیر | عنوان سکشن | -| `content` | `string` | خیر | متن سکشن | -| `items` | `string[]` | خیر | برای لیست | -| `icon` | `'droplet' \| 'leaf' \| 'warning' \| 'fertilizer' \| 'calendar'` | خیر | آیکون نمایشی | -| `frequency` | `string` | خیر | فقط برای `recommendation` | -| `amount` | `string` | خیر | فقط برای `recommendation` | -| `timing` | `string` | خیر | فقط برای `recommendation` | -| `expandableExplanation` | `string` | خیر | توضیح بازشونده | - -#### حداقل توصیه - -- `sections` همیشه به صورت آرایه برگردد، حتی اگر خالی باشد. -- `conversation_id` بعد از اولین پاسخ حتماً برگردد. -- اگر پاسخ فقط structured است، `content` می‌تواند رشته خالی باشد. - ---- - -## 2) Fertilization Recommendation - -### صفحه - -- `src/app/(dashboard)/(private)/fertilization-recommendation/page.tsx` -- کامپوننت اصلی: `src/views/dashboards/farm/smartFertilization/SmartFertilizationRecommendation.tsx` - -### APIهای موردنیاز - -1. `GET /api/fertilization-recommendation/config/` -2. `POST /api/fertilization-recommendation/recommend/` - ---- - -### 2.1) دریافت config اولیه - -#### Endpoint - -`GET /api/fertilization-recommendation/config/` - -#### Response - -```json -{ - "status": "success", - "data": { - "farmData": { - "soilType": "Loamy", - "organicMatter": "Medium (2.5%)", - "waterEC": "1.2 dS/m" - }, - "growthStages": [ - { "id": "prePlanting", "icon": "tabler-seedling" }, - { "id": "earlyGrowth", "icon": "tabler-leaf" }, - { "id": "flowering", "icon": "tabler-flower" }, - { "id": "fruiting", "icon": "tabler-apple" }, - { "id": "postHarvest", "icon": "tabler-basket" } - ], - "cropOptions": [ - { "id": "wheat", "labelKey": "wheat", "icon": "tabler-wheat" }, - { "id": "corn", "labelKey": "corn", "icon": "tabler-plant-2" } - ] - } -} -``` - -#### فیلدهای لازم - -##### `farmData` - -| فیلد | نوع | -|------|-----| -| `soilType` | `string` | -| `organicMatter` | `string` | -| `waterEC` | `string` | - -##### `growthStages[]` - -| فیلد | نوع | توضیح | -|------|-----|-------| -| `id` | `string` | id مرحله رشد | -| `icon` | `string` | نام آیکون | - -##### `cropOptions[]` - -| فیلد | نوع | توضیح | -|------|-----|-------| -| `id` | `string` | id محصول برای submit | -| `labelKey` | `string` | کلید ترجمه | -| `icon` | `string` | نام آیکون | - -#### نکته - -- اگر `growthStages` مقدار داشته باشد، اولین آیتم به عنوان پیش‌فرض انتخاب می‌شود. -- اگر `cropOptions` خالی باشد، لیست انتخاب محصول خالی نمایش داده می‌شود. - ---- - -### 2.2) دریافت برنامه کوددهی - -#### Endpoint - -`POST /api/fertilization-recommendation/recommend/` - -#### Request body - -```json -{ - "crop_id": "wheat", - "growth_stage": "flowering", - "soilType": "Loamy", - "organicMatter": "Medium (2.5%)", - "waterEC": "1.2 dS/m" -} -``` - -#### Response - -```json -{ - "status": "success", - "data": { - "plan": { - "npkRatio": "20-20-20", - "amountPerHectare": "150 kg/ha", - "applicationMethod": "Fertigation", - "applicationInterval": "Every 10 days", - "reasoning": "Balanced NPK is recommended during flowering to support bloom and fruit set." - } - } -} -``` - -#### فیلدهای لازم در `plan` - -| فیلد | نوع | مصرف در UI | -|------|-----|------------| -| `npkRatio` | `string` | نوع/نسبت کود | -| `amountPerHectare` | `string` | مقدار مصرف | -| `applicationMethod` | `string` | روش مصرف | -| `applicationInterval` | `string` | بازه تکرار | -| `reasoning` | `string` | توضیح بازشونده | - -#### نکته - -فرانت مستقیماً `data.plan` را انتظار دارد. اگر `plan` نباشد، نتیجه‌ای نمایش داده نمی‌شود. - ---- - -## 3) Irrigation Recommendation - -### صفحه - -- `src/app/(dashboard)/(private)/irrigation-recommendation/page.tsx` -- کامپوننت اصلی: `src/views/dashboards/farm/smartIrrigation/SmartIrrigationRecommendation.tsx` - -### APIهای موردنیاز - -1. `GET /api/irrigation-recommendation/config/` -2. `POST /api/irrigation-recommendation/recommend/` - ---- - -### 3.1) دریافت config اولیه - -#### Endpoint - -`GET /api/irrigation-recommendation/config/` - -#### Response - -```json -{ - "status": "success", - "data": { - "farmInfo": { - "soilType": "Loamy", - "waterQuality": "Medium EC", - "climateZone": "Temperate" - }, - "cropOptions": [ - { "id": "tomato", "labelKey": "tomato", "icon": "tabler-plant-2" }, - { "id": "cucumber", "labelKey": "cucumber", "icon": "tabler-leaf" } - ] - } -} -``` - -#### فیلدهای لازم - -##### `farmInfo` - -| فیلد | نوع | -|------|-----| -| `soilType` | `string` | -| `waterQuality` | `string` | -| `climateZone` | `string` | - -##### `cropOptions[]` - -| فیلد | نوع | توضیح | -|------|-----|-------| -| `id` | `string` | id محصول برای submit | -| `labelKey` | `string` | کلید ترجمه | -| `icon` | `string` | نام آیکون | - -#### نکته - -در این صفحه `farmInfo` بدون چک null مستقیماً set می‌شود؛ بهتر است API همیشه این آبجکت را برگرداند. - ---- - -### 3.2) دریافت برنامه آبیاری - -#### Endpoint - -`POST /api/irrigation-recommendation/recommend/` - -#### Request body - -```json -{ - "crop_id": "tomato" -} -``` - -> در سرویس، فیلدهای `soilType`, `waterQuality`, `climateZone` هم پشتیبانی شده‌اند، ولی در UI فعلی فقط `crop_id` ارسال می‌شود. - -#### Response - -```json -{ - "status": "success", - "data": { - "plan": { - "frequencyPerWeek": 3, - "durationMinutes": 25, - "bestTimeOfDay": "Early morning", - "moistureLevel": 68, - "warning": "Reduce irrigation if rainfall occurs this week." - } - } -} -``` - -#### فیلدهای لازم در `plan` - -| فیلد | نوع | مصرف در UI | -|------|-----|------------| -| `frequencyPerWeek` | `number` | تعداد دفعات در هفته | -| `durationMinutes` | `number` | مدت هر نوبت | -| `bestTimeOfDay` | `string` | زمان مناسب آبیاری | -| `moistureLevel` | `number` | درصد moisture برای دایره progress | -| `warning` | `string` | هشدار اختیاری | - -#### محدودیت مهم - -- `moistureLevel` بهتر است بین `0` تا `100` باشد، چون مستقیم برای محاسبه progress circle استفاده می‌شود. - ---- - -## جمع‌بندی سریع برای بک‌اند - -### Farm AI Assistant - -- `GET /api/farm-ai-assistant/context/` → `data: FarmContext` -- `POST /api/farm-ai-assistant/chat/` → `data: { message_id, conversation_id, content, sections }` - -### Fertilization Recommendation - -- `GET /api/fertilization-recommendation/config/` → `data: { farmData, growthStages, cropOptions }` -- `POST /api/fertilization-recommendation/recommend/` → `data: { plan }` - -### Irrigation Recommendation - -- `GET /api/irrigation-recommendation/config/` → `data: { farmInfo, cropOptions }` -- `POST /api/irrigation-recommendation/recommend/` → `data: { plan }` - ---- - -## مسیرهای مرجع کد - -- `src/libs/api/services/farmAiAssistantService.ts` -- `src/libs/api/services/fertilizationRecommendationService.ts` -- `src/libs/api/services/irrigationRecommendationService.ts` -- `src/views/dashboards/farm/farmAiAssistant/FarmAiAssistantChat.tsx` -- `src/views/dashboards/farm/smartFertilization/SmartFertilizationRecommendation.tsx` -- `src/views/dashboards/farm/smartIrrigation/SmartIrrigationRecommendation.tsx` diff --git a/PLANT_SIMULATOR_API.md b/PLANT_SIMULATOR_API.md deleted file mode 100644 index 3b2e79a..0000000 --- a/PLANT_SIMULATOR_API.md +++ /dev/null @@ -1,465 +0,0 @@ -# قرارداد API شبیه‌ساز گیاه (Plant Simulator) - -این سند تمام داده‌هایی را که باید **از بکند بیاید**، **به بکند فرستاده شود** و دوباره **از بکند برگردد** به‌همراه ساختار و داده‌های ماک توصیف می‌کند. - ---- - -## ۱. داده‌های اولیه (از بکند → فرانت) - -این داده‌ها یک‌بار هنگام لود صفحه (یا هنگام باز کردن شبیه‌ساز) از بکند گرفته می‌شوند. - -### ۱.۱ تنظیمات اسلایدرها (کنترل‌های محیطی) - -هر اسلایدر (نور، آب، pH خاک، سرعت رشد و غیره) باید از بکند **حداقل، حداکثر، گام، واحد و برچسب** بگیرد. واحد می‌تواند درصد (`percent`) یا عدد با واحد متنی (`number` + `unit`) باشد. - -```ts -// GET /api/plant-simulator/config یا بخشی از همین endpoint - -interface SliderConfig { - key: string // مثلاً: "light" | "water" | "soil_ph" | "growth_speed" - label: string // برچسب نمایشی (مثلاً "نور"، "آب") - min: number - max: number - step: number - unit_type: 'percent' | 'number' - unit?: string // وقتی unit_type === 'number' مثلاً "g/s", "ppm", "°C" - default_value: number - icon?: string // اختیاری: مثلاً "☀️", "💧" -} - -// پاسخ نمونه -{ - "sliders": [ - { - "key": "light", - "label": "نور", - "min": 0, - "max": 100, - "step": 5, - "unit_type": "percent", - "default_value": 75, - "icon": "☀️" - }, - { - "key": "water", - "label": "آب", - "min": 0, - "max": 100, - "step": 5, - "unit_type": "percent", - "default_value": 65, - "icon": "💧" - }, - { - "key": "soil_ph", - "label": "pH خاک", - "min": 4, - "max": 9, - "step": 0.5, - "unit_type": "number", - "unit": "", - "default_value": 6.5 - }, - { - "key": "growth_speed", - "label": "سرعت رشد", - "min": 0.5, - "max": 5, - "step": 0.5, - "unit_type": "number", - "unit": "×", - "default_value": 1.5 - } - ] -} -``` - -### ۱.۲ ثابت‌های شبیه‌ساز (حداکثرها و محدوده چارت) - -برای نمایش صحیح ارتفاع، برگ، شاخه، میوه، محصول و محورهای چارت باید از بکند بیاید. - -```ts -// همان config یا GET /api/plant-simulator/constants - -interface SimulatorConstants { - max_height: number // مثلاً 280 (px یا واحد ارتفاع) - max_leaves: number // مثلاً 14 - max_branches: number // مثلاً 6 - max_yield: number // مثلاً 500 (گرم) - yield_unit: string // مثلاً "g" - yield_rate_unit: string // مثلاً "g/s" - height_unit?: string // مثلاً "px" یا "cm" -} - -// پاسخ نمونه -{ - "max_height": 280, - "max_leaves": 14, - "max_branches": 6, - "max_yield": 500, - "yield_unit": "g", - "yield_rate_unit": "g/s", - "height_unit": "px" -} -``` - -### ۱.۳ تنظیمات چارت (محورها و سری‌ها) - -عنوان چارت، برچسب محورها و محدوده min/max هر محور تا در فرانت به‌درستی رسم شود. - -```ts -interface ChartConfig { - title: string - x_axis_label?: string // مثلاً "زمان (ثانیه)" - series: { - key: string // "height" | "leaves" | "yield" | "yield_rate" - label: string - y_axis_id: string // "yHeight" | "yLeaf" | "yYield" | "yYieldRate" - min: number - max: number - unit?: string - }[] -} - -// پاسخ نمونه -{ - "chart": { - "title": "پیشرفت رشد", - "x_axis_label": "زمان (ثانیه)", - "series": [ - { "key": "height", "label": "ارتفاع (px)", "y_axis_id": "yHeight", "min": 0, "max": 280, "unit": "px" }, - { "key": "leaves", "label": "تعداد برگ", "y_axis_id": "yLeaf", "min": 0, "max": 14 }, - { "key": "yield", "label": "محصول (g)", "y_axis_id": "yYield", "min": 0, "max": 500, "unit": "g" }, - { "key": "yield_rate", "label": "نرخ محصول (g/s)", "y_axis_id": "yYieldRate", "min": 0, "unit": "g/s" } - ] - } -} -``` - ---- - -## ۲. داده‌هایی که به بکند فرستاده می‌شوند - -### ۲.۱ شروع شبیه‌سازی - -وقتی کاربر دکمه «شروع» را می‌زند، مقادیر فعلی محیط و سرعت به بکند ارسال می‌شود. - -```ts -// POST /api/plant-simulator/start - -interface StartSimulationRequest { - environment: Record // key مطابق slider.key ها - growth_speed: number -} - -// مثال -{ - "environment": { - "light": 75, - "water": 65, - "soil_ph": 6.5 - }, - "growth_speed": 1.5 -} -``` - -### ۲.۲ توقف شبیه‌سازی - -```ts -// POST /api/plant-simulator/stop - -// بدنه خالی یا فقط session_id در صورت نیاز -{} -``` - -### ۲.۳ ریست - -```ts -// POST /api/plant-simulator/reset - -// بدنه خالی یا session_id -{} -``` - -### ۲.۴ به‌روزرسانی محیط (تغییر اسلایدرها) - -هر بار کاربر نور، آب، pH یا سرعت را عوض کند، در حالت real-time می‌توان این را به بکند فرستاد (اختیاری؛ بکند می‌تواند فقط با start این مقادیر را بگیرد). - -```ts -// PATCH /api/plant-simulator/environment - -interface UpdateEnvironmentRequest { - environment: Record - growth_speed?: number -} - -// مثال -{ - "environment": { "light": 80, "water": 70, "soil_ph": 6.5 }, - "growth_speed": 2 -} -``` - ---- - -## ۳. داده‌هایی که از بکند برمی‌گردند (حین / بعد شبیه‌سازی) - -این داده‌ها یا با **polling** (مثلاً هر ۱ ثانیه) یا با **WebSocket/SSE** از بکند گرفته می‌شوند و در UI و چارت به‌صورت تدریجی به‌روز می‌شوند (مثلاً آرایه‌های چارت کم‌کم طولانی می‌شوند). - -### ۳.۱ وضعیت گیاه (آمار کارت بالا) - -تعداد برگ، شاخه، میوه، ارتفاع، محصول و نرخ محصول باید از بکند بیاید تا با سرعت رشد واقعی همگام باشد. - -```ts -// GET /api/plant-simulator/state یا از طریق WebSocket - -interface PlantStateResponse { - height: number - leaves_count: number - branches_count: number - fruits_count: number - yield: number - yield_rate: number - tick: number - is_healthy: boolean // آیا گیاه می‌تواند به سلامت به رشد ادامه دهد؟ - can_continue: boolean // معادل is_healthy یا منطق جدا (مثلاً رسیدن به حداکثر ارتفاع) -} - -// پاسخ نمونه (یک لحظه از زمان) -{ - "height": 120, - "leaves_count": 4, - "branches_count": 2, - "fruits_count": 0, - "yield": 0, - "yield_rate": 0.012, - "tick": 340, - "is_healthy": true, - "can_continue": true -} -``` - -### ۳.۲ پیشرفت و وضعیت نوارهای پیشرفت - -پیشرفت رشد، وضعیت نور، وضعیت آب و محصول‌دهی برای نوارهای پیشرفت (Progress) و نمایش درصد/عدد. - -```ts -interface ProgressResponse { - growth_progress: number // 0..100 (بر اساس height / max_height) - light_status: number // مقدار فعلی نور (مثلاً 0..100 درصد) - water_status: number // مقدار فعلی آب (مثلاً 0..100 درصد) - yield_progress: number // 0..100 (بر اساس yield / max_yield) - yield_current: number - yield_rate_current: number -} - -// پاسخ نمونه -{ - "growth_progress": 42, - "light_status": 75, - "water_status": 65, - "yield_progress": 8, - "yield_current": 42.5, - "yield_rate_current": 0.093 -} -``` - -این فیلدها می‌توانند داخل همان `PlantStateResponse` یا در یک endpoint جدا برگردانده شوند. - -### ۳.۳ داده‌های چارت (تاریخچهٔ زمانی) - -داده‌های چارت به‌صورت آرایه‌هایی هستند که **بر اساس زمان (مثلاً هر ثانیه) از بکند پر می‌شوند**. فرانت این آرایه‌ها را مستقیم به نمودار می‌دهد؛ هر نقطهٔ جدید با سرعت رشد گیاه اضافه می‌شود. - -مثال: اگر بکند هر ثانیه یک بار state بفرستد، آرایه‌ها به این شکل طول می‌کشند: - -- ثانیه ۰: `[0]` -- ثانیه ۱: `[0, 5]` -- ثانیه ۲: `[0, 5, 10]` -- ثانیه ۳: `[0, 5, 10, 30]` -- ثانیه ۴: `[0, 5, 10, 30, 40]` - -یعنی **مقادیر با سرعت رشد گیاه کم‌کم به آرایه اضافه می‌شوند**. - -```ts -// GET /api/plant-simulator/state یا بخشی از همان پاسخ - -interface ChartHistoryResponse { - labels: string[] // مثلاً ["0s", "1s", "2s", ...] - height_history: number[] - leaf_history: number[] - yield_history: number[] - yield_rate_history: number[] -} - -// پاسخ نمونه (بعد از چند ثانیه شبیه‌سازی) -{ - "labels": ["0s", "1s", "2s", "3s", "4s", "5s"], - "height_history": [0, 5, 12, 28, 45, 68], - "leaf_history": [0, 0, 1, 2, 3, 4], - "yield_history": [0, 0, 0, 0.1, 0.5, 1.2], - "yield_rate_history": [0, 0, 0, 0.01, 0.03, 0.06] -} -``` - -- **ارتفاع (height_history)**: مقدار ارتفاع در هر نمونه (مثلاً هر ثانیه). -- **برگ (leaf_history)**: تعداد برگ در هر نمونه. -- **محصول (yield_history)**: مقدار تجمعی محصول (g) در هر نمونه. -- **نرخ محصول (yield_rate_history)**: نرخ لحظه‌ای (g/s) در هر نمونه. - -محتوای داخل چارت دقیقاً همین آرایه‌هاست؛ فرانت فقط این‌ها را روی محور زمان رسم می‌کند و با هر پاسخ جدید یک (یا چند) نقطه به انتهای آرایه اضافه می‌شود. - ---- - -## ۴. خلاصهٔ جریان داده - -| جهت | زمان | داده | -|-----|------|------| -| بکند → فرانت | لود صفحه | Config: اسلایدرها (min, max, step, unit)، ثابت‌ها (max_height, max_leaves, ...)، تنظیمات چارت | -| فرانت → بکند | کلیک شروع | محیط (light, water, soil_ph, ...) و growth_speed | -| فرانت → بکند | کلیک توقف / ریست | stop یا reset | -| فرانت → بکند | (اختیاری) تغییر اسلایدر | environment + growth_speed | -| بکند → فرانت | حین شبیه‌سازی (polling/WS) | PlantState (height, leaves_count, branches_count, fruits_count, yield, yield_rate, is_healthy, can_continue) | -| بکند → فرانت | همان پاسخ | Progress (growth_progress, light_status, water_status, yield_progress, yield_current, yield_rate_current) | -| بکند → فرانت | همان پاسخ | ChartHistory (labels, height_history, leaf_history, yield_history, yield_rate_history) | - ---- - -## ۵. داده‌های ماک (Mock) کامل - -برای توسعه و تست فرانت بدون بکند می‌توان از این ماک استفاده کرد. - -### ۵.۱ ماک Config (اسلایدرها + ثابت‌ها + چارت) - -```json -{ - "sliders": [ - { - "key": "light", - "label": "نور", - "min": 0, - "max": 100, - "step": 5, - "unit_type": "percent", - "default_value": 75, - "icon": "☀️" - }, - { - "key": "water", - "label": "آب", - "min": 0, - "max": 100, - "step": 5, - "unit_type": "percent", - "default_value": 65, - "icon": "💧" - }, - { - "key": "soil_ph", - "label": "pH خاک", - "min": 4, - "max": 9, - "step": 0.5, - "unit_type": "number", - "unit": "", - "default_value": 6.5 - }, - { - "key": "growth_speed", - "label": "سرعت رشد", - "min": 0.5, - "max": 5, - "step": 0.5, - "unit_type": "number", - "unit": "×", - "default_value": 1.5 - } - ], - "constants": { - "max_height": 280, - "max_leaves": 14, - "max_branches": 6, - "max_yield": 500, - "yield_unit": "g", - "yield_rate_unit": "g/s", - "height_unit": "px" - }, - "chart": { - "title": "پیشرفت رشد", - "x_axis_label": "زمان (ثانیه)", - "series": [ - { "key": "height", "label": "ارتفاع (px)", "y_axis_id": "yHeight", "min": 0, "max": 280, "unit": "px" }, - { "key": "leaves", "label": "تعداد برگ", "y_axis_id": "yLeaf", "min": 0, "max": 14 }, - { "key": "yield", "label": "محصول (g)", "y_axis_id": "yYield", "min": 0, "max": 500, "unit": "g" }, - { "key": "yield_rate", "label": "نرخ محصول (g/s)", "y_axis_id": "yYieldRate", "min": 0, "unit": "g/s" } - ] - } -} -``` - -### ۵.۲ ماک State + Progress + Chart (یک پاسخ ترکیبی) - -```json -{ - "plant": { - "height": 142, - "leaves_count": 5, - "branches_count": 2, - "fruits_count": 0, - "yield": 12.4, - "yield_rate": 0.087, - "tick": 520, - "is_healthy": true, - "can_continue": true - }, - "progress": { - "growth_progress": 50, - "light_status": 75, - "water_status": 65, - "yield_progress": 2.5, - "yield_current": 12.4, - "yield_rate_current": 0.087 - }, - "chart": { - "labels": ["0s", "1s", "2s", "3s", "4s", "5s", "6s", "7s", "8s", "9s", "10s"], - "height_history": [0, 5, 12, 28, 45, 68, 92, 110, 125, 135, 142], - "leaf_history": [0, 0, 1, 2, 3, 4, 4, 5, 5, 5, 5], - "yield_history": [0, 0, 0, 0.1, 0.5, 1.2, 3.2, 5.8, 8.2, 10.1, 12.4], - "yield_rate_history": [0, 0, 0, 0.01, 0.03, 0.05, 0.06, 0.07, 0.08, 0.085, 0.087] - } -} -``` - -### ۵.۳ ماک برای نمایش تدریجی چارت - -چارت باید بر اساس همین آرایه‌ها به‌صورت تدریجی پر شود؛ هر بار بکند state جدید می‌فرستد، یک نقطه به انتهای هر آرایه اضافه می‌شود. مثال برای چند لحظهٔ متوالی: - -```json -// t=0 -{ "labels": ["0s"], "height_history": [0], "leaf_history": [0], "yield_history": [0], "yield_rate_history": [0] } - -// t=1s -{ "labels": ["0s", "1s"], "height_history": [0, 5], "leaf_history": [0, 0], "yield_history": [0, 0], "yield_rate_history": [0, 0] } - -// t=2s -{ "labels": ["0s", "1s", "2s"], "height_history": [0, 5, 12], "leaf_history": [0, 0, 1], "yield_history": [0, 0, 0], "yield_rate_history": [0, 0, 0] } - -// t=3s -{ "labels": ["0s", "1s", "2s", "3s"], "height_history": [0, 5, 12, 30], "leaf_history": [0, 0, 1, 2], "yield_history": [0, 0, 0, 0.1], "yield_rate_history": [0, 0, 0, 0.01] } - -// t=4s -{ "labels": ["0s", "1s", "2s", "3s", "4s"], "height_history": [0, 5, 12, 30, 48], "leaf_history": [0, 0, 1, 2, 3], "yield_history": [0, 0, 0, 0.1, 0.5], "yield_rate_history": [0, 0, 0, 0.01, 0.03] } -``` - -ارتفاع و بقیهٔ مقادیر باید متناسب با سرعت رشد و منطق بکند افزایش یابند؛ اعداد بالا فقط نمونهٔ ماک هستند. - ---- - -## ۶. نکات پیاده‌سازی فرانت - -- **اسلایدرها**: با استفاده از `sliders` از config، هر کنترل با `min`, `max`, `step`, `unit_type`, `unit` و `default_value` رندر شود. -- **آمار کارت (ارتفاع، برگ، شاخه، میوه، محصول، نرخ)**: مستقیم از `plant` در پاسخ state. -- **سلامت گیاه**: با `is_healthy` و `can_continue` می‌توان پیام یا استایل متفاوت نشان داد (مثلاً هشدار وقتی `can_continue === false`). -- **نوارهای پیشرفت**: از `progress` برای پیشرفت رشد، وضعیت نور، وضعیت آب و محصول‌دهی. -- **چارت**: فقط `chart.labels` و آرایه‌های `height_history`, `leaf_history`, `yield_history`, `yield_rate_history` را به کامپوننت نمودار بدهید؛ با هر پاسخ جدید آرایه‌ها طولانی می‌شوند و نمودار به‌صورت تدریجی به‌روز می‌شود. - -اگر endpointها یا فیلدهای اضافه‌ای در بکند دارید، می‌توان آنها را به همین سند اضافه کرد تا فرانت و بکند همیشه هم‌خوان باشند. diff --git a/RECOMMENDATION_APIS.md b/RECOMMENDATION_APIS.md deleted file mode 100644 index d6f3d3a..0000000 --- a/RECOMMENDATION_APIS.md +++ /dev/null @@ -1,223 +0,0 @@ -# مستندات APIهای توصیه و تشخیص - -این سند سه گروه API را شرح می‌دهد: **توصیه آبیاری**، **تشخیص آفت** و **توصیه کوددهی**. همهٔ پاسخ‌ها در حال حاضر از دادهٔ ثابت (mock) برگردانده می‌شوند و پارامترهای ورودی در پاسخ استفاده نمی‌شوند. - -**پایهٔ آدرس API:** `/api/` - -**قالب کلی پاسخ:** -`{"status": "success", "data": }` — فقط با کد وضعیت HTTP 200. - ---- - -## ۱. توصیه آبیاری (Irrigation Recommendation) - -**پیشوند:** `api/irrigation-recommendation/` - -### ۱.۱ دریافت تنظیمات (Config) - -- **متد:** `GET` -- **آدرس:** `api/irrigation-recommendation/config/` -- **هدف:** برگرداندن اطلاعات مزرعه و لیست گزینه‌های محصول برای فرم توصیه آبیاری (هنگام بارگذاری صفحه). -- **ورودی:** ندارد. پارامترهای query خوانده یا استفاده نمی‌شوند. - -**نمونه پاسخ:** - -```json -{ - "status": "success", - "data": { - "farmInfo": { - "soilType": "Loamy", - "waterQuality": "Medium EC", - "climateZone": "Temperate" - }, - "cropOptions": [ - {"id": "wheat", "labelKey": "wheat", "icon": "tabler-wheat"}, - {"id": "corn", "labelKey": "corn", "icon": "tabler-plant-2"}, - {"id": "cotton", "labelKey": "cotton", "icon": "tabler-flower"}, - {"id": "saffron", "labelKey": "saffron", "icon": "tabler-flower-2"}, - {"id": "canola", "labelKey": "canola", "icon": "tabler-leaf"}, - {"id": "vegetables", "labelKey": "vegetables", "icon": "tabler-carrot"} - ] - } -} -``` - -| فیلد | نوع | توضیح | -|------|-----|--------| -| `farmInfo.soilType` | string | نوع خاک | -| `farmInfo.waterQuality` | string | کیفیت آب (مثلاً EC) | -| `farmInfo.climateZone` | string | منطقه اقلیمی | -| `cropOptions` | array | لیست محصولات: `id`, `labelKey`, `icon` | - ---- - -### ۱.۲ دریافت توصیه آبیاری (Recommend) - -- **متد:** `POST` -- **آدرس:** `api/irrigation-recommendation/recommend/` -- **هدف:** برگرداندن یک برنامهٔ آبیاری ثابت (تعداد در هفته، مدت، بهترین زمان، رطوبت، هشدار). -- **ورودی (بدن درخواست، اختیاری):** می‌توانید JSON با فیلدهایی مثل `crop_id`, `soilType`, `waterQuality`, `climateZone` بفرستید؛ در پاسخ فعلی استفاده نمی‌شوند. -- **CSRF:** این endpoint از CSRF معاف است (برای فراخوانی از فرانت بدون توکن). - -**نمونه پاسخ:** - -```json -{ - "status": "success", - "data": { - "plan": { - "frequencyPerWeek": 4, - "durationMinutes": 45, - "bestTimeOfDay": "05:00 - 07:00", - "moistureLevel": 72, - "warning": "Avoid irrigation during midday hours in the coming week due to forecasted high temperatures." - } - } -} -``` - -| فیلد | نوع | توضیح | -|------|-----|--------| -| `plan.frequencyPerWeek` | number | تعداد آبیاری در هفته | -| `plan.durationMinutes` | number | مدت هر نوبت (دقیقه) | -| `plan.bestTimeOfDay` | string | بهترین بازه زمانی روز | -| `plan.moistureLevel` | number | سطح رطوبت هدف (درصد) | -| `plan.warning` | string | هشدار یا توصیه اضافه | - ---- - -## ۲. تشخیص آفت (Pest Detection) - -**پیشوند:** `api/pest-detection/` - -### ۲.۱ تحلیل تصویر (Analyze) - -- **متد:** `POST` -- **آدرس:** `api/pest-detection/analyze/` -- **هدف:** برگرداندن نتیجهٔ ثابت تشخیص آفت (نام آفت، اطمینان، توضیح، درمان) — برای زمانی که کاربر تصویر گیاه را آپلود و درخواست تحلیل می‌کند. -- **ورودی (بدن درخواست، اختیاری):** JSON یا form-data (مثلاً شامل `image` یا `file`). در پاسخ فعلی استفاده نمی‌شود. -- **CSRF:** این endpoint از CSRF معاف است. - -**نمونه پاسخ:** - -```json -{ - "status": "success", - "data": { - "pest": "شپشک", - "confidence": 92, - "description": "حشرات کوچک مکنده شیره که باعث پیچ خوردگی برگ می‌شوند.", - "treatment": "یک بار در هفته از اسپری روغن نیم استفاده کنید." - } -} -``` - -| فیلد | نوع | توضیح | -|------|-----|--------| -| `pest` | string | نام آفت | -| `confidence` | number | درصد اطمینان (۰–۱۰۰) | -| `description` | string | توضیح کوتاه آفت | -| `treatment` | string | توصیه درمان | - ---- - -## ۳. توصیه کوددهی (Fertilization Recommendation) - -**پیشوند:** `api/fertilization-recommendation/` - -### ۳.۱ دریافت تنظیمات (Config) - -- **متد:** `GET` -- **آدرس:** `api/fertilization-recommendation/config/` -- **هدف:** برگرداندن دادهٔ مزرعه، مراحل رشد و گزینه‌های محصول برای فرم توصیه کوددهی (هنگام بارگذاری صفحه). -- **ورودی:** ندارد. - -**نمونه پاسخ:** - -```json -{ - "status": "success", - "data": { - "farmData": { - "soilType": "Loamy", - "organicMatter": "Medium (2.5%)", - "waterEC": "1.2 dS/m" - }, - "growthStages": [ - {"id": "prePlanting", "icon": "tabler-seedling"}, - {"id": "earlyGrowth", "icon": "tabler-leaf"}, - {"id": "flowering", "icon": "tabler-flower"}, - {"id": "fruiting", "icon": "tabler-apple"}, - {"id": "postHarvest", "icon": "tabler-basket"} - ], - "cropOptions": [ - {"id": "wheat", "labelKey": "wheat", "icon": "tabler-wheat"}, - {"id": "corn", "labelKey": "corn", "icon": "tabler-plant-2"}, - {"id": "cotton", "labelKey": "cotton", "icon": "tabler-flower"}, - {"id": "saffron", "labelKey": "saffron", "icon": "tabler-flower-2"}, - {"id": "canola", "labelKey": "canola", "icon": "tabler-leaf"}, - {"id": "vegetables", "labelKey": "vegetables", "icon": "tabler-carrot"} - ] - } -} -``` - -| فیلد | نوع | توضیح | -|------|-----|--------| -| `farmData.soilType` | string | نوع خاک | -| `farmData.organicMatter` | string | ماده آلی خاک | -| `farmData.waterEC` | string | EC آب | -| `growthStages` | array | مراحل رشد: `id`, `icon` | -| `cropOptions` | array | لیست محصولات: `id`, `labelKey`, `icon` | - ---- - -### ۳.۲ دریافت توصیه کوددهی (Recommend) - -- **متد:** `POST` -- **آدرس:** `api/fertilization-recommendation/recommend/` -- **هدف:** برگرداندن یک برنامهٔ کوددهی ثابت (نسبت NPK، مقدار در هکتار، روش و فاصله مصرف، استدلال). -- **ورودی (بدن درخواست، اختیاری):** JSON با فیلدهایی مثل `crop_id`, `growth_stage`, `soilType`, `organicMatter`, `waterEC`. در پاسخ فعلی استفاده نمی‌شوند. -- **CSRF:** این endpoint از CSRF معاف است. - -**نمونه پاسخ:** - -```json -{ - "status": "success", - "data": { - "plan": { - "npkRatio": "20-20-20 (NPK)", - "amountPerHectare": "150 kg/ha", - "applicationMethod": "Foliar spray + soil broadcast", - "applicationInterval": "Every 14 days", - "reasoning": "Your loamy soil with medium organic matter (2.5%) provides good nutrient retention. Water EC of 1.2 dS/m indicates low salinity—suitable for most crops. At the flowering stage, increased phosphorus supports bloom development. We recommend a balanced NPK to maintain nitrogen for vegetative growth while boosting phosphorous for flowering." - } - } -} -``` - -| فیلد | نوع | توضیح | -|------|-----|--------| -| `plan.npkRatio` | string | نسبت NPK پیشنهادی | -| `plan.amountPerHectare` | string | مقدار مصرف در هکتار | -| `plan.applicationMethod` | string | روش مصرف (مثلاً محلول‌پاشی، خاکی) | -| `plan.applicationInterval` | string | فاصله بین مصرف | -| `plan.reasoning` | string | توضیح/استدلال توصیه | - ---- - -## خلاصه Endpointها - -| ماژول | متد | Endpoint | -|--------|------|----------| -| Irrigation | GET | `/api/irrigation-recommendation/config/` | -| Irrigation | POST | `/api/irrigation-recommendation/recommend/` | -| Pest Detection | POST | `/api/pest-detection/analyze/` | -| Fertilization | GET | `/api/fertilization-recommendation/config/` | -| Fertilization | POST | `/api/fertilization-recommendation/recommend/` | - ---- - -**توجه:** در نسخهٔ فعلی هیچ پردازش، اعتبارسنجی یا استفاده از پارامترهای ورودی در پاسخ انجام نمی‌شود؛ همهٔ خروجی‌ها از دادهٔ ثابت (mock) هستند. diff --git a/config/settings.py b/config/settings.py index 621940e..5ff6d9a 100644 --- a/config/settings.py +++ b/config/settings.py @@ -159,3 +159,5 @@ SIMPLE_JWT = { "ROTATE_REFRESH_TOKENS": False, "BLACKLIST_AFTER_ROTATION": False, } + +CROP_ZONE_CHUNK_AREA_SQM = float(os.getenv("CROP_ZONE_CHUNK_AREA_SQM", "10000")) diff --git a/crop_zoning/migrations/0001_initial.py b/crop_zoning/migrations/0001_initial.py new file mode 100644 index 0000000..01019d6 --- /dev/null +++ b/crop_zoning/migrations/0001_initial.py @@ -0,0 +1,54 @@ +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name='CropArea', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, unique=True)), + ('geometry', models.JSONField(default=dict)), + ('points', models.JSONField(default=list)), + ('center', models.JSONField(default=dict)), + ('area_sqm', models.FloatField()), + ('area_hectares', models.FloatField()), + ('chunk_area_sqm', models.FloatField()), + ('zone_count', models.PositiveIntegerField(default=0)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'db_table': 'crop_areas', + 'ordering': ['-created_at', '-id'], + }, + ), + migrations.CreateModel( + name='CropZone', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, unique=True)), + ('zone_id', models.CharField(max_length=64)), + ('geometry', models.JSONField(default=dict)), + ('points', models.JSONField(default=list)), + ('center', models.JSONField(default=dict)), + ('area_sqm', models.FloatField()), + ('area_hectares', models.FloatField()), + ('sequence', models.PositiveIntegerField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('crop_area', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='zones', to='crop_zoning.croparea')), + ], + options={ + 'db_table': 'crop_zones', + 'ordering': ['sequence', 'id'], + 'constraints': [models.UniqueConstraint(fields=('crop_area', 'zone_id'), name='unique_crop_area_zone_id')], + }, + ), + ] diff --git a/crop_zoning/migrations/__init__.py b/crop_zoning/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/crop_zoning/models.py b/crop_zoning/models.py new file mode 100644 index 0000000..107d7b5 --- /dev/null +++ b/crop_zoning/models.py @@ -0,0 +1,51 @@ +import uuid + +from django.db import models + + +class CropArea(models.Model): + uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True) + geometry = models.JSONField(default=dict) + points = models.JSONField(default=list) + center = models.JSONField(default=dict) + area_sqm = models.FloatField() + area_hectares = models.FloatField() + chunk_area_sqm = models.FloatField() + zone_count = models.PositiveIntegerField(default=0) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + db_table = "crop_areas" + ordering = ["-created_at", "-id"] + + def __str__(self): + return f"Area {self.uuid}" + + +class CropZone(models.Model): + uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True) + crop_area = models.ForeignKey( + CropArea, + on_delete=models.CASCADE, + related_name="zones", + ) + zone_id = models.CharField(max_length=64) + geometry = models.JSONField(default=dict) + points = models.JSONField(default=list) + center = models.JSONField(default=dict) + area_sqm = models.FloatField() + area_hectares = models.FloatField() + sequence = models.PositiveIntegerField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + db_table = "crop_zones" + ordering = ["sequence", "id"] + constraints = [ + models.UniqueConstraint(fields=["crop_area", "zone_id"], name="unique_crop_area_zone_id"), + ] + + def __str__(self): + return self.zone_id diff --git a/crop_zoning/services.py b/crop_zoning/services.py new file mode 100644 index 0000000..9655f1d --- /dev/null +++ b/crop_zoning/services.py @@ -0,0 +1,202 @@ +import math +from copy import deepcopy + +from django.conf import settings +from django.db import transaction + +from .models import CropArea, CropZone + +EARTH_RADIUS_METERS = 6378137.0 + + +def get_chunk_area_sqm(): + raw_value = getattr(settings, "CROP_ZONE_CHUNK_AREA_SQM", 0) + try: + chunk_area = float(raw_value) + except (TypeError, ValueError): + chunk_area = 0 + if chunk_area <= 0: + raise ValueError("CROP_ZONE_CHUNK_AREA_SQM must be a positive number.") + return chunk_area + + +def get_polygon_ring(area_feature): + geometry = (area_feature or {}).get("geometry", {}) + coordinates = geometry.get("coordinates", []) + if not coordinates or not coordinates[0]: + raise ValueError("Area polygon coordinates are required.") + return coordinates[0] + + +def polygon_area_sqm(ring): + if len(ring) < 4: + return 0.0 + + latitudes = [point[1] for point in ring] + mean_latitude = math.radians(sum(latitudes) / len(latitudes)) + + projected_points = [] + for longitude, latitude in ring: + x = math.radians(longitude) * EARTH_RADIUS_METERS * math.cos(mean_latitude) + y = math.radians(latitude) * EARTH_RADIUS_METERS + projected_points.append((x, y)) + + area = 0.0 + for index in range(len(projected_points) - 1): + x1, y1 = projected_points[index] + x2, y2 = projected_points[index + 1] + area += (x1 * y2) - (x2 * y1) + + return abs(area) / 2.0 + + +def normalize_points(ring): + if len(ring) > 1 and ring[0] == ring[-1]: + ring = ring[:-1] + return [[point[0], point[1]] for point in ring] + + +def calculate_center(points): + if not points: + return {"longitude": 0.0, "latitude": 0.0} + + longitude = sum(point[0] for point in points) / len(points) + latitude = sum(point[1] for point in points) / len(points) + return { + "longitude": round(longitude, 8), + "latitude": round(latitude, 8), + } + + +def build_zone_square(area_points, center, zone_area_sqm): + if len(area_points) < 4: + return area_points + + width = math.sqrt(max(zone_area_sqm, 1)) + half_width = width / 2.0 + + latitude_factor = 111320.0 + longitude_factor = 111320.0 * math.cos(math.radians(center["latitude"])) + if longitude_factor == 0: + longitude_factor = 1.0 + + delta_lat = half_width / latitude_factor + delta_lng = half_width / longitude_factor + + return [ + [round(center["longitude"] - delta_lng, 8), round(center["latitude"] - delta_lat, 8)], + [round(center["longitude"] + delta_lng, 8), round(center["latitude"] - delta_lat, 8)], + [round(center["longitude"] + delta_lng, 8), round(center["latitude"] + delta_lat, 8)], + [round(center["longitude"] - delta_lng, 8), round(center["latitude"] + delta_lat, 8)], + ] + + +def split_area_into_zones(area_feature): + area_ring = get_polygon_ring(area_feature) + area_points = normalize_points(area_ring) + area_center = calculate_center(area_points) + total_area_sqm = polygon_area_sqm(area_ring) + chunk_area_sqm = get_chunk_area_sqm() + zone_count = max(1, math.ceil(total_area_sqm / chunk_area_sqm)) + + zones = [] + remaining_area = total_area_sqm + base_longitude = area_center["longitude"] + base_latitude = area_center["latitude"] + + for sequence in range(zone_count): + zone_area_sqm = min(chunk_area_sqm, remaining_area) if sequence < zone_count - 1 else remaining_area + if zone_area_sqm <= 0: + zone_area_sqm = min(chunk_area_sqm, total_area_sqm) + + shift = (sequence - ((zone_count - 1) / 2)) * 0.0003 + zone_center = { + "longitude": round(base_longitude + shift, 8), + "latitude": round(base_latitude, 8), + } + zone_points = build_zone_square(area_points, zone_center, zone_area_sqm) + zone_geometry = { + "type": "Feature", + "properties": { + "zone_id": f"zone-{sequence}", + "sequence": sequence, + "area_sqm": round(zone_area_sqm, 2), + "area_hectares": round(zone_area_sqm / 10000, 4), + "center": zone_center, + }, + "geometry": { + "type": "Polygon", + "coordinates": [[*zone_points, zone_points[0]]], + }, + } + zones.append( + { + "zone_id": f"zone-{sequence}", + "geometry": zone_geometry, + "points": zone_points, + "center": zone_center, + "area_sqm": zone_area_sqm, + "area_hectares": zone_area_sqm / 10000, + "sequence": sequence, + } + ) + remaining_area = max(0.0, remaining_area - zone_area_sqm) + + area_geometry = deepcopy(area_feature) + area_geometry.setdefault("properties", {}) + area_geometry["properties"].update( + { + "center": area_center, + "area_sqm": round(total_area_sqm, 2), + "area_hectares": round(total_area_sqm / 10000, 4), + } + ) + + return { + "area": { + "geometry": area_geometry, + "points": area_points, + "center": area_center, + "area_sqm": total_area_sqm, + "area_hectares": total_area_sqm / 10000, + "chunk_area_sqm": chunk_area_sqm, + "zone_count": zone_count, + }, + "zones": zones, + } + + +def persist_zones(area_feature): + zoning_result = split_area_into_zones(area_feature) + area_data = zoning_result["area"] + + with transaction.atomic(): + crop_area = CropArea.objects.create( + geometry=area_data["geometry"], + points=area_data["points"], + center=area_data["center"], + area_sqm=round(area_data["area_sqm"], 2), + area_hectares=round(area_data["area_hectares"], 4), + chunk_area_sqm=round(area_data["chunk_area_sqm"], 2), + zone_count=area_data["zone_count"], + ) + + CropZone.objects.bulk_create( + [ + CropZone( + crop_area=crop_area, + zone_id=zone["zone_id"], + geometry=zone["geometry"], + points=zone["points"], + center=zone["center"], + area_sqm=round(zone["area_sqm"], 2), + area_hectares=round(zone["area_hectares"], 4), + sequence=zone["sequence"], + ) + for zone in zoning_result["zones"] + ] + ) + + zoning_result["area"]["id"] = crop_area.id + zoning_result["area"]["uuid"] = str(crop_area.uuid) + return zoning_result diff --git a/crop_zoning/views.py b/crop_zoning/views.py index 32910a2..5d569b8 100644 --- a/crop_zoning/views.py +++ b/crop_zoning/views.py @@ -5,6 +5,7 @@ Response format: {"status": "success", "data": }. HTTP 200 only. No processing, validation, or use of input parameters in responses. """ +from django.core.exceptions import ImproperlyConfigured from rest_framework import serializers, status from rest_framework.response import Response from rest_framework.views import APIView @@ -21,6 +22,7 @@ from .mock_data import ( ZONES_SOIL_QUALITY_RESPONSE_DATA, ZONES_WATER_NEED_RESPONSE_DATA, ) +from .services import persist_zones class AreaView(APIView): @@ -88,23 +90,17 @@ class ZonesInitialView(APIView): 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). + Accepts the main area polygon and creates zones based on configured + area chunk size. Stores generated zones in database. 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. + - area: GeoJSON Feature. Location: body. Main land polygon. + If omitted, the static area from mock data is used. Response structure: - - status: string, always "success". + - status: string. - 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. + chunk_area_sqm and zones. """ @extend_schema( @@ -113,12 +109,54 @@ class ZonesInitialView(APIView): responses={200: status_response("CropZoningZonesInitialResponse", data=serializers.JSONField())}, ) def post(self, request): + area_feature = request.data.get("area") or AREA_RESPONSE_DATA.get("area") + + try: + zoning_result = persist_zones(area_feature) + except ValueError as exc: + return Response( + {"status": "error", "message": str(exc)}, + status=status.HTTP_400_BAD_REQUEST, + ) + except ImproperlyConfigured as exc: + return Response( + {"status": "error", "message": str(exc)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + area_data = zoning_result["area"] + response_data = { + "area": { + "id": area_data["id"], + "uuid": area_data["uuid"], + "geometry": area_data["geometry"], + "points": area_data["points"], + "center": area_data["center"], + "area_sqm": round(area_data["area_sqm"], 2), + "area_hectares": round(area_data["area_hectares"], 4), + "chunk_area_sqm": round(area_data["chunk_area_sqm"], 2), + "zone_count": area_data["zone_count"], + }, + "zones": [ + { + "zoneId": zone["zone_id"], + "geometry": zone["geometry"], + "points": zone["points"], + "center": zone["center"], + "area_sqm": round(zone["area_sqm"], 2), + "area_hectares": round(zone["area_hectares"], 4), + } + for zone in zoning_result["zones"] + ], + } + return Response( - {"status": "success", "data": ZONES_INITIAL_RESPONSE_DATA}, + {"status": "success", "data": response_data}, status=status.HTTP_200_OK, ) + class ZonesWaterNeedView(APIView): """ POST endpoint for water need per zone (water need layer). diff --git a/external_api_adapter/json/ai/tasks/status/get_200_failure.json b/external_api_adapter/json/ai/tasks/status/get_200_failure.json index 2c34884..c524d68 100644 --- a/external_api_adapter/json/ai/tasks/status/get_200_failure.json +++ b/external_api_adapter/json/ai/tasks/status/get_200_failure.json @@ -2,7 +2,7 @@ "code": 200, "msg": "success", "data": { - "task_id": "sample-task-123", + "task_id": "farm-ai-chat-task-123", "status": "FAILURE", "error": "Sample task failed." } diff --git a/external_api_adapter/json/ai/tasks/status/get_200_pending.json b/external_api_adapter/json/ai/tasks/status/get_200_pending.json index c6ce7cb..5470c7d 100644 --- a/external_api_adapter/json/ai/tasks/status/get_200_pending.json +++ b/external_api_adapter/json/ai/tasks/status/get_200_pending.json @@ -2,7 +2,7 @@ "code": 200, "msg": "success", "data": { - "task_id": "sample-task-123", + "task_id": "farm-ai-chat-task-123", "status": "PENDING", "message": "تسک در صف یا یافت نشد." } diff --git a/external_api_adapter/json/ai/tasks/status/get_200_progress.json b/external_api_adapter/json/ai/tasks/status/get_200_progress.json index abb1b76..0a3107e 100644 --- a/external_api_adapter/json/ai/tasks/status/get_200_progress.json +++ b/external_api_adapter/json/ai/tasks/status/get_200_progress.json @@ -2,7 +2,7 @@ "code": 200, "msg": "success", "data": { - "task_id": "sample-task-123", + "task_id": "farm-ai-chat-task-123", "status": "PROGRESS", "progress": { "current": 1, diff --git a/external_api_adapter/json/ai/tasks/status/get_200_success.json b/external_api_adapter/json/ai/tasks/status/get_200_success.json index 5b55965..c5b78bd 100644 --- a/external_api_adapter/json/ai/tasks/status/get_200_success.json +++ b/external_api_adapter/json/ai/tasks/status/get_200_success.json @@ -2,8 +2,10 @@ "code": 200, "msg": "success", "data": { - "task_id": "sample-task-123", + "task_id": "farm-ai-chat-task-123", "status": "SUCCESS", - "result": "done" + "result": { + "$ref": "rag/chat-post_200_stream.json" + } } } diff --git a/external_api_adapter/mock_loader.py b/external_api_adapter/mock_loader.py index e54e1b1..17a10c7 100644 --- a/external_api_adapter/mock_loader.py +++ b/external_api_adapter/mock_loader.py @@ -25,8 +25,13 @@ class MockLoader: selected_file = sorted(mock_files, key=self._mock_file_priority)[0] with selected_file.open("r", encoding="utf-8") as file: + service_root = self.base_path / service_name return LoadedMockResponse( - data=json.load(file), + data=self._resolve_references( + json.load(file), + current_file=selected_file, + service_root=service_root, + ), status_code=self._extract_status_code(selected_file), file_path=str(selected_file), ) @@ -99,3 +104,60 @@ class MockLoader: elif "pending" in stem or "progress" in stem: keyword_rank = 1 return (status_code >= 400, keyword_rank, stem) + + def _resolve_references(self, value, current_file, service_root, seen=None): + current_file = current_file.resolve() + service_root = service_root.resolve() + seen = seen or {current_file} + + if isinstance(value, list): + return [ + self._resolve_references(item, current_file=current_file, service_root=service_root, seen=seen) + for item in value + ] + + if not isinstance(value, dict): + return value + + ref_path = value.get("$ref") + if isinstance(ref_path, str) and len(value) == 1: + referenced_file = self._resolve_reference_path( + ref_path=ref_path, + current_file=current_file, + service_root=service_root, + ) + + if referenced_file in seen: + raise MockFileNotFound(f"Circular mock reference detected for '{referenced_file}'.") + + with referenced_file.open("r", encoding="utf-8") as file: + return self._resolve_references( + json.load(file), + current_file=referenced_file, + service_root=service_root, + seen=seen | {referenced_file}, + ) + + return { + key: self._resolve_references(item, current_file=current_file, service_root=service_root, seen=seen) + for key, item in value.items() + } + + @staticmethod + def _resolve_reference_path(ref_path, current_file, service_root): + candidates = [ + current_file.parent / ref_path, + service_root / ref_path, + ] + + service_root_resolved = service_root.resolve() + for candidate in candidates: + candidate_resolved = candidate.resolve() + try: + candidate_resolved.relative_to(service_root_resolved) + except ValueError: + continue + if candidate_resolved.is_file(): + return candidate_resolved + + raise MockFileNotFound(f"Referenced mock file '{ref_path}' was not found.") diff --git a/farm_ai_assistant/tests.py b/farm_ai_assistant/tests.py new file mode 100644 index 0000000..b2a45df --- /dev/null +++ b/farm_ai_assistant/tests.py @@ -0,0 +1,58 @@ +from django.contrib.auth import get_user_model +from django.test import TestCase, override_settings +from rest_framework.test import APIRequestFactory, force_authenticate + +from .models import Conversation, Message +from .views import ChatTaskStatusView + + +@override_settings(USE_EXTERNAL_API_MOCK=True) +class ChatTaskStatusViewTests(TestCase): + def setUp(self): + self.factory = APIRequestFactory() + self.user = get_user_model().objects.create_user( + username="farmer", + password="secret123", + email="farmer@example.com", + phone_number="09120000000", + ) + self.conversation = Conversation.objects.create( + owner=self.user, + title="Irrigation chat", + farm_context={}, + ) + self.user_message = Message.objects.create( + conversation=self.conversation, + role=Message.ROLE_USER, + content="What is the best irrigation plan?", + raw_response={ + "task_id": "farm-ai-chat-task-123", + "status": "PENDING", + "status_url": "/api/tasks/farm-ai-chat-task-123/status/", + }, + ) + + def test_status_success_uses_chat_mock_result_and_persists_assistant_message(self): + request = self.factory.get("/api/farm-ai-assistant/chat/task/farm-ai-chat-task-123/status/") + force_authenticate(request, user=self.user) + + response = ChatTaskStatusView.as_view()(request, task_id="farm-ai-chat-task-123") + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["status"], "success") + self.assertEqual(response.data["data"]["task_id"], "farm-ai-chat-task-123") + self.assertEqual(response.data["data"]["status"], "SUCCESS") + self.assertEqual(response.data["data"]["conversation_id"], str(self.conversation.uuid)) + self.assertEqual(response.data["data"]["result"]["content"], "Here is the recommended plan.") + self.assertEqual(len(response.data["data"]["result"]["sections"]), 3) + self.assertEqual(response.data["data"]["result"]["task_id"], "farm-ai-chat-task-123") + + assistant_message = ( + self.conversation.messages.filter(role=Message.ROLE_ASSISTANT) + .order_by("-created_at") + .first() + ) + self.assertIsNotNone(assistant_message) + self.assertEqual(assistant_message.content, "Here is the recommended plan.") + self.assertEqual(assistant_message.raw_response["task_id"], "farm-ai-chat-task-123") + self.assertEqual(len(assistant_message.raw_response["sections"]), 3) diff --git a/farm_ai_assistant/urls.py b/farm_ai_assistant/urls.py index 1b1af3f..06610de 100644 --- a/farm_ai_assistant/urls.py +++ b/farm_ai_assistant/urls.py @@ -12,7 +12,7 @@ from .views import ( urlpatterns = [ path("context/", ContextView.as_view(), name="farm-ai-assistant-context"), - path("chat/", ChatView.as_view(), name="farm-ai-assistant-chat"), + # path("chat/", ChatView.as_view(), name="farm-ai-assistant-chat"), path("chat/task/", ChatTaskCreateView.as_view(), name="farm-ai-assistant-chat-task-create"), path("chat/task//status/", ChatTaskStatusView.as_view(), name="farm-ai-assistant-chat-task-status"), path("chats/", ChatListCreateView.as_view(), name="farm-ai-assistant-chat-list-create"), diff --git a/farm_ai_assistant/views.py b/farm_ai_assistant/views.py index e986af6..f7f0f39 100644 --- a/farm_ai_assistant/views.py +++ b/farm_ai_assistant/views.py @@ -201,6 +201,29 @@ class ConversationAccessMixin: return task_status_payload + def _extract_structured_task_result(self, adapter_data): + payload_source = adapter_data + if isinstance(adapter_data, dict) and isinstance(adapter_data.get("data"), dict): + payload_source = adapter_data["data"] + + if not isinstance(payload_source, dict): + return None + + result = payload_source.get("result") + if isinstance(result, dict): + return result + + if payload_source.get("status") == "SUCCESS": + content = payload_source.get("content") + sections = payload_source.get("sections") + if content or sections: + return { + "content": content or "", + "sections": sections or [], + } + + return None + @staticmethod def _serialize_chat_message(message): raw_response = message.raw_response if isinstance(message.raw_response, dict) else {} @@ -541,7 +564,10 @@ class ChatTaskStatusView(ConversationAccessMixin, APIView): conversation_id=conversation_id, ) - result = task_status_payload.get("result") + result = self._extract_structured_task_result(adapter_response.data) + if result is not None: + task_status_payload["result"] = result + if user_message and task_status_payload.get("status") == "SUCCESS" and isinstance(result, dict): assistant_payload = self._persist_task_result(user_message, task_id, result) task_status_payload["result"] = assistant_payload diff --git a/json/mock_data/tasks/status/get_200_failure.json b/json/mock_data/tasks/status/get_200_failure.json index 2c34884..c524d68 100644 --- a/json/mock_data/tasks/status/get_200_failure.json +++ b/json/mock_data/tasks/status/get_200_failure.json @@ -2,7 +2,7 @@ "code": 200, "msg": "success", "data": { - "task_id": "sample-task-123", + "task_id": "farm-ai-chat-task-123", "status": "FAILURE", "error": "Sample task failed." } diff --git a/json/mock_data/tasks/status/get_200_pending.json b/json/mock_data/tasks/status/get_200_pending.json index c6ce7cb..5470c7d 100644 --- a/json/mock_data/tasks/status/get_200_pending.json +++ b/json/mock_data/tasks/status/get_200_pending.json @@ -2,7 +2,7 @@ "code": 200, "msg": "success", "data": { - "task_id": "sample-task-123", + "task_id": "farm-ai-chat-task-123", "status": "PENDING", "message": "تسک در صف یا یافت نشد." } diff --git a/json/mock_data/tasks/status/get_200_progress.json b/json/mock_data/tasks/status/get_200_progress.json index abb1b76..0a3107e 100644 --- a/json/mock_data/tasks/status/get_200_progress.json +++ b/json/mock_data/tasks/status/get_200_progress.json @@ -2,7 +2,7 @@ "code": 200, "msg": "success", "data": { - "task_id": "sample-task-123", + "task_id": "farm-ai-chat-task-123", "status": "PROGRESS", "progress": { "current": 1, diff --git a/json/mock_data/tasks/status/get_200_success.json b/json/mock_data/tasks/status/get_200_success.json index 5b55965..c5b78bd 100644 --- a/json/mock_data/tasks/status/get_200_success.json +++ b/json/mock_data/tasks/status/get_200_success.json @@ -2,8 +2,10 @@ "code": 200, "msg": "success", "data": { - "task_id": "sample-task-123", + "task_id": "farm-ai-chat-task-123", "status": "SUCCESS", - "result": "done" + "result": { + "$ref": "rag/chat-post_200_stream.json" + } } }