UPDATE
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
# External API Adapter
|
||||
|
||||
## Settings
|
||||
|
||||
```python
|
||||
USE_EXTERNAL_API_MOCK = os.getenv("USE_EXTERNAL_API_MOCK", "false").lower() == "true"
|
||||
|
||||
EXTERNAL_SERVICES = {
|
||||
"ai": {
|
||||
"base_url": os.getenv("AI_SERVICE_BASE_URL", ""),
|
||||
"api_key": os.getenv("AI_SERVICE_API_KEY", ""),
|
||||
},
|
||||
"farm_hub": {
|
||||
"base_url": os.getenv("FARM_HUB_SERVICE_BASE_URL", ""),
|
||||
"api_key": os.getenv("FARM_HUB_SERVICE_API_KEY", ""),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from external_api_adapter import request
|
||||
|
||||
|
||||
class PredictionProxyView(APIView):
|
||||
def get(self, request_obj):
|
||||
adapter_response = request("ai", "/predict")
|
||||
return Response(adapter_response.data, status=adapter_response.status_code)
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
from .adapter import ExternalAPIAdapter, request
|
||||
|
||||
__all__ = ["ExternalAPIAdapter", "request"]
|
||||
@@ -0,0 +1,176 @@
|
||||
from dataclasses import dataclass, field
|
||||
import json
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
|
||||
from .exceptions import ExternalAPIRequestError
|
||||
from .exceptions import MockDirectoryNotFound, MockFileNotFound
|
||||
from .mock_loader import MockLoader
|
||||
from .services import ServiceRegistry
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdapterResponse:
|
||||
status_code: int
|
||||
data: object
|
||||
headers: dict = field(default_factory=dict)
|
||||
is_mock: bool = False
|
||||
|
||||
|
||||
class ExternalAPIAdapter:
|
||||
def __init__(self, service_registry=None, mock_loader=None):
|
||||
self.service_registry = service_registry or ServiceRegistry()
|
||||
self.mock_loader = mock_loader or MockLoader()
|
||||
|
||||
def request(self, service_name, path, method="GET", payload=None, query=None, headers=None):
|
||||
request_method = method.upper()
|
||||
self._validate_method(request_method)
|
||||
service = self.service_registry.get(service_name)
|
||||
logger.warning(
|
||||
"External API adapter request start: service=%s method=%s path=%s payload_type=%s payload_keys=%s query_keys=%s header_keys=%s",
|
||||
service_name,
|
||||
request_method,
|
||||
path,
|
||||
type(payload).__name__,
|
||||
sorted(payload.keys()) if isinstance(payload, dict) else None,
|
||||
sorted(query.keys()) if isinstance(query, dict) else None,
|
||||
sorted(headers.keys()) if isinstance(headers, dict) else None,
|
||||
)
|
||||
|
||||
use_mock = getattr(settings, "USE_EXTERNAL_API_MOCK", False) and service_name != "ai"
|
||||
if use_mock:
|
||||
try:
|
||||
mock_response = self.mock_loader.load(service_name=service_name, path=path, method=request_method)
|
||||
return AdapterResponse(
|
||||
status_code=mock_response.status_code,
|
||||
data=mock_response.data,
|
||||
headers={"X-Mock-File": mock_response.file_path},
|
||||
is_mock=True,
|
||||
)
|
||||
except (MockDirectoryNotFound, MockFileNotFound):
|
||||
pass
|
||||
|
||||
return self._call_real_api(
|
||||
service=service,
|
||||
path=path,
|
||||
method=request_method,
|
||||
payload=payload,
|
||||
query=query,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
def _call_real_api(self, service, path, method, payload=None, query=None, headers=None):
|
||||
base_url = service.get("base_url", "").rstrip("/")
|
||||
api_key = service.get("api_key", "")
|
||||
host_header = service.get("host_header", "").strip()
|
||||
if not base_url:
|
||||
raise ExternalAPIRequestError("External service base_url is not configured.")
|
||||
url = f"{base_url}/{str(path).lstrip('/')}"
|
||||
|
||||
files = None
|
||||
request_payload = self._make_json_safe(payload)
|
||||
request_query = self._make_json_safe(query)
|
||||
request_headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
if host_header:
|
||||
request_headers["Host"] = host_header
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
|
||||
if isinstance(payload, dict) and payload.get("__files__"):
|
||||
files = payload["__files__"]
|
||||
request_payload = {
|
||||
key: value
|
||||
for key, value in payload.items()
|
||||
if key != "__files__"
|
||||
}
|
||||
request_headers.pop("Content-Type", None)
|
||||
|
||||
try:
|
||||
request_kwargs = {
|
||||
"method": method,
|
||||
"url": url,
|
||||
"params": request_query,
|
||||
"headers": request_headers,
|
||||
"timeout": getattr(settings, "EXTERNAL_API_TIMEOUT", 30),
|
||||
}
|
||||
if files:
|
||||
request_kwargs["data"] = request_payload
|
||||
request_kwargs["files"] = files
|
||||
else:
|
||||
request_kwargs["json"] = request_payload
|
||||
|
||||
logger.warning(
|
||||
"External API adapter outbound request: method=%s url=%s has_files=%s json_keys=%s data_keys=%s timeout=%s",
|
||||
method,
|
||||
url,
|
||||
bool(files),
|
||||
sorted(request_payload.keys()) if isinstance(request_payload, dict) and not files else None,
|
||||
sorted(request_payload.keys()) if isinstance(request_payload, dict) and files else None,
|
||||
request_kwargs["timeout"],
|
||||
)
|
||||
|
||||
response = requests.request(
|
||||
**request_kwargs,
|
||||
)
|
||||
except requests.RequestException as exc:
|
||||
raise ExternalAPIRequestError(f"External API request failed for '{url}': {exc}") from exc
|
||||
|
||||
try:
|
||||
response_data = response.json()
|
||||
except ValueError:
|
||||
response_data = response.text
|
||||
|
||||
logger.warning(
|
||||
"External API adapter inbound response: method=%s url=%s status_code=%s response_type=%s response_keys=%s text_length=%s",
|
||||
method,
|
||||
url,
|
||||
response.status_code,
|
||||
type(response_data).__name__,
|
||||
sorted(response_data.keys()) if isinstance(response_data, dict) else None,
|
||||
len(response_data) if isinstance(response_data, str) else None,
|
||||
)
|
||||
logger.warning("Response : %s",response_data)
|
||||
|
||||
return AdapterResponse(
|
||||
status_code=response.status_code,
|
||||
data=response_data,
|
||||
headers=dict(response.headers),
|
||||
is_mock=False,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _validate_method(method):
|
||||
supported_methods = {"GET", "POST", "PUT", "DELETE"}
|
||||
if method not in supported_methods:
|
||||
raise ValueError(f"Unsupported HTTP method '{method}'. Supported methods: {sorted(supported_methods)}")
|
||||
|
||||
@staticmethod
|
||||
def _make_json_safe(value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# Match Django/DRF JSON rendering so UUID/date-like values can be forwarded safely.
|
||||
return json.loads(json.dumps(value, cls=DjangoJSONEncoder))
|
||||
|
||||
|
||||
_default_adapter = ExternalAPIAdapter()
|
||||
|
||||
|
||||
def request(service_name, path, method="GET", payload=None, query=None, headers=None):
|
||||
return _default_adapter.request(
|
||||
service_name=service_name,
|
||||
path=path,
|
||||
method=method,
|
||||
payload=payload,
|
||||
query=query,
|
||||
headers=headers,
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ExternalApiAdapterConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "external_api_adapter"
|
||||
verbose_name = "External API Adapter"
|
||||
@@ -0,0 +1,18 @@
|
||||
class ExternalAPIAdapterError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ServiceNotFound(ExternalAPIAdapterError):
|
||||
pass
|
||||
|
||||
|
||||
class MockDirectoryNotFound(ExternalAPIAdapterError):
|
||||
pass
|
||||
|
||||
|
||||
class MockFileNotFound(ExternalAPIAdapterError):
|
||||
pass
|
||||
|
||||
|
||||
class ExternalAPIRequestError(ExternalAPIAdapterError):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"code": 202,
|
||||
"msg": "dashboard task queued",
|
||||
"data": {
|
||||
"task_id": "dashboard-task-123",
|
||||
"status_url": "/api/dashboard-data/dashboard-task-123/status/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "پارامتر sensor_id الزامی است.",
|
||||
"data": null
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "dashboard-task-123",
|
||||
"status": "FAILURE",
|
||||
"error": "خطا در ساخت کارتهای داشبورد."
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "dashboard-task-123",
|
||||
"status": "PENDING",
|
||||
"message": "تسک در صف یا یافت نشد."
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "dashboard-task-123",
|
||||
"status": "PROGRESS",
|
||||
"progress": {
|
||||
"current": 5,
|
||||
"total": 15,
|
||||
"card": "sensorValuesList",
|
||||
"message": "processing sensorValuesList"
|
||||
}
|
||||
}
|
||||
}
|
||||
+611
@@ -0,0 +1,611 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "dashboard-task-123",
|
||||
"status": "SUCCESS",
|
||||
"result": {
|
||||
"sensor_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"all_cards": {
|
||||
"farmOverviewKpis": {
|
||||
"kpis": [
|
||||
{
|
||||
"id": "farm_health_score",
|
||||
"title": "امتیاز سلامت مزرعه",
|
||||
"subtitle": "تحلیل هوشمند",
|
||||
"stats": "87%",
|
||||
"avatarColor": "success",
|
||||
"avatarIcon": "tabler-heartbeat",
|
||||
"chipText": "خوب",
|
||||
"chipColor": "success"
|
||||
},
|
||||
{
|
||||
"id": "water_stress_index",
|
||||
"title": "شاخص تنش آبی",
|
||||
"subtitle": "فعلی",
|
||||
"stats": "12%",
|
||||
"avatarColor": "info",
|
||||
"avatarIcon": "tabler-droplet",
|
||||
"chipText": "پایین",
|
||||
"chipColor": "success"
|
||||
},
|
||||
{
|
||||
"id": "disease_risk",
|
||||
"title": "ریسک بیماری",
|
||||
"subtitle": "۷ روز اخیر",
|
||||
"stats": "پایین",
|
||||
"avatarColor": "success",
|
||||
"avatarIcon": "tabler-bug",
|
||||
"chipText": "5%",
|
||||
"chipColor": "success"
|
||||
},
|
||||
{
|
||||
"id": "avg_soil_moisture",
|
||||
"title": "میانگین رطوبت خاک",
|
||||
"subtitle": "کل مزرعه",
|
||||
"stats": "65%",
|
||||
"avatarColor": "primary",
|
||||
"avatarIcon": "tabler-plant-2",
|
||||
"chipText": "بهینه",
|
||||
"chipColor": "success"
|
||||
},
|
||||
{
|
||||
"id": "yield_prediction",
|
||||
"title": "پیشبینی عملکرد",
|
||||
"subtitle": "این فصل",
|
||||
"stats": "42 تن",
|
||||
"avatarColor": "secondary",
|
||||
"avatarIcon": "tabler-chart-bar",
|
||||
"chipText": "+8%",
|
||||
"chipColor": "success"
|
||||
},
|
||||
{
|
||||
"id": "pest_risk",
|
||||
"title": "ریسک آفات",
|
||||
"subtitle": "پیشبینی هوشمند",
|
||||
"stats": "15%",
|
||||
"avatarColor": "warning",
|
||||
"avatarIcon": "tabler-bug-off",
|
||||
"chipText": "تحت نظر",
|
||||
"chipColor": "warning"
|
||||
}
|
||||
]
|
||||
},
|
||||
"farmWeatherCard": {
|
||||
"condition": "صاف",
|
||||
"temperature": 24,
|
||||
"unit": "°C",
|
||||
"humidity": 45,
|
||||
"windSpeed": 12,
|
||||
"windUnit": "km/h",
|
||||
"chartData": {
|
||||
"labels": [
|
||||
"۶ صبح",
|
||||
"۹ صبح",
|
||||
"۱۲ ظهر",
|
||||
"۳ بعدازظهر",
|
||||
"۶ عصر",
|
||||
"۹ شب",
|
||||
"۱۲ شب"
|
||||
],
|
||||
"series": [
|
||||
[
|
||||
18,
|
||||
22,
|
||||
26,
|
||||
28,
|
||||
25,
|
||||
20,
|
||||
18
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"farmAlertsTracker": {
|
||||
"totalAlerts": 3,
|
||||
"radialBarValue": 30,
|
||||
"alertStats": [
|
||||
{
|
||||
"title": "کمبود آب",
|
||||
"count": "2",
|
||||
"avatarColor": "error",
|
||||
"avatarIcon": "tabler-droplet-half-2"
|
||||
},
|
||||
{
|
||||
"title": "ریسک قارچی",
|
||||
"count": "1",
|
||||
"avatarColor": "warning",
|
||||
"avatarIcon": "tabler-mushroom"
|
||||
},
|
||||
{
|
||||
"title": "هشدار یخبندان",
|
||||
"count": "0",
|
||||
"avatarColor": "info",
|
||||
"avatarIcon": "tabler-snowflake"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sensorValuesList": {
|
||||
"sensors": [
|
||||
{
|
||||
"title": "28°C",
|
||||
"subtitle": "دمای هوا",
|
||||
"trendNumber": 2.1,
|
||||
"trend": "positive",
|
||||
"unit": "°C"
|
||||
},
|
||||
{
|
||||
"title": "24°C",
|
||||
"subtitle": "دمای خاک",
|
||||
"trendNumber": -0.5,
|
||||
"trend": "negative",
|
||||
"unit": "°C"
|
||||
},
|
||||
{
|
||||
"title": "65%",
|
||||
"subtitle": "رطوبت هوا",
|
||||
"trendNumber": 3.2,
|
||||
"trend": "positive",
|
||||
"unit": "%"
|
||||
},
|
||||
{
|
||||
"title": "42%",
|
||||
"subtitle": "رطوبت خاک (۱۰ سانتیمتر)",
|
||||
"trendNumber": -1.8,
|
||||
"trend": "negative",
|
||||
"unit": "%"
|
||||
},
|
||||
{
|
||||
"title": "6.8",
|
||||
"subtitle": "pH خاک",
|
||||
"trendNumber": 0.2,
|
||||
"trend": "positive",
|
||||
"unit": "pH"
|
||||
},
|
||||
{
|
||||
"title": "1.2",
|
||||
"subtitle": "هدایت الکتریکی (dS/m)",
|
||||
"trendNumber": 0.1,
|
||||
"trend": "positive",
|
||||
"unit": "dS/m"
|
||||
},
|
||||
{
|
||||
"title": "850",
|
||||
"subtitle": "شدت نور (لوکس)",
|
||||
"trendNumber": 15.3,
|
||||
"trend": "positive",
|
||||
"unit": "lux"
|
||||
},
|
||||
{
|
||||
"title": "12",
|
||||
"subtitle": "سرعت باد (کیلومتر/ساعت)",
|
||||
"trendNumber": -2.4,
|
||||
"trend": "negative",
|
||||
"unit": "km/h"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sensorRadarChart": {
|
||||
"labels": [
|
||||
"دما",
|
||||
"رطوبت",
|
||||
"pH",
|
||||
"هدایت الکتریکی",
|
||||
"نور",
|
||||
"باد"
|
||||
],
|
||||
"series": [
|
||||
{
|
||||
"name": "امروز",
|
||||
"data": [
|
||||
75,
|
||||
65,
|
||||
80,
|
||||
70,
|
||||
85,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ایدهآل",
|
||||
"data": [
|
||||
80,
|
||||
70,
|
||||
75,
|
||||
75,
|
||||
90,
|
||||
50
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"sensorComparisonChart": {
|
||||
"currentValue": 48,
|
||||
"vsLastWeek": "+5%",
|
||||
"vsLastWeekValue": 5,
|
||||
"categories": [
|
||||
"دوشنبه",
|
||||
"سهشنبه",
|
||||
"چهارشنبه",
|
||||
"پنجشنبه",
|
||||
"جمعه",
|
||||
"شنبه",
|
||||
"یکشنبه"
|
||||
],
|
||||
"series": [
|
||||
{
|
||||
"name": "امروز",
|
||||
"data": [
|
||||
42,
|
||||
45,
|
||||
48,
|
||||
52,
|
||||
50,
|
||||
48,
|
||||
46
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "هفته قبل",
|
||||
"data": [
|
||||
38,
|
||||
40,
|
||||
42,
|
||||
45,
|
||||
43,
|
||||
40,
|
||||
38
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"anomalyDetectionCard": {
|
||||
"anomalies": [
|
||||
{
|
||||
"sensor": "رطوبت خاک زون ۳",
|
||||
"value": "38%",
|
||||
"expected": "45-65%",
|
||||
"deviation": "-12%",
|
||||
"severity": "warning"
|
||||
},
|
||||
{
|
||||
"sensor": "pH بخش ۲",
|
||||
"value": "5.2",
|
||||
"expected": "6.0-7.0",
|
||||
"deviation": "-0.8",
|
||||
"severity": "error"
|
||||
}
|
||||
]
|
||||
},
|
||||
"farmAlertsTimeline": {
|
||||
"alerts": [
|
||||
{
|
||||
"title": "ریسک کمبود آب",
|
||||
"description": "رطوبت خاک در عمق ۱۰ سانتیمتر (۴۲٪) کمتر از حد بهینه است. پیشبینی: در صورت عدم آبیاری، تنش طی ۲ تا ۳ روز. توصیه: آبیاری ظرف ۲۴ ساعت.",
|
||||
"time": "۱۵ دقیقه پیش",
|
||||
"color": "warning"
|
||||
},
|
||||
{
|
||||
"title": "ریسک بیماری قارچی",
|
||||
"description": "رطوبت بالا (۶۵٪) و دمای ۲۴ درجه شرایط مساعد برای رشد قارچ. استفاده از قارچکش پیشگیرانه یا کاهش آبیاری را در نظر بگیرید.",
|
||||
"time": "۱ ساعت پیش",
|
||||
"color": "error"
|
||||
},
|
||||
{
|
||||
"title": "پیشنهاد آبیاری",
|
||||
"description": "بازه بهینه آبیاری: ۶:۰۰ تا ۸:۰۰ صبح. حجم پیشنهادی: ۴۵۰ مترمکعب برای زون آ. بهبود راندمان مورد انتظار: ۱۲٪.",
|
||||
"time": "۲ ساعت پیش",
|
||||
"color": "info"
|
||||
},
|
||||
{
|
||||
"title": "بررسی شوری خاک",
|
||||
"description": "مقدار هدایت الکتریکی ۱/۲ dS/m در محدوده مجاز است. نیازی به اقدام نیست. بررسی بعدی توصیه میشود ظرف ۵ روز.",
|
||||
"time": "۴ ساعت پیش",
|
||||
"color": "success"
|
||||
}
|
||||
]
|
||||
},
|
||||
"waterNeedPrediction": {
|
||||
"totalNext7Days": 3290,
|
||||
"unit": "m³",
|
||||
"categories": [
|
||||
"روز ۱",
|
||||
"روز ۲",
|
||||
"روز ۳",
|
||||
"روز ۴",
|
||||
"روز ۵",
|
||||
"روز ۶",
|
||||
"روز ۷"
|
||||
],
|
||||
"series": [
|
||||
{
|
||||
"name": "نیاز آبی",
|
||||
"data": [
|
||||
420,
|
||||
450,
|
||||
480,
|
||||
460,
|
||||
490,
|
||||
510,
|
||||
480
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"harvestPredictionCard": {
|
||||
"date": "2025-10-15",
|
||||
"dateFormatted": "۱۵ اکتبر ۲۰۲۵",
|
||||
"daysUntil": 58,
|
||||
"description": "بر اساس تجمع GDD فعلی و پیشبینی آب و هوا. بازه بهینه برداشت: ۱۲ تا ۱۸ اکتبر.",
|
||||
"optimalWindowStart": "2025-10-12",
|
||||
"optimalWindowEnd": "2025-10-18"
|
||||
},
|
||||
"yieldPredictionChart": {
|
||||
"categories": [
|
||||
"ژانویه",
|
||||
"فوریه",
|
||||
"مارس",
|
||||
"آوریل",
|
||||
"می",
|
||||
"ژوئن",
|
||||
"ژوئیه",
|
||||
"آگوست",
|
||||
"سپتامبر",
|
||||
"اکتبر",
|
||||
"نوامبر",
|
||||
"دسامبر"
|
||||
],
|
||||
"series": [
|
||||
{
|
||||
"name": "امسال",
|
||||
"data": [
|
||||
35,
|
||||
38,
|
||||
40,
|
||||
42,
|
||||
45,
|
||||
48,
|
||||
50,
|
||||
48,
|
||||
46,
|
||||
44,
|
||||
42,
|
||||
42
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "سال گذشته",
|
||||
"data": [
|
||||
32,
|
||||
34,
|
||||
36,
|
||||
38,
|
||||
40,
|
||||
42,
|
||||
44,
|
||||
42,
|
||||
40,
|
||||
38,
|
||||
36,
|
||||
38
|
||||
]
|
||||
}
|
||||
],
|
||||
"summary": [
|
||||
{
|
||||
"title": "عملکرد پیشبینیشده",
|
||||
"subtitle": "این فصل",
|
||||
"amount": "42 تن",
|
||||
"avatarColor": "primary",
|
||||
"avatarIcon": "tabler-chart-bar"
|
||||
},
|
||||
{
|
||||
"title": "تاریخ برداشت",
|
||||
"subtitle": "حدود ۱۵ اکتبر",
|
||||
"amount": "+8%",
|
||||
"avatarColor": "success",
|
||||
"avatarIcon": "tabler-calendar"
|
||||
}
|
||||
]
|
||||
},
|
||||
"soilMoistureHeatmap": {
|
||||
"zones": [
|
||||
"زون ۱",
|
||||
"زون ۲",
|
||||
"زون ۳",
|
||||
"زون ۴",
|
||||
"زون ۵",
|
||||
"زون ۶",
|
||||
"زون ۷"
|
||||
],
|
||||
"hours": [
|
||||
"۶ ص",
|
||||
"۸ ص",
|
||||
"۱۰ ص",
|
||||
"۱۲ ظ",
|
||||
"۱۴ ع",
|
||||
"۱۶ ع",
|
||||
"۱۸ ع"
|
||||
],
|
||||
"series": [
|
||||
{
|
||||
"name": "زون ۱",
|
||||
"data": [
|
||||
{
|
||||
"x": "۶ ص",
|
||||
"y": 52
|
||||
},
|
||||
{
|
||||
"x": "۸ ص",
|
||||
"y": 48
|
||||
},
|
||||
{
|
||||
"x": "۱۰ ص",
|
||||
"y": 55
|
||||
},
|
||||
{
|
||||
"x": "۱۲ ظ",
|
||||
"y": 60
|
||||
},
|
||||
{
|
||||
"x": "۱۴ ع",
|
||||
"y": 58
|
||||
},
|
||||
{
|
||||
"x": "۱۶ ع",
|
||||
"y": 54
|
||||
},
|
||||
{
|
||||
"x": "۱۸ ع",
|
||||
"y": 50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "زون ۲",
|
||||
"data": [
|
||||
{
|
||||
"x": "۶ ص",
|
||||
"y": 45
|
||||
},
|
||||
{
|
||||
"x": "۸ ص",
|
||||
"y": 42
|
||||
},
|
||||
{
|
||||
"x": "۱۰ ص",
|
||||
"y": 48
|
||||
},
|
||||
{
|
||||
"x": "۱۲ ظ",
|
||||
"y": 52
|
||||
},
|
||||
{
|
||||
"x": "۱۴ ع",
|
||||
"y": 50
|
||||
},
|
||||
{
|
||||
"x": "۱۶ ع",
|
||||
"y": 47
|
||||
},
|
||||
{
|
||||
"x": "۱۸ ع",
|
||||
"y": 44
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ndviHealthCard": {
|
||||
"ndviIndex": 0.78,
|
||||
"healthData": [
|
||||
{
|
||||
"title": "تنش نیتروژن",
|
||||
"value": "پایین",
|
||||
"color": "success",
|
||||
"icon": "tabler-leaf"
|
||||
},
|
||||
{
|
||||
"title": "سلامت محصول",
|
||||
"value": "خوب",
|
||||
"color": "success",
|
||||
"icon": "tabler-plant"
|
||||
}
|
||||
]
|
||||
},
|
||||
"recommendationsList": {
|
||||
"recommendations": [
|
||||
{
|
||||
"title": "آبیاری: ۶:۰۰ تا ۸:۰۰ صبح",
|
||||
"subtitle": "۴۵۰ مترمکعب برای زون آ. بدون آبیاری، عملکرد ممکن است حدود ۸٪ کاهش یابد.",
|
||||
"avatarIcon": "tabler-droplet",
|
||||
"avatarColor": "primary"
|
||||
},
|
||||
{
|
||||
"title": "کود: NPK 20-20-20",
|
||||
"subtitle": "اعمال ۲۵ کیلوگرم در هکتار ظرف ۷ روز. کمبود نیتروژن فعلی در بخش ۲.",
|
||||
"avatarIcon": "tabler-leaf",
|
||||
"avatarColor": "success"
|
||||
},
|
||||
{
|
||||
"title": "قارچکش: پیشگیرانه",
|
||||
"subtitle": "رطوبت و دما مساعد قارچ. سمپاشی بر پایه مس را در نظر بگیرید.",
|
||||
"avatarIcon": "tabler-mushroom",
|
||||
"avatarColor": "warning"
|
||||
},
|
||||
{
|
||||
"title": "بازه برداشت: ۱۲ تا ۱۸ اکتبر",
|
||||
"subtitle": "اوج رسیدگی حدود ۱۵ اکتبر. نیروی کار را متناسب برنامهریزی کنید.",
|
||||
"avatarIcon": "tabler-calendar-event",
|
||||
"avatarColor": "info"
|
||||
}
|
||||
]
|
||||
},
|
||||
"economicOverview": {
|
||||
"economicData": [
|
||||
{
|
||||
"title": "هزینه آب",
|
||||
"value": "€720",
|
||||
"subtitle": "این ماه",
|
||||
"avatarIcon": "tabler-droplet",
|
||||
"avatarColor": "primary"
|
||||
},
|
||||
{
|
||||
"title": "صرفهجویی آب هوشمند",
|
||||
"value": "€156",
|
||||
"subtitle": "۱۸٪ صرفهجویی شده",
|
||||
"avatarIcon": "tabler-bulb",
|
||||
"avatarColor": "success"
|
||||
},
|
||||
{
|
||||
"title": "بازده سرمایه پلتفرم",
|
||||
"value": "127%",
|
||||
"subtitle": "نسبت به سال گذشته",
|
||||
"avatarIcon": "tabler-chart-line",
|
||||
"avatarColor": "info"
|
||||
},
|
||||
{
|
||||
"title": "پیشبینی درآمد",
|
||||
"value": "€42k",
|
||||
"subtitle": "این فصل",
|
||||
"avatarIcon": "tabler-currency-euro",
|
||||
"avatarColor": "success"
|
||||
}
|
||||
],
|
||||
"chartSeries": [
|
||||
{
|
||||
"name": "هزینه آب",
|
||||
"data": [
|
||||
120,
|
||||
115,
|
||||
110,
|
||||
125,
|
||||
118,
|
||||
122
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "کود",
|
||||
"data": [
|
||||
80,
|
||||
85,
|
||||
90,
|
||||
75,
|
||||
82,
|
||||
78
|
||||
]
|
||||
}
|
||||
],
|
||||
"chartCategories": [
|
||||
"ژانویه",
|
||||
"فوریه",
|
||||
"مارس",
|
||||
"آوریل",
|
||||
"می",
|
||||
"ژوئن"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"code": 202,
|
||||
"msg": "تسک توصیه کودهی در صف قرار گرفت.",
|
||||
"data": {
|
||||
"task_id": "fert-task-123",
|
||||
"status_url": "/api/fertilization/recommend/fert-task-123/status/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"farm_uuid": [
|
||||
"This field is required."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "fert-task-123",
|
||||
"status": "FAILURE",
|
||||
"error": "خطا در دریافت توصیه کودهی."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "fert-task-123",
|
||||
"status": "PENDING",
|
||||
"message": "تسک در صف یا یافت نشد."
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "fert-task-123",
|
||||
"status": "PROGRESS",
|
||||
"progress": {
|
||||
"message": "در حال پردازش توصیه کودهی..."
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "fert-task-123",
|
||||
"status": "SUCCESS",
|
||||
"result": {
|
||||
"plan": {
|
||||
"npkRatio": "20-20-20",
|
||||
"amountPerHectare": "150 kg/ha",
|
||||
"applicationMethod": "کودآبیاری در دو نوبت",
|
||||
"applicationInterval": "هر ۱۰ روز",
|
||||
"reasoning": "نیتروژن و پتاسیم خاک در محدوده متوسط است و گیاه در فاز رویشی نیاز تغذیهای بالاتری دارد."
|
||||
},
|
||||
"raw_response": "{\"plan\":{\"npkRatio\":\"20-20-20\"}}",
|
||||
"status": "completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,892 @@
|
||||
[
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/dashboard-data/generate/",
|
||||
"status_code": 202,
|
||||
"description": "Dashboard data task queued",
|
||||
"file": "json/mock_data/dashboard-data/generate/post_202.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/dashboard-data/generate/",
|
||||
"status_code": 400,
|
||||
"description": "Missing sensor_id",
|
||||
"file": "json/mock_data/dashboard-data/generate/post_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/dashboard-data/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Pending dashboard task",
|
||||
"file": "json/mock_data/dashboard-data/status/get_200_pending.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/dashboard-data/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Dashboard task in progress",
|
||||
"file": "json/mock_data/dashboard-data/status/get_200_progress.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/dashboard-data/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Successful dashboard task",
|
||||
"file": "json/mock_data/dashboard-data/status/get_200_success.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/dashboard-data/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Failed dashboard task",
|
||||
"file": "json/mock_data/dashboard-data/status/get_200_failure.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/fertilization/recommend/",
|
||||
"status_code": 202,
|
||||
"description": "Fertilization task queued",
|
||||
"file": "json/mock_data/fertilization/recommend/post_202.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/fertilization/recommend/",
|
||||
"status_code": 400,
|
||||
"description": "Validation error",
|
||||
"file": "json/mock_data/fertilization/recommend/post_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/fertilization/recommend/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Fertilization status pending",
|
||||
"file": "json/mock_data/fertilization/status/get_200_pending.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/fertilization/recommend/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Fertilization status progress",
|
||||
"file": "json/mock_data/fertilization/status/get_200_progress.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/fertilization/recommend/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Fertilization status success",
|
||||
"file": "json/mock_data/fertilization/status/get_200_success.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/fertilization/recommend/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Fertilization status failure",
|
||||
"file": "json/mock_data/fertilization/status/get_200_failure.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/irrigation/",
|
||||
"status_code": 200,
|
||||
"description": "List irrigation methods",
|
||||
"file": "json/mock_data/irrigation/methods/get_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/irrigation/",
|
||||
"status_code": 201,
|
||||
"description": "Create irrigation method",
|
||||
"file": "json/mock_data/irrigation/methods/post_201.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/irrigation/",
|
||||
"status_code": 400,
|
||||
"description": "Irrigation create validation error",
|
||||
"file": "json/mock_data/irrigation/methods/post_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/irrigation/recommend/",
|
||||
"status_code": 202,
|
||||
"description": "Irrigation recommendation task queued",
|
||||
"file": "json/mock_data/irrigation/recommend/post_202.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/irrigation/recommend/",
|
||||
"status_code": 400,
|
||||
"description": "Irrigation recommendation validation error",
|
||||
"file": "json/mock_data/irrigation/recommend/post_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/irrigation/recommend/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Irrigation recommendation status pending",
|
||||
"file": "json/mock_data/irrigation/recommend/status/get_200_pending.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/irrigation/recommend/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Irrigation recommendation status progress",
|
||||
"file": "json/mock_data/irrigation/recommend/status/get_200_progress.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/irrigation/recommend/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Irrigation recommendation status success",
|
||||
"file": "json/mock_data/irrigation/recommend/status/get_200_success.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/irrigation/recommend/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Irrigation recommendation status failure",
|
||||
"file": "json/mock_data/irrigation/recommend/status/get_200_failure.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/irrigation/{pk}/",
|
||||
"status_code": 200,
|
||||
"description": "Irrigation method get success",
|
||||
"file": "json/mock_data/irrigation/method-detail/get_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/irrigation/{pk}/",
|
||||
"status_code": 404,
|
||||
"description": "Irrigation method get not found",
|
||||
"file": "json/mock_data/irrigation/method-detail/get_404.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/irrigation/{pk}/",
|
||||
"status_code": 200,
|
||||
"description": "Irrigation method put success",
|
||||
"file": "json/mock_data/irrigation/method-detail/put_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/irrigation/{pk}/",
|
||||
"status_code": 400,
|
||||
"description": "Irrigation method put validation error",
|
||||
"file": "json/mock_data/irrigation/method-detail/put_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/irrigation/{pk}/",
|
||||
"status_code": 404,
|
||||
"description": "Irrigation method put not found",
|
||||
"file": "json/mock_data/irrigation/method-detail/put_404.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/irrigation/{pk}/",
|
||||
"status_code": 200,
|
||||
"description": "Irrigation method patch success",
|
||||
"file": "json/mock_data/irrigation/method-detail/patch_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/irrigation/{pk}/",
|
||||
"status_code": 400,
|
||||
"description": "Irrigation method patch validation error",
|
||||
"file": "json/mock_data/irrigation/method-detail/patch_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/irrigation/{pk}/",
|
||||
"status_code": 404,
|
||||
"description": "Irrigation method patch not found",
|
||||
"file": "json/mock_data/irrigation/method-detail/patch_404.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/api/irrigation/{pk}/",
|
||||
"status_code": 200,
|
||||
"description": "Delete irrigation method",
|
||||
"file": "json/mock_data/irrigation/method-detail/delete_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/api/irrigation/{pk}/",
|
||||
"status_code": 404,
|
||||
"description": "Delete irrigation method not found",
|
||||
"file": "json/mock_data/irrigation/method-detail/delete_404.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/soil-data/",
|
||||
"status_code": 200,
|
||||
"description": "Soil data served from database",
|
||||
"file": "json/mock_data/soil-data/get_200_database.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/soil-data/",
|
||||
"status_code": 202,
|
||||
"description": "Soil data fetch task queued",
|
||||
"file": "json/mock_data/soil-data/get_202_queued.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/soil-data/",
|
||||
"status_code": 400,
|
||||
"description": "Soil data validation error",
|
||||
"file": "json/mock_data/soil-data/get_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/soil-data/",
|
||||
"status_code": 200,
|
||||
"description": "Soil data POST served from database",
|
||||
"file": "json/mock_data/soil-data/post_200_database.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/soil-data/",
|
||||
"status_code": 202,
|
||||
"description": "Soil data POST task queued",
|
||||
"file": "json/mock_data/soil-data/post_202_queued.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/soil-data/",
|
||||
"status_code": 400,
|
||||
"description": "Soil data POST validation error",
|
||||
"file": "json/mock_data/soil-data/post_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/soil-data/tasks/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Soil task status pending",
|
||||
"file": "json/mock_data/soil-data/status/get_200_pending.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/soil-data/tasks/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Soil task status progress",
|
||||
"file": "json/mock_data/soil-data/status/get_200_progress.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/soil-data/tasks/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Soil task status success",
|
||||
"file": "json/mock_data/soil-data/status/get_200_success.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/soil-data/tasks/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Soil task status failure",
|
||||
"file": "json/mock_data/soil-data/status/get_200_failure.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/plants/",
|
||||
"status_code": 200,
|
||||
"description": "List plants",
|
||||
"file": "json/mock_data/plant/list-get_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/plants/",
|
||||
"status_code": 201,
|
||||
"description": "Create plant",
|
||||
"file": "json/mock_data/plant/create-post_201.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/plants/",
|
||||
"status_code": 400,
|
||||
"description": "Plant create validation error",
|
||||
"file": "json/mock_data/plant/create-post_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/plants/{pk}/",
|
||||
"status_code": 200,
|
||||
"description": "Plant detail get success",
|
||||
"file": "json/mock_data/plant/detail-get_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/plants/{pk}/",
|
||||
"status_code": 404,
|
||||
"description": "Plant detail get not found",
|
||||
"file": "json/mock_data/plant/detail-get_404.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/plants/{pk}/",
|
||||
"status_code": 200,
|
||||
"description": "Plant detail put success",
|
||||
"file": "json/mock_data/plant/detail-put_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/plants/{pk}/",
|
||||
"status_code": 400,
|
||||
"description": "Plant detail put validation error",
|
||||
"file": "json/mock_data/plant/detail-put_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/plants/{pk}/",
|
||||
"status_code": 404,
|
||||
"description": "Plant detail put not found",
|
||||
"file": "json/mock_data/plant/detail-put_404.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/plants/{pk}/",
|
||||
"status_code": 200,
|
||||
"description": "Plant detail patch success",
|
||||
"file": "json/mock_data/plant/detail-patch_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/plants/{pk}/",
|
||||
"status_code": 400,
|
||||
"description": "Plant detail patch validation error",
|
||||
"file": "json/mock_data/plant/detail-patch_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/plants/{pk}/",
|
||||
"status_code": 404,
|
||||
"description": "Plant detail patch not found",
|
||||
"file": "json/mock_data/plant/detail-patch_404.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/api/plants/{pk}/",
|
||||
"status_code": 200,
|
||||
"description": "Delete plant success",
|
||||
"file": "json/mock_data/plant/detail-delete_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/api/plants/{pk}/",
|
||||
"status_code": 404,
|
||||
"description": "Delete plant not found",
|
||||
"file": "json/mock_data/plant/detail-delete_404.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/plants/fetch-info/",
|
||||
"status_code": 200,
|
||||
"description": "Fetch plant info success",
|
||||
"file": "json/mock_data/plant/fetch-info-post_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/plants/fetch-info/",
|
||||
"status_code": 400,
|
||||
"description": "Fetch plant info missing name",
|
||||
"file": "json/mock_data/plant/fetch-info-post_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/plants/fetch-info/",
|
||||
"status_code": 503,
|
||||
"description": "Fetch plant info service unavailable",
|
||||
"file": "json/mock_data/plant/fetch-info-post_503.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/rag/chat/",
|
||||
"status_code": 200,
|
||||
"description": "RAG chat streaming response",
|
||||
"file": "json/mock_data/rag/chat-post_200_stream.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/rag/chat/",
|
||||
"status_code": 400,
|
||||
"description": "Missing query",
|
||||
"file": "json/mock_data/rag/chat-post_400_missing_query.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/rag/chat/",
|
||||
"status_code": 400,
|
||||
"description": "Invalid service id",
|
||||
"file": "json/mock_data/rag/chat-post_400_invalid_service.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/rag/chat/",
|
||||
"status_code": 400,
|
||||
"description": "Missing user_id for service",
|
||||
"file": "json/mock_data/rag/chat-post_400_missing_user.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/rag/recommend/irrigation/",
|
||||
"status_code": 202,
|
||||
"description": "RAG irrigation task queued",
|
||||
"file": "json/mock_data/rag/irrigation/post_202.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/rag/recommend/irrigation/",
|
||||
"status_code": 400,
|
||||
"description": "RAG irrigation validation error",
|
||||
"file": "json/mock_data/rag/irrigation/post_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/rag/recommend/irrigation/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "RAG irrigation status pending",
|
||||
"file": "json/mock_data/rag/irrigation/status/get_200_pending.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/rag/recommend/irrigation/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "RAG irrigation status progress",
|
||||
"file": "json/mock_data/rag/irrigation/status/get_200_progress.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/rag/recommend/irrigation/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "RAG irrigation status success",
|
||||
"file": "json/mock_data/rag/irrigation/status/get_200_success.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/rag/recommend/irrigation/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "RAG irrigation status failure",
|
||||
"file": "json/mock_data/rag/irrigation/status/get_200_failure.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/rag/recommend/fertilization/",
|
||||
"status_code": 202,
|
||||
"description": "RAG fertilization task queued",
|
||||
"file": "json/mock_data/rag/fertilization/post_202.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/rag/recommend/fertilization/",
|
||||
"status_code": 400,
|
||||
"description": "RAG fertilization validation error",
|
||||
"file": "json/mock_data/rag/fertilization/post_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/rag/recommend/fertilization/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "RAG fertilization status pending",
|
||||
"file": "json/mock_data/rag/fertilization/status/get_200_pending.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/rag/recommend/fertilization/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "RAG fertilization status progress",
|
||||
"file": "json/mock_data/rag/fertilization/status/get_200_progress.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/rag/recommend/fertilization/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "RAG fertilization status success",
|
||||
"file": "json/mock_data/rag/fertilization/status/get_200_success.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/rag/recommend/fertilization/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "RAG fertilization status failure",
|
||||
"file": "json/mock_data/rag/fertilization/status/get_200_failure.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/sensor-data/{farm_uuid}/",
|
||||
"status_code": 200,
|
||||
"description": "Sensor update put success",
|
||||
"file": "json/mock_data/sensor-data/update-put_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/sensor-data/{farm_uuid}/",
|
||||
"status_code": 400,
|
||||
"description": "Sensor update put validation error",
|
||||
"file": "json/mock_data/sensor-data/update-put_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/sensor-data/{farm_uuid}/",
|
||||
"status_code": 404,
|
||||
"description": "Sensor update put location not found",
|
||||
"file": "json/mock_data/sensor-data/update-put_404.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/sensor-data/{farm_uuid}/",
|
||||
"status_code": 200,
|
||||
"description": "Sensor update patch success",
|
||||
"file": "json/mock_data/sensor-data/update-patch_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/sensor-data/{farm_uuid}/",
|
||||
"status_code": 400,
|
||||
"description": "Sensor update patch validation error",
|
||||
"file": "json/mock_data/sensor-data/update-patch_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/sensor-data/{farm_uuid}/",
|
||||
"status_code": 404,
|
||||
"description": "Sensor update patch location not found",
|
||||
"file": "json/mock_data/sensor-data/update-patch_404.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/sensor-data/parameters/",
|
||||
"status_code": 201,
|
||||
"description": "Create sensor parameter",
|
||||
"file": "json/mock_data/sensor-data/parameters-post_201.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/sensor-data/parameters/",
|
||||
"status_code": 400,
|
||||
"description": "Sensor parameter validation error",
|
||||
"file": "json/mock_data/sensor-data/parameters-post_400.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/tasks/",
|
||||
"status_code": 200,
|
||||
"description": "Task trigger success",
|
||||
"file": "json/mock_data/tasks/post_200.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/tasks/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Task status pending",
|
||||
"file": "json/mock_data/tasks/status/get_200_pending.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/tasks/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Task status progress",
|
||||
"file": "json/mock_data/tasks/status/get_200_progress.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/tasks/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Task status success",
|
||||
"file": "json/mock_data/tasks/status/get_200_success.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/tasks/{task_id}/status/",
|
||||
"status_code": 200,
|
||||
"description": "Task status failure",
|
||||
"file": "json/mock_data/tasks/status/get_200_failure.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/pest-detection/risk-summary/",
|
||||
"status_code": 200,
|
||||
"description": "Pest and disease risk summary success",
|
||||
"file": "json/ai/pest-detection/risk-summary/get_200_success.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/weather-forecast/card/",
|
||||
"status_code": 200,
|
||||
"description": "Farm weather card data",
|
||||
"file": "json/ai/weather-forecast/card/get_200_success.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Spec example for a route family that exists in code, but this file remains mock/contract documentation only."
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/yield-harvest/summary/",
|
||||
"status_code": 200,
|
||||
"description": "Yield prediction card, chart and harvest prediction card",
|
||||
"file": "json/ai/yield-harvest/summary/get_200_success.json",
|
||||
"contract_status": "contract_only",
|
||||
"integration_status": "mock_spec",
|
||||
"notes": "Mock/spec example only; verify actual route registration before treating as implemented."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "روش آبیاری با موفقیت حذف شد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "روش آبیاری یافت نشد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "آبیاری قطرهای",
|
||||
"category": "موضعی",
|
||||
"description": "آبیاری با دبی کم و راندمان بالا",
|
||||
"water_efficiency_percent": 90.0,
|
||||
"water_pressure_required": "۱-۲ اتمسفر",
|
||||
"flow_rate": "۲-۸ لیتر در ساعت",
|
||||
"coverage_area": "بسته به طراحی سیستم",
|
||||
"soil_type": "اکثر خاکها",
|
||||
"climate_suitability": "گرم و خشک",
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "روش آبیاری یافت نشد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "آبیاری قطرهای",
|
||||
"category": "موضعی",
|
||||
"description": "آبیاری با دبی کم و راندمان بالا",
|
||||
"water_efficiency_percent": 90.0,
|
||||
"water_pressure_required": "۱-۲ اتمسفر",
|
||||
"flow_rate": "۲-۸ لیتر در ساعت",
|
||||
"coverage_area": "بسته به طراحی سیستم",
|
||||
"soil_type": "اکثر خاکها",
|
||||
"climate_suitability": "گرم و خشک",
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"name": [
|
||||
"This field may not be blank."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "روش آبیاری یافت نشد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "آبیاری قطرهای",
|
||||
"category": "موضعی",
|
||||
"description": "آبیاری با دبی کم و راندمان بالا",
|
||||
"water_efficiency_percent": 90.0,
|
||||
"water_pressure_required": "۱-۲ اتمسفر",
|
||||
"flow_rate": "۲-۸ لیتر در ساعت",
|
||||
"coverage_area": "بسته به طراحی سیستم",
|
||||
"soil_type": "اکثر خاکها",
|
||||
"climate_suitability": "گرم و خشک",
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"name": [
|
||||
"This field may not be blank."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "روش آبیاری یافت نشد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "آبیاری قطرهای",
|
||||
"category": "موضعی",
|
||||
"description": "آبیاری با دبی کم و راندمان بالا",
|
||||
"water_efficiency_percent": 90.0,
|
||||
"water_pressure_required": "۱-۲ اتمسفر",
|
||||
"flow_rate": "۲-۸ لیتر در ساعت",
|
||||
"coverage_area": "بسته به طراحی سیستم",
|
||||
"soil_type": "اکثر خاکها",
|
||||
"climate_suitability": "گرم و خشک",
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"code": 201,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "آبیاری قطرهای",
|
||||
"category": "موضعی",
|
||||
"description": "آبیاری با دبی کم و راندمان بالا",
|
||||
"water_efficiency_percent": 90.0,
|
||||
"water_pressure_required": "۱-۲ اتمسفر",
|
||||
"flow_rate": "۲-۸ لیتر در ساعت",
|
||||
"coverage_area": "بسته به طراحی سیستم",
|
||||
"soil_type": "اکثر خاکها",
|
||||
"climate_suitability": "گرم و خشک",
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"name": [
|
||||
"This field is required."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"code": 202,
|
||||
"msg": "تسک توصیه آبیاری در صف قرار گرفت.",
|
||||
"data": {
|
||||
"task_id": "irr-task-123",
|
||||
"status_url": "/api/irrigation/recommend/irr-task-123/status/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"farm_uuid": [
|
||||
"This field is required."
|
||||
]
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "irr-task-123",
|
||||
"status": "FAILURE",
|
||||
"error": "خطا در دریافت توصیه آبیاری."
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "irr-task-123",
|
||||
"status": "PENDING",
|
||||
"message": "تسک در صف یا یافت نشد."
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "irr-task-123",
|
||||
"status": "PROGRESS",
|
||||
"progress": {
|
||||
"message": "در حال پردازش توصیه آبیاری..."
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "irr-task-123",
|
||||
"status": "SUCCESS",
|
||||
"result": {
|
||||
"plan": {
|
||||
"frequencyPerWeek": 3,
|
||||
"durationMinutes": 42,
|
||||
"bestTimeOfDay": "صبح زود",
|
||||
"moistureLevel": 68,
|
||||
"warning": "در صورت بارش موثر، نوبت سوم این هفته را حذف کنید."
|
||||
},
|
||||
"raw_response": "{\"plan\":{\"frequencyPerWeek\":3,\"durationMinutes\":42}}",
|
||||
"water_balance": {
|
||||
"daily": [
|
||||
{
|
||||
"forecast_date": "2025-03-25",
|
||||
"et0_mm": 4.7,
|
||||
"etc_mm": 5.6,
|
||||
"effective_rainfall_mm": 0.0,
|
||||
"gross_irrigation_mm": 6.2,
|
||||
"irrigation_timing": "06:00-08:00"
|
||||
}
|
||||
],
|
||||
"crop_profile": {
|
||||
"kc_initial": 0.6,
|
||||
"kc_mid": 1.15,
|
||||
"kc_end": 0.8
|
||||
},
|
||||
"active_kc": 1.15
|
||||
},
|
||||
"status": "completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"status": "success",
|
||||
"service": "ai",
|
||||
"path": "/pest-detection/risk-summary",
|
||||
"result": {
|
||||
"disease_risk": {
|
||||
"id": "disease_risk",
|
||||
"title": "ریسک بیماری",
|
||||
"subtitle": "۷ روز اخیر",
|
||||
"stats": "پایین",
|
||||
"avatarColor": "success",
|
||||
"avatarIcon": "tabler-bug",
|
||||
"chipText": "5%",
|
||||
"chipColor": "success",
|
||||
"details": {
|
||||
"risk_level": "low",
|
||||
"risk_percentage": 5,
|
||||
"detected_diseases": [],
|
||||
"last_assessed_at": "2025-07-10T06:00:00Z",
|
||||
"recommendation": "شرایط فعلی مناسب است. پایش هفتگی توصیه میشود."
|
||||
}
|
||||
},
|
||||
"pest_risk": {
|
||||
"id": "pest_risk",
|
||||
"title": "ریسک آفات",
|
||||
"subtitle": "پیشبینی هوشمند",
|
||||
"stats": "15%",
|
||||
"avatarColor": "warning",
|
||||
"avatarIcon": "tabler-bug-off",
|
||||
"chipText": "تحت نظر",
|
||||
"chipColor": "warning",
|
||||
"details": {
|
||||
"risk_level": "moderate",
|
||||
"risk_percentage": 15,
|
||||
"detected_pests": [
|
||||
{
|
||||
"name": "شپشک",
|
||||
"confidence": 0.72,
|
||||
"affected_area_percent": 8
|
||||
}
|
||||
],
|
||||
"last_assessed_at": "2025-07-10T06:00:00Z",
|
||||
"recommendation": "بازرسی مزرعه هر ۳ روز یک بار انجام شود. در صورت افزایش، اسپری روغن نیم توصیه میشود."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"code": 201,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "گوجهفرنگی",
|
||||
"light": "آفتاب کامل",
|
||||
"watering": "منظم، هفتهای ۲ تا ۳ بار",
|
||||
"soil": "لومی، غنی از مواد آلی",
|
||||
"temperature": "۲۰ تا ۳۰ درجه سانتیگراد",
|
||||
"planting_season": "بهار",
|
||||
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
|
||||
"spacing": "۴۵ تا ۶۰ سانتیمتر",
|
||||
"fertilizer": "کود NPK متعادل",
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"name": [
|
||||
"This field is required."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "گیاه با موفقیت حذف شد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "گیاه یافت نشد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "گوجهفرنگی",
|
||||
"light": "آفتاب کامل",
|
||||
"watering": "منظم، هفتهای ۲ تا ۳ بار",
|
||||
"soil": "لومی، غنی از مواد آلی",
|
||||
"temperature": "۲۰ تا ۳۰ درجه سانتیگراد",
|
||||
"planting_season": "بهار",
|
||||
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
|
||||
"spacing": "۴۵ تا ۶۰ سانتیمتر",
|
||||
"fertilizer": "کود NPK متعادل",
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "گیاه یافت نشد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "گوجهفرنگی",
|
||||
"light": "آفتاب کامل",
|
||||
"watering": "منظم، هفتهای ۲ تا ۳ بار",
|
||||
"soil": "لومی، غنی از مواد آلی",
|
||||
"temperature": "۲۰ تا ۳۰ درجه سانتیگراد",
|
||||
"planting_season": "بهار",
|
||||
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
|
||||
"spacing": "۴۵ تا ۶۰ سانتیمتر",
|
||||
"fertilizer": "کود NPK متعادل",
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"name": [
|
||||
"This field may not be blank."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "گیاه یافت نشد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "گوجهفرنگی",
|
||||
"light": "آفتاب کامل",
|
||||
"watering": "منظم، هفتهای ۲ تا ۳ بار",
|
||||
"soil": "لومی، غنی از مواد آلی",
|
||||
"temperature": "۲۰ تا ۳۰ درجه سانتیگراد",
|
||||
"planting_season": "بهار",
|
||||
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
|
||||
"spacing": "۴۵ تا ۶۰ سانتیمتر",
|
||||
"fertilizer": "کود NPK متعادل",
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"name": [
|
||||
"This field may not be blank."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "گیاه یافت نشد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "گوجهفرنگی",
|
||||
"light": "آفتاب کامل",
|
||||
"watering": "منظم، هفتهای ۲ تا ۳ بار",
|
||||
"soil": "لومی، غنی از مواد آلی",
|
||||
"temperature": "۲۰ تا ۳۰ درجه سانتیگراد",
|
||||
"planting_season": "بهار",
|
||||
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
|
||||
"spacing": "۴۵ تا ۶۰ سانتیمتر",
|
||||
"fertilizer": "کود NPK متعادل",
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "نام گیاه الزامی است.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 503,
|
||||
"msg": "سرویس API هنوز پیادهسازی نشده است.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "گوجهفرنگی",
|
||||
"light": "آفتاب کامل",
|
||||
"watering": "منظم، هفتهای ۲ تا ۳ بار",
|
||||
"soil": "لومی، غنی از مواد آلی",
|
||||
"temperature": "۲۰ تا ۳۰ درجه سانتیگراد",
|
||||
"planting_season": "بهار",
|
||||
"harvest_time": "۷۰ تا ۹۰ روز پس از کاشت",
|
||||
"spacing": "۴۵ تا ۶۰ سانتیمتر",
|
||||
"fertilizer": "کود NPK متعادل",
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"status": "success",
|
||||
"service": "ai",
|
||||
"path": "/predict",
|
||||
"result": {
|
||||
"prediction": "healthy",
|
||||
"confidence": 0.97
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"content": "Here is the recommended plan.",
|
||||
"sections": [
|
||||
{
|
||||
"type": "recommendation",
|
||||
"title": "Irrigation Plan",
|
||||
"icon": "droplet",
|
||||
"frequency": "3 times per week",
|
||||
"amount": "15 liters per plant",
|
||||
"timing": "Early morning",
|
||||
"expandableExplanation": "Loamy soil holds moisture well, so moderate frequency is enough."
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"title": "Important Notes",
|
||||
"icon": "leaf",
|
||||
"items": [
|
||||
"Avoid watering at noon",
|
||||
"Check leaf stress every two days"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "warning",
|
||||
"title": "Heat Alert",
|
||||
"icon": "warning",
|
||||
"content": "Increase irrigation if temperature rises above 35°C."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "service_id نامعتبر است: unknown_service"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "پارامتر query الزامی است."
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "برای این service_id، پارامتر user_id الزامی است."
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 202,
|
||||
"msg": "تسک چت دستیار مزرعه در صف قرار گرفت.",
|
||||
"data": {
|
||||
"task_id": "farm-ai-chat-task-123",
|
||||
"status": "PENDING",
|
||||
"status_url": "/api/tasks/farm-ai-chat-task-123/status/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"code": 202,
|
||||
"msg": "تسک توصیه کودهی در صف قرار گرفت.",
|
||||
"data": {
|
||||
"task_id": "rag-fert-123",
|
||||
"status_url": "/api/rag/recommend/fertilization/rag-fert-123/status/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "پارامتر farm_uuid الزامی است.",
|
||||
"data": null
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "rag-fert-123",
|
||||
"status": "FAILURE",
|
||||
"error": "خطا در دریافت توصیه کودهی."
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "rag-fert-123",
|
||||
"status": "PENDING",
|
||||
"message": "تسک در صف یا یافت نشد."
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "rag-fert-123",
|
||||
"status": "PROGRESS",
|
||||
"progress": {
|
||||
"message": "در حال پردازش توصیه کودهی..."
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "rag-fert-123",
|
||||
"status": "SUCCESS",
|
||||
"result": {
|
||||
"plan": {
|
||||
"npkRatio": "20-20-20",
|
||||
"amountPerHectare": "150 kg/ha",
|
||||
"applicationMethod": "کودآبیاری در دو نوبت",
|
||||
"applicationInterval": "هر ۱۰ روز",
|
||||
"reasoning": "نیتروژن و پتاسیم خاک در محدوده متوسط است و گیاه در فاز رویشی نیاز تغذیهای بالاتری دارد."
|
||||
},
|
||||
"raw_response": "{\"plan\":{\"npkRatio\":\"20-20-20\"}}",
|
||||
"status": "completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"code": 202,
|
||||
"msg": "تسک توصیه آبیاری در صف قرار گرفت.",
|
||||
"data": {
|
||||
"task_id": "rag-irr-123",
|
||||
"status_url": "/api/rag/recommend/irrigation/rag-irr-123/status/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "پارامتر farm_uuid الزامی است.",
|
||||
"data": null
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "rag-irr-123",
|
||||
"status": "FAILURE",
|
||||
"error": "خطا در دریافت توصیه آبیاری."
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "rag-irr-123",
|
||||
"status": "PENDING",
|
||||
"message": "تسک در صف یا یافت نشد."
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "rag-irr-123",
|
||||
"status": "PROGRESS",
|
||||
"progress": {
|
||||
"message": "در حال پردازش توصیه آبیاری..."
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "rag-irr-123",
|
||||
"status": "SUCCESS",
|
||||
"result": {
|
||||
"plan": {
|
||||
"frequencyPerWeek": 3,
|
||||
"durationMinutes": 42,
|
||||
"bestTimeOfDay": "صبح زود",
|
||||
"moistureLevel": 68,
|
||||
"warning": "در صورت بارش موثر، نوبت سوم این هفته را حذف کنید."
|
||||
},
|
||||
"raw_response": "{\"plan\":{\"frequencyPerWeek\":3,\"durationMinutes\":42}}",
|
||||
"water_balance": {
|
||||
"daily": [
|
||||
{
|
||||
"forecast_date": "2025-03-25",
|
||||
"et0_mm": 4.7,
|
||||
"etc_mm": 5.6,
|
||||
"effective_rainfall_mm": 0.0,
|
||||
"gross_irrigation_mm": 6.2,
|
||||
"irrigation_timing": "06:00-08:00"
|
||||
}
|
||||
],
|
||||
"crop_profile": {
|
||||
"kc_initial": 0.6,
|
||||
"kc_mid": 1.15,
|
||||
"kc_end": 0.8
|
||||
},
|
||||
"active_kc": 1.15
|
||||
},
|
||||
"status": "completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"code": 201,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"id": 3,
|
||||
"code": "soil_moisture",
|
||||
"name_fa": "رطوبت خاک",
|
||||
"unit": "%",
|
||||
"created_at": "2025-03-24T10:00:00Z",
|
||||
"action": "added"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"code": [
|
||||
"This field is required."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"location_id": 12,
|
||||
"soil_moisture": 45.2,
|
||||
"soil_temperature": 22.5,
|
||||
"soil_ph": 6.8,
|
||||
"electrical_conductivity": 1.2,
|
||||
"nitrogen": 30.0,
|
||||
"phosphorus": 15.0,
|
||||
"potassium": 20.0,
|
||||
"plant_ids": [
|
||||
1
|
||||
],
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"location_id": [
|
||||
"This field is required."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "location_id یافت نشد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"location_id": 12,
|
||||
"soil_moisture": 45.2,
|
||||
"soil_temperature": 22.5,
|
||||
"soil_ph": 6.8,
|
||||
"electrical_conductivity": 1.2,
|
||||
"nitrogen": 30.0,
|
||||
"phosphorus": 15.0,
|
||||
"potassium": 20.0,
|
||||
"plant_ids": [
|
||||
1
|
||||
],
|
||||
"created_at": "2025-03-20T10:00:00Z",
|
||||
"updated_at": "2025-03-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"location_id": [
|
||||
"This field is required."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": 404,
|
||||
"msg": "location_id یافت نشد.",
|
||||
"data": null
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"source": "database",
|
||||
"id": 12,
|
||||
"lon": "51.389000",
|
||||
"lat": "35.689200",
|
||||
"depths": [
|
||||
{
|
||||
"depth_label": "0-5cm",
|
||||
"bdod": 1.31,
|
||||
"cec": 18.4,
|
||||
"cfvo": 2.0,
|
||||
"clay": 24.0,
|
||||
"nitrogen": 0.18,
|
||||
"ocd": 32.0,
|
||||
"ocs": 4.1,
|
||||
"phh2o": 7.2,
|
||||
"sand": 34.0,
|
||||
"silt": 42.0,
|
||||
"soc": 1.6,
|
||||
"wv0010": 0.31,
|
||||
"wv0033": 0.22,
|
||||
"wv1500": 0.11
|
||||
},
|
||||
{
|
||||
"depth_label": "5-15cm",
|
||||
"bdod": 1.35,
|
||||
"cec": 17.2,
|
||||
"cfvo": 2.3,
|
||||
"clay": 26.0,
|
||||
"nitrogen": 0.16,
|
||||
"ocd": 28.0,
|
||||
"ocs": 3.7,
|
||||
"phh2o": 7.1,
|
||||
"sand": 36.0,
|
||||
"silt": 38.0,
|
||||
"soc": 1.4,
|
||||
"wv0010": 0.29,
|
||||
"wv0033": 0.2,
|
||||
"wv1500": 0.1
|
||||
},
|
||||
{
|
||||
"depth_label": "15-30cm",
|
||||
"bdod": 1.39,
|
||||
"cec": 15.8,
|
||||
"cfvo": 2.8,
|
||||
"clay": 28.0,
|
||||
"nitrogen": 0.13,
|
||||
"ocd": 22.0,
|
||||
"ocs": 3.2,
|
||||
"phh2o": 7.0,
|
||||
"sand": 38.0,
|
||||
"silt": 34.0,
|
||||
"soc": 1.1,
|
||||
"wv0010": 0.26,
|
||||
"wv0033": 0.18,
|
||||
"wv1500": 0.09
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"code": 202,
|
||||
"msg": "تسک در صف. وضعیت را با task_id بررسی کنید.",
|
||||
"data": {
|
||||
"source": "task",
|
||||
"task_id": "soil-task-123",
|
||||
"lon": 51.389,
|
||||
"lat": 35.6892,
|
||||
"status_url": "/api/soil-data/tasks/soil-task-123/status/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"lat": [
|
||||
"This field is required."
|
||||
],
|
||||
"lon": [
|
||||
"This field is required."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"source": "database",
|
||||
"id": 12,
|
||||
"lon": "51.389000",
|
||||
"lat": "35.689200",
|
||||
"depths": [
|
||||
{
|
||||
"depth_label": "0-5cm",
|
||||
"bdod": 1.31,
|
||||
"cec": 18.4,
|
||||
"cfvo": 2.0,
|
||||
"clay": 24.0,
|
||||
"nitrogen": 0.18,
|
||||
"ocd": 32.0,
|
||||
"ocs": 4.1,
|
||||
"phh2o": 7.2,
|
||||
"sand": 34.0,
|
||||
"silt": 42.0,
|
||||
"soc": 1.6,
|
||||
"wv0010": 0.31,
|
||||
"wv0033": 0.22,
|
||||
"wv1500": 0.11
|
||||
},
|
||||
{
|
||||
"depth_label": "5-15cm",
|
||||
"bdod": 1.35,
|
||||
"cec": 17.2,
|
||||
"cfvo": 2.3,
|
||||
"clay": 26.0,
|
||||
"nitrogen": 0.16,
|
||||
"ocd": 28.0,
|
||||
"ocs": 3.7,
|
||||
"phh2o": 7.1,
|
||||
"sand": 36.0,
|
||||
"silt": 38.0,
|
||||
"soc": 1.4,
|
||||
"wv0010": 0.29,
|
||||
"wv0033": 0.2,
|
||||
"wv1500": 0.1
|
||||
},
|
||||
{
|
||||
"depth_label": "15-30cm",
|
||||
"bdod": 1.39,
|
||||
"cec": 15.8,
|
||||
"cfvo": 2.8,
|
||||
"clay": 28.0,
|
||||
"nitrogen": 0.13,
|
||||
"ocd": 22.0,
|
||||
"ocs": 3.2,
|
||||
"phh2o": 7.0,
|
||||
"sand": 38.0,
|
||||
"silt": 34.0,
|
||||
"soc": 1.1,
|
||||
"wv0010": 0.26,
|
||||
"wv0033": 0.18,
|
||||
"wv1500": 0.09
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"code": 202,
|
||||
"msg": "تسک در صف. وضعیت را با task_id بررسی کنید.",
|
||||
"data": {
|
||||
"source": "task",
|
||||
"task_id": "soil-task-123",
|
||||
"lon": 51.389,
|
||||
"lat": 35.6892,
|
||||
"status_url": "/api/soil-data/tasks/soil-task-123/status/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "داده نامعتبر.",
|
||||
"data": {
|
||||
"lat": [
|
||||
"A valid number is required."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "soil-task-123",
|
||||
"status": "FAILURE",
|
||||
"error": "خطا در واکشی داده خاک."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "soil-task-123",
|
||||
"status": "PENDING",
|
||||
"message": "تسک در صف یا یافت نشد."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "soil-task-123",
|
||||
"status": "PROGRESS",
|
||||
"progress": {
|
||||
"step": "fetch",
|
||||
"percent": 60
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "soil-task-123",
|
||||
"status": "SUCCESS",
|
||||
"result": {
|
||||
"source": "database",
|
||||
"id": 12,
|
||||
"lon": "51.389000",
|
||||
"lat": "35.689200",
|
||||
"depths": [
|
||||
{
|
||||
"depth_label": "0-5cm",
|
||||
"bdod": 1.31,
|
||||
"cec": 18.4,
|
||||
"cfvo": 2.0,
|
||||
"clay": 24.0,
|
||||
"nitrogen": 0.18,
|
||||
"ocd": 32.0,
|
||||
"ocs": 4.1,
|
||||
"phh2o": 7.2,
|
||||
"sand": 34.0,
|
||||
"silt": 42.0,
|
||||
"soc": 1.6,
|
||||
"wv0010": 0.31,
|
||||
"wv0033": 0.22,
|
||||
"wv1500": 0.11
|
||||
},
|
||||
{
|
||||
"depth_label": "5-15cm",
|
||||
"bdod": 1.35,
|
||||
"cec": 17.2,
|
||||
"cfvo": 2.3,
|
||||
"clay": 26.0,
|
||||
"nitrogen": 0.16,
|
||||
"ocd": 28.0,
|
||||
"ocs": 3.7,
|
||||
"phh2o": 7.1,
|
||||
"sand": 36.0,
|
||||
"silt": 38.0,
|
||||
"soc": 1.4,
|
||||
"wv0010": 0.29,
|
||||
"wv0033": 0.2,
|
||||
"wv1500": 0.1
|
||||
},
|
||||
{
|
||||
"depth_label": "15-30cm",
|
||||
"bdod": 1.39,
|
||||
"cec": 15.8,
|
||||
"cfvo": 2.8,
|
||||
"clay": 28.0,
|
||||
"nitrogen": 0.13,
|
||||
"ocd": 22.0,
|
||||
"ocs": 3.2,
|
||||
"phh2o": 7.0,
|
||||
"sand": 38.0,
|
||||
"silt": 34.0,
|
||||
"soc": 1.1,
|
||||
"wv0010": 0.26,
|
||||
"wv0033": 0.18,
|
||||
"wv1500": 0.09
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "sample-task-123"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "farm-ai-chat-task-123",
|
||||
"status": "FAILURE",
|
||||
"error": "Sample task failed."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "farm-ai-chat-task-123",
|
||||
"status": "PENDING",
|
||||
"message": "تسک در صف یا یافت نشد."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "farm-ai-chat-task-123",
|
||||
"status": "PROGRESS",
|
||||
"progress": {
|
||||
"current": 1,
|
||||
"total": 3,
|
||||
"message": "در حال پردازش..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"task_id": "farm-ai-chat-task-123",
|
||||
"status": "SUCCESS",
|
||||
"result": {
|
||||
"$ref": "rag/chat-post_200_stream.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"status": "success",
|
||||
"service": "ai",
|
||||
"path": "/weather-forecast/card",
|
||||
"result": {
|
||||
"condition": "صاف",
|
||||
"temperature": 24,
|
||||
"unit": "°C",
|
||||
"humidity": 45,
|
||||
"windSpeed": 12,
|
||||
"windUnit": "km/h",
|
||||
"chartData": {
|
||||
"labels": ["۶ صبح", "۹ صبح", "۱۲ ظهر", "۳ بعدازظهر", "۶ عصر", "۹ شب", "۱۲ شب"],
|
||||
"series": [[18, 22, 26, 28, 25, 20, 18]]
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"status": "success",
|
||||
"service": "ai",
|
||||
"path": "/yield-harvest/summary",
|
||||
"result": {
|
||||
"yield_prediction_card": {
|
||||
"id": "yield_prediction",
|
||||
"title": "پیشبینی عملکرد",
|
||||
"subtitle": "این فصل",
|
||||
"stats": "42 تن",
|
||||
"avatarColor": "secondary",
|
||||
"avatarIcon": "tabler-chart-bar",
|
||||
"chipText": "+8%",
|
||||
"chipColor": "success"
|
||||
},
|
||||
"yield_prediction_chart": {
|
||||
"categories": [
|
||||
"ژانویه", "فوریه", "مارس", "آوریل", "می", "ژوئن",
|
||||
"ژوئیه", "آگوست", "سپتامبر", "اکتبر", "نوامبر", "دسامبر"
|
||||
],
|
||||
"series": [
|
||||
{"name": "امسال", "data": [35, 38, 40, 42, 45, 48, 50, 48, 46, 44, 42, 42]},
|
||||
{"name": "سال گذشته", "data": [32, 34, 36, 38, 40, 42, 44, 42, 40, 38, 36, 38]}
|
||||
],
|
||||
"summary": [
|
||||
{
|
||||
"title": "عملکرد پیشبینیشده",
|
||||
"subtitle": "این فصل",
|
||||
"amount": "42 تن",
|
||||
"avatarColor": "primary",
|
||||
"avatarIcon": "tabler-chart-bar"
|
||||
},
|
||||
{
|
||||
"title": "تاریخ برداشت",
|
||||
"subtitle": "حدود ۱۵ اکتبر",
|
||||
"amount": "+8%",
|
||||
"avatarColor": "success",
|
||||
"avatarIcon": "tabler-calendar"
|
||||
}
|
||||
]
|
||||
},
|
||||
"harvest_prediction_card": {
|
||||
"date": "2025-10-15",
|
||||
"dateFormatted": "۱۵ اکتبر ۲۰۲۵",
|
||||
"daysUntil": 58,
|
||||
"description": "بر اساس تجمع GDD فعلی و پیشبینی آب و هوا. بازه بهینه برداشت: ۱۲ تا ۱۸ اکتبر.",
|
||||
"optimalWindowStart": "2025-10-12",
|
||||
"optimalWindowEnd": "2025-10-18"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from .exceptions import MockDirectoryNotFound, MockFileNotFound
|
||||
|
||||
|
||||
@dataclass
|
||||
class LoadedMockResponse:
|
||||
data: object
|
||||
status_code: int
|
||||
file_path: str
|
||||
|
||||
|
||||
class MockLoader:
|
||||
def __init__(self, base_path=None):
|
||||
self.base_path = Path(base_path or Path(__file__).resolve().parent / "json")
|
||||
|
||||
def load(self, service_name, path, method):
|
||||
mock_files = self._find_mock_files(service_name=service_name, path=path, method=method)
|
||||
if not mock_files:
|
||||
raise MockFileNotFound(
|
||||
f"No mock file found for service='{service_name}' path='{path}' method='{method}'."
|
||||
)
|
||||
|
||||
selected_file = sorted(mock_files, key=self._mock_file_priority)[0]
|
||||
with selected_file.open("r", encoding="utf-8") as file:
|
||||
service_root = self.base_path / service_name
|
||||
return LoadedMockResponse(
|
||||
data=self._resolve_references(
|
||||
json.load(file),
|
||||
current_file=selected_file,
|
||||
service_root=service_root,
|
||||
),
|
||||
status_code=self._extract_status_code(selected_file),
|
||||
file_path=str(selected_file),
|
||||
)
|
||||
|
||||
def _find_mock_files(self, service_name, path, method):
|
||||
service_root = self.base_path / service_name
|
||||
directory_path = service_root / self._build_directory_path(path)
|
||||
pattern = f"{method.lower()}_*.json"
|
||||
|
||||
if directory_path.exists() and directory_path.is_dir():
|
||||
return list(directory_path.glob(pattern))
|
||||
|
||||
leaf_name = self._extract_leaf_name(path)
|
||||
parent_directory = directory_path.parent
|
||||
if parent_directory.exists() and parent_directory.is_dir():
|
||||
flat_pattern = f"{leaf_name}-{method.lower()}_*.json"
|
||||
flat_files = list(parent_directory.glob(flat_pattern))
|
||||
if flat_files:
|
||||
return flat_files
|
||||
|
||||
normalized_parts = [part for part in str(path).strip().strip("/").split("/") if part]
|
||||
for index in range(len(normalized_parts) - 1, -1, -1):
|
||||
candidate_parts = normalized_parts[:index] + normalized_parts[index + 1 :]
|
||||
if not candidate_parts:
|
||||
continue
|
||||
|
||||
candidate_directory = service_root / Path(*candidate_parts)
|
||||
if candidate_directory.exists() and candidate_directory.is_dir():
|
||||
candidate_files = list(candidate_directory.glob(pattern))
|
||||
if candidate_files:
|
||||
return candidate_files
|
||||
|
||||
raise MockDirectoryNotFound(
|
||||
f"Mock directory not found for service='{service_name}' path='{path}': {directory_path}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _build_directory_path(path):
|
||||
normalized = str(path).strip().strip("/")
|
||||
if not normalized:
|
||||
return Path(".")
|
||||
return Path(*normalized.split("/"))
|
||||
|
||||
@staticmethod
|
||||
def _extract_status_code(file_path):
|
||||
parts = file_path.stem.split("_")
|
||||
if len(parts) < 2:
|
||||
return 200
|
||||
try:
|
||||
return int(parts[1])
|
||||
except ValueError:
|
||||
return 200
|
||||
|
||||
@staticmethod
|
||||
def _extract_leaf_name(path):
|
||||
normalized = str(path).strip().strip("/")
|
||||
if not normalized:
|
||||
return ""
|
||||
return normalized.split("/")[-1]
|
||||
|
||||
@staticmethod
|
||||
def _mock_file_priority(file_path):
|
||||
stem = file_path.stem.lower()
|
||||
status_code = MockLoader._extract_status_code(file_path)
|
||||
keyword_rank = 2
|
||||
if "success" in stem:
|
||||
keyword_rank = 0
|
||||
elif "stream" in stem:
|
||||
keyword_rank = 0
|
||||
elif "pending" in stem or "progress" in stem:
|
||||
keyword_rank = 1
|
||||
return (status_code >= 400, keyword_rank, stem)
|
||||
|
||||
def _resolve_references(self, value, current_file, service_root, seen=None):
|
||||
current_file = current_file.resolve()
|
||||
service_root = service_root.resolve()
|
||||
seen = seen or {current_file}
|
||||
|
||||
if isinstance(value, list):
|
||||
return [
|
||||
self._resolve_references(item, current_file=current_file, service_root=service_root, seen=seen)
|
||||
for item in value
|
||||
]
|
||||
|
||||
if not isinstance(value, dict):
|
||||
return value
|
||||
|
||||
ref_path = value.get("$ref")
|
||||
if isinstance(ref_path, str) and len(value) == 1:
|
||||
referenced_file = self._resolve_reference_path(
|
||||
ref_path=ref_path,
|
||||
current_file=current_file,
|
||||
service_root=service_root,
|
||||
)
|
||||
|
||||
if referenced_file in seen:
|
||||
raise MockFileNotFound(f"Circular mock reference detected for '{referenced_file}'.")
|
||||
|
||||
with referenced_file.open("r", encoding="utf-8") as file:
|
||||
return self._resolve_references(
|
||||
json.load(file),
|
||||
current_file=referenced_file,
|
||||
service_root=service_root,
|
||||
seen=seen | {referenced_file},
|
||||
)
|
||||
|
||||
return {
|
||||
key: self._resolve_references(item, current_file=current_file, service_root=service_root, seen=seen)
|
||||
for key, item in value.items()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _resolve_reference_path(ref_path, current_file, service_root):
|
||||
candidates = [
|
||||
current_file.parent / ref_path,
|
||||
service_root / ref_path,
|
||||
]
|
||||
|
||||
service_root_resolved = service_root.resolve()
|
||||
for candidate in candidates:
|
||||
candidate_resolved = candidate.resolve()
|
||||
try:
|
||||
candidate_resolved.relative_to(service_root_resolved)
|
||||
except ValueError:
|
||||
continue
|
||||
if candidate_resolved.is_file():
|
||||
return candidate_resolved
|
||||
|
||||
raise MockFileNotFound(f"Referenced mock file '{ref_path}' was not found.")
|
||||
@@ -0,0 +1,14 @@
|
||||
from django.conf import settings
|
||||
|
||||
from .exceptions import ServiceNotFound
|
||||
|
||||
|
||||
class ServiceRegistry:
|
||||
def __init__(self):
|
||||
self._services = getattr(settings, "EXTERNAL_SERVICES", {})
|
||||
|
||||
def get(self, service_name):
|
||||
service = self._services.get(service_name)
|
||||
if service is None:
|
||||
raise ServiceNotFound(f"Unknown external service: '{service_name}'")
|
||||
return service
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user