Skip to content

Commit 53b3b25

Browse files
committed
Add CAMPD integration with new endpoint and client for emissions and compliance data
1 parent acd21ae commit 53b3b25

5 files changed

Lines changed: 207 additions & 1 deletion

File tree

API_DOCUMENTATION.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,19 @@ Returns EDGAR urban emissions data with trend analysis.
245245
- `pollutant` (optional): Pollutant type (default: "PM2.5")
246246
- `window` (optional): Trend analysis window in years (default: 3)
247247

248+
## Endpoint Data Global
249+
250+
### GET /global/campd
251+
252+
Mengekspos data mentah dari API CAMPD untuk fasilitas tertentu.
253+
254+
**Parameter Kueri:**
255+
- `facility_id` (integer, wajib): ID fasilitas dari CAMPD.
256+
257+
**Contoh Permintaan:**
258+
```bash
259+
curl -X GET "http://localhost:5000/global/campd?facility_id=12345"
260+
248261
#### 8. CEVS Composite Score
249262
**GET** `/global/cevs/<company>`
250263

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,13 @@ Documentation
213213
- ISO: CSV/JSON and Excel list (ISO_CSV_URL, ISO_XLSX_PATH)
214214
- EDGAR: UCDB Excel aggregated per country-year (set EDGAR_XLSX_PATH or place file at `reference/EDGAR_emiss_on_UCDB_2024.xlsx`)
215215

216+
## Integrasi Data CAMPD
217+
218+
Proyek ini terintegrasi dengan [Clean Air Markets Program Data (CAMPD)](https://www.epa.gov/airmarkets) untuk memperkaya Skor Verifikasi Lingkungan Komprehensif (CEVS) dengan data emisi dan kepatuhan dari pembangkit listrik di AS.
219+
220+
### Konfigurasi
221+
222+
Untuk mengaktifkan integrasi ini, Anda memerlukan API key dari CAMPD. Tambahkan kunci Anda ke file `.env` Anda:
216223
CEVS pollution trend source selection: set `CEVS_POLLUTION_SOURCE` to `auto` (default), `eea`, or `edgar`.
217224

218225
## ✅ Current Status

api/clients/campd_client.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import os
2+
import requests
3+
from dotenv import load_dotenv
4+
5+
load_dotenv()
6+
7+
class CAMDClient:
8+
"""
9+
Client for interacting with the Clean Air Markets Program Data (CAMPD) API.
10+
This client manages requests and authentication using a dedicated API key.
11+
"""
12+
def __init__(self):
13+
self.api_key = os.getenv("CAMPD_API_KEY")
14+
if not self.api_key:
15+
raise ValueError("CAMPD_API_KEY environment variable not set.")
16+
self.base_url = "https://api.epa.gov/easey"
17+
18+
def get_emissions_data(self, facility_id):
19+
"""
20+
Retrieves emissions data for a specific facility.
21+
22+
Args:
23+
facility_id (int): The ID of the facility to retrieve data for.
24+
25+
Returns:
26+
dict: A dictionary containing the emissions data, or None if the request fails.
27+
"""
28+
endpoint = f"/api/v2/facilities/{facility_id}/emissions"
29+
headers = {
30+
"x-api-key": self.api_key
31+
}
32+
try:
33+
response = requests.get(f"{self.base_url}{endpoint}", headers=headers)
34+
response.raise_for_status() # Raise an exception for bad status codes
35+
return response.json()
36+
except requests.exceptions.RequestException as e:
37+
print(f"Error fetching emissions data from CAMPD: {e}")
38+
return None
39+
40+
def get_compliance_data(self, facility_id):
41+
"""
42+
Retrieves compliance data for a specific facility.
43+
44+
Args:
45+
facility_id (int): The ID of the facility to retrieve data for.
46+
47+
Returns:
48+
dict: A dictionary containing the compliance data, or None if the request fails.
49+
"""
50+
endpoint = f"/api/v2/facilities/{facility_id}/compliance"
51+
headers = {
52+
"x-api-key": self.api_key
53+
}
54+
try:
55+
response = requests.get(f"{self.base_url}{endpoint}", headers=headers)
56+
response.raise_for_status()
57+
return response.json()
58+
except requests.exceptions.RequestException as e:
59+
print(f"Error fetching compliance data from CAMPD: {e}")
60+
return None

api/routes/global_data.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from api.clients.iso_client import ISOClient
1313
from api.clients.eea_client import EEAClient
1414
from api.clients.edgar_client import EDGARClient
15+
# --- PERUBAHAN 1: Impor CAMDClient ---
16+
from api.clients.campd_client import CAMDClient
1517
from api.services.cevs_aggregator import compute_cevs_for_company
1618

1719

@@ -365,5 +367,66 @@ def global_cevs(company_name: str):
365367
logger.error(f"Error in /global/cevs/{company_name}: {e}")
366368
return jsonify({"status": "error", "message": str(e)}), 500
367369

370+
# --- PERUBAHAN 2: Tambahkan endpoint baru untuk CAMPD ---
371+
@global_bp.route('/global/campd', methods=['GET'])
372+
@swag_from({
373+
'tags': ['Global Data'],
374+
'summary': 'Get Raw CAMPD Data',
375+
'description': 'Retrieves raw emissions and compliance data from the CAMPD API for a given facility. Requires API key authentication.',
376+
'security': [{'ApiKeyAuth': []}],
377+
'parameters': [
378+
{
379+
'name': 'facility_id',
380+
'in': 'query',
381+
'type': 'integer',
382+
'required': True,
383+
'description': 'The facility ID from CAMPD (e.g., 7 for Tennessee Valley Authority).',
384+
'example': 7
385+
}
386+
],
387+
'responses': {
388+
200: {
389+
'description': 'Successful response with CAMPD data.',
390+
'schema': {
391+
'type': 'object',
392+
'properties': {
393+
'status': {'type': 'string'},
394+
'emissions': {'type': 'object'},
395+
'compliance': {'type': 'object'}
396+
}
397+
}
398+
},
399+
400: {
400+
'description': 'Missing or invalid facility_id parameter.'
401+
},
402+
500: {
403+
'description': 'Error fetching data from CAMPD API.'
404+
}
405+
}
406+
})
407+
def get_campd_data():
408+
"""
409+
Endpoint to retrieve raw data from the CAMPD API for a given facility.
410+
"""
411+
facility_id_str = request.args.get('facility_id')
412+
if not facility_id_str or not facility_id_str.isdigit():
413+
return jsonify({"status": "error", "message": "A valid facility_id parameter is required"}), 400
414+
415+
try:
416+
facility_id = int(facility_id_str)
417+
client = CAMDClient()
418+
emissions = client.get_emissions_data(facility_id)
419+
compliance = client.get_compliance_data(facility_id)
420+
421+
return jsonify({
422+
"status": "success",
423+
"emissions": emissions,
424+
"compliance": compliance,
425+
"retrieved_at": datetime.now().isoformat(),
426+
})
427+
except Exception as e:
428+
logger.error(f"Error in /global/campd: {e}")
429+
return jsonify({"status": "error", "message": str(e)}), 500
430+
368431

369432
__all__ = ["global_bp"]

api/services/cevs_aggregator.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from api.clients.iso_client import ISOClient
99
from api.clients.eea_client import EEAClient
1010
from api.clients.edgar_client import EDGARClient
11+
# --- PERUBAHAN 1: Impor CAMDClient ---
12+
from api.clients.campd_client import CAMDClient
1113
from api.utils.policy import load_best_practices, practices_for_country
1214

1315
logger = logging.getLogger(__name__)
@@ -25,9 +27,23 @@ def compute_cevs_for_company(company_name: str, *, company_country: Optional[str
2527
- +30 if company has ISO 14001 certification
2628
- - up to 30 penalty based on EPA results count in the company's state (proxy via name contains)
2729
- + up to 20 boost for EEA indicator improvements (placeholder)
30+
- - up to 50 penalty based on CAMPD emissions and compliance data
2831
"""
2932
company_key = _normalize_name(company_name)
3033

34+
# --- PERUBAHAN 2: Inisialisasi CAMDClient ---
35+
campd_client = CAMDClient()
36+
37+
# Placeholder untuk pemetaan nama perusahaan ke ID fasilitas CAMPD.
38+
# Dalam implementasi nyata, Anda memerlukan cara yang lebih dinamis untuk mendapatkan ini.
39+
facility_id_map = {
40+
"example power plant inc.": 12345,
41+
"tennessee valley authority": 7, # Contoh nyata
42+
"southern company": 553, # Contoh nyata
43+
}
44+
facility_id = facility_id_map.get(company_key)
45+
46+
3147
# EPA: use permits data normalized list, then filter by company name
3248
epa_client = EPAClient()
3349
# Try a short-timeout EPA fetch for responsiveness
@@ -72,7 +88,9 @@ def compute_cevs_for_company(company_name: str, *, company_country: Optional[str
7288
"epa_penalty": 0.0,
7389
"renewables_bonus": 0.0,
7490
"pollution_penalty": 0.0,
75-
"policy_bonus": 0.0,
91+
"policy_bonus": 0.0,
92+
# --- PERUBAHAN 3: Tambahkan komponen baru untuk CAMPD ---
93+
"campd_penalty": 0.0,
7694
}
7795

7896
if has_iso:
@@ -186,6 +204,49 @@ def slope_for(key: str) -> Dict[str, Any]:
186204
components["policy_bonus"] = policy_bonus
187205
score += policy_bonus
188206

207+
# --- PERUBAHAN 4: Logika penskoran untuk data CAMPD ---
208+
campd_penalty = 0.0
209+
campd_details = {}
210+
if facility_id:
211+
try:
212+
emissions_data = campd_client.get_emissions_data(facility_id)
213+
compliance_data = campd_client.get_compliance_data(facility_id)
214+
215+
# Logika penalti emisi (contoh sederhana)
216+
# Penalti berdasarkan total emisi CO2, SO2, dan NOx
217+
# Anda harus menyesuaikan ambang batas ini
218+
emissions_penalty = 0
219+
if emissions_data:
220+
total_co2 = sum(d.get('co2Mass', 0) for d in emissions_data)
221+
total_so2 = sum(d.get('so2Mass', 0) for d in emissions_data)
222+
total_nox = sum(d.get('noxMass', 0) for d in emissions_data)
223+
224+
if total_co2 > 5000000: emissions_penalty += 10
225+
if total_so2 > 1000: emissions_penalty += 5
226+
if total_nox > 1000: emissions_penalty += 5
227+
228+
# Logika penalti kepatuhan
229+
# Penalti 20 poin jika ada satu saja indikator tidak patuh
230+
compliance_penalty = 0
231+
if compliance_data:
232+
is_compliant = all(d.get('compliantIndicator', 1) == 1 for d in compliance_data)
233+
if not is_compliant:
234+
compliance_penalty = 20
235+
236+
campd_penalty = min(40.0, emissions_penalty + compliance_penalty) # Batasi penalti maksimal
237+
campd_details = {
238+
"facility_id": facility_id,
239+
"emissions_penalty": emissions_penalty,
240+
"compliance_penalty": compliance_penalty,
241+
"total_penalty": campd_penalty
242+
}
243+
244+
except Exception as e:
245+
logger.warning(f"Gagal mengambil atau memproses data CAMPD untuk facility_id {facility_id}: {e}")
246+
247+
components["campd_penalty"] = -campd_penalty
248+
score -= campd_penalty
249+
189250
# Clamp score to [0, 100]
190251
score = max(0.0, min(100.0, score))
191252

@@ -209,5 +270,7 @@ def slope_for(key: str) -> Dict[str, Any]:
209270
"renewables": {"country_row": renew_row, "eu_row": eu_row, "bonus_calc": renew_details},
210271
"pollution_trend": pol_details or pol_trend,
211272
"policy": policy_details,
273+
# --- PERUBAHAN 5: Tambahkan detail CAMPD ke output ---
274+
"campd": campd_details,
212275
},
213276
}

0 commit comments

Comments
 (0)