KSvend Claude Happy commited on
Commit
12eb573
·
1 Parent(s): 27ce485

docs: spec for AOI advisor — Claude-powered region insight

Browse files

LLM-driven advice card that recommends timeframe and indicator
priorities when user places an AOI. Uses Claude Haiku via backend proxy.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

docs/superpowers/specs/2026-04-06-aoi-advisor-design.md ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AOI Advisor — Claude-Powered Region Insight
2
+
3
+ **Date:** 2026-04-06
4
+ **Status:** Approved
5
+ **Depends on:** AOI click-to-place redesign (2026-04-06)
6
+
7
+ ## Summary
8
+
9
+ When a user places an AOI on the map, the backend calls the Claude API to generate a brief region-aware recommendation: what timeframe is interesting for the EO products in that area, and which indicators to prioritize. The advice auto-fills the date pickers and appears as a card in the sidebar. The user can adjust before continuing.
10
+
11
+ ## Motivation
12
+
13
+ Aperture users (humanitarian programme teams) often don't know the optimal analysis window for a given region. A rainy season, a drought, a displacement event — these affect which time window and indicators will produce the most useful results. A lightweight Claude call with the coordinates can provide immediate, contextual guidance that improves analysis quality.
14
+
15
+ ## User Flow
16
+
17
+ 1. User places AOI (click or geocode) → AOI appears on map
18
+ 2. Sidebar shows "Analyzing region..." with a loading indicator
19
+ 3. Continue button is disabled during loading
20
+ 4. Frontend sends `POST /api/aoi-advice` with the bbox
21
+ 5. Backend calls Claude API, returns structured advice
22
+ 6. Frontend displays advice card, auto-fills date pickers with recommended window
23
+ 7. User reads advice, adjusts dates/indicators if needed, clicks Continue
24
+
25
+ If the Claude API call fails (missing key, timeout, error), the advice card is skipped silently — Continue enables immediately and the user proceeds without guidance.
26
+
27
+ ## Advice Card UI
28
+
29
+ Appears in the sidebar between the AOI size toggles and the date pickers:
30
+
31
+ ```
32
+ ┌─────────────────────────────────┐
33
+ │ Region Insight │
34
+ │ │
35
+ │ This area in eastern Chad near │
36
+ │ the Sudanese border has seen │
37
+ │ significant displacement since │
38
+ │ mid-2024. The rainy season runs │
39
+ │ June–October. │
40
+ │ │
41
+ │ Recommended: Jun 2025 – Apr 2026│
42
+ │ Priority: NDVI, Water, SAR │
43
+ └─────────────────────────────────┘
44
+ ```
45
+
46
+ - Styled with MERLx warm surface background (`--shell-warm`), subtle border
47
+ - Uses `--font-ui` for text, `--text-xs` size
48
+ - "Region Insight" heading in `--font-ui` bold, `--text-sm`
49
+ - "Recommended" line in `--font-data`
50
+ - "Priority" line lists indicator names as small pill badges
51
+ - No dismiss button — card stays visible until AOI changes
52
+ - When AOI is re-placed (new click or geocode), card resets to loading state and fetches again
53
+
54
+ ## API Endpoint
55
+
56
+ ### `POST /api/aoi-advice`
57
+
58
+ **Request:**
59
+ ```json
60
+ {
61
+ "bbox": [32.4, 15.6, 32.65, 15.8]
62
+ }
63
+ ```
64
+
65
+ **Response (success):**
66
+ ```json
67
+ {
68
+ "context": "This area covers parts of North Khartoum, Sudan. The region has experienced conflict-related displacement since April 2023. The rainy season runs June through October, with peak vegetation in September.",
69
+ "recommended_start": "2025-04-01",
70
+ "recommended_end": "2026-04-01",
71
+ "indicator_priorities": ["ndvi", "water", "sar", "buildup"],
72
+ "reasoning": "NDVI captures drought and agricultural impact. Water monitors Nile flooding risk. SAR detects ground changes from conflict damage. Built-up tracks displacement-driven settlement changes."
73
+ }
74
+ ```
75
+
76
+ **Response (failure):**
77
+ ```json
78
+ {
79
+ "context": null,
80
+ "recommended_start": null,
81
+ "recommended_end": null,
82
+ "indicator_priorities": null,
83
+ "reasoning": null
84
+ }
85
+ ```
86
+
87
+ Frontend treats all-null response same as a failed request: skip the card, enable Continue.
88
+
89
+ ### Authentication
90
+
91
+ The endpoint requires the same user auth token as other `/api/` endpoints (Bearer header).
92
+
93
+ ## Backend Implementation
94
+
95
+ ### Config
96
+
97
+ New environment variable: `ANTHROPIC_API_KEY`
98
+
99
+ In `app/config.py`:
100
+ ```python
101
+ ANTHROPIC_API_KEY: str | None = os.environ.get("ANTHROPIC_API_KEY")
102
+ ```
103
+
104
+ ### New Module: `app/advisor.py`
105
+
106
+ Single async function `get_aoi_advice(bbox: list[float]) -> dict` that:
107
+
108
+ 1. Computes center point from bbox
109
+ 2. Builds a prompt with center coordinates, current date, and indicator descriptions
110
+ 3. Calls Claude API (`claude-haiku-4-5-20251001` for speed/cost) with structured JSON output
111
+ 4. Parses response, validates dates, returns the dict
112
+ 5. On any error: logs warning, returns all-null dict
113
+
114
+ ### Claude Prompt
115
+
116
+ **System prompt:**
117
+ ```
118
+ You are a remote sensing advisor for humanitarian programme teams. Given a geographic location, provide a brief analysis recommendation.
119
+
120
+ Available Earth observation indicators:
121
+ - NDVI: Vegetation health from Sentinel-2. Detects drought, crop stress, deforestation.
122
+ - Water (MNDWI): Water extent from Sentinel-2. Detects flooding, drought, reservoir changes.
123
+ - SAR: Radar backscatter from Sentinel-1. Detects ground surface changes, flooding, construction.
124
+ - Built-up (NDBI): Settlement extent from Sentinel-2. Detects urban growth, displacement camps.
125
+
126
+ Coverage: East Africa region. Resolution: 100m. Max analysis window: 3 years.
127
+
128
+ Respond with JSON only, no markdown. Structure:
129
+ {
130
+ "context": "1-3 sentences about this region and recent relevant events",
131
+ "recommended_start": "YYYY-MM-DD",
132
+ "recommended_end": "YYYY-MM-DD",
133
+ "indicator_priorities": ["indicator_id", ...],
134
+ "reasoning": "1 sentence per indicator explaining why it's relevant here"
135
+ }
136
+ ```
137
+
138
+ **User prompt:**
139
+ ```
140
+ Location: {center_lat}°N, {center_lng}°E
141
+ Current date: {today}
142
+ Recommend an analysis timeframe and indicator priorities for this area.
143
+ ```
144
+
145
+ **Model:** `claude-haiku-4-5-20251001` (fast, cheap, sufficient for this structured task)
146
+ **Temperature:** 0
147
+ **Max tokens:** 500
148
+
149
+ ### Route
150
+
151
+ New route in `app/main.py` or a new router file `app/routes/advisor.py`:
152
+
153
+ ```python
154
+ @app.post("/api/aoi-advice")
155
+ async def aoi_advice(request: AoiAdviceRequest, user = Depends(get_current_user)):
156
+ result = await get_aoi_advice(request.bbox)
157
+ return result
158
+ ```
159
+
160
+ ### Request Model
161
+
162
+ In `app/models.py`:
163
+ ```python
164
+ class AoiAdviceRequest(BaseModel):
165
+ bbox: list[float]
166
+ ```
167
+
168
+ ## Frontend Implementation
169
+
170
+ ### Changes to `frontend/js/map.js`
171
+
172
+ No changes. The map module just places the AOI and fires the callback.
173
+
174
+ ### Changes to `frontend/js/api.js`
175
+
176
+ New function:
177
+ ```javascript
178
+ export async function getAoiAdvice(bbox) {
179
+ const res = await fetch('/api/aoi-advice', {
180
+ method: 'POST',
181
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
182
+ body: JSON.stringify({ bbox }),
183
+ });
184
+ if (!res.ok) return null;
185
+ return res.json();
186
+ }
187
+ ```
188
+
189
+ ### Changes to `frontend/index.html`
190
+
191
+ Add the advice card placeholder between the size toggles and the date pickers:
192
+
193
+ ```html
194
+ <!-- AOI Advisor -->
195
+ <div id="aoi-advisor" class="aoi-advisor" style="display:none;">
196
+ <div id="aoi-advisor-loading" class="aoi-advisor-loading">
197
+ Analyzing region…
198
+ </div>
199
+ <div id="aoi-advisor-content" style="display:none;">
200
+ <div class="aoi-advisor-heading">Region Insight</div>
201
+ <p id="advisor-context" class="aoi-advisor-text"></p>
202
+ <p id="advisor-dates" class="aoi-advisor-meta"></p>
203
+ <div id="advisor-priorities" class="aoi-advisor-priorities"></div>
204
+ </div>
205
+ </div>
206
+ ```
207
+
208
+ ### Changes to `frontend/js/app.js`
209
+
210
+ In the `onAoiChange` callback inside `setupDefineArea()`:
211
+
212
+ 1. When bbox is set: show advisor card in loading state, disable Continue
213
+ 2. Call `getAoiAdvice(bbox)`
214
+ 3. On success: populate card, auto-fill date pickers, enable Continue
215
+ 4. On failure/null: hide advisor card, enable Continue immediately
216
+
217
+ ### Changes to `frontend/css/merlx.css`
218
+
219
+ New styles for the advisor card:
220
+
221
+ ```css
222
+ .aoi-advisor {
223
+ background: var(--shell-warm);
224
+ border: 1px solid var(--border);
225
+ border-radius: var(--radius-sm);
226
+ padding: var(--space-5);
227
+ margin-top: var(--space-3);
228
+ }
229
+
230
+ .aoi-advisor-loading {
231
+ font-family: var(--font-ui);
232
+ font-size: var(--text-xs);
233
+ color: var(--ink-muted);
234
+ }
235
+
236
+ .aoi-advisor-heading {
237
+ font-family: var(--font-ui);
238
+ font-size: var(--text-sm);
239
+ font-weight: 600;
240
+ color: var(--ink);
241
+ margin-bottom: var(--space-2);
242
+ }
243
+
244
+ .aoi-advisor-text {
245
+ font-family: var(--font-ui);
246
+ font-size: var(--text-xs);
247
+ color: var(--ink);
248
+ line-height: 1.5;
249
+ margin-bottom: var(--space-3);
250
+ }
251
+
252
+ .aoi-advisor-meta {
253
+ font-family: var(--font-data);
254
+ font-size: var(--text-xs);
255
+ color: var(--ink-muted);
256
+ margin-bottom: var(--space-2);
257
+ }
258
+
259
+ .aoi-advisor-priorities {
260
+ display: flex;
261
+ gap: var(--space-2);
262
+ flex-wrap: wrap;
263
+ }
264
+ ```
265
+
266
+ ## Dependencies
267
+
268
+ New Python package: `anthropic` (add to `pyproject.toml` dependencies).
269
+
270
+ ## Files Modified
271
+
272
+ | File | Changes |
273
+ |------|---------|
274
+ | `app/config.py` | Add `ANTHROPIC_API_KEY` |
275
+ | `app/models.py` | Add `AoiAdviceRequest` model |
276
+ | `app/advisor.py` | New module: `get_aoi_advice()` function |
277
+ | `app/main.py` | Add `/api/aoi-advice` route |
278
+ | `pyproject.toml` | Add `anthropic` dependency |
279
+ | `frontend/index.html` | Add advisor card HTML |
280
+ | `frontend/js/api.js` | Add `getAoiAdvice()` function |
281
+ | `frontend/js/app.js` | Wire advisor into AOI placement flow |
282
+ | `frontend/css/merlx.css` | Add advisor card styles |
283
+
284
+ ## What This Does NOT Change
285
+
286
+ - AOI placement mechanics (click-to-place from prior spec)
287
+ - Indicator selection page (user still picks which indicators to run)
288
+ - Job submission payload structure
289
+ - Backend EO processing pipeline
290
+ - Results display
291
+
292
+ ## Cost Estimate
293
+
294
+ Claude Haiku at ~500 tokens per call: approximately $0.001 per AOI advice request. Negligible.