This commit is contained in:
2026-05-05 21:02:12 +03:30
parent 5301071df5
commit 1679825ae2
47 changed files with 1347 additions and 1403 deletions
+39
View File
@@ -0,0 +1,39 @@
from __future__ import annotations
from datetime import datetime
from typing import Any
def _isoformat(value: Any) -> Any:
if isinstance(value, datetime):
return value.isoformat()
return value
def build_integration_meta(
*,
flow_type: str,
source_type: str,
source_service: str,
ownership: str,
live: bool,
cached: bool,
generated_at: Any = None,
snapshot_at: Any = None,
notes: list[str] | None = None,
) -> dict[str, Any]:
meta = {
"flow_type": flow_type,
"source_type": source_type,
"source_service": source_service,
"ownership": ownership,
"live": live,
"cached": cached,
}
if generated_at is not None:
meta["generated_at"] = _isoformat(generated_at)
if snapshot_at is not None:
meta["snapshot_at"] = _isoformat(snapshot_at)
if notes:
meta["notes"] = notes
return meta
+1 -1
View File
@@ -226,7 +226,7 @@ services:
use_user_embeddings: true
description: "سرویس روایت داشبورد عملکرد و برداشت"
fallback_behavior:
on_invalid_json: "return_mocked_narrative"
on_invalid_json: "raise_validation_error"
on_missing_context: "use_only_deterministic_data"
on_number_conflict: "prefer_deterministic_data"
prompt_template: "config/tones/yield_harvest_tone.txt"
+34 -2
View File
@@ -1,6 +1,7 @@
import os
import importlib.util
from pathlib import Path
from django.core.exceptions import ImproperlyConfigured
try:
from dotenv import load_dotenv
@@ -30,6 +31,7 @@ FILE_LOGGING_ENABLED = _can_use_file_logging(LOG_DIR)
SECRET_KEY = os.environ.get("SECRET_KEY", "django-insecure-dev-only")
DEBUG = os.environ.get("DEBUG", "0") == "1"
DEVELOP = os.environ.get("DEVELOP", "false").strip().lower() in {"1", "true", "yes", "on"}
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
INSTALLED_APPS = [
@@ -56,6 +58,8 @@ INSTALLED_APPS = [
for optional_app in [
"rest_framework",
"corsheaders",
"drf_spectacular",
"drf_spectacular_sidecar",
]:
if importlib.util.find_spec(optional_app):
INSTALLED_APPS.insert(6, optional_app)
@@ -73,6 +77,9 @@ MIDDLEWARE = [
if importlib.util.find_spec("corsheaders"):
MIDDLEWARE.insert(1, "corsheaders.middleware.CorsMiddleware")
if importlib.util.find_spec("whitenoise"):
MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware")
ROOT_URLCONF = "config.urls"
WSGI_APPLICATION = "config.wsgi.application"
@@ -121,6 +128,9 @@ USE_TZ = True
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
if importlib.util.find_spec("whitenoise"):
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
CACHES = {
@@ -134,6 +144,22 @@ REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.AllowAny",
],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
SPECTACULAR_SETTINGS = {
"TITLE": "CropLogic AI API",
"DESCRIPTION": "Swagger/OpenAPI documentation for all CropLogic AI API endpoints.",
"VERSION": "1.0.0",
"SERVE_INCLUDE_SCHEMA": False,
"SWAGGER_UI_DIST": "SIDECAR",
"SWAGGER_UI_FAVICON_HREF": "SIDECAR",
"REDOC_DIST": "SIDECAR",
"SCHEMA_PATH_PREFIX": r"/api/",
"SERVE_PERMISSIONS": ["rest_framework.permissions.AllowAny"],
"SWAGGER_UI_SETTINGS": {
"persistAuthorization": True,
},
}
CORS_ALLOW_ALL_ORIGINS = DEBUG
@@ -161,16 +187,22 @@ WEATHER_API_BASE_URL = os.environ.get(
"WEATHER_API_BASE_URL", "https://api.open-meteo.com/v1/forecast"
)
WEATHER_API_KEY = os.environ.get("WEATHER_API_KEY", "")
WEATHER_DATA_PROVIDER = os.environ.get("WEATHER_DATA_PROVIDER", "mock").strip().lower()
WEATHER_DATA_PROVIDER = os.environ.get("WEATHER_DATA_PROVIDER", "open-meteo").strip().lower()
WEATHER_MOCK_DELAY_SECONDS = float(os.environ.get("WEATHER_MOCK_DELAY_SECONDS", "0.8"))
WEATHER_TIMEOUT_SECONDS = float(os.environ.get("WEATHER_TIMEOUT_SECONDS", "60"))
SOIL_DATA_PROVIDER = os.environ.get("SOIL_DATA_PROVIDER", "mock").strip().lower()
SOIL_DATA_PROVIDER = os.environ.get("SOIL_DATA_PROVIDER", "soilgrids").strip().lower()
SOIL_MOCK_DELAY_SECONDS = float(os.environ.get("SOIL_MOCK_DELAY_SECONDS", "0.8"))
SOILGRIDS_TIMEOUT_SECONDS = float(os.environ.get("SOILGRIDS_TIMEOUT_SECONDS", "60"))
BACKEND_PLANT_SYNC_BASE_URL = os.environ.get("BACKEND_PLANT_SYNC_BASE_URL", "")
BACKEND_PLANT_SYNC_API_KEY = os.environ.get("BACKEND_PLANT_SYNC_API_KEY", "")
BACKEND_PLANT_SYNC_TIMEOUT = int(os.environ.get("BACKEND_PLANT_SYNC_TIMEOUT", "20"))
if not (DEBUG or DEVELOP):
if WEATHER_DATA_PROVIDER == "mock":
raise ImproperlyConfigured("WEATHER_DATA_PROVIDER=mock is allowed only in dev/test environments.")
if SOIL_DATA_PROVIDER == "mock":
raise ImproperlyConfigured("SOIL_DATA_PROVIDER=mock is allowed only in dev/test environments.")
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
+4
View File
@@ -1,8 +1,12 @@
from django.contrib import admin
from django.urls import include, path
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView
urlpatterns = [
path("admin/", admin.site.urls),
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path("api/docs/swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
path("api/docs/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
# --- App APIs ---
path("api/rag/", include("rag.urls")),
path("api/farm-alerts/", include("farm_alerts.urls")),