KSvend commited on
Commit
9f90242
·
1 Parent(s): 7534962

feat: add advisor module — Claude-powered region insight

Browse files
Files changed (1) hide show
  1. app/advisor.py +95 -0
app/advisor.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AOI Advisor — Claude-powered region insight for analysis recommendations."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import logging
6
+ from datetime import date
7
+
8
+ from app.config import ANTHROPIC_API_KEY
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ _SYSTEM_PROMPT = """\
13
+ You are a remote sensing advisor for humanitarian programme teams. Given a geographic location, provide a brief analysis recommendation.
14
+
15
+ Available Earth observation indicators:
16
+ - ndvi: Vegetation health from Sentinel-2. Detects drought, crop stress, deforestation.
17
+ - water: Water extent (MNDWI) from Sentinel-2. Detects flooding, drought, reservoir changes.
18
+ - sar: Radar backscatter from Sentinel-1. Detects ground surface changes, flooding, construction.
19
+ - buildup: Settlement extent (NDBI) from Sentinel-2. Detects urban growth, displacement camps.
20
+
21
+ Coverage: East Africa region. Resolution: 100m. Max analysis window: 3 years.
22
+
23
+ Respond with JSON only, no markdown. Structure:
24
+ {
25
+ "context": "1-3 sentences about this region and recent relevant events",
26
+ "recommended_start": "YYYY-MM-DD",
27
+ "recommended_end": "YYYY-MM-DD",
28
+ "indicator_priorities": ["indicator_id", ...],
29
+ "reasoning": "1 sentence per indicator explaining why it is relevant here"
30
+ }"""
31
+
32
+ _EMPTY_RESPONSE = {
33
+ "context": None,
34
+ "recommended_start": None,
35
+ "recommended_end": None,
36
+ "indicator_priorities": None,
37
+ "reasoning": None,
38
+ }
39
+
40
+
41
+ async def get_aoi_advice(bbox: list[float]) -> dict:
42
+ """Call Claude to get region-aware analysis recommendations.
43
+
44
+ Returns structured advice dict. On any failure, returns all-null dict.
45
+ """
46
+ if not ANTHROPIC_API_KEY:
47
+ logger.warning("ANTHROPIC_API_KEY not set — skipping AOI advisor")
48
+ return _EMPTY_RESPONSE
49
+
50
+ center_lng = (bbox[0] + bbox[2]) / 2
51
+ center_lat = (bbox[1] + bbox[3]) / 2
52
+ today = date.today().isoformat()
53
+
54
+ user_prompt = (
55
+ f"Location: {center_lat:.4f}°N, {center_lng:.4f}°E\n"
56
+ f"Current date: {today}\n"
57
+ f"Recommend an analysis timeframe and indicator priorities for this area."
58
+ )
59
+
60
+ try:
61
+ import anthropic
62
+
63
+ client = anthropic.AsyncAnthropic(api_key=ANTHROPIC_API_KEY)
64
+ message = await client.messages.create(
65
+ model="claude-haiku-4-5-20251001",
66
+ max_tokens=500,
67
+ temperature=0,
68
+ system=_SYSTEM_PROMPT,
69
+ messages=[{"role": "user", "content": user_prompt}],
70
+ )
71
+
72
+ raw = message.content[0].text
73
+ advice = json.loads(raw)
74
+
75
+ # Validate expected keys are present
76
+ for key in ("context", "recommended_start", "recommended_end", "indicator_priorities", "reasoning"):
77
+ if key not in advice:
78
+ logger.warning("AOI advisor response missing key: %s", key)
79
+ return _EMPTY_RESPONSE
80
+
81
+ # Validate dates are parseable
82
+ date.fromisoformat(advice["recommended_start"])
83
+ date.fromisoformat(advice["recommended_end"])
84
+
85
+ # Filter indicator_priorities to only known indicators
86
+ valid_ids = {"ndvi", "water", "sar", "buildup"}
87
+ advice["indicator_priorities"] = [
88
+ i for i in advice["indicator_priorities"] if i in valid_ids
89
+ ]
90
+
91
+ return advice
92
+
93
+ except Exception as exc:
94
+ logger.warning("AOI advisor failed: %s", exc)
95
+ return _EMPTY_RESPONSE