UPDATE
This commit is contained in:
@@ -2,6 +2,8 @@ from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from django.db.models import Avg
|
||||
@@ -9,7 +11,7 @@ from django.db.models import Avg
|
||||
from crop_simulation.growth_simulation import GrowthSimulationContext, _run_projection_engine
|
||||
from crop_simulation.services import PcseSimulationManager, build_simulation_payload_from_farm
|
||||
from farm_data.services import get_canonical_farm_record, get_farm_plant_assignments
|
||||
from .models import AnalysisGridObservation, RemoteSensingClusterBlock
|
||||
from .models import AnalysisGridObservation, RemoteSensingClusterBlock, RemoteSensingSubdivisionResult
|
||||
from .satellite_snapshot import build_location_block_satellite_snapshots
|
||||
|
||||
|
||||
@@ -70,6 +72,23 @@ def _clamp(value: float, minimum: float, maximum: float) -> float:
|
||||
return max(minimum, min(value, maximum))
|
||||
|
||||
|
||||
def _json_safe(value: Any) -> Any:
|
||||
if isinstance(value, Decimal):
|
||||
return float(value)
|
||||
if isinstance(value, datetime):
|
||||
formatted = value.isoformat()
|
||||
if formatted.endswith("+00:00"):
|
||||
return formatted[:-6] + "Z"
|
||||
return formatted
|
||||
if isinstance(value, date):
|
||||
return value.isoformat()
|
||||
if isinstance(value, dict):
|
||||
return {str(key): _json_safe(item) for key, item in value.items()}
|
||||
if isinstance(value, (list, tuple)):
|
||||
return [_json_safe(item) for item in value]
|
||||
return value
|
||||
|
||||
|
||||
def _build_cluster_entries(
|
||||
snapshots: list[dict[str, Any]],
|
||||
*,
|
||||
@@ -353,6 +372,21 @@ def build_cluster_crop_recommendations(farm_uuid: str) -> dict[str, Any]:
|
||||
if not cluster_entries:
|
||||
raise ClusterRecommendationNotFound("برای این مزرعه هنوز کلاستر قابل استفاده پیدا نشد.")
|
||||
|
||||
recommendation_result_ids = sorted(
|
||||
{
|
||||
int(cluster_block.result_id)
|
||||
for cluster_block in cluster_blocks_by_uuid.values()
|
||||
if cluster_block.result_id
|
||||
}
|
||||
)
|
||||
cached_payload = _load_cached_cluster_recommendations(
|
||||
farm_uuid=str(farm.farm_uuid),
|
||||
result_ids=recommendation_result_ids,
|
||||
plant_assignments=plant_assignments,
|
||||
)
|
||||
if cached_payload is not None:
|
||||
return cached_payload
|
||||
|
||||
base_payloads: dict[str, dict[str, Any]] = {}
|
||||
for assignment in plant_assignments:
|
||||
plant_name = str(getattr(assignment.plant, "name", "") or "").strip()
|
||||
@@ -392,7 +426,7 @@ def build_cluster_crop_recommendations(farm_uuid: str) -> dict[str, Any]:
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
payload = {
|
||||
"farm_uuid": str(farm.farm_uuid),
|
||||
"location_id": location.id,
|
||||
"evaluated_plant_count": len(base_payloads),
|
||||
@@ -413,3 +447,68 @@ def build_cluster_crop_recommendations(farm_uuid: str) -> dict[str, Any]:
|
||||
"snapshot_block_count": len(snapshots),
|
||||
},
|
||||
}
|
||||
_store_cached_cluster_recommendations(
|
||||
farm_uuid=str(farm.farm_uuid),
|
||||
result_ids=recommendation_result_ids,
|
||||
plant_assignments=plant_assignments,
|
||||
payload=payload,
|
||||
)
|
||||
return payload
|
||||
|
||||
|
||||
def _build_assignment_cache_signature(plant_assignments: list[Any]) -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"plant_id": getattr(assignment.plant, "backend_plant_id", None),
|
||||
"position": int(assignment.position or 0),
|
||||
"stage": str(assignment.stage or ""),
|
||||
}
|
||||
for assignment in plant_assignments
|
||||
]
|
||||
|
||||
|
||||
def _load_cached_cluster_recommendations(
|
||||
*,
|
||||
farm_uuid: str,
|
||||
result_ids: list[int],
|
||||
plant_assignments: list[Any],
|
||||
) -> dict[str, Any] | None:
|
||||
if not result_ids:
|
||||
return None
|
||||
cache_key = f"farm::{farm_uuid}"
|
||||
assignment_signature = _build_assignment_cache_signature(plant_assignments)
|
||||
for result in RemoteSensingSubdivisionResult.objects.filter(id__in=result_ids):
|
||||
metadata = dict(result.metadata or {})
|
||||
recommendation_cache = dict(metadata.get("cluster_recommendations") or {})
|
||||
cached_entry = recommendation_cache.get(cache_key)
|
||||
if not isinstance(cached_entry, dict):
|
||||
continue
|
||||
if cached_entry.get("assignment_signature") != assignment_signature:
|
||||
continue
|
||||
payload = cached_entry.get("payload")
|
||||
if isinstance(payload, dict):
|
||||
return payload
|
||||
return None
|
||||
|
||||
|
||||
def _store_cached_cluster_recommendations(
|
||||
*,
|
||||
farm_uuid: str,
|
||||
result_ids: list[int],
|
||||
plant_assignments: list[Any],
|
||||
payload: dict[str, Any],
|
||||
) -> None:
|
||||
if not result_ids:
|
||||
return
|
||||
cache_key = f"farm::{farm_uuid}"
|
||||
assignment_signature = _build_assignment_cache_signature(plant_assignments)
|
||||
for result in RemoteSensingSubdivisionResult.objects.filter(id__in=result_ids):
|
||||
metadata = dict(result.metadata or {})
|
||||
recommendation_cache = dict(metadata.get("cluster_recommendations") or {})
|
||||
recommendation_cache[cache_key] = {
|
||||
"assignment_signature": assignment_signature,
|
||||
"payload": _json_safe(payload),
|
||||
}
|
||||
metadata["cluster_recommendations"] = recommendation_cache
|
||||
result.metadata = metadata
|
||||
result.save(update_fields=["metadata", "updated_at"])
|
||||
|
||||
Reference in New Issue
Block a user