atmoswing-api 0.1.0__py3-none-any.whl
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.
- app/__init__.py +0 -0
- app/main.py +23 -0
- app/models/__init__.py +0 -0
- app/models/forecast.py +120 -0
- app/models/meta.py +23 -0
- app/routes/__init__.py +0 -0
- app/routes/aggregations.py +89 -0
- app/routes/forecasts.py +251 -0
- app/routes/meta.py +85 -0
- app/services/__init__.py +0 -0
- app/services/aggregations.py +210 -0
- app/services/forecasts.py +430 -0
- app/services/meta.py +181 -0
- app/utils/__init__.py +0 -0
- app/utils/utils.py +154 -0
- atmoswing_api-0.1.0.dist-info/METADATA +23 -0
- atmoswing_api-0.1.0.dist-info/RECORD +20 -0
- atmoswing_api-0.1.0.dist-info/WHEEL +5 -0
- atmoswing_api-0.1.0.dist-info/licenses/LICENSE +21 -0
- atmoswing_api-0.1.0.dist-info/top_level.txt +1 -0
app/__init__.py
ADDED
|
File without changes
|
app/main.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from fastapi import FastAPI
|
|
3
|
+
from app.routes import meta, forecasts, aggregations
|
|
4
|
+
|
|
5
|
+
# Configure logging
|
|
6
|
+
logging.basicConfig(
|
|
7
|
+
level=logging.INFO,
|
|
8
|
+
format="%(asctime)s -%(levelname)s - %(message)s",
|
|
9
|
+
handlers=[
|
|
10
|
+
logging.StreamHandler()
|
|
11
|
+
]
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
app = FastAPI(
|
|
15
|
+
title="AtmoSwing Web Forecast API",
|
|
16
|
+
description="API to provide forecasts generated by AtmoSwing.",
|
|
17
|
+
version="1.0.0",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Include the routes
|
|
21
|
+
app.include_router(meta.router, prefix="/meta", tags=["Metadata"])
|
|
22
|
+
app.include_router(forecasts.router, prefix="/forecasts", tags=["Data from a single forecast"])
|
|
23
|
+
app.include_router(aggregations.router, prefix="/aggregations", tags=["Aggregated forecast data"])
|
app/models/__init__.py
ADDED
|
File without changes
|
app/models/forecast.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from pydantic import BaseModel, field_validator
|
|
2
|
+
from typing import List
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ReferenceValues(BaseModel):
|
|
7
|
+
axis: List[float]
|
|
8
|
+
values: List[float]
|
|
9
|
+
|
|
10
|
+
@field_validator("axis")
|
|
11
|
+
def round_axis(cls, v: List[float]) -> List[float]:
|
|
12
|
+
return [round(float(value), 2) for value in v]
|
|
13
|
+
|
|
14
|
+
@field_validator("values")
|
|
15
|
+
def round_values(cls, v: List[float]) -> List[float]:
|
|
16
|
+
return [round(float(value), 2) for value in v]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Analog(BaseModel):
|
|
20
|
+
date: datetime
|
|
21
|
+
value: float
|
|
22
|
+
criteria: float
|
|
23
|
+
rank: int
|
|
24
|
+
|
|
25
|
+
@field_validator("value")
|
|
26
|
+
def round_value(cls, v: float) -> float:
|
|
27
|
+
return round(float(v), 2)
|
|
28
|
+
|
|
29
|
+
@field_validator("criteria")
|
|
30
|
+
def round_criteria(cls, v: float) -> float:
|
|
31
|
+
return round(float(v), 3)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Analogs(BaseModel):
|
|
35
|
+
analogs: List[Analog]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AnalogValues(BaseModel):
|
|
39
|
+
values: List[float]
|
|
40
|
+
|
|
41
|
+
@field_validator("values")
|
|
42
|
+
def round_values(cls, v: List[float]) -> List[float]:
|
|
43
|
+
return [round(float(value), 2) for value in v]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AnalogValuesPercentiles(BaseModel):
|
|
47
|
+
percentiles: List[int]
|
|
48
|
+
values: List[float]
|
|
49
|
+
|
|
50
|
+
@field_validator("values")
|
|
51
|
+
def round_values(cls, v: List[float]) -> List[float]:
|
|
52
|
+
return [round(float(value), 2) for value in v]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AnalogDates(BaseModel):
|
|
56
|
+
dates: List[datetime]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class AnalogCriteria(BaseModel):
|
|
60
|
+
criteria: List[float]
|
|
61
|
+
|
|
62
|
+
@field_validator("criteria")
|
|
63
|
+
def round_criteria(cls, v: List[float]) -> List[float]:
|
|
64
|
+
return [round(float(value), 3) for value in v]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SeriesAnalogValues(BaseModel):
|
|
68
|
+
series_values: List[List[float]]
|
|
69
|
+
|
|
70
|
+
@field_validator("series_values")
|
|
71
|
+
def round_series(cls, v: List[List[float]]) -> List[List[float]]:
|
|
72
|
+
return [[round(float(value), 2) for value in series] for series in v]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SeriesAnalogValuesPercentile(BaseModel):
|
|
76
|
+
percentile: int
|
|
77
|
+
series_values: List[float]
|
|
78
|
+
|
|
79
|
+
@field_validator("series_values")
|
|
80
|
+
def round_series(cls, v: List[float]) -> List[float]:
|
|
81
|
+
return [round(float(value), 2) for value in v]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class SeriesAnalogValuesPercentiles(BaseModel):
|
|
85
|
+
forecast_date: datetime
|
|
86
|
+
target_dates: List[datetime]
|
|
87
|
+
series_percentiles: List[SeriesAnalogValuesPercentile]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class SeriesAnalogValuesPercentilesHistory(BaseModel):
|
|
91
|
+
past_forecasts: List[SeriesAnalogValuesPercentiles]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class EntitiesAnalogValuesPercentile(BaseModel):
|
|
95
|
+
entity_ids: List[int]
|
|
96
|
+
values: List[float]
|
|
97
|
+
|
|
98
|
+
@field_validator("values")
|
|
99
|
+
def round_values(cls, v: List[float]) -> List[float]:
|
|
100
|
+
return [round(float(value), 2) for value in v]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class SeriesSynthesisPerMethod(BaseModel):
|
|
104
|
+
method_id: str
|
|
105
|
+
target_dates: List[datetime]
|
|
106
|
+
values: List[float]
|
|
107
|
+
|
|
108
|
+
@field_validator("values")
|
|
109
|
+
def round_values(cls, v: List[float]) -> List[float]:
|
|
110
|
+
return [round(float(value), 2) for value in v]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SeriesSynthesisTotal(BaseModel):
|
|
114
|
+
time_step: int
|
|
115
|
+
target_dates: List[datetime]
|
|
116
|
+
values: List[float]
|
|
117
|
+
|
|
118
|
+
@field_validator("values")
|
|
119
|
+
def round_values(cls, v: List[float]) -> List[float]:
|
|
120
|
+
return [round(float(value), 2) for value in v]
|
app/models/meta.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from typing import List
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
class Method(BaseModel):
|
|
6
|
+
id: str
|
|
7
|
+
name: str
|
|
8
|
+
|
|
9
|
+
class Configuration(BaseModel):
|
|
10
|
+
id: str
|
|
11
|
+
name: str
|
|
12
|
+
|
|
13
|
+
class MethodConfig(BaseModel):
|
|
14
|
+
id: str
|
|
15
|
+
name: str
|
|
16
|
+
configurations: List[Configuration]
|
|
17
|
+
|
|
18
|
+
class Entity(BaseModel):
|
|
19
|
+
id: int
|
|
20
|
+
name: str
|
|
21
|
+
x: float
|
|
22
|
+
y: float
|
|
23
|
+
official_id: Optional[str] = None
|
app/routes/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from fastapi import APIRouter, HTTPException, Depends, Query
|
|
5
|
+
from typing_extensions import Annotated
|
|
6
|
+
|
|
7
|
+
import config
|
|
8
|
+
from app.models.forecast import *
|
|
9
|
+
from app.services.aggregations import *
|
|
10
|
+
|
|
11
|
+
router = APIRouter()
|
|
12
|
+
debug = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@lru_cache
|
|
16
|
+
def get_settings():
|
|
17
|
+
return config.Settings()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Helper function to handle requests and catch exceptions
|
|
21
|
+
async def _handle_request(func, settings: config.Settings, region: str, **kwargs):
|
|
22
|
+
try:
|
|
23
|
+
result = await func(settings.data_dir, region, **kwargs)
|
|
24
|
+
if debug:
|
|
25
|
+
logging.info(f"Result from {func.__name__}: {result}")
|
|
26
|
+
if result is None:
|
|
27
|
+
raise ValueError("The result is None")
|
|
28
|
+
return result
|
|
29
|
+
except FileNotFoundError:
|
|
30
|
+
logging.error(f"Files not found for region: {region}")
|
|
31
|
+
raise HTTPException(status_code=404, detail="Region or forecast not found")
|
|
32
|
+
except Exception as e:
|
|
33
|
+
logging.error(f"An unexpected error occurred: {e}")
|
|
34
|
+
raise HTTPException(status_code=500, detail="Internal Server Error")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.get("/{region}/{forecast_date}/{method}/{lead_time}/analog-values-percentile/{percentile}",
|
|
38
|
+
summary="Analog values for a given region, forecast_date, method, "
|
|
39
|
+
"lead time, and percentile, aggregated by selecting the "
|
|
40
|
+
"relevant configuration per entity",
|
|
41
|
+
response_model=EntitiesAnalogValuesPercentile)
|
|
42
|
+
async def entities_analog_values_percentile(
|
|
43
|
+
region: str,
|
|
44
|
+
forecast_date: str,
|
|
45
|
+
method: str,
|
|
46
|
+
lead_time: int|str,
|
|
47
|
+
percentile: int,
|
|
48
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
49
|
+
"""
|
|
50
|
+
Get the analog dates for a given region, forecast_date, method, configuration, and lead_time.
|
|
51
|
+
"""
|
|
52
|
+
return await _handle_request(get_entities_analog_values_percentile, settings,
|
|
53
|
+
region, forecast_date=forecast_date, method=method,
|
|
54
|
+
lead_time=lead_time, percentile=percentile)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@router.get("/{region}/{forecast_date}/series-synthesis-per-method/{percentile}",
|
|
58
|
+
summary="Largest values for a given region, forecast_date, method, "
|
|
59
|
+
"and percentile, aggregated by selecting the largest values for "
|
|
60
|
+
"the relevant configurations per entity",
|
|
61
|
+
response_model=List[SeriesSynthesisPerMethod])
|
|
62
|
+
async def series_synthesis_per_method(
|
|
63
|
+
region: str,
|
|
64
|
+
forecast_date: str,
|
|
65
|
+
percentile: int,
|
|
66
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
67
|
+
"""
|
|
68
|
+
Get the largest analog values for a given region, forecast_date, and percentile.
|
|
69
|
+
"""
|
|
70
|
+
return await _handle_request(get_series_synthesis_per_method, settings,
|
|
71
|
+
region, forecast_date=forecast_date,
|
|
72
|
+
percentile=percentile)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@router.get("/{region}/{forecast_date}/series-synthesis-total/{percentile}",
|
|
76
|
+
summary="Largest values for a given region, forecast_date, "
|
|
77
|
+
"and percentile, aggregated by time steps",
|
|
78
|
+
response_model=List[SeriesSynthesisTotal])
|
|
79
|
+
async def series_synthesis_total(
|
|
80
|
+
region: str,
|
|
81
|
+
forecast_date: str,
|
|
82
|
+
percentile: int,
|
|
83
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
84
|
+
"""
|
|
85
|
+
Get the largest analog values for a given region, forecast_date, and percentile.
|
|
86
|
+
"""
|
|
87
|
+
return await _handle_request(get_series_synthesis_total, settings,
|
|
88
|
+
region, forecast_date=forecast_date,
|
|
89
|
+
percentile=percentile)
|
app/routes/forecasts.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from fastapi import APIRouter, HTTPException, Depends, Query
|
|
5
|
+
from typing_extensions import Annotated
|
|
6
|
+
|
|
7
|
+
import config
|
|
8
|
+
from app.models.forecast import *
|
|
9
|
+
from app.services.forecasts import *
|
|
10
|
+
|
|
11
|
+
router = APIRouter()
|
|
12
|
+
debug = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@lru_cache
|
|
16
|
+
def get_settings():
|
|
17
|
+
return config.Settings()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Helper function to handle requests and catch exceptions
|
|
21
|
+
async def _handle_request(func, settings: config.Settings, region: str, **kwargs):
|
|
22
|
+
try:
|
|
23
|
+
result = await func(settings.data_dir, region, **kwargs)
|
|
24
|
+
if debug:
|
|
25
|
+
logging.info(f"Result from {func.__name__}: {result}")
|
|
26
|
+
if result is None:
|
|
27
|
+
raise ValueError("The result is None")
|
|
28
|
+
return result
|
|
29
|
+
except FileNotFoundError:
|
|
30
|
+
logging.error(f"Files not found for region: {region}")
|
|
31
|
+
raise HTTPException(status_code=404, detail="Region or forecast not found")
|
|
32
|
+
except Exception as e:
|
|
33
|
+
logging.error(f"An unexpected error occurred: {e}")
|
|
34
|
+
raise HTTPException(status_code=500, detail="Internal Server Error")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/{lead_time}/analog-dates",
|
|
38
|
+
summary="Analog dates for a given forecast and target date",
|
|
39
|
+
response_model=AnalogDates)
|
|
40
|
+
async def analog_dates(
|
|
41
|
+
region: str,
|
|
42
|
+
forecast_date: str,
|
|
43
|
+
method: str,
|
|
44
|
+
configuration: str,
|
|
45
|
+
lead_time: int|str,
|
|
46
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
47
|
+
"""
|
|
48
|
+
Get the analog dates for a given region, forecast date, method, configuration, and lead time.
|
|
49
|
+
"""
|
|
50
|
+
return await _handle_request(get_analog_dates, settings, region,
|
|
51
|
+
forecast_date=forecast_date, method=method,
|
|
52
|
+
configuration=configuration, lead_time=lead_time)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/{lead_time}/analogy-criteria",
|
|
56
|
+
summary="Analog criteria for a given forecast and target date",
|
|
57
|
+
response_model=AnalogCriteria)
|
|
58
|
+
async def analog_criteria(
|
|
59
|
+
region: str,
|
|
60
|
+
forecast_date: str,
|
|
61
|
+
method: str,
|
|
62
|
+
configuration: str,
|
|
63
|
+
lead_time: int|str,
|
|
64
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
65
|
+
"""
|
|
66
|
+
Get the analog criteria for a given region, forecast date, method, configuration, and lead time.
|
|
67
|
+
"""
|
|
68
|
+
return await _handle_request(get_analog_criteria, settings, region,
|
|
69
|
+
forecast_date=forecast_date, method=method,
|
|
70
|
+
configuration=configuration, lead_time=lead_time)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/{lead_time}/entities-values-percentile/{percentile}",
|
|
74
|
+
summary="Values for all entities for a given quantile, forecast and target date",
|
|
75
|
+
response_model=EntitiesAnalogValuesPercentile)
|
|
76
|
+
async def entities_analog_values_percentile(
|
|
77
|
+
region: str,
|
|
78
|
+
forecast_date: str,
|
|
79
|
+
method: str,
|
|
80
|
+
configuration: str,
|
|
81
|
+
lead_time: int|str,
|
|
82
|
+
percentile: int,
|
|
83
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
84
|
+
"""
|
|
85
|
+
Get the precipitation values for a given region, forecast date, method, configuration, lead time, and percentile.
|
|
86
|
+
"""
|
|
87
|
+
return await _handle_request(get_entities_analog_values_percentile, settings, region,
|
|
88
|
+
forecast_date=forecast_date, method=method,
|
|
89
|
+
configuration=configuration, lead_time=lead_time,
|
|
90
|
+
percentile=percentile)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/{entity}/reference-values",
|
|
94
|
+
summary="Reference values (e.g. for different return periods) for a given entity",
|
|
95
|
+
response_model=ReferenceValues)
|
|
96
|
+
async def reference_values(
|
|
97
|
+
region: str,
|
|
98
|
+
forecast_date: str,
|
|
99
|
+
method: str,
|
|
100
|
+
configuration: str,
|
|
101
|
+
entity: int,
|
|
102
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
103
|
+
"""
|
|
104
|
+
Get the reference values for a given region, forecast date, method, configuration, and entity.
|
|
105
|
+
"""
|
|
106
|
+
return await _handle_request(get_reference_values, settings, region,
|
|
107
|
+
forecast_date=forecast_date, method=method,
|
|
108
|
+
configuration=configuration, entity=entity)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/{entity}/series-values-best-analogs",
|
|
112
|
+
summary="Analog values of the best analogs for a given entity (time series)",
|
|
113
|
+
response_model=SeriesAnalogValues)
|
|
114
|
+
async def series_analog_values_best(
|
|
115
|
+
region: str,
|
|
116
|
+
forecast_date: str,
|
|
117
|
+
method: str,
|
|
118
|
+
configuration: str,
|
|
119
|
+
entity: int,
|
|
120
|
+
settings: Annotated[config.Settings, Depends(get_settings)],
|
|
121
|
+
number: int = 10):
|
|
122
|
+
"""
|
|
123
|
+
Get the precipitation values for the best analogs and for a given region, forecast date, method, configuration, and entity.
|
|
124
|
+
"""
|
|
125
|
+
return await _handle_request(get_series_analog_values_best, settings, region,
|
|
126
|
+
forecast_date=forecast_date, method=method,
|
|
127
|
+
configuration=configuration, entity=entity,
|
|
128
|
+
number=number)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/{entity}/series-values-percentiles",
|
|
132
|
+
summary="Values for one entity for a given quantile, forecast and target date",
|
|
133
|
+
response_model=SeriesAnalogValuesPercentiles)
|
|
134
|
+
async def series_analog_values_percentiles(
|
|
135
|
+
region: str,
|
|
136
|
+
forecast_date: str,
|
|
137
|
+
method: str,
|
|
138
|
+
configuration: str,
|
|
139
|
+
entity: int,
|
|
140
|
+
settings: Annotated[config.Settings, Depends(get_settings)],
|
|
141
|
+
percentiles: List[int] = Query([20, 60, 90])):
|
|
142
|
+
"""
|
|
143
|
+
Get the precipitation values for the provided percentiles and for a given region, forecast date, method, configuration, and entity.
|
|
144
|
+
"""
|
|
145
|
+
return await _handle_request(get_series_analog_values_percentiles, settings, region,
|
|
146
|
+
forecast_date=forecast_date, method=method,
|
|
147
|
+
configuration=configuration, entity=entity,
|
|
148
|
+
percentiles=percentiles)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/{entity}/series-values-percentiles-history",
|
|
152
|
+
summary="Values for one entity for a given quantile, forecast and target date",
|
|
153
|
+
response_model=SeriesAnalogValuesPercentilesHistory)
|
|
154
|
+
async def series_analog_values_percentiles_history(
|
|
155
|
+
region: str,
|
|
156
|
+
forecast_date: str,
|
|
157
|
+
method: str,
|
|
158
|
+
configuration: str,
|
|
159
|
+
entity: int,
|
|
160
|
+
settings: Annotated[config.Settings, Depends(get_settings)],
|
|
161
|
+
percentiles: List[int] = Query([20, 60, 90]),
|
|
162
|
+
number: int = 5):
|
|
163
|
+
"""
|
|
164
|
+
Get the precipitation values for the provided percentiles and for a given region, forecast date, method, configuration, and entity.
|
|
165
|
+
"""
|
|
166
|
+
return await _handle_request(get_series_analog_values_percentiles_history, settings,
|
|
167
|
+
region, forecast_date=forecast_date, method=method,
|
|
168
|
+
configuration=configuration, entity=entity,
|
|
169
|
+
percentiles=percentiles, number=number)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/{entity}/{lead_time}/analogs",
|
|
173
|
+
summary="Details of the analogs (rank, date, criteria, value) for a given forecast and entity",
|
|
174
|
+
response_model=Analogs)
|
|
175
|
+
async def analogs(
|
|
176
|
+
region: str,
|
|
177
|
+
forecast_date: str,
|
|
178
|
+
method: str,
|
|
179
|
+
configuration: str,
|
|
180
|
+
entity: int,
|
|
181
|
+
lead_time: int|str,
|
|
182
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
183
|
+
"""
|
|
184
|
+
Get the analogs for a given region, forecast date, method, configuration, entity, and lead time.
|
|
185
|
+
"""
|
|
186
|
+
return await _handle_request(get_analogs, settings, region,
|
|
187
|
+
forecast_date=forecast_date, method=method,
|
|
188
|
+
configuration=configuration, entity=entity,
|
|
189
|
+
lead_time=lead_time)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/{entity}/{lead_time}/analog-values",
|
|
193
|
+
summary="Analog values for a given entity and target date",
|
|
194
|
+
response_model=AnalogValues)
|
|
195
|
+
async def analog_values(
|
|
196
|
+
region: str,
|
|
197
|
+
forecast_date: str,
|
|
198
|
+
method: str,
|
|
199
|
+
configuration: str,
|
|
200
|
+
entity: int,
|
|
201
|
+
lead_time: int|str,
|
|
202
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
203
|
+
"""
|
|
204
|
+
Get the precipitation values for a given region, forecast date, method, configuration, entity, lead time.
|
|
205
|
+
"""
|
|
206
|
+
return await _handle_request(get_analog_values, settings, region,
|
|
207
|
+
forecast_date=forecast_date, method=method,
|
|
208
|
+
configuration=configuration, entity=entity,
|
|
209
|
+
lead_time=lead_time)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/{entity}/{lead_time}/analog-values-percentiles",
|
|
213
|
+
summary="Values for one entity for a given quantile, forecast and target date",
|
|
214
|
+
response_model=AnalogValuesPercentiles)
|
|
215
|
+
async def analog_values_percentiles(
|
|
216
|
+
region: str,
|
|
217
|
+
forecast_date: str,
|
|
218
|
+
method: str,
|
|
219
|
+
configuration: str,
|
|
220
|
+
entity: int,
|
|
221
|
+
lead_time: int|str,
|
|
222
|
+
settings: Annotated[config.Settings, Depends(get_settings)],
|
|
223
|
+
percentiles: List[int] = Query([20, 60, 90])):
|
|
224
|
+
"""
|
|
225
|
+
Get the precipitation values for a given region, forecast date, method, configuration, entity, lead time, and percentile.
|
|
226
|
+
"""
|
|
227
|
+
return await _handle_request(get_analog_values_percentiles, settings, region,
|
|
228
|
+
forecast_date=forecast_date, method=method,
|
|
229
|
+
configuration=configuration, entity=entity,
|
|
230
|
+
lead_time=lead_time, percentiles=percentiles)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/{entity}/{lead_time}/analog-values-best",
|
|
234
|
+
summary="Values for one entity for a given quantile, forecast and target date",
|
|
235
|
+
response_model=AnalogValues)
|
|
236
|
+
async def analog_values_best(
|
|
237
|
+
region: str,
|
|
238
|
+
forecast_date: str,
|
|
239
|
+
method: str,
|
|
240
|
+
configuration: str,
|
|
241
|
+
entity: int,
|
|
242
|
+
lead_time: int|str,
|
|
243
|
+
settings: Annotated[config.Settings, Depends(get_settings)],
|
|
244
|
+
number: int = 10):
|
|
245
|
+
"""
|
|
246
|
+
Get the precipitation values for the best analogs and for a given region, forecast date, method, configuration, entity, and lead time.
|
|
247
|
+
"""
|
|
248
|
+
return await _handle_request(get_analog_values_best, settings, region,
|
|
249
|
+
forecast_date=forecast_date, method=method,
|
|
250
|
+
configuration=configuration, entity=entity,
|
|
251
|
+
lead_time=lead_time, number=number)
|
app/routes/meta.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
from fastapi import APIRouter, HTTPException, Depends
|
|
4
|
+
from typing_extensions import Annotated
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
import config
|
|
8
|
+
from app.services.meta import get_last_forecast_date_from_files, \
|
|
9
|
+
get_method_list, get_method_configs_list, get_entities_list
|
|
10
|
+
from app.models.meta import Entity, Method, MethodConfig
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@lru_cache
|
|
16
|
+
def get_settings():
|
|
17
|
+
return config.Settings()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Helper function to handle requests and catch exceptions
|
|
21
|
+
async def _handle_request(func, settings: config.Settings, region: str, **kwargs):
|
|
22
|
+
try:
|
|
23
|
+
return await func(settings.data_dir, region, **kwargs)
|
|
24
|
+
except FileNotFoundError:
|
|
25
|
+
logging.error(f"Files not found for region: {region}")
|
|
26
|
+
raise HTTPException(status_code=404, detail="Region or forecast not found")
|
|
27
|
+
except Exception as e:
|
|
28
|
+
logging.error(f"An unexpected error occurred: {e}")
|
|
29
|
+
raise HTTPException(status_code=500, detail="Internal Server Error")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@router.get("/{region}/last-forecast-date",
|
|
33
|
+
summary="Last available forecast date")
|
|
34
|
+
async def get_last_forecast_date(
|
|
35
|
+
region: str,
|
|
36
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
37
|
+
"""
|
|
38
|
+
Get the last available forecast date for a given region.
|
|
39
|
+
"""
|
|
40
|
+
return await _handle_request(get_last_forecast_date_from_files, settings, region)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@router.get("/{region}/{forecast_date}/methods",
|
|
44
|
+
summary="List of available methods",
|
|
45
|
+
response_model=List[Method])
|
|
46
|
+
async def list_methods(
|
|
47
|
+
region: str,
|
|
48
|
+
forecast_date: str,
|
|
49
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
50
|
+
"""
|
|
51
|
+
Get the list of available methods for a given region.
|
|
52
|
+
"""
|
|
53
|
+
return await _handle_request(get_method_list, settings, region,
|
|
54
|
+
forecast_date=forecast_date)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@router.get("/{region}/{forecast_date}/methods-and-configs",
|
|
58
|
+
summary="List of available methods and configurations",
|
|
59
|
+
response_model=List[MethodConfig])
|
|
60
|
+
async def list_methods_and_configs(
|
|
61
|
+
region: str,
|
|
62
|
+
forecast_date: str,
|
|
63
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
64
|
+
"""
|
|
65
|
+
Get the list of available methods and configs for a given region.
|
|
66
|
+
"""
|
|
67
|
+
return await _handle_request(get_method_configs_list, settings, region,
|
|
68
|
+
forecast_date=forecast_date)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@router.get("/{region}/{forecast_date}/{method}/{configuration}/entities",
|
|
72
|
+
summary="List of available entities",
|
|
73
|
+
response_model=List[Entity])
|
|
74
|
+
async def list_entities(
|
|
75
|
+
region: str,
|
|
76
|
+
forecast_date: str,
|
|
77
|
+
method: str,
|
|
78
|
+
configuration: str,
|
|
79
|
+
settings: Annotated[config.Settings, Depends(get_settings)]):
|
|
80
|
+
"""
|
|
81
|
+
Get the list of available entities for a given region, forecast_date, method, and configuration.
|
|
82
|
+
"""
|
|
83
|
+
return await _handle_request(get_entities_list, settings, region,
|
|
84
|
+
forecast_date=forecast_date, method=method,
|
|
85
|
+
configuration=configuration)
|
app/services/__init__.py
ADDED
|
File without changes
|