Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .gitignore
Binary file not shown.
10 changes: 10 additions & 0 deletions modules/strategy-agent/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app ./app

EXPOSE 8010
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8010"]
Binary file added modules/strategy-agent/IA_Brief_adverse.pdf
Binary file not shown.
Binary file added modules/strategy-agent/IA_Brief_baseline.pdf
Binary file not shown.
Binary file added modules/strategy-agent/IA_Brief_recovery.pdf
Binary file not shown.
255 changes: 252 additions & 3 deletions modules/strategy-agent/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,253 @@
# Strategy & Feedback Agent
Owner: Antonio Soto Grande
Goal: /recommend, /counterfactual, /brief + templates.
# Strategy & Feedback Agent — IA Edition
**Owner:** Antonio Soto Grande
**Version:** 0.3.0
**Part of:** OpenPolicyStack

---

## Overview

The Strategy & Feedback Agent (SFA) is an AI-based microservice that automates EU Impact Assessment (IA) artefacts aligned with the European Commission's Better Regulation framework. Given a structured IA blueprint and live open-data indicators, it scores policy options across six IA criteria, runs weight sensitivity analysis, generates evidence-linked stakeholder briefs, produces LLM-enhanced narratives, and exports professional PDF reports.

The system is piloted on the **EU Quantum Act** Impact Assessment but is designed to be reusable across any EU technical legislation (Chips Act, Cloud infrastructure directive, etc.) by swapping the indicator catalogue. It is designed to be rerun each time new data is available, producing consistent, comparable, traceable outputs across time.

---

## Architecture

```
AssessRequest
└── IABlueprint (legislation + options + objectives)
└── indicators[] ← Eurostat + CORDIS live fetch (or explicit values)
scoring.py MCDA weighted additive scoring (6 IA criteria)
explain.py Driver attribution (top-3 contributing criteria)
evidence.py Evidence payload + assumption collection
AssessResponse Ranked OptionScore[] + drivers + evidence

sensitivity.py Weight variation across 4 stakeholder scenarios
SensitivityResponse Per-scenario rankings + stability flag

├──▶ /brief Deterministic markdown brief (always works)
├──▶ /brief/llm LLM-enhanced brief via Anthropic API
└──▶ /brief/pdf Professional 3-page PDF export
```

---

## API

**Base URL:** `http://localhost:8010`

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/health` | Liveness check |
| GET | `/indicators` | Fetch live Eurostat + CORDIS indicators |
| POST | `/assess` | Score policy options (MCDA) |
| POST | `/sensitivity` | Weight sensitivity analysis |
| POST | `/brief` | Deterministic evidence-linked markdown brief |
| POST | `/brief/llm` | LLM-enhanced brief (requires `ANTHROPIC_API_KEY`) |
| POST | `/brief/pdf` | Professional 3-page PDF export |

---

## IA Criteria and Scoring Scale

Each policy option is scored on six criteria derived from the EU Better Regulation framework:

| Criterion | Weight (default) |
|-----------|-----------------|
| Economic impact | 0.25 |
| Competitiveness | 0.25 |
| Social impact | 0.15 |
| Feasibility | 0.15 |
| Environmental impact | 0.10 |
| Coherence | 0.10 |

**Scale:** `-2` (strongly negative) to `+2` (strongly positive).
Every score is linked to an evidence item or carries an explicit assumption tag.

---

## Data Sources

| Indicator | Source | Dataset |
|-----------|--------|---------|
| EU GERD as % of GDP | Eurostat | `rd_e_gerdtot` |
| R&D personnel as % active pop | Eurostat | `rd_p_persocc` |
| High-tech employment % | Eurostat | `htec_emp_nat2` |
| Quantum project count | CORDIS | public search |
| Quantum funding (M EUR) | CORDIS | public search |

All sources are free and require no authentication.
On fetch failure, the system falls back to proxy values with a `quality_flag: proxy` tag.

---

## Run (Docker-first)

```bash
# Build
docker build -t ops-strategy-agent modules/strategy-agent

# Run (with LLM support)
docker run --rm -p 8010:8010 \
-e ANTHROPIC_API_KEY="your-key-here" \
ops-strategy-agent

# Run (without LLM — /brief/llm falls back to deterministic)
docker run --rm -p 8010:8010 ops-strategy-agent

# Health check
curl http://localhost:8010/health

# Fetch live indicators
curl http://localhost:8010/indicators

# Run baseline assessment
curl -X POST http://localhost:8010/assess \
-H "Content-Type: application/json" \
-d @modules/strategy-agent/examples/assess_request_baseline.json

# Generate PDF brief
curl -X POST http://localhost:8010/brief/pdf \
-H "Content-Type: application/json" \
-d @modules/strategy-agent/examples/assess_response_baseline.json \
--output modules/strategy-agent/IA_Brief_baseline.pdf
```

---

## Streamlit UI

A Streamlit visualization app is included for interactive demonstration and exploration.

**Requirements:** Python 3.10+ with streamlit and requests installed.

```bash
pip install streamlit requests pandas
```

**Run (Docker service must be running first):**

```bash
python -m streamlit run modules/strategy-agent/streamlit_app.py
```

The app opens automatically in your browser and provides:

| Tab | Description |
|-----|-------------|
| 📊 MCDA Scoring | Full criteria scoring table, key drivers, per-option rationale |
| 🔄 Sensitivity Analysis | Weight scenario matrix showing ranking stability |
| 📝 Brief Comparison | Side-by-side deterministic vs LLM-enhanced brief |
| 📋 Evidence & Provenance | Full indicator table with quality flags and assumptions |

The sidebar allows scenario selection (baseline / adverse / recovery) and PDF download.

---

## PDF Brief

The `/brief/pdf` endpoint generates a professional 3-page PDF:

- **Page 1** — Executive summary (LLM narrative + option ranking table)
- **Page 2** — Full MCDA scoring table with rationales per option
- **Page 3** — Evidence payload, indicator notes, and explicit assumptions

The PDF uses EU Commission colour styling and includes a full methodology note and traceability footer on every page.

---

## LLM Layer

The `/brief/llm` endpoint calls the Anthropic API (`claude-sonnet-4-20250514`) to generate a readable narrative brief constrained strictly to structured scoring outputs. The system prompt enforces:

- No new facts beyond what is in the structured payload
- No changes to rankings, scores, or option names
- Explicit reflection of assumption and proxy flags
- Output under 400 words in plain accessible language

If the API key is missing or the call fails, the endpoint falls back gracefully to the deterministic `/brief` output. The service never breaks the orchestrator.

---

## Examples

Golden request/response pairs are in `examples/` and serve as both documentation and integration tests:

| File | Description |
|------|-------------|
| `assess_request_baseline.json` | Baseline scenario — live Eurostat/CORDIS fetch |
| `assess_response_baseline.json` | Expected response, opt-2 preferred (score: +1.425) |
| `assess_request_adverse.json` | Adverse shock — declining GERD + reduced funding |
| `assess_response_adverse.json` | Expected response, opt-3 preferred (score: +1.275) |
| `assess_request_recovery.json` | Recovery — rising GERD + oversubscribed quantum calls |
| `assess_response_recovery.json` | Expected response, opt-2 preferred (score: +1.625) |

---

## Repository Structure

```
modules/strategy-agent/
├── app/
│ ├── main.py # FastAPI app — 6 endpoints
│ ├── schemas.py # Pydantic models (all I/O types)
│ ├── config.py # IA criteria weights + scoring anchors
│ ├── indicator_catalogue.py # Live Eurostat + CORDIS fetchers
│ ├── scoring.py # MCDA scoring engine
│ ├── sensitivity.py # Weight sensitivity analysis
│ ├── explain.py # Driver attribution
│ ├── evidence.py # Evidence payload builder
│ ├── llm_brief.py # Constrained LLM brief via Anthropic API
│ ├── pdf_brief.py # Professional PDF generator (reportlab)
│ └── utils.py # Shared helpers
├── examples/
│ ├── assess_request_baseline.json
│ ├── assess_response_baseline.json
│ ├── assess_request_adverse.json
│ ├── assess_response_adverse.json
│ ├── assess_request_recovery.json
│ └── assess_response_baseline.json
├── streamlit_app.py # Interactive Streamlit UI
├── Dockerfile
├── requirements.txt
└── README.md
```

---

## Evidence and Provenance

Every output includes a structured evidence payload. Each item records:
- `indicator_id` — unique indicator reference
- `source_type` — `eurostat` | `cordis` | `derived` | `manual`
- `source_ref` — dataset code or endpoint (e.g. `eurostat:rd_e_gerdtot`)
- `field_path` — specific field used (e.g. `gerd_pct_gdp.latest`)
- `quality_flag` — `verified` | `proxy` | `assumption` | `stale`

The `no evidence → no claim` rule is enforced: any criteria score without a referenced indicator must carry an explicit `assumption` string.

---

## Sensitivity Analysis

Four weight scenarios test ranking robustness:

| Scenario | Focus |
|----------|-------|
| Economic priority | Elevated weights on economic + competitiveness criteria |
| Social/environmental priority | Elevated weights on social + environmental criteria |
| Feasibility priority | Elevated weight on feasibility (risk-averse perspective) |
| Equal weights | All criteria weighted equally (baseline sensitivity check) |

Results show whether the preferred option is stable across stakeholder value assumptions.
85 changes: 85 additions & 0 deletions modules/strategy-agent/app/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""
config.py — IA criteria weights and scoring configuration.
Uses plain string keys throughout to avoid enum import issues.
"""

DEFAULT_WEIGHTS: dict = {
"economic": 0.25,
"social": 0.15,
"environmental": 0.10,
"competitiveness": 0.25,
"feasibility": 0.15,
"coherence": 0.10,
}

SCORING_ANCHORS: dict = {
"economic": {
"+2": "Strong positive GDP/investment impact, well-evidenced",
"+1": "Moderate positive economic effect",
"0": "Neutral or negligible economic impact",
"-1": "Moderate negative effect on costs or market",
"-2": "Strong negative economic impact or significant compliance cost",
},
"social": {
"+2": "Major improvement in jobs, skills, or inclusion",
"+1": "Moderate positive social effect",
"0": "No significant social impact",
"-1": "Some negative social effect",
"-2": "Significant negative social consequences",
},
"environmental": {
"+2": "Strong reduction in environmental footprint",
"+1": "Moderate environmental benefit",
"0": "Neutral environmental impact",
"-1": "Minor negative environmental effect",
"-2": "Significant environmental harm",
},
"competitiveness": {
"+2": "Major boost to EU strategic autonomy and global tech leadership",
"+1": "Moderate improvement in competitiveness",
"0": "No significant effect on competitiveness",
"-1": "Slight competitiveness disadvantage",
"-2": "Significant competitive harm vs non-EU players",
},
"feasibility": {
"+2": "Highly feasible, low implementation risk, clear governance",
"+1": "Feasible with manageable challenges",
"0": "Uncertain feasibility",
"-1": "Difficult to implement, high risk",
"-2": "Not feasible in current context",
},
"coherence": {
"+2": "Fully coherent with EU objectives and existing regulation",
"+1": "Largely coherent with minor tensions",
"0": "Neutral coherence",
"-1": "Some tension with existing policy frameworks",
"-2": "Significant incoherence or contradiction with EU law",
},
}

SENSITIVITY_WEIGHT_GRID: list = [
{
"label": "economic_priority",
"economic": 0.40, "social": 0.10, "environmental": 0.05,
"competitiveness": 0.30, "feasibility": 0.10, "coherence": 0.05,
},
{
"label": "social_env_priority",
"economic": 0.15, "social": 0.25, "environmental": 0.25,
"competitiveness": 0.15, "feasibility": 0.10, "coherence": 0.10,
},
{
"label": "feasibility_priority",
"economic": 0.20, "social": 0.10, "environmental": 0.05,
"competitiveness": 0.20, "feasibility": 0.35, "coherence": 0.10,
},
{
"label": "equal_weights",
"economic": 0.167, "social": 0.167, "environmental": 0.167,
"competitiveness": 0.167, "feasibility": 0.167, "coherence": 0.165,
},
]

TOP_K_DRIVERS = 3
SCORE_MIN = -2.0
SCORE_MAX = 2.0
Loading