enerlytics-ai 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. enerlytics_ai-0.1.0/LICENSE +21 -0
  2. enerlytics_ai-0.1.0/PKG-INFO +142 -0
  3. enerlytics_ai-0.1.0/README.md +89 -0
  4. enerlytics_ai-0.1.0/pyproject.toml +42 -0
  5. enerlytics_ai-0.1.0/setup.cfg +4 -0
  6. enerlytics_ai-0.1.0/src/enerlytics_ai/__init__.py +0 -0
  7. enerlytics_ai-0.1.0/src/enerlytics_ai/api/__init__.py +1 -0
  8. enerlytics_ai-0.1.0/src/enerlytics_ai/api/routes.py +105 -0
  9. enerlytics_ai-0.1.0/src/enerlytics_ai/app/__init__.py +1 -0
  10. enerlytics_ai-0.1.0/src/enerlytics_ai/app/config.py +47 -0
  11. enerlytics_ai-0.1.0/src/enerlytics_ai/app/main.py +11 -0
  12. enerlytics_ai-0.1.0/src/enerlytics_ai/models/__init__.py +1 -0
  13. enerlytics_ai-0.1.0/src/enerlytics_ai/models/energy_model.py +7 -0
  14. enerlytics_ai-0.1.0/src/enerlytics_ai/models/lcoe_model.py +15 -0
  15. enerlytics_ai-0.1.0/src/enerlytics_ai/services/__init__.py +1 -0
  16. enerlytics_ai-0.1.0/src/enerlytics_ai/services/pvgis_data_service.py +148 -0
  17. enerlytics_ai-0.1.0/src/enerlytics_ai/services/solar_data_service.py +188 -0
  18. enerlytics_ai-0.1.0/src/enerlytics_ai/services/solar_model_service.py +35 -0
  19. enerlytics_ai-0.1.0/src/enerlytics_ai/utils/__init__.py +1 -0
  20. enerlytics_ai-0.1.0/src/enerlytics_ai/utils/constants.py +17 -0
  21. enerlytics_ai-0.1.0/src/enerlytics_ai/utils/helpers.py +11 -0
  22. enerlytics_ai-0.1.0/src/enerlytics_ai.egg-info/PKG-INFO +142 -0
  23. enerlytics_ai-0.1.0/src/enerlytics_ai.egg-info/SOURCES.txt +24 -0
  24. enerlytics_ai-0.1.0/src/enerlytics_ai.egg-info/dependency_links.txt +1 -0
  25. enerlytics_ai-0.1.0/src/enerlytics_ai.egg-info/requires.txt +11 -0
  26. enerlytics_ai-0.1.0/src/enerlytics_ai.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Huseyin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: enerlytics-ai
3
+ Version: 0.1.0
4
+ Summary: AI platform for predicting renewable energy investment opportunities using meteorological data, machine learning and financial analytics.
5
+ Author-email: Huseyin Kucukogul <huseyinkucukogulcontact@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Huseyin
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/Kucukogul/enerlytics.ai
29
+ Project-URL: Repository, https://github.com/Kucukogul/enerlytics.ai
30
+ Keywords: energy,AI,machine learning,renewable energy,solar,analytics
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Intended Audience :: Science/Research
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.10
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
38
+ Requires-Python: >=3.10
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ Requires-Dist: fastapi>=0.110.0
42
+ Requires-Dist: uvicorn>=0.27.0
43
+ Requires-Dist: pandas>=2.1.0
44
+ Requires-Dist: numpy>=1.26.0
45
+ Requires-Dist: requests>=2.31.0
46
+ Requires-Dist: python-dotenv>=1.0.0
47
+ Requires-Dist: pyyaml>=6.0.0
48
+ Requires-Dist: pyarrow>=14.0.0
49
+ Requires-Dist: shapely>=2.0.0
50
+ Requires-Dist: tqdm>=4.66.0
51
+ Requires-Dist: tenacity>=8.2.0
52
+ Dynamic: license-file
53
+
54
+ # Enerlytics.ai
55
+
56
+ AI platform for predicting renewable energy investment opportunities in Turkiye.
57
+
58
+ Combines meteorological data, machine learning and financial analytics to estimate solar energy potential and evaluate project feasibility at any location in Turkiye.
59
+
60
+ ---
61
+
62
+ ## Setup
63
+
64
+ ```bash
65
+ python -m venv .venv && source .venv/bin/activate
66
+ pip install -r requirements.txt
67
+ cp .env.example .env
68
+ uvicorn enerlytics_ai.app.main:app --app-dir src --reload
69
+ ```
70
+
71
+ API → `http://127.0.0.1:8000` · Docs → `http://127.0.0.1:8000/docs`
72
+
73
+ ---
74
+
75
+ ## Quick Start
76
+
77
+ ```bash
78
+ # Site analysis
79
+ curl -X POST "http://127.0.0.1:8000/api/v1/analyze-site" \
80
+ -H "Content-Type: application/json" \
81
+ -d '{"latitude": 39.9208, "longitude": 32.8541}'
82
+
83
+ # Historical solar data
84
+ curl -X POST "http://127.0.0.1:8000/api/v1/historical-solar" \
85
+ -H "Content-Type: application/json" \
86
+ -d '{"latitude": 39.9208, "longitude": 32.8541, "start_year": 2015, "end_year": 2024}'
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Structure
92
+
93
+ ```
94
+ enerlytics.ai/
95
+
96
+ ├── src/enerlytics_ai/
97
+ │ ├── api/
98
+ │ │ └── routes.py # API route definitions
99
+ │ ├── app/
100
+ │ │ ├── main.py # FastAPI app entry point
101
+ │ │ └── config.py # app configuration
102
+ │ ├── models/
103
+ │ │ ├── energy_model.py # solar energy estimation model
104
+ │ │ └── lcoe_model.py # LCOE financial model
105
+ │ ├── services/
106
+ │ │ ├── pvgis_data_service.py # PVGIS API integration
107
+ │ │ ├── solar_data_service.py # solar data fetching & caching
108
+ │ │ └── solar_model_service.py # model inference service
109
+ │ └── utils/
110
+ │ ├── constants.py # shared constants
111
+ │ └── helpers.py # utility functions
112
+
113
+ ├── pipelines/
114
+ │ ├── province_scan.py # scans all 81 TR provinces
115
+ │ └── scoring.py # investment scoring logic
116
+
117
+ ├── analysis/
118
+ │ ├── notebooks/
119
+ │ │ ├── 01_eda.ipynb # exploratory data analysis
120
+ │ │ └── 02_turkiye_geneli_eda.ipynb # Turkiye-wide solar EDA
121
+ │ └── outputs/
122
+ │ ├── monthly_stats.csv
123
+ │ ├── tr81_monthly_risk_band.csv
124
+ │ └── tr81_province_ranking.csv # province investment ranking
125
+
126
+ ├── data/
127
+ │ ├── raw/pvgis/ # raw PVGIS API responses
128
+ │ └── processed/ # cleaned province datasets
129
+
130
+ ├── scripts/
131
+ │ └── fetch_pvgis_81_seriescalc.py # data collection script
132
+
133
+ └── tests/
134
+ ├── unit/
135
+ │ └── test_scoring.py
136
+ └── integration/
137
+ └── test_province_pipeline.py
138
+ ```
139
+
140
+ ---
141
+
142
+ **Author:** Huseyin Kucukogul · MIT License
@@ -0,0 +1,89 @@
1
+ # Enerlytics.ai
2
+
3
+ AI platform for predicting renewable energy investment opportunities in Turkiye.
4
+
5
+ Combines meteorological data, machine learning and financial analytics to estimate solar energy potential and evaluate project feasibility at any location in Turkiye.
6
+
7
+ ---
8
+
9
+ ## Setup
10
+
11
+ ```bash
12
+ python -m venv .venv && source .venv/bin/activate
13
+ pip install -r requirements.txt
14
+ cp .env.example .env
15
+ uvicorn enerlytics_ai.app.main:app --app-dir src --reload
16
+ ```
17
+
18
+ API → `http://127.0.0.1:8000` · Docs → `http://127.0.0.1:8000/docs`
19
+
20
+ ---
21
+
22
+ ## Quick Start
23
+
24
+ ```bash
25
+ # Site analysis
26
+ curl -X POST "http://127.0.0.1:8000/api/v1/analyze-site" \
27
+ -H "Content-Type: application/json" \
28
+ -d '{"latitude": 39.9208, "longitude": 32.8541}'
29
+
30
+ # Historical solar data
31
+ curl -X POST "http://127.0.0.1:8000/api/v1/historical-solar" \
32
+ -H "Content-Type: application/json" \
33
+ -d '{"latitude": 39.9208, "longitude": 32.8541, "start_year": 2015, "end_year": 2024}'
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Structure
39
+
40
+ ```
41
+ enerlytics.ai/
42
+
43
+ ├── src/enerlytics_ai/
44
+ │ ├── api/
45
+ │ │ └── routes.py # API route definitions
46
+ │ ├── app/
47
+ │ │ ├── main.py # FastAPI app entry point
48
+ │ │ └── config.py # app configuration
49
+ │ ├── models/
50
+ │ │ ├── energy_model.py # solar energy estimation model
51
+ │ │ └── lcoe_model.py # LCOE financial model
52
+ │ ├── services/
53
+ │ │ ├── pvgis_data_service.py # PVGIS API integration
54
+ │ │ ├── solar_data_service.py # solar data fetching & caching
55
+ │ │ └── solar_model_service.py # model inference service
56
+ │ └── utils/
57
+ │ ├── constants.py # shared constants
58
+ │ └── helpers.py # utility functions
59
+
60
+ ├── pipelines/
61
+ │ ├── province_scan.py # scans all 81 TR provinces
62
+ │ └── scoring.py # investment scoring logic
63
+
64
+ ├── analysis/
65
+ │ ├── notebooks/
66
+ │ │ ├── 01_eda.ipynb # exploratory data analysis
67
+ │ │ └── 02_turkiye_geneli_eda.ipynb # Turkiye-wide solar EDA
68
+ │ └── outputs/
69
+ │ ├── monthly_stats.csv
70
+ │ ├── tr81_monthly_risk_band.csv
71
+ │ └── tr81_province_ranking.csv # province investment ranking
72
+
73
+ ├── data/
74
+ │ ├── raw/pvgis/ # raw PVGIS API responses
75
+ │ └── processed/ # cleaned province datasets
76
+
77
+ ├── scripts/
78
+ │ └── fetch_pvgis_81_seriescalc.py # data collection script
79
+
80
+ └── tests/
81
+ ├── unit/
82
+ │ └── test_scoring.py
83
+ └── integration/
84
+ └── test_province_pipeline.py
85
+ ```
86
+
87
+ ---
88
+
89
+ **Author:** Huseyin Kucukogul · MIT License
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "enerlytics-ai"
7
+ version = "0.1.0"
8
+ description = "AI platform for predicting renewable energy investment opportunities using meteorological data, machine learning and financial analytics."
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [{ name = "Huseyin Kucukogul", email = "huseyinkucukogulcontact@gmail.com" }]
12
+ requires-python = ">=3.10"
13
+ keywords = ["energy", "AI", "machine learning", "renewable energy", "solar", "analytics"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Science/Research",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
22
+ ]
23
+ dependencies = [
24
+ "fastapi>=0.110.0",
25
+ "uvicorn>=0.27.0",
26
+ "pandas>=2.1.0",
27
+ "numpy>=1.26.0",
28
+ "requests>=2.31.0",
29
+ "python-dotenv>=1.0.0",
30
+ "pyyaml>=6.0.0",
31
+ "pyarrow>=14.0.0",
32
+ "shapely>=2.0.0",
33
+ "tqdm>=4.66.0",
34
+ "tenacity>=8.2.0",
35
+ ]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/Kucukogul/enerlytics.ai"
39
+ Repository = "https://github.com/Kucukogul/enerlytics.ai"
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,105 @@
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel, Field
3
+ from requests import exceptions as requests_exceptions
4
+
5
+ from enerlytics_ai.services.solar_data_service import fetch_historical_monthly_ghi_kwh_m2
6
+ from enerlytics_ai.services.solar_model_service import analyze_site
7
+
8
+ router = APIRouter()
9
+
10
+
11
+ class AnalyzeRequest(BaseModel):
12
+ latitude: float = Field(..., ge=-90, le=90)
13
+ longitude: float = Field(..., ge=-180, le=180)
14
+
15
+
16
+ class AnalyzeResponse(BaseModel):
17
+ data_source: str
18
+ annual_energy_kwh: float
19
+ estimated_lcoe: float
20
+ lcoe_try_kwh: float
21
+ simple_payback_years: float | None
22
+ summary: str
23
+
24
+
25
+ class HistoricalSolarRequest(BaseModel):
26
+ latitude: float = Field(..., ge=35.0, le=43.0)
27
+ longitude: float = Field(..., ge=25.0, le=46.0)
28
+ start_year: int = Field(..., ge=1984, le=2100)
29
+ end_year: int = Field(..., ge=1984, le=2100)
30
+
31
+
32
+ class HistoricalMonthlyPoint(BaseModel):
33
+ year: int
34
+ month: int
35
+ ghi_kwh_m2_day: float
36
+
37
+
38
+ class HistoricalAnnualSummaryPoint(BaseModel):
39
+ year: int
40
+ total_ghi_kwh_m2_day: float
41
+ average_ghi_kwh_m2_day: float
42
+
43
+
44
+ class HistoricalSolarResponse(BaseModel):
45
+ data_source: str
46
+ parameter: str
47
+ latitude: float
48
+ longitude: float
49
+ start_year: int
50
+ end_year: int
51
+ monthly_series: list[HistoricalMonthlyPoint]
52
+ annual_summary: list[HistoricalAnnualSummaryPoint]
53
+
54
+
55
+ @router.post("/api/v1/analyze-site", response_model=AnalyzeResponse)
56
+ def analyze(payload: AnalyzeRequest) -> AnalyzeResponse:
57
+ try:
58
+ result = analyze_site(payload.latitude, payload.longitude)
59
+ except requests_exceptions.Timeout as exc:
60
+ raise HTTPException(status_code=504, detail="Upstream solar data request timed out.") from exc
61
+ except requests_exceptions.ConnectionError as exc:
62
+ raise HTTPException(status_code=503, detail="Could not connect to upstream solar data provider.") from exc
63
+ except requests_exceptions.HTTPError as exc:
64
+ raise HTTPException(status_code=502, detail="Upstream solar data provider returned an error.") from exc
65
+ except requests_exceptions.RequestException as exc:
66
+ raise HTTPException(status_code=502, detail="Unexpected upstream request error during analysis.") from exc
67
+ except ValueError as exc:
68
+ raise HTTPException(status_code=422, detail=f"Analysis validation failed: {exc}") from exc
69
+ return AnalyzeResponse(**result)
70
+
71
+
72
+ @router.post("/api/v1/historical-solar", response_model=HistoricalSolarResponse)
73
+ def historical_solar(payload: HistoricalSolarRequest) -> HistoricalSolarResponse:
74
+ try:
75
+ result = fetch_historical_monthly_ghi_kwh_m2(
76
+ latitude=payload.latitude,
77
+ longitude=payload.longitude,
78
+ start_year=payload.start_year,
79
+ end_year=payload.end_year,
80
+ )
81
+ except requests_exceptions.Timeout as exc:
82
+ raise HTTPException(status_code=504, detail="Upstream solar data request timed out.") from exc
83
+ except requests_exceptions.ConnectionError as exc:
84
+ raise HTTPException(status_code=503, detail="Could not connect to upstream solar data provider.") from exc
85
+ except requests_exceptions.HTTPError as exc:
86
+ status_code = exc.response.status_code if exc.response is not None else "unknown"
87
+ raise HTTPException(
88
+ status_code=502,
89
+ detail=f"Upstream solar data provider returned an error (status: {status_code}).",
90
+ ) from exc
91
+ except requests_exceptions.RequestException as exc:
92
+ raise HTTPException(status_code=502, detail="Unexpected upstream request error during historical fetch.") from exc
93
+ except ValueError as exc:
94
+ raise HTTPException(status_code=422, detail=f"Historical solar validation failed: {exc}") from exc
95
+
96
+ return HistoricalSolarResponse(
97
+ data_source=result["source"],
98
+ parameter=result["parameter"],
99
+ latitude=result["latitude"],
100
+ longitude=result["longitude"],
101
+ start_year=result["start_year"],
102
+ end_year=result["end_year"],
103
+ monthly_series=result["monthly_series"],
104
+ annual_summary=result["annual_summary"],
105
+ )
@@ -0,0 +1,47 @@
1
+ import os
2
+ from dataclasses import dataclass
3
+
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class Settings:
11
+ solar_data_provider: str = os.getenv("SOLAR_DATA_PROVIDER", "nasa").lower()
12
+ nasa_power_base_url: str = os.getenv(
13
+ "NASA_POWER_BASE_URL",
14
+ "https://power.larc.nasa.gov/api/temporal/climatology/point",
15
+ )
16
+ nasa_power_monthly_base_url: str = os.getenv(
17
+ "NASA_POWER_MONTHLY_BASE_URL",
18
+ "https://power.larc.nasa.gov/api/temporal/monthly/point",
19
+ )
20
+ nasa_power_parameter: str = os.getenv("NASA_POWER_PARAMETER", "ALLSKY_SFC_SW_DWN")
21
+ nasa_power_community: str = os.getenv("NASA_POWER_COMMUNITY", "RE")
22
+ nasa_power_format: str = os.getenv("NASA_POWER_FORMAT", "JSON")
23
+ request_timeout_seconds: int = int(os.getenv("REQUEST_TIMEOUT_SECONDS", "15"))
24
+ enable_nasa_cache: bool = os.getenv("ENABLE_NASA_CACHE", "true").lower() == "true"
25
+ nasa_cache_dir: str = os.getenv("NASA_CACHE_DIR", "data/raw/nasa_power")
26
+ nasa_cache_ttl_hours: int = int(os.getenv("NASA_CACHE_TTL_HOURS", "720"))
27
+ pvgis_base_url: str = os.getenv("PVGIS_BASE_URL", "https://re.jrc.ec.europa.eu/api/v5_2/MRcalc")
28
+ pvgis_start_year: int = int(os.getenv("PVGIS_START_YEAR", "2015"))
29
+ pvgis_end_year: int = int(os.getenv("PVGIS_END_YEAR", "2020"))
30
+ enable_pvgis_cache: bool = os.getenv("ENABLE_PVGIS_CACHE", "true").lower() == "true"
31
+ pvgis_cache_dir: str = os.getenv("PVGIS_CACHE_DIR", "data/raw/pvgis")
32
+ pvgis_cache_ttl_hours: int = int(os.getenv("PVGIS_CACHE_TTL_HOURS", "720"))
33
+
34
+ panel_efficiency: float = float(os.getenv("PANEL_EFFICIENCY", "0.20"))
35
+ system_losses: float = float(os.getenv("SYSTEM_LOSSES", "0.14"))
36
+ default_system_area_m2: float = float(os.getenv("DEFAULT_SYSTEM_AREA_M2", "25"))
37
+ project_lifetime_years: int = int(os.getenv("PROJECT_LIFETIME_YEARS", "25"))
38
+
39
+ capex_usd: float = float(os.getenv("CAPEX_USD", "12000"))
40
+ opex_annual_usd: float = float(os.getenv("OPEX_ANNUAL_USD", "180"))
41
+ usd_try: float = float(os.getenv("USD_TRY", "32.0"))
42
+ discount_rate_tr: float = float(os.getenv("DISCOUNT_RATE_TR", "0.30"))
43
+ land_cost_try: float = float(os.getenv("LAND_COST_TRY", "0"))
44
+ electricity_sale_price_try_kwh: float = float(os.getenv("ELECTRICITY_SALE_PRICE_TRY_KWH", "2.5"))
45
+
46
+
47
+ settings = Settings()
@@ -0,0 +1,11 @@
1
+ from fastapi import FastAPI
2
+
3
+ from enerlytics_ai.api.routes import router as api_router
4
+
5
+ app = FastAPI(title="Enerlytics.ai", version="0.1.0")
6
+ app.include_router(api_router)
7
+
8
+
9
+ @app.get("/health")
10
+ def health() -> dict:
11
+ return {"status": "ok"}
@@ -0,0 +1,7 @@
1
+ from enerlytics_ai.app.config import settings
2
+
3
+
4
+ def estimate_annual_energy_kwh(annual_irradiance_kwh_m2: float) -> float:
5
+ raw_output = annual_irradiance_kwh_m2 * settings.default_system_area_m2 * settings.panel_efficiency
6
+ net_output = raw_output * (1 - settings.system_losses)
7
+ return round(net_output, 2)
@@ -0,0 +1,15 @@
1
+ from enerlytics_ai.app.config import settings
2
+
3
+
4
+ def estimate_lcoe_usd_per_kwh(annual_energy_kwh: float) -> float:
5
+ if annual_energy_kwh <= 0:
6
+ raise ValueError("Annual energy must be greater than zero.")
7
+ if settings.project_lifetime_years <= 0:
8
+ raise ValueError("Project lifetime must be greater than zero years.")
9
+ if settings.capex_usd < 0 or settings.opex_annual_usd < 0:
10
+ raise ValueError("CAPEX and annual OPEX cannot be negative.")
11
+
12
+ total_cost = settings.capex_usd + (settings.opex_annual_usd * settings.project_lifetime_years)
13
+ total_energy_output = annual_energy_kwh * settings.project_lifetime_years
14
+ lcoe = total_cost / total_energy_output
15
+ return round(lcoe, 4)
@@ -0,0 +1,148 @@
1
+ import json
2
+ import os
3
+ import time
4
+ from collections import defaultdict
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ import requests
9
+
10
+ from enerlytics_ai.app.config import settings
11
+
12
+
13
+ def _cache_file_path(latitude: float, longitude: float, start_year: int, end_year: int) -> Path:
14
+ cache_key = (
15
+ f"{latitude:.4f}_{longitude:.4f}_{start_year}_{end_year}".replace("-", "m")
16
+ )
17
+ return Path(settings.pvgis_cache_dir) / f"{cache_key}.json"
18
+
19
+
20
+ def _read_cached_payload(
21
+ latitude: float, longitude: float, start_year: int, end_year: int
22
+ ) -> Optional[Dict[str, Any]]:
23
+ if not settings.enable_pvgis_cache:
24
+ return None
25
+
26
+ cache_path = _cache_file_path(latitude, longitude, start_year, end_year)
27
+ if not cache_path.exists():
28
+ return None
29
+
30
+ try:
31
+ with cache_path.open("r", encoding="utf-8") as cache_file:
32
+ payload = json.load(cache_file)
33
+ except (OSError, json.JSONDecodeError, TypeError, ValueError):
34
+ try:
35
+ cache_path.unlink(missing_ok=True)
36
+ except OSError:
37
+ pass
38
+ return None
39
+
40
+ fetched_at = float(payload.get("fetched_at_unix", 0))
41
+ ttl_seconds = settings.pvgis_cache_ttl_hours * 3600
42
+ if ttl_seconds <= 0 or (time.time() - fetched_at) > ttl_seconds:
43
+ return None
44
+
45
+ monthly_entries = payload.get("monthly_entries")
46
+ if not isinstance(monthly_entries, list) or not monthly_entries:
47
+ return None
48
+ return monthly_entries
49
+
50
+
51
+ def _write_cache_payload(
52
+ latitude: float,
53
+ longitude: float,
54
+ start_year: int,
55
+ end_year: int,
56
+ monthly_entries: List[Dict[str, Any]],
57
+ ) -> None:
58
+ if not settings.enable_pvgis_cache:
59
+ return
60
+
61
+ cache_path = _cache_file_path(latitude, longitude, start_year, end_year)
62
+ os.makedirs(cache_path.parent, exist_ok=True)
63
+
64
+ with cache_path.open("w", encoding="utf-8") as cache_file:
65
+ json.dump(
66
+ {
67
+ "fetched_at_unix": int(time.time()),
68
+ "monthly_entries": monthly_entries,
69
+ },
70
+ cache_file,
71
+ )
72
+
73
+
74
+ def _request_monthly_entries(
75
+ latitude: float, longitude: float, start_year: int, end_year: int
76
+ ) -> List[Dict[str, Any]]:
77
+ params = {
78
+ "lat": latitude,
79
+ "lon": longitude,
80
+ "horirrad": 1,
81
+ "outputformat": "json",
82
+ "startyear": start_year,
83
+ "endyear": end_year,
84
+ }
85
+
86
+ response = requests.get(
87
+ settings.pvgis_base_url,
88
+ params=params,
89
+ timeout=settings.request_timeout_seconds,
90
+ )
91
+ response.raise_for_status()
92
+ payload = response.json()
93
+
94
+ monthly_entries = payload.get("outputs", {}).get("monthly", [])
95
+ if not isinstance(monthly_entries, list) or not monthly_entries:
96
+ raise ValueError("PVGIS MRcalc response missing monthly entries.")
97
+ return monthly_entries
98
+
99
+
100
+ def _annual_irradiance_from_monthly_entries(monthly_entries: List[Dict[str, Any]]) -> float:
101
+ yearly_totals: Dict[int, float] = defaultdict(float)
102
+ yearly_month_counts: Dict[int, int] = defaultdict(int)
103
+
104
+ for entry in monthly_entries:
105
+ try:
106
+ year = int(entry["year"])
107
+ value = float(entry["H(h)_m"])
108
+ except (KeyError, TypeError, ValueError) as exc:
109
+ raise ValueError("PVGIS monthly entry has invalid format.") from exc
110
+ yearly_totals[year] += value
111
+ yearly_month_counts[year] += 1
112
+
113
+ complete_years = [y for y, count in yearly_month_counts.items() if count == 12]
114
+ if not complete_years:
115
+ raise ValueError("PVGIS response has no complete-year monthly coverage.")
116
+
117
+ yearly_sums = [yearly_totals[year] for year in complete_years]
118
+ return sum(yearly_sums) / len(yearly_sums)
119
+
120
+
121
+ def fetch_annual_ghi_kwh_m2(latitude: float, longitude: float) -> Dict[str, Any]:
122
+ start_year = settings.pvgis_start_year
123
+ end_year = settings.pvgis_end_year
124
+ if start_year > end_year:
125
+ raise ValueError("PVGIS_START_YEAR must be less than or equal to PVGIS_END_YEAR.")
126
+
127
+ cached_entries = _read_cached_payload(latitude, longitude, start_year, end_year)
128
+ if cached_entries is not None:
129
+ annual_ghi = _annual_irradiance_from_monthly_entries(cached_entries)
130
+ return {
131
+ "source": "PVGIS (cache)",
132
+ "annual_irradiance_kwh_m2": round(annual_ghi, 2),
133
+ "monthly_entries": cached_entries,
134
+ "start_year": start_year,
135
+ "end_year": end_year,
136
+ }
137
+
138
+ monthly_entries = _request_monthly_entries(latitude, longitude, start_year, end_year)
139
+ _write_cache_payload(latitude, longitude, start_year, end_year, monthly_entries)
140
+ annual_ghi = _annual_irradiance_from_monthly_entries(monthly_entries)
141
+
142
+ return {
143
+ "source": "PVGIS",
144
+ "annual_irradiance_kwh_m2": round(annual_ghi, 2),
145
+ "monthly_entries": monthly_entries,
146
+ "start_year": start_year,
147
+ "end_year": end_year,
148
+ }
@@ -0,0 +1,188 @@
1
+ import json
2
+ import os
3
+ import time
4
+ from pathlib import Path
5
+ from typing import Any, Dict, Optional
6
+
7
+ import requests
8
+
9
+ from enerlytics_ai.app.config import settings
10
+ from enerlytics_ai.utils.helpers import monthly_to_annual_irradiance
11
+
12
+
13
+ def _cache_file_path(latitude: float, longitude: float) -> Path:
14
+ cache_key = f"{latitude:.4f}_{longitude:.4f}_{settings.nasa_power_parameter}".replace("-", "m")
15
+ return Path(settings.nasa_cache_dir) / f"{cache_key}.json"
16
+
17
+
18
+ def _read_cached_monthly_data(latitude: float, longitude: float) -> Optional[Dict[str, Any]]:
19
+ if not settings.enable_nasa_cache:
20
+ return None
21
+
22
+ cache_path = _cache_file_path(latitude, longitude)
23
+ if not cache_path.exists():
24
+ return None
25
+
26
+ try:
27
+ with cache_path.open("r", encoding="utf-8") as cache_file:
28
+ payload = json.load(cache_file)
29
+ except (OSError, json.JSONDecodeError, TypeError, ValueError):
30
+ try:
31
+ cache_path.unlink(missing_ok=True)
32
+ except OSError:
33
+ pass
34
+ return None
35
+
36
+ fetched_at = float(payload.get("fetched_at_unix", 0))
37
+ ttl_seconds = settings.nasa_cache_ttl_hours * 3600
38
+ if ttl_seconds <= 0 or (time.time() - fetched_at) > ttl_seconds:
39
+ return None
40
+
41
+ monthly_daily_ghi = payload.get("monthly_daily_ghi")
42
+ if not isinstance(monthly_daily_ghi, dict):
43
+ return None
44
+ return monthly_daily_ghi
45
+
46
+
47
+ def _write_cache_payload(latitude: float, longitude: float, monthly_daily_ghi: Dict[str, Any]) -> None:
48
+ if not settings.enable_nasa_cache:
49
+ return
50
+
51
+ cache_path = _cache_file_path(latitude, longitude)
52
+ os.makedirs(cache_path.parent, exist_ok=True)
53
+
54
+ with cache_path.open("w", encoding="utf-8") as cache_file:
55
+ json.dump(
56
+ {
57
+ "fetched_at_unix": int(time.time()),
58
+ "monthly_daily_ghi": monthly_daily_ghi,
59
+ },
60
+ cache_file,
61
+ )
62
+
63
+
64
+ def fetch_annual_ghi_kwh_m2(latitude: float, longitude: float) -> Dict[str, Any]:
65
+ cached_monthly_data = _read_cached_monthly_data(latitude=latitude, longitude=longitude)
66
+ if cached_monthly_data is not None:
67
+ annual_ghi = monthly_to_annual_irradiance(cached_monthly_data)
68
+ return {
69
+ "source": "NASA POWER (cache)",
70
+ "annual_irradiance_kwh_m2": round(annual_ghi, 2),
71
+ "monthly_daily_ghi": cached_monthly_data,
72
+ }
73
+
74
+ params = {
75
+ "parameters": settings.nasa_power_parameter,
76
+ "community": settings.nasa_power_community,
77
+ "longitude": longitude,
78
+ "latitude": latitude,
79
+ "format": settings.nasa_power_format,
80
+ }
81
+
82
+ response = requests.get(
83
+ settings.nasa_power_base_url,
84
+ params=params,
85
+ timeout=settings.request_timeout_seconds,
86
+ )
87
+ response.raise_for_status()
88
+ payload = response.json()
89
+
90
+ monthly_data = (
91
+ payload.get("properties", {})
92
+ .get("parameter", {})
93
+ .get(settings.nasa_power_parameter, {})
94
+ )
95
+ _write_cache_payload(latitude=latitude, longitude=longitude, monthly_daily_ghi=monthly_data)
96
+ annual_ghi = monthly_to_annual_irradiance(monthly_data)
97
+
98
+ return {
99
+ "source": "NASA POWER",
100
+ "annual_irradiance_kwh_m2": round(annual_ghi, 2),
101
+ "monthly_daily_ghi": monthly_data,
102
+ }
103
+
104
+
105
+ def fetch_historical_monthly_ghi_kwh_m2(
106
+ latitude: float,
107
+ longitude: float,
108
+ start_year: int,
109
+ end_year: int,
110
+ ) -> Dict[str, Any]:
111
+ if start_year > end_year:
112
+ raise ValueError("start_year must be less than or equal to end_year.")
113
+
114
+ params = {
115
+ "parameters": settings.nasa_power_parameter,
116
+ "community": settings.nasa_power_community,
117
+ "longitude": longitude,
118
+ "latitude": latitude,
119
+ "format": settings.nasa_power_format,
120
+ "start": str(start_year),
121
+ "end": str(end_year),
122
+ }
123
+
124
+ response = requests.get(
125
+ settings.nasa_power_monthly_base_url,
126
+ params=params,
127
+ timeout=settings.request_timeout_seconds,
128
+ )
129
+ response.raise_for_status()
130
+ payload = response.json()
131
+
132
+ monthly_data = (
133
+ payload.get("properties", {})
134
+ .get("parameter", {})
135
+ .get(settings.nasa_power_parameter, {})
136
+ )
137
+ if not isinstance(monthly_data, dict):
138
+ raise ValueError("Historical solar data format is invalid.")
139
+
140
+ monthly_series = []
141
+ for key, value in monthly_data.items():
142
+ if not (isinstance(key, str) and len(key) == 6 and key.isdigit()):
143
+ continue
144
+ year = int(key[:4])
145
+ month = int(key[4:])
146
+ if year < start_year or year > end_year or month < 1 or month > 12:
147
+ continue
148
+ monthly_series.append(
149
+ {
150
+ "year": year,
151
+ "month": month,
152
+ "ghi_kwh_m2_day": float(value),
153
+ }
154
+ )
155
+
156
+ monthly_series.sort(key=lambda item: (item["year"], item["month"]))
157
+ if not monthly_series:
158
+ raise ValueError("No historical monthly solar data returned for requested range.")
159
+
160
+ annual_summary_map: Dict[int, Dict[str, float]] = {}
161
+ for point in monthly_series:
162
+ year = point["year"]
163
+ annual_summary_map.setdefault(year, {"sum_ghi_kwh_m2_day": 0.0, "month_count": 0})
164
+ annual_summary_map[year]["sum_ghi_kwh_m2_day"] += point["ghi_kwh_m2_day"]
165
+ annual_summary_map[year]["month_count"] += 1
166
+
167
+ annual_summary = []
168
+ for year in sorted(annual_summary_map):
169
+ year_total = annual_summary_map[year]["sum_ghi_kwh_m2_day"]
170
+ month_count = annual_summary_map[year]["month_count"]
171
+ annual_summary.append(
172
+ {
173
+ "year": year,
174
+ "total_ghi_kwh_m2_day": round(year_total, 4),
175
+ "average_ghi_kwh_m2_day": round(year_total / month_count, 4),
176
+ }
177
+ )
178
+
179
+ return {
180
+ "source": "NASA POWER",
181
+ "parameter": settings.nasa_power_parameter,
182
+ "latitude": latitude,
183
+ "longitude": longitude,
184
+ "start_year": start_year,
185
+ "end_year": end_year,
186
+ "monthly_series": monthly_series,
187
+ "annual_summary": annual_summary,
188
+ }
@@ -0,0 +1,35 @@
1
+ from enerlytics_ai.models.energy_model import estimate_annual_energy_kwh
2
+ from enerlytics_ai.models.lcoe_model import estimate_lcoe_usd_per_kwh
3
+ from enerlytics_ai.app.config import settings
4
+ from enerlytics_ai.services import pvgis_data_service, solar_data_service
5
+
6
+
7
+ def _fetch_solar_data(latitude: float, longitude: float, provider: str | None = None) -> dict:
8
+ selected = (provider or settings.solar_data_provider).lower()
9
+ if selected == "nasa":
10
+ return solar_data_service.fetch_annual_ghi_kwh_m2(latitude=latitude, longitude=longitude)
11
+ if selected == "pvgis":
12
+ return pvgis_data_service.fetch_annual_ghi_kwh_m2(latitude=latitude, longitude=longitude)
13
+ raise ValueError("Invalid solar data provider. Supported values: nasa, pvgis.")
14
+
15
+
16
+ def analyze_site(latitude: float, longitude: float, provider: str | None = None) -> dict:
17
+ solar_data = _fetch_solar_data(latitude=latitude, longitude=longitude, provider=provider)
18
+ annual_energy_kwh = estimate_annual_energy_kwh(solar_data["annual_irradiance_kwh_m2"])
19
+ estimated_lcoe_usd = estimate_lcoe_usd_per_kwh(annual_energy_kwh)
20
+ lcoe_try_kwh = round(estimated_lcoe_usd * settings.usd_try, 4)
21
+ annual_revenue_try = annual_energy_kwh * settings.electricity_sale_price_try_kwh
22
+ total_investment_try = (settings.capex_usd * settings.usd_try) + settings.land_cost_try
23
+ simple_payback_years = round(total_investment_try / annual_revenue_try, 2) if annual_revenue_try > 0 else None
24
+
25
+ return {
26
+ "data_source": solar_data["source"],
27
+ "annual_energy_kwh": annual_energy_kwh,
28
+ "estimated_lcoe": estimated_lcoe_usd,
29
+ "lcoe_try_kwh": lcoe_try_kwh,
30
+ "simple_payback_years": simple_payback_years,
31
+ "summary": (
32
+ f"Estimated annual production is {annual_energy_kwh} kWh with "
33
+ f"an LCOE of {estimated_lcoe_usd} USD/kWh ({lcoe_try_kwh} TRY/kWh)."
34
+ ),
35
+ }
@@ -0,0 +1,17 @@
1
+ MONTHS_IN_YEAR = 12
2
+ DAYS_IN_YEAR = 365
3
+
4
+ NASA_MONTH_KEYS = (
5
+ "JAN",
6
+ "FEB",
7
+ "MAR",
8
+ "APR",
9
+ "MAY",
10
+ "JUN",
11
+ "JUL",
12
+ "AUG",
13
+ "SEP",
14
+ "OCT",
15
+ "NOV",
16
+ "DEC",
17
+ )
@@ -0,0 +1,11 @@
1
+ from typing import Dict
2
+
3
+ from enerlytics_ai.utils.constants import DAYS_IN_YEAR, MONTHS_IN_YEAR, NASA_MONTH_KEYS
4
+
5
+
6
+ def monthly_to_annual_irradiance(monthly_daily_ghi: Dict[str, float]) -> float:
7
+ values = [float(monthly_daily_ghi[m]) for m in NASA_MONTH_KEYS if m in monthly_daily_ghi]
8
+ if len(values) != MONTHS_IN_YEAR:
9
+ raise ValueError("Incomplete monthly GHI data from NASA POWER.")
10
+ average_daily_ghi = sum(values) / MONTHS_IN_YEAR
11
+ return average_daily_ghi * DAYS_IN_YEAR
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: enerlytics-ai
3
+ Version: 0.1.0
4
+ Summary: AI platform for predicting renewable energy investment opportunities using meteorological data, machine learning and financial analytics.
5
+ Author-email: Huseyin Kucukogul <huseyinkucukogulcontact@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Huseyin
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/Kucukogul/enerlytics.ai
29
+ Project-URL: Repository, https://github.com/Kucukogul/enerlytics.ai
30
+ Keywords: energy,AI,machine learning,renewable energy,solar,analytics
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Intended Audience :: Science/Research
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.10
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
38
+ Requires-Python: >=3.10
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ Requires-Dist: fastapi>=0.110.0
42
+ Requires-Dist: uvicorn>=0.27.0
43
+ Requires-Dist: pandas>=2.1.0
44
+ Requires-Dist: numpy>=1.26.0
45
+ Requires-Dist: requests>=2.31.0
46
+ Requires-Dist: python-dotenv>=1.0.0
47
+ Requires-Dist: pyyaml>=6.0.0
48
+ Requires-Dist: pyarrow>=14.0.0
49
+ Requires-Dist: shapely>=2.0.0
50
+ Requires-Dist: tqdm>=4.66.0
51
+ Requires-Dist: tenacity>=8.2.0
52
+ Dynamic: license-file
53
+
54
+ # Enerlytics.ai
55
+
56
+ AI platform for predicting renewable energy investment opportunities in Turkiye.
57
+
58
+ Combines meteorological data, machine learning and financial analytics to estimate solar energy potential and evaluate project feasibility at any location in Turkiye.
59
+
60
+ ---
61
+
62
+ ## Setup
63
+
64
+ ```bash
65
+ python -m venv .venv && source .venv/bin/activate
66
+ pip install -r requirements.txt
67
+ cp .env.example .env
68
+ uvicorn enerlytics_ai.app.main:app --app-dir src --reload
69
+ ```
70
+
71
+ API → `http://127.0.0.1:8000` · Docs → `http://127.0.0.1:8000/docs`
72
+
73
+ ---
74
+
75
+ ## Quick Start
76
+
77
+ ```bash
78
+ # Site analysis
79
+ curl -X POST "http://127.0.0.1:8000/api/v1/analyze-site" \
80
+ -H "Content-Type: application/json" \
81
+ -d '{"latitude": 39.9208, "longitude": 32.8541}'
82
+
83
+ # Historical solar data
84
+ curl -X POST "http://127.0.0.1:8000/api/v1/historical-solar" \
85
+ -H "Content-Type: application/json" \
86
+ -d '{"latitude": 39.9208, "longitude": 32.8541, "start_year": 2015, "end_year": 2024}'
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Structure
92
+
93
+ ```
94
+ enerlytics.ai/
95
+
96
+ ├── src/enerlytics_ai/
97
+ │ ├── api/
98
+ │ │ └── routes.py # API route definitions
99
+ │ ├── app/
100
+ │ │ ├── main.py # FastAPI app entry point
101
+ │ │ └── config.py # app configuration
102
+ │ ├── models/
103
+ │ │ ├── energy_model.py # solar energy estimation model
104
+ │ │ └── lcoe_model.py # LCOE financial model
105
+ │ ├── services/
106
+ │ │ ├── pvgis_data_service.py # PVGIS API integration
107
+ │ │ ├── solar_data_service.py # solar data fetching & caching
108
+ │ │ └── solar_model_service.py # model inference service
109
+ │ └── utils/
110
+ │ ├── constants.py # shared constants
111
+ │ └── helpers.py # utility functions
112
+
113
+ ├── pipelines/
114
+ │ ├── province_scan.py # scans all 81 TR provinces
115
+ │ └── scoring.py # investment scoring logic
116
+
117
+ ├── analysis/
118
+ │ ├── notebooks/
119
+ │ │ ├── 01_eda.ipynb # exploratory data analysis
120
+ │ │ └── 02_turkiye_geneli_eda.ipynb # Turkiye-wide solar EDA
121
+ │ └── outputs/
122
+ │ ├── monthly_stats.csv
123
+ │ ├── tr81_monthly_risk_band.csv
124
+ │ └── tr81_province_ranking.csv # province investment ranking
125
+
126
+ ├── data/
127
+ │ ├── raw/pvgis/ # raw PVGIS API responses
128
+ │ └── processed/ # cleaned province datasets
129
+
130
+ ├── scripts/
131
+ │ └── fetch_pvgis_81_seriescalc.py # data collection script
132
+
133
+ └── tests/
134
+ ├── unit/
135
+ │ └── test_scoring.py
136
+ └── integration/
137
+ └── test_province_pipeline.py
138
+ ```
139
+
140
+ ---
141
+
142
+ **Author:** Huseyin Kucukogul · MIT License
@@ -0,0 +1,24 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/enerlytics_ai/__init__.py
5
+ src/enerlytics_ai.egg-info/PKG-INFO
6
+ src/enerlytics_ai.egg-info/SOURCES.txt
7
+ src/enerlytics_ai.egg-info/dependency_links.txt
8
+ src/enerlytics_ai.egg-info/requires.txt
9
+ src/enerlytics_ai.egg-info/top_level.txt
10
+ src/enerlytics_ai/api/__init__.py
11
+ src/enerlytics_ai/api/routes.py
12
+ src/enerlytics_ai/app/__init__.py
13
+ src/enerlytics_ai/app/config.py
14
+ src/enerlytics_ai/app/main.py
15
+ src/enerlytics_ai/models/__init__.py
16
+ src/enerlytics_ai/models/energy_model.py
17
+ src/enerlytics_ai/models/lcoe_model.py
18
+ src/enerlytics_ai/services/__init__.py
19
+ src/enerlytics_ai/services/pvgis_data_service.py
20
+ src/enerlytics_ai/services/solar_data_service.py
21
+ src/enerlytics_ai/services/solar_model_service.py
22
+ src/enerlytics_ai/utils/__init__.py
23
+ src/enerlytics_ai/utils/constants.py
24
+ src/enerlytics_ai/utils/helpers.py
@@ -0,0 +1,11 @@
1
+ fastapi>=0.110.0
2
+ uvicorn>=0.27.0
3
+ pandas>=2.1.0
4
+ numpy>=1.26.0
5
+ requests>=2.31.0
6
+ python-dotenv>=1.0.0
7
+ pyyaml>=6.0.0
8
+ pyarrow>=14.0.0
9
+ shapely>=2.0.0
10
+ tqdm>=4.66.0
11
+ tenacity>=8.2.0
@@ -0,0 +1 @@
1
+ enerlytics_ai