diff --git a/arpav_ppcv/cliapp/app.py b/arpav_ppcv/cliapp/app.py index 59456cfc..a5fc4513 100644 --- a/arpav_ppcv/cliapp/app.py +++ b/arpav_ppcv/cliapp/app.py @@ -71,47 +71,6 @@ def delete_station( database.delete_station(session, station_id) -@app.command(name="list-variables") -def list_variables(ctx: typer.Context) -> None: - """List variables.""" - with sqlmodel.Session(ctx.obj["engine"]) as session: - result = [ - schemas.VariableRead(**v.model_dump()) - for v in database.collect_all_variables(session) - ] - print(pydantic_core.to_json(result, indent=_JSON_INDENTATION).decode("utf-8")) - - -@app.command(name="create-variable") -def create_variable( - ctx: typer.Context, - name: str, - description: str, - unit: Optional[str] = "", -) -> None: - variable_create = schemas.VariableCreate( - name=name, description=description, unit=unit - ) - """Create a new variable.""" - with sqlmodel.Session(ctx.obj["engine"]) as session: - db_variable = database.create_variable(session, variable_create) - print( - schemas.VariableRead(**db_variable.model_dump()).model_dump_json( - indent=_JSON_INDENTATION - ) - ) - - -@app.command(name="delete-variable") -def delete_variable( - ctx: typer.Context, - variable_id: uuid.UUID, -) -> None: - """Delete a variable.""" - with sqlmodel.Session(ctx.obj["engine"]) as session: - database.delete_variable(session, variable_id) - - @app.command(name="list-monthly-measurements") def list_monthly_measurements(ctx: typer.Context) -> None: """List monthly measurements.""" diff --git a/arpav_ppcv/operations.py b/arpav_ppcv/operations.py index 968b6fa7..fbe9b035 100644 --- a/arpav_ppcv/operations.py +++ b/arpav_ppcv/operations.py @@ -23,7 +23,7 @@ import shapely.io import sqlmodel from anyio.from_thread import start_blocking_portal -from arpav_ppcv.schemas.base import CoreConfParamName + from dateutil.parser import isoparse from geoalchemy2.shape import to_shape from pandas.core.indexes.datetimes import DatetimeIndex @@ -40,6 +40,7 @@ coverages, observations, ) +from .schemas.base import CoreConfParamName from .thredds import ( crawler, ncss, @@ -1018,27 +1019,21 @@ def list_coverage_identifiers_by_param_values( limit: int = 20, offset: int = 0, ) -> list[str]: - valid_variables = _filter_configuration_parameter_values( + valid_climatic_indicators = _list_possible_climatic_indicators( session, climatological_variable_filter or [], - CoreConfParamName.CLIMATOLOGICAL_VARIABLE.value, + measure_filter or [], + aggregation_period_filter or [], ) + logger.debug(f"{locals()=}") valid_climatological_models = _filter_configuration_parameter_values( session, climatological_model_filter or [], CoreConfParamName.CLIMATOLOGICAL_MODEL.value, ) - valid_aggregation_periods = _filter_configuration_parameter_values( - session, - aggregation_period_filter or [], - CoreConfParamName.AGGREGATION_PERIOD.value, - ) valid_scenarios = _filter_configuration_parameter_values( session, scenario_filter or [], CoreConfParamName.SCENARIO.value ) - valid_measures = _filter_configuration_parameter_values( - session, measure_filter or [], CoreConfParamName.MEASURE.value - ) valid_year_periods = _filter_configuration_parameter_values( session, year_period_filter or [], CoreConfParamName.YEAR_PERIOD.value ) @@ -1053,28 +1048,16 @@ def list_coverage_identifiers_by_param_values( valid_time_window_names = [tw.name for tw in valid_time_windows if tw is not None] coverage_identifiers = set() for combination in itertools.product( - valid_variables, - valid_aggregation_periods, + valid_climatic_indicators, valid_climatological_models, valid_scenarios, - valid_measures, valid_year_periods, valid_time_windows, ): - ( - variable, - aggregation_period, - model, - scenario, - measure, - year_period, - time_window, - ) = combination + climatic_indicator, model, scenario, year_period, time_window = combination logger.debug( - f"getting links for variable: {variable.name!r}, " - f"aggregation_period: {aggregation_period.name!r}, " + f"getting links for climatic_indicator: {climatic_indicator.identifier!r}, " f"model: {model.name!r}, scenario: {scenario.name!r}, " - f"measure: {measure.name!r}, " f"year_period: {year_period.name!r}, " f"time_window: {time_window.name if time_window else time_window!r}..." ) @@ -1082,11 +1065,8 @@ def list_coverage_identifiers_by_param_values( database.get_configuration_parameter_value_by_names( session, CoreConfParamName.ARCHIVE.value, "forecast" ), - variable, - aggregation_period, model, scenario, - measure, year_period, time_window, ] @@ -1096,6 +1076,7 @@ def list_coverage_identifiers_by_param_values( limit=limit, offset=offset, configuration_parameter_values_filter=filter_, + climatic_indicator_filter=climatic_indicator, ) for cov_conf in cov_confs: identifiers = database.generate_coverage_identifiers( @@ -1119,3 +1100,29 @@ def list_coverage_identifiers_by_param_values( if len(coverage_identifiers) > (offset + limit): break return sorted(coverage_identifiers)[offset : offset + limit] + + +def _list_possible_climatic_indicators( + session: sqlmodel.Session, + variable_names: Sequence[str], + measure_types: Sequence[str], + aggregation_periods: Sequence[str], +) -> list[climaticindicators.ClimaticIndicator]: + filtered = database.collect_all_climatic_indicators(session) + if len(variable_names) > 0: + filtered = [i for i in filtered if i.name in variable_names] + if len(measure_types) > 0: + filtered = [ + i for i in filtered if i.measure_type.value.lower() in measure_types + ] + if len(aggregation_periods) > 0: + periods = [] + for p in aggregation_periods: + if p == "30yr": + periods.append("thirty_year") + else: + periods.append(p) + filtered = [ + i for i in filtered if i.aggregation_period.value.lower() in periods + ] + return filtered diff --git a/arpav_ppcv/prefect/flows/observations.py b/arpav_ppcv/prefect/flows/observations.py index 6818b4d3..5ac411e1 100644 --- a/arpav_ppcv/prefect/flows/observations.py +++ b/arpav_ppcv/prefect/flows/observations.py @@ -1,5 +1,8 @@ import datetime as dt -from typing import Sequence +from typing import ( + Sequence, + Union, +) import httpx import sqlmodel @@ -552,26 +555,28 @@ def _build_created_measurements_table( def build_monthly_measurement_id( - measurement: observations.MonthlyMeasurement - | observations.MonthlyMeasurementCreate, + measurement: Union[ + observations.MonthlyMeasurement, observations.MonthlyMeasurementCreate + ], ) -> str: return "-".join( ( str(measurement.station_id), - str(measurement.variable_id), + str(measurement.climatic_indicator_id), measurement.date.strftime("%Y%m"), ) ) def build_seasonal_measurement_id( - measurement: observations.SeasonalMeasurement - | observations.SeasonalMeasurementCreate, + measurement: Union[ + observations.SeasonalMeasurement, observations.SeasonalMeasurementCreate + ], ) -> str: return "-".join( ( str(measurement.station_id), - str(measurement.variable_id), + str(measurement.climatic_indicator_id), str(measurement.year), measurement.season.value, ) @@ -579,12 +584,14 @@ def build_seasonal_measurement_id( def build_yearly_measurement_id( - measurement: observations.YearlyMeasurement | observations.YearlyMeasurementCreate, + measurement: Union[ + observations.YearlyMeasurement, observations.YearlyMeasurementCreate + ], ) -> str: return "-".join( ( str(measurement.station_id), - str(measurement.variable_id), + str(measurement.climatic_indicator_id), str(measurement.year), ) ) diff --git a/arpav_ppcv/schemas/observations.py b/arpav_ppcv/schemas/observations.py index d9f64853..4936100d 100644 --- a/arpav_ppcv/schemas/observations.py +++ b/arpav_ppcv/schemas/observations.py @@ -298,7 +298,6 @@ class MonthlyMeasurement(MonthlyMeasurementBase, table=True): class MonthlyMeasurementCreate(sqlmodel.SQLModel): station_id: pydantic.UUID4 - # variable_id: pydantic.UUID4 climatic_indicator_id: int value: float date: dt.date @@ -361,7 +360,7 @@ class SeasonalMeasurement(sqlmodel.SQLModel, table=True): class SeasonalMeasurementCreate(sqlmodel.SQLModel): station_id: pydantic.UUID4 - climatic_indicator_id: pydantic.UUID4 + climatic_indicator_id: int value: float year: int season: base.Season diff --git a/arpav_ppcv/webapp/api_v2/routers/coverages.py b/arpav_ppcv/webapp/api_v2/routers/coverages.py index 51757e33..7cde1ecd 100644 --- a/arpav_ppcv/webapp/api_v2/routers/coverages.py +++ b/arpav_ppcv/webapp/api_v2/routers/coverages.py @@ -228,25 +228,17 @@ def list_coverage_identifiers( Query(), ] = None, ): - conf_param_values_filter = [] - for possible in possible_value or []: - param_name, param_value = possible.partition(":")[::2] - db_parameter_value = db.get_configuration_parameter_value_by_names( - db_session, param_name, param_value - ) - if db_parameter_value is not None: - conf_param_values_filter.append(db_parameter_value) - else: - logger.debug( - f"ignoring unknown parameter/value pair {param_name}:{param_value}" - ) + conf_param_values_filter, climatic_indicator = _retrieve_climatic_indicator_filter( + db_session, possible_value or [] + ) cov_internals, filtered_total = db.list_coverage_identifiers( db_session, limit=list_params.limit, offset=list_params.offset, include_total=True, name_filter=name_contains, - configuration_parameter_values_filter=conf_param_values_filter or None, + configuration_parameter_values_filter=conf_param_values_filter, + climatic_indicator_filter=climatic_indicator, ) _, unfiltered_total = db.list_coverage_identifiers( db_session, limit=1, offset=0, include_total=True diff --git a/arpav_ppcv/webapp/api_v2/routers/observations.py b/arpav_ppcv/webapp/api_v2/routers/observations.py index 8e31ea2f..f3447c27 100644 --- a/arpav_ppcv/webapp/api_v2/routers/observations.py +++ b/arpav_ppcv/webapp/api_v2/routers/observations.py @@ -131,7 +131,7 @@ def list_monthly_measurements( db_session: Annotated[Session, Depends(dependencies.get_db_session)], list_params: Annotated[dependencies.CommonListFilterParameters, Depends()], station_code: str | None = None, - variable_name: str | None = None, + climatic_indicator_identifier: str | None = None, month: Annotated[int | None, fastapi.Query(le=1, ge=12)] = None, ): """List known monthly measurements.""" @@ -143,20 +143,22 @@ def list_monthly_measurements( raise ValueError("Invalid station code") else: station_id = None - if variable_name is not None: - db_variable = db.get_variable_by_name(db_session, variable_name) - if db_variable is not None: - variable_id = db_variable.id + if climatic_indicator_identifier is not None: + db_climatic_indicator = db.get_climatic_indicator_by_identifier( + db_session, climatic_indicator_identifier + ) + if db_climatic_indicator is not None: + climatic_indicator_id = db_climatic_indicator.id else: - raise ValueError("Invalid variable name") + raise ValueError("Invalid climatic indicator identifier") else: - variable_id = None + climatic_indicator_id = None monthly_measurements, filtered_total = db.list_monthly_measurements( db_session, limit=list_params.limit, offset=list_params.offset, station_id_filter=station_id, - variable_id_filter=variable_id, + climatic_indicator_id_filter=climatic_indicator_id, month_filter=month, include_total=True, ) diff --git a/arpav_ppcv/webapp/api_v2/schemas/observations.py b/arpav_ppcv/webapp/api_v2/schemas/observations.py index 51f11c13..51b839d7 100644 --- a/arpav_ppcv/webapp/api_v2/schemas/observations.py +++ b/arpav_ppcv/webapp/api_v2/schemas/observations.py @@ -69,7 +69,7 @@ def from_db_instance( class MonthlyMeasurementReadListItem(observations.MonthlyMeasurementBase): url: pydantic.AnyHttpUrl - variable_name: str + climatic_indicator_identifier: str station_code: str @classmethod @@ -80,7 +80,7 @@ def from_db_instance( ) -> "MonthlyMeasurementReadListItem": return cls( **instance.model_dump(), - variable_name=instance.variable.name, + climatic_indicator_identifier=instance.climatic_indicator.identifier, station_code=instance.station.code, url=str( request.url_for( @@ -92,7 +92,7 @@ def from_db_instance( class SeasonalMeasurementReadListItem(pydantic.BaseModel): url: pydantic.AnyHttpUrl - variable_name: str + climatic_indicator_identifier: str station_code: str year: int season: Season @@ -106,7 +106,7 @@ def from_db_instance( ) -> "SeasonalMeasurementReadListItem": return cls( **instance.model_dump(), - variable_name=instance.variable.name, + climatic_indicator_identifier=instance.climatic_indicator.identifier, station_code=instance.station.code, url=str( request.url_for( @@ -118,7 +118,7 @@ def from_db_instance( class YearlyMeasurementReadListItem(pydantic.BaseModel): url: pydantic.AnyHttpUrl - variable_name: str + climatic_indicator_identifier: str station_code: str year: int value: float @@ -131,7 +131,7 @@ def from_db_instance( ) -> "YearlyMeasurementReadListItem": return cls( **instance.model_dump(), - variable_name=instance.variable.name, + climatic_indicator_identifier=instance.climatic_indicator.identifier, station_code=instance.station.code, url=str( request.url_for( diff --git a/tests/conftest.py b/tests/conftest.py index 840b288b..c5bf948a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,6 +20,7 @@ main, ) from arpav_ppcv.schemas import ( + climaticindicators, coverages, observations, ) @@ -29,15 +30,12 @@ from arpav_ppcv.bootstrapper.configurationparameters import ( generate_configuration_parameters as bootstrappable_configuration_parameters, ) -from arpav_ppcv.bootstrapper.climaticindicators import ( - tas as tas_climatic_indicators_bootstrappable_configurations, +from arpav_ppcv.bootstrapper.climaticindicators.tas import ( + generate_climatic_indicators as generate_tas_climatic_indicators, ) from arpav_ppcv.bootstrapper.coverage_configurations.forecast import ( tas as tas_forecast_bootstrappable_configurations, ) -from arpav_ppcv.bootstrapper.variables import ( - generate_variable_configurations as bootstrappable_variables, -) @pytest.fixture @@ -166,52 +164,42 @@ def sample_stations(arpav_db_session) -> list[observations.Station]: @pytest.fixture() -def sample_variables(arpav_db_session) -> list[observations.Variable]: - db_variables = [] - for i in range(20): - db_variables.append( - observations.Variable( - name=f"testvariable{i}", - description=f"Description for test variable {i}", - ) - ) - for db_variable in db_variables: - arpav_db_session.add(db_variable) - arpav_db_session.commit() - for db_station in db_variables: - arpav_db_session.refresh(db_station) - return db_variables - - -@pytest.fixture() -def sample_real_variables(arpav_db_session) -> list[observations.Variable]: +def sample_real_climatic_indicators( + arpav_db_session, +) -> list[climaticindicators.ClimaticIndicator]: created = [] - for var_to_create in bootstrappable_variables(): - created.append(database.create_variable(arpav_db_session, var_to_create)) + for indicator_to_create in generate_tas_climatic_indicators(): + created.append( + database.create_climatic_indicator(arpav_db_session, indicator_to_create) + ) return created @pytest.fixture() def sample_monthly_measurements( - arpav_db_session, sample_variables, sample_stations + arpav_db_session, + sample_real_climatic_indicators, + sample_stations, ) -> list[observations.MonthlyMeasurement]: db_monthly_measurements = [] unique_measurement_instances = set() while len(unique_measurement_instances) < 200: sampled_date = dt.date(random.randrange(1920, 2020), random.randrange(1, 13), 1) sampled_station_id = random.choice(sample_stations).id - sampled_variable_id = random.choice(sample_variables).id + sampled_climatic_indicator_id = random.choice( + sample_real_climatic_indicators + ).id unique_measurement_instances.add( - (sampled_date, sampled_station_id, sampled_variable_id) + (sampled_date, sampled_station_id, sampled_climatic_indicator_id) ) - for date_, station_id, variable_id in unique_measurement_instances: + for date_, station_id, climatic_indicator_id in unique_measurement_instances: db_monthly_measurements.append( observations.MonthlyMeasurement( value=random.random() * 20 - 10, date=date_, station_id=station_id, - variable_id=variable_id, + climatic_indicator_id=climatic_indicator_id, ) ) for db_monthly_measurement in db_monthly_measurements: @@ -298,27 +286,12 @@ def sample_coverage_configurations( return db_cov_confs -@pytest.fixture() -def sample_real_climatic_indicators( - arpav_db_session, -): - to_create = tas_climatic_indicators_bootstrappable_configurations.generate_climatic_indicators() - created = [] - for clim_ind_to_create in to_create: - created.append( - database.create_climatic_indicator(arpav_db_session, clim_ind_to_create) - ) - return created - - @pytest.fixture() def sample_real_coverage_configurations( arpav_db_session, sample_real_configuration_parameters, sample_real_climatic_indicators, - sample_real_variables, ): - all_vars = database.collect_all_variables(arpav_db_session) all_conf_param_values = database.collect_all_configuration_parameter_values( arpav_db_session ) @@ -329,7 +302,6 @@ def sample_real_coverage_configurations( (pv.configuration_parameter.name, pv.name): pv for pv in all_conf_param_values }, - variables={v.name: v for v in all_vars}, climatic_indicators={i.identifier: i.id for i in all_climatic_indicators}, ) ) @@ -463,7 +435,7 @@ def sample_tas_csv_data(): @pytest.fixture() def sample_real_monthly_measurements( arpav_db_session, - sample_real_variables, + sample_real_climatic_indicators, sample_real_station, ) -> observations.MonthlyMeasurement: raw_measurements = io.StringIO( @@ -918,7 +890,7 @@ def sample_real_monthly_measurements( """.strip() ) reader = csv.reader(raw_measurements, delimiter=",") - vars = {v.name: v for v in sample_real_variables} + indicators = {i.identifier: i.id for i in sample_real_climatic_indicators} measurements = [] for idx, row in enumerate(reader): if idx == 0: # skip the header @@ -927,7 +899,7 @@ def sample_real_monthly_measurements( measurements.append( observations.MonthlyMeasurement( station_id=sample_real_station.id, - variable_id=vars["TDd"].id, + climatic_indicator_id=indicators["tas-absolute-annual"], value=float(value), date=dt.datetime.strptime(raw_date, "%Y-%m-%d"), ) diff --git a/tests/test_cliapp.py b/tests/test_cliapp.py deleted file mode 100644 index 388415bd..00000000 --- a/tests/test_cliapp.py +++ /dev/null @@ -1,75 +0,0 @@ -import pytest - - -@pytest.mark.parametrize( - "code, lon, lat, altitude, name, type_", - [ - pytest.param("fakecode1", "-10.44", "39.45", "5", "fakename1", "faketype1"), - pytest.param("fakecode2", "-10.44", "39.45", None, None, None), - ], -) -def test_create_station(cli_runner, cli_app, code, lon, lat, altitude, name, type_): - execution_args = [ - "app", - "create-station", - code, - lon, - lat, - ] - if altitude is not None: - execution_args.extend(["--altitude", altitude]) - if name is not None: - execution_args.extend(["--name", name]) - if type_ is not None: - execution_args.extend(["--type", type_]) - result = cli_runner.invoke(cli_app, execution_args) - assert result.exit_code == 0 - - -@pytest.mark.parametrize( - "name, description, unit", - [ - pytest.param("fakevar1", "Some fake var 1", "ÂșC"), - pytest.param("fakevar2", "Some fake var 2", None), - ], -) -def test_create_variable( - cli_runner, - cli_app, - name, - description, - unit, -): - execution_args = [ - "app", - "create-variable", - name, - description, - ] - if unit is not None: - execution_args.extend(["--unit", unit]) - result = cli_runner.invoke(cli_app, execution_args) - assert result.exit_code == 0 - - -@pytest.mark.parametrize( - "date, value", - [ - pytest.param("2020-01-01", "-34.23"), - ], -) -def test_create_monthly_measurement( - cli_runner, cli_app, sample_stations, sample_variables, date, value -): - target_station = sample_stations[0] - target_variable = sample_variables[0] - execution_args = [ - "app", - "create-monthly-measurement", - target_station.code, - target_variable.name, - "2020-01-01", - "23.33", - ] - result = cli_runner.invoke(cli_app, execution_args) - assert result.exit_code == 0 diff --git a/tests/test_database.py b/tests/test_database.py index da338d4b..b6caf678 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1,5 +1,6 @@ import random from contextlib import nullcontext as does_not_raise +from operator import attrgetter import pydantic import pytest @@ -121,20 +122,25 @@ def test_generate_coverage_identifiers( pytest.param(5, 2, True), ], ) -def test_list_variables( - arpav_db_session, sample_variables, limit, offset, include_total +def test_list_climatic_indicators( + arpav_db_session, sample_real_climatic_indicators, limit, offset, include_total ): - ordered_variables = sorted(sample_variables, key=lambda variable: variable.name) - expected_names = [v.name for v in ordered_variables][offset : offset + limit] - db_variables, total = database.list_variables( + ordered_indicators = sorted( + sample_real_climatic_indicators, + key=attrgetter("sort_order", "name", "aggregation_period", "measure_type"), + ) + expected_identifiers = [i.identifier for i in ordered_indicators][ + offset : offset + limit + ] + db_climatic_indicators, total = database.list_climatic_indicators( arpav_db_session, limit=limit, offset=offset, include_total=include_total ) if include_total: - assert total == len(sample_variables) + assert total == len(sample_real_climatic_indicators) else: assert total is None - for index, db_variable in enumerate(db_variables): - assert db_variable.name == expected_names[index] + for index, db_climatic_indicator in enumerate(db_climatic_indicators): + assert db_climatic_indicator.identifier == expected_identifiers[index] @pytest.mark.parametrize( diff --git a/tests/test_observations_harvester_operations.py b/tests/test_observations_harvester_operations.py index 2bd37660..75e19561 100644 --- a/tests/test_observations_harvester_operations.py +++ b/tests/test_observations_harvester_operations.py @@ -13,7 +13,7 @@ def test_fetch_remote_stations( httpx_mock, arpav_db_session, - sample_real_variables, + sample_real_climatic_indicators, ): httpx_mock.add_response( url=re.compile(r"https://api.arpa.veneto.it/REST/v1/.*"), @@ -37,7 +37,7 @@ def test_fetch_remote_stations( fetched = list( operations.fetch_remote_stations( client, - sample_real_variables[0:1], + climatic_indicators=sample_real_climatic_indicators[0:1], fetch_stations_with_months=True, fetch_stations_with_seasons=False, fetch_stations_with_yearly_measurements=False, diff --git a/tests/test_operations.py b/tests/test_operations.py index bbf5c69c..10699f22 100644 --- a/tests/test_operations.py +++ b/tests/test_operations.py @@ -195,160 +195,80 @@ def test_get_related_coverage_configurations( ], [ pytest.param( + ["tas"], + ["30yr"], + ["model_ensemble"], + ["rcp85"], + ["anomaly"], + ["winter"], + ["tw1"], [ - "tas", - ], - [ - "30yr", - ], - [ - "model_ensemble", - ], - [ - "rcp85", - ], - [ - "anomaly", - ], - [ - "winter", - ], - [ - "tw1", - ], - [ - "tas_30yr_anomaly_seasonal_agree_model_ensemble-30yr-forecast-model_ensemble-tas-anomaly-rcp85-tw1-winter" + "tas_30yr_anomaly_seasonal_agree_model_ensemble-tas-anomaly-thirty_year-forecast-model_ensemble-rcp85-tw1-winter" ], ), pytest.param( - [ - "tas", - ], - [ - "30yr", - ], - [ - "model_ensemble", - ], - [ - "rcp85", - ], - [ - "anomaly", - ], - [ - "winter", - ], + ["tas"], + ["30yr"], + ["model_ensemble"], + ["rcp85"], + ["anomaly"], + ["winter"], None, [ - "tas_30yr_anomaly_seasonal_agree_model_ensemble-30yr-forecast-model_ensemble-tas-anomaly-rcp85-tw1-winter", - "tas_30yr_anomaly_seasonal_agree_model_ensemble-30yr-forecast-model_ensemble-tas-anomaly-rcp85-tw2-winter", + "tas_30yr_anomaly_seasonal_agree_model_ensemble-tas-anomaly-thirty_year-forecast-model_ensemble-rcp85-tw1-winter", + "tas_30yr_anomaly_seasonal_agree_model_ensemble-tas-anomaly-thirty_year-forecast-model_ensemble-rcp85-tw2-winter", ], ), pytest.param( - [ - "tas", - ], - [ - "annual", - ], - [ - "model_ensemble", - ], - [ - "rcp85", - ], - [ - "anomaly", - ], - [ - "winter", - ], + ["tas"], + ["annual"], + ["model_ensemble"], + ["rcp85"], + ["anomaly"], + ["winter"], None, [ - "tas_seasonal_anomaly_model_ensemble-annual-forecast-model_ensemble-tas-anomaly-rcp85-winter", - "tas_seasonal_anomaly_model_ensemble_lower_uncertainty-annual-forecast-model_ensemble-tas-anomaly-rcp85-lower_bound-winter", - "tas_seasonal_anomaly_model_ensemble_upper_uncertainty-annual-forecast-model_ensemble-tas-anomaly-rcp85-upper_bound-winter", + "tas_seasonal_anomaly_model_ensemble-tas-anomaly-annual-forecast-model_ensemble-rcp85-winter", + "tas_seasonal_anomaly_model_ensemble_lower_uncertainty-tas-anomaly-annual-forecast-model_ensemble-rcp85-lower_bound-winter", + "tas_seasonal_anomaly_model_ensemble_upper_uncertainty-tas-anomaly-annual-forecast-model_ensemble-rcp85-upper_bound-winter", ], ), pytest.param( - [ - "tas", - ], - [ - "annual", - ], - [ - "model_ensemble", - ], - [ - "rcp85", - ], - [ - "anomaly", - ], - [ - "winter", - ], - [ - "tw1", - ], + ["tas"], + ["annual"], + ["model_ensemble"], + ["rcp85"], + ["anomaly"], + ["winter"], + ["tw1"], [], ), pytest.param( + ["tas"], + ["annual", "30yr"], + ["model_ensemble"], + ["rcp85"], + ["anomaly"], + ["winter"], + ["tw1"], [ - "tas", - ], - [ - "annual", - "30yr", - ], - [ - "model_ensemble", - ], - [ - "rcp85", - ], - [ - "anomaly", - ], - [ - "winter", - ], - [ - "tw1", - ], - [ - "tas_30yr_anomaly_seasonal_agree_model_ensemble-30yr-forecast-model_ensemble-tas-anomaly-rcp85-tw1-winter", + "tas_30yr_anomaly_seasonal_agree_model_ensemble-tas-anomaly-thirty_year-forecast-model_ensemble-rcp85-tw1-winter", ], ), pytest.param( - [ - "tas", - ], - [ - "annual", - "30yr", - ], - [ - "model_ensemble", - ], - [ - "rcp85", - ], - [ - "anomaly", - ], - [ - "winter", - ], + ["tas"], + ["annual", "30yr"], + ["model_ensemble"], + ["rcp85"], + ["anomaly"], + ["winter"], None, [ - "tas_seasonal_anomaly_model_ensemble-annual-forecast-model_ensemble-tas-anomaly-rcp85-winter", - "tas_seasonal_anomaly_model_ensemble_lower_uncertainty-annual-forecast-model_ensemble-tas-anomaly-rcp85-lower_bound-winter", - "tas_seasonal_anomaly_model_ensemble_upper_uncertainty-annual-forecast-model_ensemble-tas-anomaly-rcp85-upper_bound-winter", - "tas_30yr_anomaly_seasonal_agree_model_ensemble-30yr-forecast-model_ensemble-tas-anomaly-rcp85-tw1-winter", - "tas_30yr_anomaly_seasonal_agree_model_ensemble-30yr-forecast-model_ensemble-tas-anomaly-rcp85-tw2-winter", + "tas_seasonal_anomaly_model_ensemble-tas-anomaly-annual-forecast-model_ensemble-rcp85-winter", + "tas_seasonal_anomaly_model_ensemble_lower_uncertainty-tas-anomaly-annual-forecast-model_ensemble-rcp85-lower_bound-winter", + "tas_seasonal_anomaly_model_ensemble_upper_uncertainty-tas-anomaly-annual-forecast-model_ensemble-rcp85-upper_bound-winter", + "tas_30yr_anomaly_seasonal_agree_model_ensemble-tas-anomaly-thirty_year-forecast-model_ensemble-rcp85-tw1-winter", + "tas_30yr_anomaly_seasonal_agree_model_ensemble-tas-anomaly-thirty_year-forecast-model_ensemble-rcp85-tw2-winter", ], ), ], @@ -382,3 +302,57 @@ def test_list_coverage_identifiers_by_param_values( assert result_item in expected for expected_item in expected: assert expected_item in result + + +@pytest.mark.parametrize( + "variables, measures, aggregation_periods, expected_identifiers", + [ + pytest.param( + [], + [], + [], + [ + "tas-absolute-annual", + "tas-absolute-thirty_year", + "tas-anomaly-annual", + "tas-anomaly-thirty_year", + ], + ), + pytest.param(["fake"], [], [], []), + pytest.param( + [ + "tas", + ], + [ + "absolute", + ], + [], + ["tas-absolute-annual", "tas-absolute-thirty_year"], + ), + pytest.param( + [], + [], + ["annual"], + [ + "tas-absolute-annual", + "tas-anomaly-annual", + ], + ), + pytest.param(["tas"], ["absolute"], ["annual"], ["tas-absolute-annual"]), + ], +) +def test_list_possible_climatic_indicators( + arpav_db_session, + sample_real_climatic_indicators, + variables, + measures, + aggregation_periods, + expected_identifiers, +): + result = operations._list_possible_climatic_indicators( + arpav_db_session, variables, measures, aggregation_periods + ) + for result_climatic_indicator in result: + assert result_climatic_indicator.identifier in expected_identifiers + for expected_identifier in expected_identifiers: + assert expected_identifier in [i.identifier for i in result] diff --git a/tests/test_prefect_flows_observations.py b/tests/test_prefect_flows_observations.py index 02d587f1..578f2eae 100644 --- a/tests/test_prefect_flows_observations.py +++ b/tests/test_prefect_flows_observations.py @@ -9,46 +9,51 @@ @pytest.mark.parametrize( - "station_id, variable_id, value, date, expected", + "station_id, climatic_indicator_id, value, date, expected", [ pytest.param( uuid.UUID("65af54f0-1df2-423b-994f-03fa1195dd7b"), - uuid.UUID("2a20f72f-2e0f-4a0c-9cd4-d3215aa9b8fc"), + 1, 10.23, dt.date(2020, 1, 1), - "65af54f0-1df2-423b-994f-03fa1195dd7b-2a20f72f-2e0f-4a0c-9cd4-d3215aa9b8fc-202001", + "65af54f0-1df2-423b-994f-03fa1195dd7b-1-202001", ), ], ) -def test_build_monthly_measurement_id(station_id, variable_id, value, date, expected): +def test_build_monthly_measurement_id( + station_id, climatic_indicator_id, value, date, expected +): result = observations.build_monthly_measurement_id( observation_schemas.MonthlyMeasurementCreate( - station_id=station_id, variable_id=variable_id, value=value, date=date + station_id=station_id, + climatic_indicator_id=climatic_indicator_id, + value=value, + date=date, ) ) assert result == expected @pytest.mark.parametrize( - "station_id, variable_id, value, year, season, expected", + "station_id, climatic_indicator_id, value, year, season, expected", [ pytest.param( uuid.UUID("65af54f0-1df2-423b-994f-03fa1195dd7b"), - uuid.UUID("2a20f72f-2e0f-4a0c-9cd4-d3215aa9b8fc"), + 2, 10.23, 2020, Season.SUMMER, - "65af54f0-1df2-423b-994f-03fa1195dd7b-2a20f72f-2e0f-4a0c-9cd4-d3215aa9b8fc-2020-SUMMER", + "65af54f0-1df2-423b-994f-03fa1195dd7b-2-2020-SUMMER", ), ], ) def test_build_seasonal_measurement_id( - station_id, variable_id, value, year, season, expected + station_id, climatic_indicator_id, value, year, season, expected ): result = observations.build_seasonal_measurement_id( observation_schemas.SeasonalMeasurementCreate( station_id=station_id, - variable_id=variable_id, + climatic_indicator_id=climatic_indicator_id, value=value, year=year, season=season, @@ -58,22 +63,24 @@ def test_build_seasonal_measurement_id( @pytest.mark.parametrize( - "station_id, variable_id, value, year, expected", + "station_id, climatic_indicator_id, value, year, expected", [ pytest.param( uuid.UUID("65af54f0-1df2-423b-994f-03fa1195dd7b"), - uuid.UUID("2a20f72f-2e0f-4a0c-9cd4-d3215aa9b8fc"), + 3, 10.23, 2020, - "65af54f0-1df2-423b-994f-03fa1195dd7b-2a20f72f-2e0f-4a0c-9cd4-d3215aa9b8fc-2020", + "65af54f0-1df2-423b-994f-03fa1195dd7b-3-2020", ), ], ) -def test_build_yearly_measurement_id(station_id, variable_id, value, year, expected): +def test_build_yearly_measurement_id( + station_id, climatic_indicator_id, value, year, expected +): result = observations.build_yearly_measurement_id( observation_schemas.YearlyMeasurementCreate( station_id=station_id, - variable_id=variable_id, + climatic_indicator_id=climatic_indicator_id, value=value, year=year, ) diff --git a/tests/test_webapp_v2_routers_climaticindicators.py b/tests/test_webapp_v2_routers_climaticindicators.py new file mode 100644 index 00000000..4d1b820f --- /dev/null +++ b/tests/test_webapp_v2_routers_climaticindicators.py @@ -0,0 +1,30 @@ +import httpx + +from arpav_ppcv.schemas.climaticindicators import ClimaticIndicator + + +def test_climatic_indicator_list( + test_client_v2_app: httpx.Client, + sample_real_climatic_indicators: list[ClimaticIndicator], +): + list_response = test_client_v2_app.get( + test_client_v2_app.app.url_path_for("list_climatic_indicators") + ) + assert list_response.status_code == 200 + assert len(list_response.json()["items"]) == 4 + + +def test_climatic_indicator_detail( + test_client_v2_app: httpx.Client, + sample_real_climatic_indicators: list[ClimaticIndicator], +): + target_indicator = sample_real_climatic_indicators[0] + detail_response = test_client_v2_app.get( + test_client_v2_app.app.url_path_for( + "get_climatic_indicator", + climatic_indicator_identifier=target_indicator.identifier, + ) + ) + assert detail_response.status_code == 200 + payload = detail_response.json() + assert payload["identifier"] == target_indicator.identifier diff --git a/tests/test_webapp_v2_routers_observations.py b/tests/test_webapp_v2_routers_observations.py index 9d5eb799..9f9b825a 100644 --- a/tests/test_webapp_v2_routers_observations.py +++ b/tests/test_webapp_v2_routers_observations.py @@ -38,30 +38,6 @@ def test_station_detail( assert uuid.UUID(payload["id"]) == target_station.id -def test_variable_list( - test_client_v2_app: httpx.Client, sample_variables: list[observations.Variable] -): - list_response = test_client_v2_app.get( - test_client_v2_app.app.url_path_for("list_variables") - ) - assert list_response.status_code == 200 - assert len(list_response.json()["items"]) == 20 - - -def test_variable_detail( - test_client_v2_app: httpx.Client, sample_variables: list[observations.Variable] -): - target_variable = sample_variables[0] - detail_response = test_client_v2_app.get( - test_client_v2_app.app.url_path_for( - "get_variable", variable_id=target_variable.id - ) - ) - assert detail_response.status_code == 200 - payload = detail_response.json() - assert uuid.UUID(payload["id"]) == target_variable.id - - def test_monthly_measurement_list( test_client_v2_app: httpx.Client, sample_monthly_measurements: list[observations.MonthlyMeasurement], @@ -93,16 +69,18 @@ def test_monthly_measurement_list_filter_by_variable_name( test_client_v2_app: httpx.Client, sample_monthly_measurements: list[observations.MonthlyMeasurement], ): - target_variable = sample_monthly_measurements[0].variable + target_climatic_indicator = sample_monthly_measurements[0].climatic_indicator list_response = test_client_v2_app.get( test_client_v2_app.app.url_path_for("list_monthly_measurements"), - params={"variable_name": target_variable.name}, + params={"climatic_indicator_identifier": target_climatic_indicator.identifier}, ) assert list_response.status_code == 200 payload = list_response.json() assert len(payload["items"]) > 0 for returned_item in payload["items"]: - assert returned_item["variable_name"] == target_variable.name + assert ( + returned_item["climatic_identifier"] == target_climatic_indicator.identifier + ) def test_monthly_measurement_detail(