-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathapp_v1.py
More file actions
303 lines (262 loc) · 20.6 KB
/
Copy pathapp_v1.py
File metadata and controls
303 lines (262 loc) · 20.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
from fastapi import FastAPI, HTTPException, Query
from typing import List, Optional
from starlette.middleware.wsgi import WSGIMiddleware
from datetime import datetime
from zoneinfo import ZoneInfo
import pandas as pd
from pytz import timezone
from typing import List, Dict, Any
from pydantic import BaseModel
import numpy as np
import asyncio
import time
import os
from pywisconet.data import *
from pywisconet.process import *
#from ag_models_wrappers.process_ibm_risk_v2 import *
#from ag_models_wrappers.process_wisconet import *
from api import * # was: from ag_models_wrappers.process_wisconet import *
from api.services.ibm_service import get_weather_with_risk # was: from ag_models_wrappers.process_ibm_risk_v2 import *
#app = FastAPI()
from fastapi import FastAPI
app_v1 = FastAPI(
title="Ag Forecasting API - Legacy",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc",
openapi_url="/openapi.json"
)
# ---------------------------------------------------------------------------
# Static field registry
# ---------------------------------------------------------------------------
# The legacy /fields/?legacy_only=True endpoint is no longer available.
# Fields are now fetched from /fields/{station_id}/available_fields per station,
# or can be seeded from the known global field definitions below.
# This list mirrors the full fields catalogue returned by the new endpoint.
_STATIC_FIELDS_JSON = [
{"id": 1, "standard_name": "5min_air_temp_f_avg", "use_for": "", "measure_type": "Air Temp", "qualifier": "avg", "sensor": "", "source_field": "airtemp_c_avg@Table5", "data_type": "float", "source_units": "celsius", "final_units": "fahrenheit", "units_abbrev": "f", "conversion_type": "c2f", "collection_frequency": "5min"},
{"id": 2, "standard_name": "60min_air_temp_f_avg", "use_for": "", "measure_type": "Air Temp", "qualifier": "avg", "sensor": "", "source_field": "airtemp_c_avg@Table60", "data_type": "float", "source_units": "celsius", "final_units": "fahrenheit", "units_abbrev": "f", "conversion_type": "c2f", "collection_frequency": "60min"},
{"id": 3, "standard_name": "daily_air_temp_f_avg", "use_for": "", "measure_type": "Air Temp", "qualifier": "avg", "sensor": "", "source_field": "WN_calc", "data_type": "float", "source_units": "celsius", "final_units": "fahrenheit", "units_abbrev": "f", "conversion_type": "c2f", "collection_frequency": "daily"},
{"id": 4, "standard_name": "daily_air_temp_f_max", "use_for": "", "measure_type": "Air Temp", "qualifier": "max", "sensor": "", "source_field": "airtemp_c_max@Table24", "data_type": "float", "source_units": "celsius", "final_units": "fahrenheit", "units_abbrev": "f", "conversion_type": "c2f", "collection_frequency": "daily"},
{"id": 6, "standard_name": "daily_air_temp_f_min", "use_for": "", "measure_type": "Air Temp", "qualifier": "min", "sensor": "", "source_field": "airtemp_c_min@Table24", "data_type": "float", "source_units": "celsius", "final_units": "fahrenheit", "units_abbrev": "f", "conversion_type": "c2f", "collection_frequency": "daily"},
{"id": 7, "standard_name": "60min_battery_v_avg", "use_for": "", "measure_type": "Battery", "qualifier": "avg", "sensor": "", "source_field": "battery_v_avg@Table60", "data_type": "float", "source_units": "volts", "final_units": "volts", "units_abbrev": "v", "conversion_type": "", "collection_frequency": "60min"},
{"id": 9, "standard_name": "5min_dew_point_f_avg", "use_for": "", "measure_type": "Dew Point", "qualifier": "avg", "sensor": "", "source_field": "dewpointtemp_c_avg@Table5", "data_type": "float", "source_units": "celsius", "final_units": "fahrenheit", "units_abbrev": "f", "conversion_type": "c2f", "collection_frequency": "5min"},
{"id": 10, "standard_name": "60min_dew_point_f_avg", "use_for": "", "measure_type": "Dew Point", "qualifier": "avg", "sensor": "", "source_field": "dewpointtemp_c_avg@Table60", "data_type": "float", "source_units": "celsius", "final_units": "fahrenheit", "units_abbrev": "f", "conversion_type": "c2f", "collection_frequency": "60min"},
{"id": 11, "standard_name": "daily_dew_point_f_max", "use_for": "", "measure_type": "Dew Point", "qualifier": "max", "sensor": "", "source_field": "dewpointtemp_c_max@Table24", "data_type": "float", "source_units": "celsius", "final_units": "fahrenheit", "units_abbrev": "f", "conversion_type": "c2f", "collection_frequency": "daily"},
{"id": 12, "standard_name": "daily_dew_point_f_min", "use_for": "", "measure_type": "Dew Point", "qualifier": "min", "sensor": "", "source_field": "dewpointtemp_c_min@Table24", "data_type": "float", "source_units": "celsius", "final_units": "fahrenheit", "units_abbrev": "f", "conversion_type": "c2f", "collection_frequency": "daily"},
{"id": 15, "standard_name": "daily_rain_in_tot", "use_for": "", "measure_type": "Rain", "qualifier": "total", "sensor": "", "source_field": "rain_mm_tot@Table24", "data_type": "float", "source_units": "millimeters", "final_units": "inches", "units_abbrev": "in", "conversion_type": "mm2in", "collection_frequency": "daily"},
{"id": 16, "standard_name": "5min_rain_in_tot", "use_for": "", "measure_type": "Rain", "qualifier": "total", "sensor": "", "source_field": "rain_mm_tot@Table5", "data_type": "float", "source_units": "millimeters", "final_units": "inches", "units_abbrev": "in", "conversion_type": "mm2in", "collection_frequency": "5min"},
{"id": 17, "standard_name": "60min_rain_in_tot", "use_for": "", "measure_type": "Rain", "qualifier": "total", "sensor": "", "source_field": "rain_mm_tot@Table60", "data_type": "float", "source_units": "millimeters", "final_units": "inches", "units_abbrev": "in", "conversion_type": "mm2in", "collection_frequency": "60min"},
{"id": 18, "standard_name": "5min_relative_humidity_pct_avg", "use_for": "", "measure_type": "Relative Humidity", "qualifier": "avg", "sensor": "", "source_field": "relhum_avg@Table5", "data_type": "float", "source_units": "pct", "final_units": "pct", "units_abbrev": "pct", "conversion_type": "", "collection_frequency": "5min"},
{"id": 19, "standard_name": "60min_relative_humidity_pct_avg", "use_for": "", "measure_type": "Relative Humidity", "qualifier": "avg", "sensor": "", "source_field": "relhum_avg@Table60", "data_type": "float", "source_units": "pct", "final_units": "pct", "units_abbrev": "pct", "conversion_type": "", "collection_frequency": "60min"},
{"id": 20, "standard_name": "daily_relative_humidity_pct_max", "use_for": "", "measure_type": "Relative Humidity", "qualifier": "max", "sensor": "", "source_field": "relhum_max@Table24", "data_type": "float", "source_units": "pct", "final_units": "pct", "units_abbrev": "pct", "conversion_type": "", "collection_frequency": "daily"},
{"id": 21, "standard_name": "daily_relative_humidity_pct_min", "use_for": "", "measure_type": "Relative Humidity", "qualifier": "min", "sensor": "", "source_field": "relhum_min@Table24", "data_type": "float", "source_units": "pct", "final_units": "pct", "units_abbrev": "pct", "conversion_type": "", "collection_frequency": "daily"},
{"id": 55, "standard_name": "5min_wind_speed_mph_avg", "use_for": "", "measure_type": "Wind Speed", "qualifier": "avg", "sensor": "", "source_field": "windspd_ms_3m_avg@Table5", "data_type": "float", "source_units": "meters/sec", "final_units": "mph", "units_abbrev": "mph", "conversion_type": "ms2mph", "collection_frequency": "5min"},
{"id": 56, "standard_name": "daily_wind_speed_mph_max", "use_for": "", "measure_type": "Wind Speed", "qualifier": "max", "sensor": "", "source_field": "windspd_ms_3m_max@Table24", "data_type": "float", "source_units": "meters/sec", "final_units": "mph", "units_abbrev": "mph", "conversion_type": "ms2mph", "collection_frequency": "daily"},
{"id": 57, "standard_name": "60min_wind_speed_mph_max", "use_for": "", "measure_type": "Wind Speed", "qualifier": "max", "sensor": "", "source_field": "windspd_ms_3m_max@Table60", "data_type": "float", "source_units": "meters/sec", "final_units": "mph", "units_abbrev": "mph", "conversion_type": "ms2mph", "collection_frequency": "60min"},
{"id": 60, "standard_name": "daily_air_temp_f_max_time", "use_for": "", "measure_type": "Air Temp", "qualifier": "max_time", "sensor": "", "source_field": "airtemp_c_tmx@Table24", "data_type": "integer", "source_units": "seconds", "final_units": "seconds", "units_abbrev": "f", "conversion_type": "date2ts", "collection_frequency": "daily"},
{"id": 61, "standard_name": "daily_air_temp_f_min_time", "use_for": "", "measure_type": "Air Temp", "qualifier": "min_time", "sensor": "", "source_field": "airtemp_c_tmn@Table24", "data_type": "integer", "source_units": "seconds", "final_units": "seconds", "units_abbrev": "f", "conversion_type": "date2ts", "collection_frequency": "daily"},
{"id": 62, "standard_name": "daily_dew_point_f_max_time", "use_for": "", "measure_type": "Dew Point", "qualifier": "max_time", "sensor": "", "source_field": "dewpointtemp_c_tmx@Table24", "data_type": "integer", "source_units": "seconds", "final_units": "seconds", "units_abbrev": "f", "conversion_type": "date2ts", "collection_frequency": "daily"},
{"id": 63, "standard_name": "daily_relative_humidity_min_time", "use_for": "", "measure_type": "Relative Humidity", "qualifier": "min_time", "sensor": "", "source_field": "relhum_tmn@Table24", "data_type": "integer", "source_units": "seconds", "final_units": "seconds", "units_abbrev": "", "conversion_type": "date2ts", "collection_frequency": "daily"},
{"id": 64, "standard_name": "daily_relative_humidity_max_time", "use_for": "", "measure_type": "Relative Humidity", "qualifier": "max_time", "sensor": "", "source_field": "relhum_tmx@Table24", "data_type": "integer", "source_units": "seconds", "final_units": "seconds", "units_abbrev": "", "conversion_type": "date2ts", "collection_frequency": "daily"},
{"id": 65, "standard_name": "60min_canopy_wetness_pct", "use_for": "", "measure_type": "Canopy Wetness", "qualifier": "pct", "sensor": "", "source_field": "lw_canopy_mv_hst@Table60", "data_type": "float", "source_units": "pct", "final_units": "pct", "units_abbrev": "pct", "conversion_type": "lw2status","collection_frequency": "60min"},
{"id": 87, "standard_name": "60min_wind_speed_mph_avg", "use_for": "", "measure_type": "Wind Speed", "qualifier": "avg", "sensor": "", "source_field": "WN_calc", "data_type": "float", "source_units": "meters/sec", "final_units": "mph", "units_abbrev": "mph", "conversion_type": "ms2mph", "collection_frequency": "60min"},
]
# Build a reusable list of Field objects from the static registry.
ALL_FIELDS: list[Field] = [Field(**f) for f in _STATIC_FIELDS_JSON]
def get_station_fields(station_id: str) -> list[Field]:
"""
Return available Field objects for a station.
Strategy:
1. Try the per-station endpoint /fields/{station_id}/available_fields
2. Fall back to ALL_FIELDS if the request fails.
"""
try:
return station_fields(station_id)
except Exception:
return ALL_FIELDS
# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------
@app_v1.get('/bulk_measures/{station_id}')
def bulk_measures_query(
station_id: str,
start_date: str = Query(..., description="Start date in format YYYY-MM-DD (e.g., 2024-07-01) assumed CT"),
end_date: str = Query(..., description="End date in format YYYY-MM-DD (e.g., 2024-07-02) assumed CT"),
measurements: str = Query(..., description="Measurements (e.g., AIRTEMP, DEW_POINT, WIND_SPEED, RELATIVE_HUMIDITY, ALL)"),
frequency: str = Query(..., description="Frequency of measurements (e.g., MIN60, MIN5, DAILY)")
):
"""
Query bulk measurements for a given station, date range, and measurement type.
"""
cols = ['collection_time', 'collection_time_ct', 'hour_ct',
'value', 'id', 'collection_frequency',
'final_units', 'measure_type', 'qualifier', 'source_field',
'standard_name', 'units_abbrev']
if measurements is not None:
this_station_fields = get_station_fields(station_id)
if measurements == 'ALL':
filtered_field_standard_names = filter_fields(
this_station_fields,
criteria=[
MeasureType.RELATIVE_HUMIDITY,
MeasureType.AIRTEMP,
MeasureType.DEW_POINT,
MeasureType.WIND_SPEED,
CollectionFrequency[frequency]
]
)
elif measurements == 'RELATIVE_HUMIDITY':
filtered_field_standard_names = filter_fields(
this_station_fields,
criteria=[MeasureType.RELATIVE_HUMIDITY, CollectionFrequency[frequency], Units.PCT]
)
elif measurements == 'AIRTEMP':
filtered_field_standard_names = filter_fields(
this_station_fields,
criteria=[MeasureType.AIRTEMP, CollectionFrequency[frequency], Units.FAHRENHEIT]
)
elif measurements == 'DEW_POINT':
filtered_field_standard_names = filter_fields(
this_station_fields,
criteria=[MeasureType.DEW_POINT, CollectionFrequency[frequency], Units.FAHRENHEIT]
)
elif measurements == 'WIND_SPEED':
filtered_field_standard_names = filter_fields(
this_station_fields,
criteria=[MeasureType.WIND_SPEED, CollectionFrequency[frequency], Units.METERSPERSECOND]
)
else:
raise HTTPException(status_code=400, detail=f"Unknown measurement type: {measurements}")
bulk_measure_response = bulk_measures(
station_id,
start_date,
end_date,
filtered_field_standard_names
)
df = bulk_measures_to_df(bulk_measure_response)
df['collection_time_utc'] = pd.to_datetime(df['collection_time']).dt.tz_localize('UTC')
df['collection_time_ct'] = df['collection_time_utc'].dt.tz_convert('US/Central')
df['hour_ct'] = df['collection_time_ct'].dt.hour
return df[cols].to_dict(orient="records")
@app_v1.get("/wisconet/active_stations/")
def stations_query(
min_days_active: int,
start_date: str = Query(..., description="Start date in format YYYY-MM-DD (e.g., 2024-07-01)")
):
try:
start_date = datetime.strptime(start_date.strip(), "%Y-%m-%d").replace(tzinfo=ZoneInfo("UTC"))
result = all_stations(min_days_active, start_date)
if result is None:
raise HTTPException(status_code=404, detail="Stations not found")
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app_v1.get("/ag_models_wrappers/ibm")
def all_data_from_ibm_query(
forecasting_date: str,
latitude: float = Query(..., description="Latitude of the location"),
longitude: float = Query(..., description="Longitude of the location"),
API_KEY: str = Query(..., description="api key"),
TENANT_ID: str = Query(..., description="Tenant id"),
ORG_ID: str = Query(..., description="organization id")
):
try:
weather_data = get_weather_with_risk(latitude, longitude, forecasting_date,
ORG_ID, TENANT_ID, API_KEY)
df = weather_data['daily']
df_cleaned = df.replace([np.inf, -np.inf, np.nan], None).where(pd.notnull(df), None)
return df_cleaned.to_dict(orient="records")
except ValueError as e:
raise HTTPException(status_code=400, detail=f"Invalid input: {e}")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal server error: {e}")
@app_v1.get("/ag_models_wrappers/wisconet")
def all_data_from_wisconet_query(
forecasting_date: str,
risk_days: int = 1,
station_id: str = None
):
try:
df = retrieve(input_date=forecasting_date, input_station_id=station_id, days=risk_days)
if df is None or len(df) == 0:
return {}
df_cleaned = df.replace([np.inf, -np.inf, np.nan], None).where(pd.notnull(df), None)
return df_cleaned.to_dict(orient="records")
except ValueError as e:
raise HTTPException(status_code=400, detail=f"Invalid input in all_data_from_wisconet_query: {e}")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal server error: {e}")
from api.schemas.geojson_schema import FeatureCollection
@app_v1.get("/ag_models_wrappers/wisconet_g", response_model=FeatureCollection)
def wisconet_geojson_grouped(
forecasting_date: str,
risk_days: int = 1,
station_id: str = None
):
try:
df = retrieve(
input_date=forecasting_date,
input_station_id=station_id,
days=risk_days
)
if df is None or len(df) == 0:
return {"type": "FeatureCollection", "features": []}
df = df.replace([np.inf, -np.inf, np.nan], None)
features = []
for station, group in df.groupby("station_id"):
first_row = group.iloc[0]
lat = first_row["latitude"]
lon = first_row["longitude"]
time_series = group.drop(
columns=["latitude", "longitude"]
).to_dict(orient="records")
feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [lon, lat]
},
"properties": {
"station_id": station,
"station_name": first_row["station_name"],
"city": first_row["city"],
"county": first_row["county"],
"region": first_row["region"],
"state": first_row["state"],
"time_series": time_series
}
}
features.append(feature)
return FeatureCollection(
type="FeatureCollection",
fields=YOUR_FIELDS_LIST,
features=features
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app_v1.get("/")
def read_root():
return {"message": "Welcome to the Wisconsin Weather API"}
# ---------------------------------------------------------------------------
# WSGI shim (kept for backward compatibility)
# ---------------------------------------------------------------------------
from starlette.applications import Starlette
from starlette.routing import Mount
from starlette.types import ASGIApp
def create_wsgi_app():
async def app(scope, receive, send):
if scope["type"] == "http":
await app(scope, receive, send)
else:
await send({
"type": "http.response.start",
"status": 404,
"headers": [(b"content-type", b"text/plain")]
})
await send({
"type": "http.response.body",
"body": b"Not Found"
})
return app
wsgi_app = WSGIMiddleware(create_wsgi_app())