"""AOI Advisor — Claude-powered region insight for analysis recommendations.""" from __future__ import annotations import json import logging from datetime import date from app.config import ANTHROPIC_API_KEY logger = logging.getLogger(__name__) _SYSTEM_PROMPT = """\ You are a remote sensing advisor for humanitarian programme teams. Given a geographic location, provide a brief analysis recommendation. Available EO products: - ndvi: Vegetation health from Sentinel-2. Detects drought, crop stress, deforestation. - water: Water extent (MNDWI) from Sentinel-2. Detects flooding, drought, reservoir changes. - sar: Radar backscatter from Sentinel-1. Detects ground surface changes, flooding, construction. - buildup: Settlement extent (NDBI) from Sentinel-2. Detects urban growth, displacement camps. Coverage: East Africa region. Resolution: 100m. Max analysis window: 3 years. Respond with JSON only, no markdown. Structure: { "context": "1-3 sentences about this region and recent relevant events", "recommended_start": "YYYY-MM-DD", "recommended_end": "YYYY-MM-DD", "product_priorities": ["product_id", ...], "reasoning": "1 sentence per EO product explaining why it is relevant here" }""" _EMPTY_RESPONSE = { "context": None, "recommended_start": None, "recommended_end": None, "product_priorities": None, "reasoning": None, } # Lazily-initialized Anthropic client (reused across requests for connection pooling) _client = None def _get_client(): global _client if _client is None: import anthropic _client = anthropic.AsyncAnthropic(api_key=ANTHROPIC_API_KEY) return _client async def get_aoi_advice(bbox: list[float]) -> dict: """Call Claude to get region-aware analysis recommendations. Returns structured advice dict. On any failure, returns all-null dict. """ if not ANTHROPIC_API_KEY: logger.warning("ANTHROPIC_API_KEY not set — skipping AOI advisor") return _EMPTY_RESPONSE center_lng = (bbox[0] + bbox[2]) / 2 center_lat = (bbox[1] + bbox[3]) / 2 today = date.today().isoformat() lat_label = f"{abs(center_lat):.4f}°{'N' if center_lat >= 0 else 'S'}" lng_label = f"{abs(center_lng):.4f}°{'E' if center_lng >= 0 else 'W'}" user_prompt = ( f"Location: {lat_label}, {lng_label}\n" f"Current date: {today}\n" f"Recommend an analysis timeframe and indicator priorities for this area." ) try: client = _get_client() message = await client.messages.create( model="claude-haiku-4-5-20251001", max_tokens=500, temperature=0, system=_SYSTEM_PROMPT, messages=[{"role": "user", "content": user_prompt}], ) raw = message.content[0].text advice = json.loads(raw) # Validate expected keys are present for key in ("context", "recommended_start", "recommended_end", "product_priorities", "reasoning"): if key not in advice: logger.warning("AOI advisor response missing key: %s", key) return _EMPTY_RESPONSE # Validate dates are parseable date.fromisoformat(advice["recommended_start"]) date.fromisoformat(advice["recommended_end"]) # Filter product_priorities to only known indicators valid_ids = {"ndvi", "water", "sar", "buildup"} advice["product_priorities"] = [ i for i in advice["product_priorities"] if i in valid_ids ] return advice except Exception as exc: logger.warning("AOI advisor failed: %s", exc) return _EMPTY_RESPONSE