UPDATE
This commit is contained in:
@@ -13,7 +13,12 @@ from typing import Any
|
||||
from django.apps import apps
|
||||
|
||||
from farm_data.models import SensorData
|
||||
from farm_data.services import clone_snapshot_as_runtime_plant, get_farm_plant_snapshot_by_name
|
||||
from farm_data.services import (
|
||||
build_ai_farm_snapshot,
|
||||
clone_snapshot_as_runtime_plant,
|
||||
get_ai_snapshot_weather,
|
||||
get_farm_plant_snapshot_by_name,
|
||||
)
|
||||
from rag.api_provider import get_chat_client
|
||||
from rag.chat import (
|
||||
_complete_audit_log,
|
||||
@@ -628,6 +633,7 @@ def get_fertilization_recommendation(
|
||||
.filter(farm_uuid=resolved_farm_uuid)
|
||||
.first()
|
||||
)
|
||||
ai_snapshot = build_ai_farm_snapshot(resolved_farm_uuid)
|
||||
|
||||
plant_config = apps.get_app_config("plant")
|
||||
resolved_plant_name = plant_config.resolve_plant_name(plant_name)
|
||||
@@ -662,6 +668,7 @@ def get_fertilization_recommendation(
|
||||
plant=plant,
|
||||
forecasts=forecasts,
|
||||
growth_stage=resolved_growth_stage,
|
||||
ai_snapshot=ai_snapshot,
|
||||
)
|
||||
|
||||
context = build_rag_context(
|
||||
@@ -727,6 +734,9 @@ def get_fertilization_recommendation(
|
||||
growth_stage=resolved_growth_stage,
|
||||
forecasts=forecasts,
|
||||
)
|
||||
result.setdefault("source_metadata", {})
|
||||
result["source_metadata"]["farm_metrics"] = (ai_snapshot or {}).get("source_metadata", {}).get("farm_metrics", {})
|
||||
result["source_metadata"]["weather"] = {"source": "center_location_forecast", "policy": "center_location_latest_forecast"}
|
||||
result = _validate_fertilization_response(result)
|
||||
result["raw_response"] = raw
|
||||
result["simulation_optimizer"] = optimized_result
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
|
||||
from farm_data.services import build_ai_farm_snapshot
|
||||
from rag.api_provider import get_chat_client
|
||||
from rag.chat import (
|
||||
_complete_audit_log,
|
||||
@@ -124,7 +125,14 @@ class FertilizationPlanParserService:
|
||||
"partial_plan": normalized_partial,
|
||||
"required_core_fields": CORE_FIELDS,
|
||||
"service": "fertilization_plan_parser",
|
||||
"endpoint_policy": "parser_first",
|
||||
}
|
||||
if farm_uuid:
|
||||
# Parser-first endpoint: farm context is optional enrichment only.
|
||||
structured_context["farm_context_source_metadata"] = {
|
||||
"source": "build_ai_farm_snapshot",
|
||||
"optional": True,
|
||||
}
|
||||
|
||||
rag_query = self._build_retrieval_query(
|
||||
message=normalized_message,
|
||||
|
||||
+17
-10
@@ -11,7 +11,10 @@ from django.db import transaction
|
||||
|
||||
from farm_data.models import SensorData
|
||||
from farm_data.services import (
|
||||
build_ai_farm_snapshot,
|
||||
clone_snapshot_as_runtime_plant,
|
||||
get_ai_snapshot_metric,
|
||||
get_ai_snapshot_weather,
|
||||
get_farm_plant_snapshot_by_name,
|
||||
)
|
||||
from irrigation.evapotranspiration import (
|
||||
@@ -55,13 +58,15 @@ def _safe_float(value: Any, default: float = 0.0) -> float:
|
||||
return default
|
||||
|
||||
|
||||
def _sensor_metric(sensor: SensorData | None, metric: str) -> float | None:
|
||||
if sensor is None or not isinstance(sensor.sensor_payload, dict):
|
||||
return None
|
||||
for payload in sensor.sensor_payload.values():
|
||||
if isinstance(payload, dict) and payload.get(metric) is not None:
|
||||
return _safe_float(payload.get(metric), default=0.0)
|
||||
return None
|
||||
def _aggregated_metric(ai_snapshot: dict[str, Any] | None, metric: str) -> float | None:
|
||||
value = get_ai_snapshot_metric(ai_snapshot, metric)
|
||||
return _safe_float(value, default=0.0) if value is not None else None
|
||||
|
||||
|
||||
|
||||
def _aggregated_metric_fallback(ai_snapshot: dict[str, Any] | None, metric: str) -> float | None:
|
||||
"""Limited fallback for missing aggregated metrics only; raw payload is intentionally not consulted."""
|
||||
return _aggregated_metric(ai_snapshot, metric)
|
||||
|
||||
|
||||
def _coerce_list(value: Any) -> list[Any]:
|
||||
@@ -275,9 +280,9 @@ def _build_irrigation_ui_payload(
|
||||
crop_profile: dict[str, Any],
|
||||
active_kc: float,
|
||||
irrigation_method: IrrigationMethod | None,
|
||||
sensor: SensorData | None,
|
||||
ai_snapshot: dict[str, Any] | None,
|
||||
) -> dict[str, Any]:
|
||||
soil_moisture = _sensor_metric(sensor, "soil_moisture")
|
||||
soil_moisture = _aggregated_metric_fallback(ai_snapshot, "soil_moisture")
|
||||
plan = _normalize_plan(
|
||||
llm_result,
|
||||
optimizer_result,
|
||||
@@ -380,6 +385,7 @@ def get_irrigation_recommendation(
|
||||
.filter(farm_uuid=resolved_farm_uuid)
|
||||
.first()
|
||||
)
|
||||
ai_snapshot = build_ai_farm_snapshot(resolved_farm_uuid)
|
||||
irrigation_method = _resolve_irrigation_method(sensor, irrigation_method_name)
|
||||
_persist_irrigation_method_on_farm(sensor, irrigation_method)
|
||||
|
||||
@@ -423,6 +429,7 @@ def get_irrigation_recommendation(
|
||||
if plant is not None and forecasts:
|
||||
optimized_result = _get_optimizer().optimize_irrigation(
|
||||
sensor=sensor,
|
||||
ai_snapshot=ai_snapshot,
|
||||
plant=plant,
|
||||
forecasts=forecasts,
|
||||
daily_water_needs=daily_water_needs,
|
||||
@@ -518,7 +525,7 @@ def get_irrigation_recommendation(
|
||||
crop_profile,
|
||||
active_kc,
|
||||
irrigation_method,
|
||||
sensor,
|
||||
ai_snapshot,
|
||||
)
|
||||
result["raw_response"] = raw
|
||||
result["simulation_optimizer"] = optimized_result
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
|
||||
from farm_data.services import build_ai_farm_snapshot
|
||||
from rag.api_provider import get_chat_client
|
||||
from rag.chat import (
|
||||
_complete_audit_log,
|
||||
@@ -120,7 +121,14 @@ class IrrigationPlanParserService:
|
||||
"partial_plan": normalized_partial,
|
||||
"required_core_fields": CORE_FIELDS,
|
||||
"service": "irrigation_plan_parser",
|
||||
"endpoint_policy": "parser_first",
|
||||
}
|
||||
if farm_uuid:
|
||||
# Parser-first endpoint: farm context is optional enrichment only.
|
||||
structured_context["farm_context_source_metadata"] = {
|
||||
"source": "build_ai_farm_snapshot",
|
||||
"optional": True,
|
||||
}
|
||||
|
||||
rag_query = self._build_retrieval_query(
|
||||
message=normalized_message,
|
||||
|
||||
@@ -7,7 +7,7 @@ import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from farm_data.services import get_farm_details
|
||||
from farm_data.services import build_ai_farm_snapshot
|
||||
from rag.api_provider import get_chat_client
|
||||
from rag.chat import (
|
||||
_build_content_parts,
|
||||
@@ -106,7 +106,7 @@ def _clean_json(raw: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _load_farm_or_error(farm_uuid: str) -> dict[str, Any]:
|
||||
farm_details = get_farm_details(farm_uuid)
|
||||
farm_details = build_ai_farm_snapshot(farm_uuid)
|
||||
if farm_details is None:
|
||||
raise RAGServiceError(
|
||||
error_code="farm_not_found",
|
||||
@@ -134,8 +134,8 @@ def _build_service_client(cfg: RAGConfig):
|
||||
|
||||
|
||||
def _weather_risk_summary(farm_details: dict[str, Any]) -> dict[str, Any]:
|
||||
weather = farm_details.get("weather") or {}
|
||||
soil = (farm_details.get("soil") or {}).get("resolved_metrics") or {}
|
||||
weather = ((farm_details.get("weather") or {}).get("forecast") or {})
|
||||
soil = (farm_details.get("farm_metrics") or {}).get("resolved_metrics") or {}
|
||||
humidity = _safe_float(weather.get("humidity_mean"), 55.0)
|
||||
temp = _safe_float(weather.get("temperature_mean"), 24.0)
|
||||
rain = _safe_float(weather.get("precipitation"), 0.0)
|
||||
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from farm_data.services import get_farm_details
|
||||
from farm_data.services import build_ai_farm_snapshot
|
||||
from rag.api_provider import get_chat_client
|
||||
from rag.chat import (
|
||||
_complete_audit_log,
|
||||
@@ -73,7 +73,7 @@ def _clean_json(raw: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _load_farm_or_error(farm_uuid: str) -> dict[str, Any]:
|
||||
farm_details = get_farm_details(farm_uuid)
|
||||
farm_details = build_ai_farm_snapshot(farm_uuid)
|
||||
if farm_details is None:
|
||||
raise RAGServiceError(
|
||||
error_code="farm_not_found",
|
||||
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from farm_data.services import get_farm_details
|
||||
from farm_data.services import build_ai_farm_snapshot, get_ai_snapshot_metric, get_ai_snapshot_weather
|
||||
from rag.api_provider import get_chat_client
|
||||
from rag.chat import (
|
||||
_complete_audit_log,
|
||||
@@ -71,7 +71,7 @@ def _clean_json(raw: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _load_farm_or_error(farm_uuid: str) -> dict[str, Any]:
|
||||
farm_details = get_farm_details(farm_uuid)
|
||||
farm_details = build_ai_farm_snapshot(farm_uuid)
|
||||
if farm_details is None:
|
||||
raise RAGServiceError(
|
||||
error_code="farm_not_found",
|
||||
@@ -158,6 +158,16 @@ def get_water_need_prediction_insight(
|
||||
structured_context = {
|
||||
"farm_uuid": farm_uuid,
|
||||
"prediction_payload": prediction_payload,
|
||||
"source_metadata": {
|
||||
"agronomic_metrics": {
|
||||
"source": "build_ai_farm_snapshot",
|
||||
"policy": "cluster_block_farm_aggregated",
|
||||
},
|
||||
"weather": {
|
||||
"source": "center_location_forecast",
|
||||
"policy": "center_location_latest_forecast",
|
||||
},
|
||||
},
|
||||
}
|
||||
rag_context = build_rag_context(
|
||||
query=user_query,
|
||||
@@ -208,4 +218,14 @@ def get_water_need_prediction_insight(
|
||||
parsed["source"] = "llm"
|
||||
parsed["farm_uuid"] = farm_uuid
|
||||
parsed["raw_response"] = raw
|
||||
parsed["source_metadata"] = {
|
||||
"agronomic_metrics": {
|
||||
"source": "build_ai_farm_snapshot",
|
||||
"policy": "cluster_block_farm_aggregated",
|
||||
},
|
||||
"weather": {
|
||||
"source": "center_location_forecast",
|
||||
"policy": "center_location_latest_forecast",
|
||||
},
|
||||
}
|
||||
return parsed
|
||||
|
||||
Reference in New Issue
Block a user