""" تسک‌های Celery برای واکشی داده‌های خاک. """ from decimal import Decimal from config.celery import app from django.apps import apps from django.db import transaction from .models import SoilDepthData, SoilLocation from .soil_adapters import DEPTHS try: import requests except ImportError: # pragma: no cover - handled in stripped envs RequestException = Exception else: RequestException = requests.RequestException def fetch_soil_data_for_coordinates( latitude: float, longitude: float, task_id: str = "", progress_callback=None, ): """ واکشی سنکرون داده خاک برای مختصات داده‌شده و ذخیره در DB. این helper هم توسط Celery task و هم توسط endpointهای sync استفاده می‌شود. """ lat = Decimal(str(round(float(latitude), 6))) lon = Decimal(str(round(float(longitude), 6))) adapter = apps.get_app_config("location_data").get_soil_data_adapter() with transaction.atomic(): location, created = SoilLocation.objects.select_for_update().get_or_create( latitude=lat, longitude=lon, defaults={"task_id": task_id}, ) if not created and task_id: location.task_id = task_id location.save(update_fields=["task_id"]) for index, depth in enumerate(DEPTHS): if progress_callback is not None: progress_callback( state="PROGRESS", meta={ "current": index + 1, "total": len(DEPTHS), "message": f"در حال واکشی عمق {depth}...", }, ) fields = adapter.fetch_depth_fields(float(lon), float(lat), depth) with transaction.atomic(): SoilDepthData.objects.update_or_create( soil_location=location, depth_label=depth, defaults=fields, ) if task_id: with transaction.atomic(): location.task_id = "" location.save(update_fields=["task_id"]) return { "status": "completed", "location_id": location.id, "depths": DEPTHS, } @app.task(bind=True) def fetch_soil_data_task(self, latitude: float, longitude: float): """ واکشی داده‌های خاک برای مختصات داده‌شده و ذخیره در DB. برای هر عمق (0-5cm, 5-15cm, 15-30cm) یک ریکوئست/شبیه‌سازی جدا انجام می‌شود. """ try: return fetch_soil_data_for_coordinates( latitude=latitude, longitude=longitude, task_id=self.request.id, progress_callback=self.update_state, ) except RequestException as exc: lat = Decimal(str(round(float(latitude), 6))) lon = Decimal(str(round(float(longitude), 6))) location = SoilLocation.objects.filter(latitude=lat, longitude=lon).first() return { "status": "error", "location_id": getattr(location, "id", None), "error": str(exc), }