diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md new file mode 100644 index 0000000..e47319e --- /dev/null +++ b/API_DOCUMENTATION.md @@ -0,0 +1,1836 @@ +# مستندات 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 new file mode 100644 index 0000000..42e8c4d --- /dev/null +++ b/API_RESPONSE_SPEC.md @@ -0,0 +1,152 @@ +# مشخصات 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/FARM_AI_ASSISTANT_API.md b/FARM_AI_ASSISTANT_API.md new file mode 100644 index 0000000..902ecb0 --- /dev/null +++ b/FARM_AI_ASSISTANT_API.md @@ -0,0 +1,190 @@ +# مستندات 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 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/PLANT_SIMULATOR_API.md b/PLANT_SIMULATOR_API.md new file mode 100644 index 0000000..3b2e79a --- /dev/null +++ b/PLANT_SIMULATOR_API.md @@ -0,0 +1,465 @@ +# قرارداد 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 new file mode 100644 index 0000000..d6f3d3a --- /dev/null +++ b/RECOMMENDATION_APIS.md @@ -0,0 +1,223 @@ +# مستندات 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 18e1ec5..f27d74f 100644 --- a/config/settings.py +++ b/config/settings.py @@ -22,6 +22,12 @@ INSTALLED_APPS = [ "account", "sensor_hub", "dashboard", + "crop_zoning", + "plant_simulator", + "pest_detection", + "irrigation_recommendation", + "fertilization_recommendation", + "farm_ai_assistant", "rest_framework", "corsheaders", ] diff --git a/config/urls.py b/config/urls.py index 60db6b8..6cd3a86 100644 --- a/config/urls.py +++ b/config/urls.py @@ -8,4 +8,10 @@ urlpatterns = [ path("api/sensor-hub/", include("sensor_hub.urls")), path("api/farm-dashboard-config/", include("dashboard.urls_config")), path("api/farm-dashboard/", include("dashboard.urls")), + path("api/crop-zoning/", include("crop_zoning.urls")), + path("api/plant-simulator/", include("plant_simulator.urls")), + path("api/pest-detection/", include("pest_detection.urls")), + path("api/irrigation-recommendation/", include("irrigation_recommendation.urls")), + path("api/fertilization-recommendation/", include("fertilization_recommendation.urls")), + path("api/farm-ai-assistant/", include("farm_ai_assistant.urls")), ] diff --git a/crop_zoning/__init__.py b/crop_zoning/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crop_zoning/__init__.py @@ -0,0 +1 @@ + diff --git a/crop_zoning/apps.py b/crop_zoning/apps.py new file mode 100644 index 0000000..4147c50 --- /dev/null +++ b/crop_zoning/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class CropZoningConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "crop_zoning" + verbose_name = "Crop Zoning" diff --git a/crop_zoning/mock_data.py b/crop_zoning/mock_data.py new file mode 100644 index 0000000..2168a10 --- /dev/null +++ b/crop_zoning/mock_data.py @@ -0,0 +1,116 @@ +""" +Static mock data for Crop Zoning API. +Matches API_RESPONSE_SPEC.md. No database, no dynamic values. +""" + +# Response for POST optimize: GeoJSON FeatureCollection (API_RESPONSE_SPEC §1) +OPTIMIZE_ZONING_RESPONSE = { + "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}, + ], + }, + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [51.381, 35.68], + [51.382, 35.68], + [51.382, 35.681], + [51.381, 35.681], + [51.381, 35.68], + ] + ], + }, + "properties": { + "zoneId": "zone-1", + "crop": "canola", + "matchPercent": 82, + "waterNeed": "۳۵۰۰-۴۵۰۰ m³/ha", + "estimatedProfit": "۲۰-۳۰ میلیون/هکتار", + "reason": "بارش کافی، خاک با بافت مناسب", + "criteria": [ + {"name": "دما", "value": 70}, + {"name": "بارش", "value": 88}, + {"name": "خاک", "value": 75}, + {"name": "آب", "value": 90}, + ], + }, + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [51.382, 35.68], + [51.40, 35.68], + [51.40, 35.681], + [51.382, 35.681], + [51.382, 35.68], + ] + ], + }, + "properties": { + "zoneId": "zone-2", + "crop": "saffron", + "matchPercent": 65, + "waterNeed": "۲۵۰۰-۳۵۰۰ m³/ha", + "estimatedProfit": "۸۰-۱۲۰ میلیون/هکتار", + "reason": "آب و هوای خشک و سرد مناسب زعفران", + "criteria": [ + {"name": "دما", "value": 60}, + {"name": "بارش", "value": 55}, + {"name": "خاک", "value": 85}, + {"name": "آب", "value": 50}, + ], + }, + }, + ], +} + +# Response for GET initial region: GeoJSON Feature with Polygon (API_RESPONSE_SPEC §2) +INITIAL_REGION_RESPONSE = { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [51.38, 35.68], + [51.40, 35.68], + [51.40, 35.70], + [51.38, 35.70], + [51.38, 35.68], + ] + ], + }, +} diff --git a/crop_zoning/postman/crop_zoning.json b/crop_zoning/postman/crop_zoning.json new file mode 100644 index 0000000..4d7a6e0 --- /dev/null +++ b/crop_zoning/postman/crop_zoning.json @@ -0,0 +1,48 @@ +{ + "info": { + "name": "Crop Zoning", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "description": "Crop Zoning API. POST optimize (GeoJSON FeatureCollection). GET initial-region (GeoJSON Feature). Static mock only. No database." + }, + "item": [ + { + "name": "Optimize zoning (POST)", + "request": { + "method": "POST", + "header": [{"key": "Content-Type", "value": "application/json"}], + "body": { + "mode": "raw", + "raw": "{\n \"type\": \"Feature\",\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [[[51.38, 35.68], [51.40, 35.68], [51.40, 35.70], [51.38, 35.70], [51.38, 35.68]]]\n }\n}" + }, + "url": "{{baseUrl}}/api/crop-zoning/optimize/", + "description": "POST with GeoJSON Polygon (region). Returns static FeatureCollection. Input not processed." + }, + "response": [ + { + "name": "Success", + "status": "OK", + "code": 200, + "body": "{\n \"status\": \"success\",\n \"data\": {\n \"type\": \"FeatureCollection\",\n \"features\": [\n {\n \"type\": \"Feature\",\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [[[51.38, 35.68], [51.381, 35.68], [51.381, 35.681], [51.38, 35.681], [51.38, 35.68]]]\n },\n \"properties\": {\n \"zoneId\": \"zone-0\",\n \"crop\": \"wheat\",\n \"matchPercent\": 78,\n \"waterNeed\": \"۴۵۰۰-۵۵۰۰ m³/ha\",\n \"estimatedProfit\": \"۱۵-۲۵ میلیون/هکتار\",\n \"reason\": \"دمای مناسب، خاک حاصلخیز، دسترسی به آب کافی\",\n \"criteria\": [{\"name\": \"دما\", \"value\": 85}, {\"name\": \"بارش\", \"value\": 72}, {\"name\": \"خاک\", \"value\": 80}, {\"name\": \"آب\", \"value\": 65}]\n }\n },\n {\n \"type\": \"Feature\",\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [[[51.381, 35.68], [51.382, 35.68], [51.382, 35.681], [51.381, 35.681], [51.381, 35.68]]]\n },\n \"properties\": {\n \"zoneId\": \"zone-1\",\n \"crop\": \"canola\",\n \"matchPercent\": 82,\n \"waterNeed\": \"۳۵۰۰-۴۵۰۰ m³/ha\",\n \"estimatedProfit\": \"۲۰-۳۰ میلیون/هکتار\",\n \"reason\": \"بارش کافی، خاک با بافت مناسب\",\n \"criteria\": [{\"name\": \"دما\", \"value\": 70}, {\"name\": \"بارش\", \"value\": 88}, {\"name\": \"خاک\", \"value\": 75}, {\"name\": \"آب\", \"value\": 90}]\n }\n },\n {\n \"type\": \"Feature\",\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [[[51.382, 35.68], [51.40, 35.68], [51.40, 35.681], [51.382, 35.681], [51.382, 35.68]]]\n },\n \"properties\": {\n \"zoneId\": \"zone-2\",\n \"crop\": \"saffron\",\n \"matchPercent\": 65,\n \"waterNeed\": \"۲۵۰۰-۳۵۰۰ m³/ha\",\n \"estimatedProfit\": \"۸۰-۱۲۰ میلیون/هکتار\",\n \"reason\": \"آب و هوای خشک و سرد مناسب زعفران\",\n \"criteria\": [{\"name\": \"دما\", \"value\": 60}, {\"name\": \"بارش\", \"value\": 55}, {\"name\": \"خاک\", \"value\": 85}, {\"name\": \"آب\", \"value\": 50}]\n }\n }\n ]\n }\n}" + } + ] + }, + { + "name": "Get initial region (GET)", + "request": { + "method": "GET", + "header": [{"key": "Content-Type", "value": "application/json"}], + "url": "{{baseUrl}}/api/crop-zoning/initial-region/", + "description": "GET initial map region as GeoJSON Feature with Polygon. Static mock only." + }, + "response": [ + { + "name": "Success", + "status": "OK", + "code": 200, + "body": "{\n \"status\": \"success\",\n \"data\": {\n \"type\": \"Feature\",\n \"properties\": {},\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [[[51.38, 35.68], [51.40, 35.68], [51.40, 35.70], [51.38, 35.70], [51.38, 35.68]]]\n }\n }\n}" + } + ] + } + ], + "variable": [{"key": "baseUrl", "value": "http://localhost:8000"}] +} diff --git a/crop_zoning/urls.py b/crop_zoning/urls.py new file mode 100644 index 0000000..491a087 --- /dev/null +++ b/crop_zoning/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from .views import InitialRegionView, OptimizeZoningView + +urlpatterns = [ + path("optimize/", OptimizeZoningView.as_view(), name="crop-zoning-optimize"), + path("initial-region/", InitialRegionView.as_view(), name="crop-zoning-initial-region"), +] diff --git a/crop_zoning/views.py b/crop_zoning/views.py new file mode 100644 index 0000000..3023f2e --- /dev/null +++ b/crop_zoning/views.py @@ -0,0 +1,67 @@ +""" +Crop Zoning API views. +Plain Django only; no DRF. No database. All responses are static mock data. +Response format: {"status": "success", "data": }. HTTP 200 only. +No processing, validation, or use of input parameters in responses. +""" + +from django.http import JsonResponse +from django.views import View + +from .mock_data import INITIAL_REGION_RESPONSE, OPTIMIZE_ZONING_RESPONSE + + +class OptimizeZoningView(View): + """ + POST endpoint for zoning optimization. + + Purpose: + Returns a static GeoJSON FeatureCollection of zones with crop suggestions + (API_RESPONSE_SPEC §1). Used when the user selects a region on the map + and triggers "optimize zoning". No processing is performed on the request. + + Input parameters: + - body (optional): JSON body; may contain a GeoJSON Feature with Polygon. + Data type: object. Location: body. Not read or validated; not used in response. + + Response structure: + - status: string, always "success". + - data: object, GeoJSON FeatureCollection with features containing + geometry (Polygon) and properties (zoneId, crop, matchPercent, waterNeed, + estimatedProfit, reason, criteria). + + No processing or validation is performed on inputs. + """ + + def post(self, request): + return JsonResponse( + {"status": "success", "data": OPTIMIZE_ZONING_RESPONSE}, + status=200, + ) + + +class InitialRegionView(View): + """ + GET endpoint for the initial map region. + + Purpose: + Returns a static GeoJSON Feature with Polygon defining the initial + map region (API_RESPONSE_SPEC §2). Optional; used when the initial + region is loaded from the server instead of a fixed client mock. + + Input parameters: + None. Query parameters, if sent, are not read or used. + + Response structure: + - status: string, always "success". + - data: object, GeoJSON Feature with geometry.type "Polygon" and + coordinates as [longitude, latitude]; first and last point equal. + + No processing or validation is performed on inputs. + """ + + def get(self, request): + return JsonResponse( + {"status": "success", "data": INITIAL_REGION_RESPONSE}, + status=200, + ) diff --git a/docker-compose-prod.yaml b/docker-compose-prod.yaml index ac3a959..dd0547c 100644 --- a/docker-compose-prod.yaml +++ b/docker-compose-prod.yaml @@ -23,7 +23,7 @@ services: PMA_PORT: 3306 UPLOAD_LIMIT: 64M ports: - - "8080:80" + - "8081:80" depends_on: db: condition: service_healthy diff --git a/docker-compose.yaml b/docker-compose.yaml index 42cc3c3..29ff35c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -22,7 +22,7 @@ services: PMA_PORT: 3306 UPLOAD_LIMIT: 64M ports: - - "8080:80" + - "8081:80" depends_on: db: condition: service_healthy diff --git a/farm_ai_assistant/__init__.py b/farm_ai_assistant/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/farm_ai_assistant/mock_data.py b/farm_ai_assistant/mock_data.py new file mode 100644 index 0000000..2e8574d --- /dev/null +++ b/farm_ai_assistant/mock_data.py @@ -0,0 +1,44 @@ +""" +Static mock data for Farm AI Assistant API. +No database, no dynamic values. All responses are fixed JSON. +""" + +CHAT_RESPONSE_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.", + }, + ], +} + +CONTEXT_RESPONSE_DATA = { + "soilType": "Loamy", + "waterEC": "1.2 dS/m", + "selectedCrop": "Tomato", + "growthStage": "Flowering", + "lastIrrigationStatus": "2 days ago", +} diff --git a/farm_ai_assistant/postman/farm_ai_assistant.json b/farm_ai_assistant/postman/farm_ai_assistant.json new file mode 100644 index 0000000..dca0409 --- /dev/null +++ b/farm_ai_assistant/postman/farm_ai_assistant.json @@ -0,0 +1,48 @@ +{ + "info": { + "name": "Farm AI Assistant", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "description": "Farm AI Assistant API. GET context (farm bar data). POST chat (user message + optional farm_context/images). Static JSON only." + }, + "item": [ + { + "name": "Get farm context (GET)", + "request": { + "method": "GET", + "header": [{"key": "Content-Type", "value": "application/json"}], + "url": "{{baseUrl}}/api/farm-ai-assistant/context/", + "description": "Returns static farm context: soilType, waterEC, selectedCrop, growthStage, lastIrrigationStatus for the context bar." + }, + "response": [ + { + "name": "Success", + "status": "OK", + "code": 200, + "body": "{\n \"status\": \"success\",\n \"data\": {\n \"soilType\": \"Loamy\",\n \"waterEC\": \"1.2 dS/m\",\n \"selectedCrop\": \"Tomato\",\n \"growthStage\": \"Flowering\",\n \"lastIrrigationStatus\": \"2 days ago\"\n }\n}" + } + ] + }, + { + "name": "Chat (POST)", + "request": { + "method": "POST", + "header": [{"key": "Content-Type", "value": "application/json"}], + "body": { + "mode": "raw", + "raw": "{\n \"content\": \"برنامه آبیاری برای گوجه در مرحله گلدهی چطور باشد؟\",\n \"farm_context\": {\n \"soilType\": \"Loamy\",\n \"waterEC\": \"1.2 dS/m\",\n \"selectedCrop\": \"Tomato\",\n \"growthStage\": \"Flowering\",\n \"lastIrrigationStatus\": \"2 days ago\"\n }\n}" + }, + "url": "{{baseUrl}}/api/farm-ai-assistant/chat/", + "description": "Body: content (required), optional images, conversation_id, farm_context. Returns static message with sections (recommendation, list, warning). Input not processed." + }, + "response": [ + { + "name": "Success", + "status": "OK", + "code": 200, + "body": "{\n \"status\": \"success\",\n \"data\": {\n \"message_id\": \"a-1739123456789\",\n \"conversation_id\": \"conv-abc123\",\n \"content\": \"\",\n \"sections\": [\n {\n \"type\": \"recommendation\",\n \"title\": \"Irrigation recommendation\",\n \"icon\": \"droplet\",\n \"frequency\": \"3 times per week\",\n \"amount\": \"15–20 L per plant\",\n \"timing\": \"Early morning (05:00–07:00)\",\n \"expandableExplanation\": \"Your loamy soil holds moisture well...\"\n },\n {\n \"type\": \"list\",\n \"title\": \"Key points\",\n \"icon\": \"leaf\",\n \"items\": [\n \"Avoid midday watering to reduce evaporation\",\n \"Drip irrigation preferred for root zone targeting\"\n ]\n },\n {\n \"type\": \"warning\",\n \"title\": \"Weather advisory\",\n \"icon\": \"warning\",\n \"content\": \"High temps forecasted next week. Consider increasing frequency.\"\n }\n ]\n }\n}" + } + ] + } + ], + "variable": [{"key": "baseUrl", "value": "http://localhost:8000"}] +} diff --git a/farm_ai_assistant/urls.py b/farm_ai_assistant/urls.py new file mode 100644 index 0000000..09fa7d9 --- /dev/null +++ b/farm_ai_assistant/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from .views import ChatView, ContextView + +urlpatterns = [ + path("context/", ContextView.as_view(), name="farm-ai-assistant-context"), + path("chat/", ChatView.as_view(), name="farm-ai-assistant-chat"), +] diff --git a/farm_ai_assistant/views.py b/farm_ai_assistant/views.py new file mode 100644 index 0000000..cd2229a --- /dev/null +++ b/farm_ai_assistant/views.py @@ -0,0 +1,77 @@ +""" +Farm AI Assistant API views. +Plain Django only; no DRF. No database. All responses are static mock data. +Response format: {"status": "success", "data": }. HTTP 200 only. +No processing, validation, or use of input parameters in responses. +CSRF exempt on POST so frontend can call without token. +""" + +from django.http import JsonResponse +from django.utils.decorators import method_decorator +from django.views import View +from django.views.decorators.csrf import csrf_exempt + +from .mock_data import CHAT_RESPONSE_DATA, CONTEXT_RESPONSE_DATA + + +class ContextView(View): + """ + GET endpoint for farm context (Farm AI Assistant bar). + + Purpose: + Returns static farm context for the Farm AI Assistant UI bar: + soilType, waterEC, selectedCrop, growthStage, lastIrrigationStatus. + Used when loading the farm-ai-assistant page to populate the context strip. + + Input parameters: + None. Query parameters, if sent, are not read or used. + + Response structure: + - status: string, always "success". + - data: object with keys soilType, waterEC, selectedCrop, + growthStage, lastIrrigationStatus (all strings). + + No processing or validation is performed on inputs. + """ + + def get(self, request): + return JsonResponse( + {"status": "success", "data": CONTEXT_RESPONSE_DATA}, + status=200, + ) + + +@method_decorator(csrf_exempt, name="dispatch") +class ChatView(View): + """ + POST endpoint for Farm AI Assistant chat (send message, get structured reply). + + Purpose: + Accepts user message (and optional images, conversation_id, farm_context) + and returns a static structured reply with sections (recommendation, + list, warning) for rendering as cards in the chat UI. No AI or + computation; response is fixed. + + Input parameters (body, JSON; all optional except conceptually content): + - content: string. Location: body. User message text. Not read or used. + - images: array of strings (URLs or base64). Location: body. Not read. + - conversation_id: string. Location: body. Conversation id. Not used. + - farm_context: object (soilType, waterEC, selectedCrop, growthStage, + lastIrrigationStatus). Location: body. Not read or used. + + Response structure: + - status: string, always "success". + - data: object with message_id, conversation_id, content (string), + sections (array of section objects). Each section has type, title, + icon, and type-specific fields (content, items, frequency, amount, + timing, expandableExplanation). + + No processing or validation is performed on inputs. Input values are + not used in the response. + """ + + def post(self, request): + return JsonResponse( + {"status": "success", "data": CHAT_RESPONSE_DATA}, + status=200, + ) diff --git a/fertilization_recommendation/__init__.py b/fertilization_recommendation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fertilization_recommendation/apps.py b/fertilization_recommendation/apps.py new file mode 100644 index 0000000..d8e6746 --- /dev/null +++ b/fertilization_recommendation/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class FertilizationRecommendationConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "fertilization_recommendation" + verbose_name = "Fertilization Recommendation" diff --git a/fertilization_recommendation/mock_data.py b/fertilization_recommendation/mock_data.py new file mode 100644 index 0000000..84da776 --- /dev/null +++ b/fertilization_recommendation/mock_data.py @@ -0,0 +1,37 @@ +""" +Static mock data for Fertilization Recommendation API. +No database, no dynamic values. +""" + +CONFIG_RESPONSE_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"}, + ], +} + +RECOMMEND_RESPONSE_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.", + }, +} diff --git a/fertilization_recommendation/postman/fertilization_recommendation.json b/fertilization_recommendation/postman/fertilization_recommendation.json new file mode 100644 index 0000000..112bf54 --- /dev/null +++ b/fertilization_recommendation/postman/fertilization_recommendation.json @@ -0,0 +1 @@ +{"info":{"name":"Fertilization Recommendation","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json","description":"Fertilization Recommendation API. GET config (farm data, growth stages, crop options). POST recommend (optional body). Returns static plan. No database."},"item":[{"name":"Get config (GET)","request":{"method":"GET","header":[{"key":"Content-Type","value":"application/json"}],"url":"{{baseUrl}}/api/fertilization-recommendation/config/","description":"Returns static farmData, growthStages, cropOptions."},"response":[{"name":"Success","status":"OK","code":200,"body":"{\n \"status\": \"success\",\n \"data\": {\n \"farmData\": {\n \"soilType\": \"Loamy\",\n \"organicMatter\": \"Medium (2.5%)\",\n \"waterEC\": \"1.2 dS/m\"\n },\n \"growthStages\": [\n {\"id\": \"prePlanting\", \"icon\": \"tabler-seedling\"},\n {\"id\": \"earlyGrowth\", \"icon\": \"tabler-leaf\"},\n {\"id\": \"flowering\", \"icon\": \"tabler-flower\"},\n {\"id\": \"fruiting\", \"icon\": \"tabler-apple\"},\n {\"id\": \"postHarvest\", \"icon\": \"tabler-basket\"}\n ],\n \"cropOptions\": [\n {\"id\": \"wheat\", \"labelKey\": \"wheat\", \"icon\": \"tabler-wheat\"},\n {\"id\": \"corn\", \"labelKey\": \"corn\", \"icon\": \"tabler-plant-2\"},\n {\"id\": \"cotton\", \"labelKey\": \"cotton\", \"icon\": \"tabler-flower\"},\n {\"id\": \"saffron\", \"labelKey\": \"saffron\", \"icon\": \"tabler-flower-2\"},\n {\"id\": \"canola\", \"labelKey\": \"canola\", \"icon\": \"tabler-leaf\"},\n {\"id\": \"vegetables\", \"labelKey\": \"vegetables\", \"icon\": \"tabler-carrot\"}\n ]\n }\n}"}]},{"name":"Get recommendation (POST)","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n \"crop_id\": \"wheat\",\n \"growth_stage\": \"flowering\",\n \"soilType\": \"Loamy\",\n \"organicMatter\": \"Medium (2.5%)\",\n \"waterEC\": \"1.2 dS/m\"\n}"},"url":"{{baseUrl}}/api/fertilization-recommendation/recommend/","description":"Optional body: crop_id, growth_stage, farm_data. Returns static plan. Input not processed."},"response":[{"name":"Success","status":"OK","code":200,"body":"{\n \"status\": \"success\",\n \"data\": {\n \"plan\": {\n \"npkRatio\": \"20-20-20 (NPK)\",\n \"amountPerHectare\": \"150 kg/ha\",\n \"applicationMethod\": \"Foliar spray + soil broadcast\",\n \"applicationInterval\": \"Every 14 days\",\n \"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.\"\n }\n }\n}"}]}],"variable":[{"key":"baseUrl","value":"http://localhost:8000"}]} diff --git a/fertilization_recommendation/urls.py b/fertilization_recommendation/urls.py new file mode 100644 index 0000000..e52d26d --- /dev/null +++ b/fertilization_recommendation/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from .views import ConfigView, RecommendView + +urlpatterns = [ + path("config/", ConfigView.as_view(), name="fertilization-recommendation-config"), + path("recommend/", RecommendView.as_view(), name="fertilization-recommendation-recommend"), +] diff --git a/fertilization_recommendation/views.py b/fertilization_recommendation/views.py new file mode 100644 index 0000000..551de0b --- /dev/null +++ b/fertilization_recommendation/views.py @@ -0,0 +1,71 @@ +""" +Fertilization Recommendation API views. +Plain Django only; no DRF. No database. All responses are static mock data. +Response format: {"status": "success", "data": }. HTTP 200 only. +No processing, validation, or use of input parameters in responses. +CSRF exempt on POST so frontend can call without token. +""" + +from django.http import JsonResponse +from django.utils.decorators import method_decorator +from django.views import View +from django.views.decorators.csrf import csrf_exempt + +from .mock_data import CONFIG_RESPONSE_DATA, RECOMMEND_RESPONSE_DATA + + +class ConfigView(View): + """ + GET endpoint for fertilization config (farm data, growth stages, crop options). + + Purpose: + Returns static farm data (soilType, organicMatter, waterEC), growth + stages list, and crop options for the fertilization recommendation form. + Used when loading the fertilization recommendation page. + + Input parameters: + None. Query parameters, if sent, are not read or used. + + Response structure: + - status: string, always "success". + - data: object with keys farmData (object), growthStages (array of + { id, icon }), cropOptions (array of { id, labelKey, icon }). + + No processing or validation is performed on inputs. + """ + + def get(self, request): + return JsonResponse( + {"status": "success", "data": CONFIG_RESPONSE_DATA}, + status=200, + ) + + +@method_decorator(csrf_exempt, name="dispatch") +class RecommendView(View): + """ + POST endpoint for fertilization recommendation. + + Purpose: + Returns a static fertilization plan (npkRatio, amountPerHectare, + applicationMethod, applicationInterval, reasoning). Body may contain + crop_id, growth_stage, farm_data; not read or used in response. + + Input parameters: + - body (optional): JSON. May contain "crop_id", "growth_stage", + "soilType", "organicMatter", "waterEC". Data type: object. + Location: body. Not read or validated; not used in response. + + Response structure: + - status: string, always "success". + - data: object with key "plan" (object with npkRatio, amountPerHectare, + applicationMethod, applicationInterval, reasoning). + + No processing or validation is performed on inputs. + """ + + def post(self, request): + return JsonResponse( + {"status": "success", "data": RECOMMEND_RESPONSE_DATA}, + status=200, + ) diff --git a/irrigation_recommendation/__init__.py b/irrigation_recommendation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/irrigation_recommendation/apps.py b/irrigation_recommendation/apps.py new file mode 100644 index 0000000..34dad2b --- /dev/null +++ b/irrigation_recommendation/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class IrrigationRecommendationConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "irrigation_recommendation" + verbose_name = "Irrigation Recommendation" diff --git a/irrigation_recommendation/mock_data.py b/irrigation_recommendation/mock_data.py new file mode 100644 index 0000000..db90576 --- /dev/null +++ b/irrigation_recommendation/mock_data.py @@ -0,0 +1,30 @@ +""" +Static mock data for Irrigation Recommendation API. +No database, no dynamic values. +""" + +CONFIG_RESPONSE_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"}, + ], +} + +RECOMMEND_RESPONSE_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.", + }, +} diff --git a/irrigation_recommendation/postman/irrigation_recommendation.json b/irrigation_recommendation/postman/irrigation_recommendation.json new file mode 100644 index 0000000..2f0de10 --- /dev/null +++ b/irrigation_recommendation/postman/irrigation_recommendation.json @@ -0,0 +1 @@ +{"info":{"name":"Irrigation Recommendation","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json","description":"Irrigation Recommendation API. GET config (farm info + crop options). POST recommend (optional body). Returns static plan. No database."},"item":[{"name":"Get config (GET)","request":{"method":"GET","header":[{"key":"Content-Type","value":"application/json"}],"url":"{{baseUrl}}/api/irrigation-recommendation/config/","description":"Returns static farmInfo and cropOptions."},"response":[{"name":"Success","status":"OK","code":200,"body":"{\n \"status\": \"success\",\n \"data\": {\n \"farmInfo\": {\n \"soilType\": \"Loamy\",\n \"waterQuality\": \"Medium EC\",\n \"climateZone\": \"Temperate\"\n },\n \"cropOptions\": [\n {\"id\": \"wheat\", \"labelKey\": \"wheat\", \"icon\": \"tabler-wheat\"},\n {\"id\": \"corn\", \"labelKey\": \"corn\", \"icon\": \"tabler-plant-2\"},\n {\"id\": \"cotton\", \"labelKey\": \"cotton\", \"icon\": \"tabler-flower\"},\n {\"id\": \"saffron\", \"labelKey\": \"saffron\", \"icon\": \"tabler-flower-2\"},\n {\"id\": \"canola\", \"labelKey\": \"canola\", \"icon\": \"tabler-leaf\"},\n {\"id\": \"vegetables\", \"labelKey\": \"vegetables\", \"icon\": \"tabler-carrot\"}\n ]\n }\n}"}]},{"name":"Get recommendation (POST)","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n \"crop_id\": \"wheat\",\n \"soilType\": \"Loamy\",\n \"waterQuality\": \"Medium EC\",\n \"climateZone\": \"Temperate\"\n}"},"url":"{{baseUrl}}/api/irrigation-recommendation/recommend/","description":"Optional body: crop_id, farm info. Returns static plan. Input not processed."},"response":[{"name":"Success","status":"OK","code":200,"body":"{\n \"status\": \"success\",\n \"data\": {\n \"plan\": {\n \"frequencyPerWeek\": 4,\n \"durationMinutes\": 45,\n \"bestTimeOfDay\": \"05:00 - 07:00\",\n \"moistureLevel\": 72,\n \"warning\": \"Avoid irrigation during midday hours in the coming week due to forecasted high temperatures.\"\n }\n }\n}"}]}],"variable":[{"key":"baseUrl","value":"http://localhost:8000"}]} diff --git a/irrigation_recommendation/urls.py b/irrigation_recommendation/urls.py new file mode 100644 index 0000000..b3810bc --- /dev/null +++ b/irrigation_recommendation/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from .views import ConfigView, RecommendView + +urlpatterns = [ + path("config/", ConfigView.as_view(), name="irrigation-recommendation-config"), + path("recommend/", RecommendView.as_view(), name="irrigation-recommendation-recommend"), +] diff --git a/irrigation_recommendation/views.py b/irrigation_recommendation/views.py new file mode 100644 index 0000000..95e82ec --- /dev/null +++ b/irrigation_recommendation/views.py @@ -0,0 +1,71 @@ +""" +Irrigation Recommendation API views. +Plain Django only; no DRF. No database. All responses are static mock data. +Response format: {"status": "success", "data": }. HTTP 200 only. +No processing, validation, or use of input parameters in responses. +CSRF exempt on POST so frontend can call without token. +""" + +from django.http import JsonResponse +from django.utils.decorators import method_decorator +from django.views import View +from django.views.decorators.csrf import csrf_exempt + +from .mock_data import CONFIG_RESPONSE_DATA, RECOMMEND_RESPONSE_DATA + + +class ConfigView(View): + """ + GET endpoint for irrigation config (farm info and crop options). + + Purpose: + Returns static farm info (soilType, waterQuality, climateZone) and + crop options list for the irrigation recommendation form. Used when + loading the irrigation recommendation page. + + Input parameters: + None. Query parameters, if sent, are not read or used. + + Response structure: + - status: string, always "success". + - data: object with keys farmInfo (object), cropOptions (array of + { id, labelKey, icon }). + + No processing or validation is performed on inputs. + """ + + def get(self, request): + return JsonResponse( + {"status": "success", "data": CONFIG_RESPONSE_DATA}, + status=200, + ) + + +@method_decorator(csrf_exempt, name="dispatch") +class RecommendView(View): + """ + POST endpoint for irrigation recommendation. + + Purpose: + Returns a static irrigation plan (frequencyPerWeek, durationMinutes, + bestTimeOfDay, moistureLevel, warning). Body may contain crop_id + and farm info; not read or used in response. + + Input parameters: + - body (optional): JSON. May contain "crop_id", "soilType", "waterQuality", + "climateZone". Data type: object. Location: body. Not read or validated; + not used in response. + + Response structure: + - status: string, always "success". + - data: object with key "plan" (object with frequencyPerWeek, + durationMinutes, bestTimeOfDay, moistureLevel, warning). + + No processing or validation is performed on inputs. + """ + + def post(self, request): + return JsonResponse( + {"status": "success", "data": RECOMMEND_RESPONSE_DATA}, + status=200, + ) diff --git a/pest_detection/__init__.py b/pest_detection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pest_detection/apps.py b/pest_detection/apps.py new file mode 100644 index 0000000..2a25ace --- /dev/null +++ b/pest_detection/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class PestDetectionConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "pest_detection" + verbose_name = "Pest Detection" diff --git a/pest_detection/mock_data.py b/pest_detection/mock_data.py new file mode 100644 index 0000000..5eb3a4d --- /dev/null +++ b/pest_detection/mock_data.py @@ -0,0 +1,11 @@ +""" +Static mock data for Pest Detection API. +No database, no dynamic values. Used for analyze endpoint response. +""" + +ANALYZE_RESPONSE_DATA = { + "pest": "شپشک", + "confidence": 92, + "description": "حشرات کوچک مکنده شیره که باعث پیچ خوردگی برگ می‌شوند.", + "treatment": "یک بار در هفته از اسپری روغن نیم استفاده کنید.", +} diff --git a/pest_detection/postman/pest_detection.json b/pest_detection/postman/pest_detection.json new file mode 100644 index 0000000..a08da4e --- /dev/null +++ b/pest_detection/postman/pest_detection.json @@ -0,0 +1 @@ +{"info":{"name":"Pest Detection","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json","description":"Pest Detection API. POST analyze (optional body). Returns static pest result. No database."},"item":[{"name":"Analyze image (POST)","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{}"},"url":"{{baseUrl}}/api/pest-detection/analyze/","description":"POST with optional body (e.g. image reference). Returns static pest, confidence, description, treatment. Input not processed."},"response":[{"name":"Success","status":"OK","code":200,"body":"{\n \"status\": \"success\",\n \"data\": {\n \"pest\": \"شپشک\",\n \"confidence\": 92,\n \"description\": \"حشرات کوچک مکنده شیره که باعث پیچ خوردگی برگ می‌شوند.\",\n \"treatment\": \"یک بار در هفته از اسپری روغن نیم استفاده کنید.\"\n }\n}"}]}],"variable":[{"key":"baseUrl","value":"http://localhost:8000"}]} diff --git a/pest_detection/urls.py b/pest_detection/urls.py new file mode 100644 index 0000000..2cf1705 --- /dev/null +++ b/pest_detection/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from .views import AnalyzeView + +urlpatterns = [ + path("analyze/", AnalyzeView.as_view(), name="pest-detection-analyze"), +] diff --git a/pest_detection/views.py b/pest_detection/views.py new file mode 100644 index 0000000..f7a340a --- /dev/null +++ b/pest_detection/views.py @@ -0,0 +1,43 @@ +""" +Pest Detection API views. +Plain Django only; no DRF. No database. All responses are static mock data. +Response format: {"status": "success", "data": }. HTTP 200 only. +No processing, validation, or use of input parameters in responses. +CSRF exempt so frontend can call POST without token. +""" + +from django.http import JsonResponse +from django.utils.decorators import method_decorator +from django.views import View +from django.views.decorators.csrf import csrf_exempt + +from .mock_data import ANALYZE_RESPONSE_DATA + + +@method_decorator(csrf_exempt, name="dispatch") +class AnalyzeView(View): + """ + POST endpoint for pest detection analysis. + + Purpose: + Returns a static pest detection result (pest name, confidence, + description, treatment). Used when the user uploads a plant image + and requests analysis. No processing is performed on the request. + + Input parameters: + - body (optional): JSON or form-data; may contain image or file. + Data type: object. Location: body. Not read or validated; not used in response. + + Response structure: + - status: string, always "success". + - data: object with keys pest (string), confidence (number), + description (string), treatment (string). + + No processing or validation is performed on inputs. + """ + + def post(self, request): + return JsonResponse( + {"status": "success", "data": ANALYZE_RESPONSE_DATA}, + status=200, + ) diff --git a/plant_simulator/__init__.py b/plant_simulator/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/plant_simulator/__init__.py @@ -0,0 +1 @@ + diff --git a/plant_simulator/apps.py b/plant_simulator/apps.py new file mode 100644 index 0000000..b93448b --- /dev/null +++ b/plant_simulator/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class PlantSimulatorConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "plant_simulator" + verbose_name = "Plant Simulator" diff --git a/plant_simulator/mock_data.py b/plant_simulator/mock_data.py new file mode 100644 index 0000000..ab9618b --- /dev/null +++ b/plant_simulator/mock_data.py @@ -0,0 +1,176 @@ +""" +Static mock data for Plant Simulator API. +Matches PLANT_SIMULATOR_API.md. No database, no dynamic values. +Smooth animation: 51 points (0–10s, ~5 frames per second). +""" + +# --------------------------------------------------------------------------- +# GET /api/plant-simulator/config (ورود: فقط اسلایدرها) +# --------------------------------------------------------------------------- + +CONFIG_SLIDERS_ONLY = { + "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, + }, + ], +} + +# --------------------------------------------------------------------------- +# POST /api/plant-simulator/start (ثابت‌ها + چارت کانفیگ + plant + progress + chart) +# --------------------------------------------------------------------------- + +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_CONFIG = { + "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", + }, + ], +} + +# 51 نقطه برای انیمیشن نرم (۰ تا ۱۰ ثانیه، هر ~۰٫۲s) +_labels = [f"{i * 0.2:.1f}s" for i in range(51)] +_height = [round(142 * (i / 50) ** 0.9) for i in range(51)] # رشد کمی شتاب‌دار +_leaf = [min(5, int(i / 10)) for i in range(51)] # 0,0..,1,1..,2,...,5 +_yield = [round(12.4 * (i / 50) ** 1.2, 1) for i in range(51)] # محصول با شتاب ملایم +_yield_rate = [round(0.087 * max(0, (i - 15) / 35), 3) for i in range(51)] # نرخ از ثانیه ~۳ + +START_RESPONSE_DATA = { + "constants": CONSTANTS, + "chart": CHART_CONFIG, + "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_history": { + "labels": _labels, + "height_history": _height, + "leaf_history": _leaf, + "yield_history": _yield, + "yield_rate_history": _yield_rate, + }, +} + +# --------------------------------------------------------------------------- +# GET /api/plant-simulator/state (plant + progress + chart history) +# --------------------------------------------------------------------------- + +STATE_RESPONSE_DATA = { + "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": _labels, + "height_history": _height, + "leaf_history": _leaf, + "yield_history": _yield, + "yield_rate_history": _yield_rate, + }, +} \ No newline at end of file diff --git a/plant_simulator/postman/plant_simulator.json b/plant_simulator/postman/plant_simulator.json new file mode 100644 index 0000000..94540f3 --- /dev/null +++ b/plant_simulator/postman/plant_simulator.json @@ -0,0 +1,101 @@ + { + "info": { + "name": "Plant Simulator", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "description": "Plant Simulator API. GET config (sliders only), GET state, POST start (body: environment + growth_speed per serializers), stop/reset/environment. Static mock only." + }, + "item": [ + { + "name": "Get config (GET)", + "request": { + "method": "GET", + "header": [{"key": "Content-Type", "value": "application/json"}], + "url": "{{baseUrl}}/api/plant-simulator/config/", + "description": "ورود: فقط اسلایدرها. Returns sliders only (key, label, min, max, step, unit_type, default_value, icon)." + }, + "response": [ + { + "name": "Success", + "status": "OK", + "code": 200, + "body": "{\n \"status\": \"success\",\n \"data\": {\n \"sliders\": [\n {\"key\": \"light\", \"label\": \"نور\", \"min\": 0, \"max\": 100, \"step\": 5, \"unit_type\": \"percent\", \"default_value\": 75, \"icon\": \"☀️\"},\n {\"key\": \"water\", \"label\": \"آب\", \"min\": 0, \"max\": 100, \"step\": 5, \"unit_type\": \"percent\", \"default_value\": 65, \"icon\": \"💧\"},\n {\"key\": \"soil_ph\", \"label\": \"pH خاک\", \"min\": 4, \"max\": 9, \"step\": 0.5, \"unit_type\": \"number\", \"unit\": \"\", \"default_value\": 6.5},\n {\"key\": \"growth_speed\", \"label\": \"سرعت رشد\", \"min\": 0.5, \"max\": 5, \"step\": 0.5, \"unit_type\": \"number\", \"unit\": \"×\", \"default_value\": 1.5}\n ]\n }\n}" + } + ] + }, + { + "name": "Get state (GET)", + "request": { + "method": "GET", + "header": [{"key": "Content-Type", "value": "application/json"}], + "url": "{{baseUrl}}/api/plant-simulator/state/", + "description": "Returns static plant state, progress, and chart history (plant, progress, chart)." + }, + "response": [ + { + "name": "Success", + "status": "OK", + "code": 200, + "body": "{\n \"status\": \"success\",\n \"data\": {\n \"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},\n \"progress\": {\"growth_progress\": 50, \"light_status\": 75, \"water_status\": 65, \"yield_progress\": 2.5, \"yield_current\": 12.4, \"yield_rate_current\": 0.087},\n \"chart\": {\n \"labels\": [\"0s\", \"1s\", \"2s\", \"3s\", \"4s\", \"5s\", \"6s\", \"7s\", \"8s\", \"9s\", \"10s\"],\n \"height_history\": [0, 5, 12, 28, 45, 68, 92, 110, 125, 135, 142],\n \"leaf_history\": [0, 0, 1, 2, 3, 4, 4, 5, 5, 5, 5],\n \"yield_history\": [0, 0, 0, 0.1, 0.5, 1.2, 3.2, 5.8, 8.2, 10.1, 12.4],\n \"yield_rate_history\": [0, 0, 0, 0.01, 0.03, 0.05, 0.06, 0.07, 0.08, 0.085, 0.087]\n }\n }\n}" + } + ] + }, + { + "name": "Start simulation (POST)", + "request": { + "method": "POST", + "header": [{"key": "Content-Type", "value": "application/json"}], + "body": { + "mode": "raw", + "raw": "{\n \"environment\": {\n \"light\": 75,\n \"water\": 65,\n \"soil_ph\": 6.5\n },\n \"growth_speed\": 1.5\n}" + }, + "url": "{{baseUrl}}/api/plant-simulator/start/", + "description": "Body per serializers.START_REQUEST_EXAMPLE_STATIC: environment (light, water, soil_ph) + growth_speed. Returns constants, chart, plant, progress, chart_history." + }, + "response": [ + { + "name": "Success", + "status": "OK", + "code": 200, + "body": "{\n \"status\": \"success\",\n \"data\": {\n \"constants\": {\"max_height\": 280, \"max_leaves\": 14, \"max_branches\": 6, \"max_yield\": 500, \"yield_unit\": \"g\", \"yield_rate_unit\": \"g/s\", \"height_unit\": \"px\"},\n \"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\"}]},\n \"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},\n \"progress\": {\"growth_progress\": 50, \"light_status\": 75, \"water_status\": 65, \"yield_progress\": 2.5, \"yield_current\": 12.4, \"yield_rate_current\": 0.087},\n \"chart_history\": {\"labels\": [\"0.0s\", \"0.2s\", \"0.4s\", \"1.0s\", \"2.0s\", \"5.0s\", \"10.0s\"], \"height_history\": [0, 2, 5, 18, 45, 110, 142], \"leaf_history\": [0, 0, 0, 0, 0, 1, 5], \"yield_history\": [0, 0, 0, 0.2, 1.5, 6.2, 12.4], \"yield_rate_history\": [0, 0, 0, 0.01, 0.03, 0.06, 0.087]}\n }\n}" + } + ] + }, + { + "name": "Stop simulation (POST)", + "request": { + "method": "POST", + "header": [{"key": "Content-Type", "value": "application/json"}], + "body": {"mode": "raw", "raw": "{}"}, + "url": "{{baseUrl}}/api/plant-simulator/stop/", + "description": "Empty body. Returns success only." + }, + "response": [{"name": "Success", "status": "OK", "code": 200, "body": "{\n \"status\": \"success\"\n}"}] + }, + { + "name": "Reset simulation (POST)", + "request": { + "method": "POST", + "header": [{"key": "Content-Type", "value": "application/json"}], + "body": {"mode": "raw", "raw": "{}"}, + "url": "{{baseUrl}}/api/plant-simulator/reset/", + "description": "Empty body. Returns success only." + }, + "response": [{"name": "Success", "status": "OK", "code": 200, "body": "{\n \"status\": \"success\"\n}"}] + }, + { + "name": "Update environment (PATCH)", + "request": { + "method": "PATCH", + "header": [{"key": "Content-Type", "value": "application/json"}], + "body": { + "mode": "raw", + "raw": "{\n \"environment\": {\n \"light\": 80,\n \"water\": 70,\n \"soil_ph\": 6.5\n },\n \"growth_speed\": 2\n}" + }, + "url": "{{baseUrl}}/api/plant-simulator/environment/", + "description": "Body: environment (same keys as sliders) + optional growth_speed. Returns success only." + }, + "response": [{"name": "Success", "status": "OK", "code": 200, "body": "{\n \"status\": \"success\"\n}"}] + } + ], + "variable": [{"key": "baseUrl", "value": "http://localhost:8000"}] + } diff --git a/plant_simulator/serializers.py b/plant_simulator/serializers.py new file mode 100644 index 0000000..81f4651 --- /dev/null +++ b/plant_simulator/serializers.py @@ -0,0 +1,62 @@ +""" +Response serialization for Plant Simulator API. +Plain Django; no DRF. Builds response envelope only. All payloads are static mock data. + +Request: on POST /start the client must send a body with keys matching the sliders +from config (light, water, soil_ph) plus growth_speed. See START_REQUEST_EXAMPLE. +No validation or use of request in response; backend returns static mock only. +""" + +from .mock_data import CONFIG_SLIDERS_ONLY + +# --------------------------------------------------------------------------- +# POST /start — بدنه‌ای که کلاینت باید ارسال کند (مطابق اسلایدرهای config) +# --------------------------------------------------------------------------- + +# کلیدهای environment (همان key هر اسلایدر به‌جز growth_speed) +START_ENVIRONMENT_KEYS = [ + item["key"] + for item in CONFIG_SLIDERS_ONLY["sliders"] + if item["key"] != "growth_speed" +] + +# مقدار پیش‌فرض هر اسلایدر برای ساخت نمونه درخواست +def _defaults_from_sliders(): + return { + item["key"]: item["default_value"] + for item in CONFIG_SLIDERS_ONLY["sliders"] + } + +# نمونه بدنه درخواست start که کلاینت باید ارسال کند +START_REQUEST_EXAMPLE = { + "environment": { + k: v for k, v in _defaults_from_sliders().items() if k != "growth_speed" + }, + "growth_speed": _defaults_from_sliders().get("growth_speed", 1.5), +} + +# برای سازگاری: همان ساختار به صورت ثابت (بدون وابستگی به محاسبه) +START_REQUEST_EXAMPLE_STATIC = { + "environment": { + "light": 75, + "water": 65, + "soil_ph": 6.5, + }, + "growth_speed": 1.5, +} + + +def success_response(): + """ + Response when endpoint does not return data. + Returns: {"status": "success"} + """ + return {"status": "success"} + + +def success_with_data(data): + """ + Response when endpoint returns data. + Returns: {"status": "success", "data": data} + """ + return {"status": "success", "data": data} diff --git a/plant_simulator/urls.py b/plant_simulator/urls.py new file mode 100644 index 0000000..0175ec6 --- /dev/null +++ b/plant_simulator/urls.py @@ -0,0 +1,19 @@ +from django.urls import path + +from .views import ( + ConfigView, + EnvironmentView, + ResetView, + StartView, + StateView, + StopView, +) + +urlpatterns = [ + path("config/", ConfigView.as_view(), name="plant-simulator-config"), + path("state/", StateView.as_view(), name="plant-simulator-state"), + path("start/", StartView.as_view(), name="plant-simulator-start"), + path("stop/", StopView.as_view(), name="plant-simulator-stop"), + path("reset/", ResetView.as_view(), name="plant-simulator-reset"), + path("environment/", EnvironmentView.as_view(), name="plant-simulator-environment"), +] diff --git a/plant_simulator/views.py b/plant_simulator/views.py new file mode 100644 index 0000000..acd18a4 --- /dev/null +++ b/plant_simulator/views.py @@ -0,0 +1,161 @@ +""" +Plant Simulator API views. +Plain Django only; no DRF. No database. All responses are static mock data. +Response format: {"status": "success"} or {"status": "success", "data": }. HTTP 200 only. +No processing, validation, or use of input parameters in responses. +CSRF exempt so frontend (e.g. localhost:3000) can call POST/PATCH without token. +""" + +from django.http import JsonResponse +from django.utils.decorators import method_decorator +from django.views import View +from django.views.decorators.csrf import csrf_exempt + +from .mock_data import CONFIG_SLIDERS_ONLY, START_RESPONSE_DATA, STATE_RESPONSE_DATA +from .serializers import success_response, success_with_data + + +@method_decorator(csrf_exempt, name="dispatch") +class ConfigView(View): + """ + GET endpoint for simulator configuration (ورود). + + Purpose: + Returns only sliders (min, max, step, unit, label, default_value, icon). + Used when loading/entering the simulator page. + + Input parameters: + None. Query parameters, if sent, are not read or used. + + Response structure: + - status: string, always "success". + - data: object with key "sliders" (array of slider configs). + + No processing or validation is performed on inputs. + """ + + def get(self, request): + return JsonResponse(success_with_data(CONFIG_SLIDERS_ONLY), status=200) + + +@method_decorator(csrf_exempt, name="dispatch") +class StateView(View): + """ + GET endpoint for plant state, progress, and chart history. + + Purpose: + Returns static plant state (height, leaves_count, branches_count, + fruits_count, yield, yield_rate, tick, is_healthy, can_continue), + progress (growth_progress, light_status, water_status, yield_progress, + yield_current, yield_rate_current), and chart (labels, height_history, + leaf_history, yield_history, yield_rate_history). Used during or after + simulation for UI and chart. + + Input parameters: + None. Query parameters, if sent, are not read or used. + + Response structure: + - status: string, always "success". + - data: object with keys "plant", "progress", "chart" per + PLANT_SIMULATOR_API.md §3. + + No processing or validation is performed on inputs. + """ + + def get(self, request): + return JsonResponse(success_with_data(STATE_RESPONSE_DATA), status=200) + + +@method_decorator(csrf_exempt, name="dispatch") +class StartView(View): + """ + POST endpoint to start simulation. + + Purpose: + Returns constants, chart config, plant state, progress, and chart + history. Body may contain environment and growth_speed; not read or used. + + Input parameters: + - body (optional): JSON. May contain "environment" and "growth_speed". + Location: body. Not read or validated; not used in response. + + Response structure: + - status: string, always "success". + - data: object with "constants", "chart", "plant", "progress", "chart_history". + + No processing or validation is performed on inputs. + """ + + def post(self, request): + return JsonResponse(success_with_data(START_RESPONSE_DATA), status=200) + + +@method_decorator(csrf_exempt, name="dispatch") +class StopView(View): + """ + POST endpoint to stop simulation. + + Purpose: + Accepts stop request. Returns success only. No processing performed. + Body may be empty or contain session_id; not read or used. + + Input parameters: + - body (optional): JSON or empty. Location: body. Not read or used. + + Response structure: + - status: string, always "success". + No "data" field. + + No processing or validation is performed on inputs. + """ + + def post(self, request): + return JsonResponse(success_response(), status=200) + + +@method_decorator(csrf_exempt, name="dispatch") +class ResetView(View): + """ + POST endpoint to reset simulation. + + Purpose: + Accepts reset request. Returns success only. No processing performed. + Body may be empty or contain session_id; not read or used. + + Input parameters: + - body (optional): JSON or empty. Location: body. Not read or used. + + Response structure: + - status: string, always "success". + No "data" field. + + No processing or validation is performed on inputs. + """ + + def post(self, request): + return JsonResponse(success_response(), status=200) + + +@method_decorator(csrf_exempt, name="dispatch") +class EnvironmentView(View): + """ + PATCH endpoint to update environment (slider values). + + Purpose: + Accepts environment update. Returns success only. No processing + performed. Body may contain environment and growth_speed; not + read or used in the response. + + Input parameters: + - body (optional): JSON. May contain "environment" (object) + and "growth_speed" (number). Location: body. Not read or used. + + Response structure: + - status: string, always "success". + No "data" field. + + No processing or validation is performed on inputs. + """ + + def patch(self, request): + return JsonResponse(success_response(), status=200) diff --git a/sensor_hub/__init__.py b/sensor_hub/__init__.py index e69de29..8b13789 100644 --- a/sensor_hub/__init__.py +++ b/sensor_hub/__init__.py @@ -0,0 +1 @@ +