chap-core 2.0.0__tar.gz → 2.2.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.
- {chap_core-2.0.0 → chap_core-2.2.0}/PKG-INFO +7 -8
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/api_types.py +29 -2
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/backtest_plots/__init__.py +0 -1
- chap_core-2.2.0/chap_core/assessment/backtest_plots/db_dimensions.py +167 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/backtest_plots/evaluation_plot.py +3 -2
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/backtest_plots/horizon_location_grid.py +109 -42
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/backtest_plots/predicted_vs_actual_linear_plot.py +11 -11
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/backtest_plots/predicted_vs_actual_plot.py +2 -1
- chap_core-2.2.0/chap_core/assessment/backtest_plots/sample_bias_plot.py +91 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/dataset_splitting.py +11 -11
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/evaluation.py +80 -19
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/flat_representations.py +7 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/forecast.py +12 -6
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metric_plots/horizon_sum.py +4 -3
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metric_plots/time_period_mean.py +2 -2
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metric_plots/time_period_sum.py +4 -3
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/__init__.py +20 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/above_truth.py +1 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/base.py +14 -7
- chap_core-2.2.0/chap_core/assessment/metrics/crps.py +112 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/crps_norm.py +2 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/example_metric.py +2 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/mae.py +2 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/mape.py +2 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/outbreak_detection.py +9 -4
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/peak_diff.py +2 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/percentile_coverage.py +2 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/rmse.py +2 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/test_metrics.py +2 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metrics/winkler_score.py +5 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/prediction_evaluator.py +79 -20
- chap_core-2.2.0/chap_core/assessment/thresholds/seasonal.py +84 -0
- chap_core-2.2.0/chap_core/assessment/weather_providers/__init__.py +152 -0
- chap_core-2.2.0/chap_core/assessment/weather_providers/base.py +55 -0
- chap_core-2.2.0/chap_core/assessment/weather_providers/climatology.py +20 -0
- chap_core-2.2.0/chap_core/assessment/weather_providers/damped_persistence.py +82 -0
- chap_core-2.2.0/chap_core/assessment/weather_providers/mstl_arima.py +194 -0
- chap_core-2.2.0/chap_core/assessment/weather_providers/observed.py +24 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli.py +2 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/_common.py +48 -28
- chap_core-2.2.0/chap_core/cli_endpoints/ensemble.py +416 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/evaluate.py +44 -19
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/explain.py +2 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/generated_plot_ids.py +2 -2
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/utils.py +1 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/climate_predictor.py +21 -12
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/data/datasets.py +1 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/database.py +151 -46
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/model_spec_tables.py +5 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/model_template_seed.py +65 -25
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/model_templates_and_config_tables.py +100 -12
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/tables.py +11 -2
- chap_core-2.2.0/chap_core/ensemble/__init__.py +1 -0
- chap_core-2.2.0/chap_core/ensemble/_meta_models.py +131 -0
- chap_core-2.2.0/chap_core/ensemble/_predictor.py +110 -0
- chap_core-2.2.0/chap_core/ensemble/_sample_extractor.py +123 -0
- chap_core-2.2.0/chap_core/ensemble/ensemble_model.py +317 -0
- chap_core-2.2.0/chap_core/ensemble/wrappers.py +59 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/explainability/documentation.md +1 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/explainability/lime.py +41 -11
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/explainability/segment.py +4 -4
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/explainability/userguide.md +1 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/external/ExtendedPredictor.py +3 -3
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/external/github.py +23 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/file_io/example_data_set.py +1 -1
- chap_core-2.2.0/chap_core/hpo/hpoModel.py +134 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/hpo/hpoModelInterface.py +2 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/hpo/objective.py +22 -21
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/hpo/searcher.py +35 -63
- chap_core-2.2.0/chap_core/hpo/types.py +11 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/models/chapkit_rest_api_wrapper.py +78 -15
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/models/chapkit_service_manager.py +53 -4
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/models/external_chapkit_model.py +20 -9
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/models/local_configuration.py +1 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/models/utils.py +1 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/plotting/backtest_plot.py +4 -2
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/plotting/dataset_plot.py +3 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/plotting/evaluation_plot.py +4 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/plotting/yaml_plot.py +3 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/app.py +11 -1
- chap_core-2.2.0/chap_core/rest_api/auth.py +140 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/celery_tasks.py +1 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/common_routes.py +9 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/data_models.py +65 -8
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/db_worker_functions.py +55 -21
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/services/orchestrator.py +60 -33
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/services/schemas.py +4 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/v1/routers/analytics.py +97 -4
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/v1/routers/crud.py +76 -83
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/v1/routers/visualization.py +76 -19
- chap_core-2.2.0/chap_core/rest_api/v2/dependencies.py +57 -0
- chap_core-2.2.0/chap_core/rest_api/v2/rest_api.py +7 -0
- chap_core-2.2.0/chap_core/rest_api/v2/routers/proxy.py +161 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/v2/routers/services.py +1 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/runners/command_line_runner.py +16 -34
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/simulation/naive_simulator.py +9 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/util.py +15 -1
- {chap_core-2.0.0 → chap_core-2.2.0}/pyproject.toml +18 -12
- chap_core-2.0.0/chap_core/assessment/backtest_plots/metrics_dashboard.py +0 -101
- chap_core-2.0.0/chap_core/assessment/backtest_plots/sample_bias_plot.py +0 -138
- chap_core-2.0.0/chap_core/assessment/metrics/crps.py +0 -71
- chap_core-2.0.0/chap_core/assessment/thresholds/seasonal.py +0 -56
- chap_core-2.0.0/chap_core/hpo/hpoModel.py +0 -123
- chap_core-2.0.0/chap_core/rest_api/v2/dependencies.py +0 -53
- chap_core-2.0.0/chap_core/rest_api/v2/rest_api.py +0 -6
- {chap_core-2.0.0 → chap_core-2.2.0}/README.md +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/adaptors/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/adaptors/command_line_interface.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/adaptors/gluonts.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/adaptors/rest_api.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/alarms.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/api.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/causal_plot.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/data_representation_transforming.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/eval_tracking.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/evaluator.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/evaluator_suites.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metric_plots/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metric_plots/horizon_location_mean.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metric_plots/horizon_mean.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metric_plots/metric_map.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metric_plots/regional_distribution.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metric_plots/time_period_location_mean.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/metric_table.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/representations.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/thresholds/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/thresholds/base.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/_args.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/aggregate_eval.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/build_counterfactual.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/causal.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/convert.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/forecast.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/generate_modelcard.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/model.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/preference_learn.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/report.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/cli_endpoints/validate.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/climate_data/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/climate_data/seasonal_forecasts.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/data/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/data/adaptors.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/data/gluonts_adaptor/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/data/gluonts_adaptor/dataset.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/data/open_dengue.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/base_tables.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/dataset_manager.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/dataset_tables.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/datasets_seed.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/feature_tables.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/database/manager.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/datatypes.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/docker_helper_functions.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/exceptions.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/explainability/distance.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/explainability/perturb.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/explainability/plot.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/explainability/surrogate.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/explainability/testing/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/explainability/testing/metrics.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/external/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/external/external_model.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/external/mlflow_wrappers.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/external/model_configuration.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/external/python_model.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/external/r_description.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/external/r_model.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/external/r_models.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/external/uv_wrapper.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/feature_generators/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/feature_generators/seasonality_cluster.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/fetch/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/file_io/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/file_io/cleaners.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/file_io/external_file.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/file_io/file_paths.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/file_io/load.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/geo_coding/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/geojson.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/geometry.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/geoutils.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/hpo/README.md +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/hpo/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/hpo/base.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/hpo/config1.yaml +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/hpo/config2.yaml +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/hpo/config3.yaml +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/log_config.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/model_spec.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/models/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/models/configured_model.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/models/external_model.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/models/external_web_model.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/models/model_template.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/models/model_template_interface.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/pandas_adaptors.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/plotting/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/plotting/backtest_dashboard_spec.json +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/plotting/conftest.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/plotting/prediction_plot.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/plotting/produce_plots_from_yaml.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/plotting/season_plot.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/predictor/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/predictor/feature_spec.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/predictor/model_registry.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/predictor/naive_estimator.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/predictor/naive_predictor.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/predictor/poisson.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/predictor/protocol.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/preference_learning/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/preference_learning/decision_maker.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/preference_learning/preference_learner.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rbased_docker.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/experimental.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/services/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/v1/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/v1/jobs.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/v1/rest_api.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/v1/routers/dependencies.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/v2/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/v2/routers/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/rest_api/worker_functions.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/runners/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/runners/conda_runner.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/runners/docker_runner.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/runners/helper_functions.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/runners/mlflow_runner.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/runners/renv_runner.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/runners/runner.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/runners/uv_runner.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/services/dataset_validation.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/services/prediction_setup_service.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/simulation/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/simulation/simulator.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/spatio_temporal_data/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/spatio_temporal_data/converters.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/spatio_temporal_data/multi_country_dataset.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/spatio_temporal_data/temporal_dataclass.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/testing/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/testing/estimators.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/testing/external_model.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/testing/mocks.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/testing/testing.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/time_period/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/time_period/date_util_wrapper.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/time_period/multi_resolution.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/time_period/period_assignment.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/time_period/relationships.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/transformations/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/transformations/covid_mask.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/validators.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/worker/__init__.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/worker/interface.py +0 -0
- {chap_core-2.0.0 → chap_core-2.2.0}/chap_core/worker/rq_worker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: chap_core
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Climate Health Analysis Platform (CHAP)
|
|
5
5
|
Keywords: chap_core
|
|
6
6
|
Author: Chap Team
|
|
@@ -32,7 +32,8 @@ Requires-Dist: pyyaml>=6.0.3
|
|
|
32
32
|
Requires-Dist: requests>=2.34.2
|
|
33
33
|
Requires-Dist: urllib3>=2.7.0
|
|
34
34
|
Requires-Dist: rq>=2.9.0
|
|
35
|
-
Requires-Dist: scikit-learn>=1.
|
|
35
|
+
Requires-Dist: scikit-learn>=1.9.0
|
|
36
|
+
Requires-Dist: statsmodels>=0.14.6
|
|
36
37
|
Requires-Dist: topojson>=1.10
|
|
37
38
|
Requires-Dist: unidecode>=1.4.0
|
|
38
39
|
Requires-Dist: uvicorn>=0.48.0
|
|
@@ -45,21 +46,19 @@ Requires-Dist: altair>=5.5.0
|
|
|
45
46
|
Requires-Dist: virtualenv>=20.34.0
|
|
46
47
|
Requires-Dist: geojson-pydantic>=2.1.1
|
|
47
48
|
Requires-Dist: pandera>=0.31.1
|
|
48
|
-
Requires-Dist: vegafusion>=2.0.3
|
|
49
49
|
Requires-Dist: vl-convert-python>=1.9.0.post1
|
|
50
|
-
Requires-Dist:
|
|
51
|
-
Requires-Dist: numpy>=2.1.3
|
|
50
|
+
Requires-Dist: numpy>=2.3.2
|
|
52
51
|
Requires-Dist: gunicorn>=25.3.0
|
|
53
|
-
Requires-Dist: optuna>=4.
|
|
52
|
+
Requires-Dist: optuna>=4.9.0
|
|
54
53
|
Requires-Dist: httpx>=0.28.1
|
|
55
54
|
Requires-Dist: alembic>=1.18.4
|
|
56
|
-
Requires-Dist: chapkit>=0.
|
|
55
|
+
Requires-Dist: chapkit>=1.0.0,<2
|
|
57
56
|
Requires-Dist: cryptography>=46.0.7
|
|
58
57
|
Requires-Dist: croniter>=6.2.2
|
|
59
58
|
Requires-Dist: stumpy>=1.14.1
|
|
60
59
|
Requires-Dist: pyts>=0.13.0
|
|
61
60
|
Requires-Dist: fastdtw>=0.3.4
|
|
62
|
-
Requires-Python: >=3.13, <3.
|
|
61
|
+
Requires-Python: >=3.13, <3.15
|
|
63
62
|
Project-URL: Homepage, https://github.com/dhis2/chap-core
|
|
64
63
|
Description-Content-Type: text/markdown
|
|
65
64
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from enum import StrEnum
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
from typing import Any, Literal
|
|
3
4
|
|
|
4
5
|
import numpy as np
|
|
@@ -16,8 +17,9 @@ from geojson_pydantic import (
|
|
|
16
17
|
Point,
|
|
17
18
|
Polygon,
|
|
18
19
|
)
|
|
19
|
-
from pydantic import BaseModel, Field, model_validator
|
|
20
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
20
21
|
|
|
22
|
+
from chap_core.assessment.weather_providers import DEFAULT_WEATHER_PROVIDER_ID, resolve_weather_provider
|
|
21
23
|
from chap_core.database.base_tables import DBModel
|
|
22
24
|
|
|
23
25
|
|
|
@@ -124,6 +126,18 @@ class BacktestParams(DBModel):
|
|
|
124
126
|
gt=0,
|
|
125
127
|
description="Number of times the model is retrained, evenly spaced across the splits. 1 means train once.",
|
|
126
128
|
)
|
|
129
|
+
future_weather_provider: str = Field(
|
|
130
|
+
default=DEFAULT_WEATHER_PROVIDER_ID,
|
|
131
|
+
description="Id of the registered future-weather provider supplying climate covariates for each "
|
|
132
|
+
"forecast window. Use the same provider here and on the prediction so backtest scores reflect "
|
|
133
|
+
"what the model will see in production. See GET /v1/analytics/weather-providers.",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
@field_validator("future_weather_provider")
|
|
137
|
+
@classmethod
|
|
138
|
+
def _check_weather_provider(cls, value: str) -> str:
|
|
139
|
+
resolve_weather_provider(value)
|
|
140
|
+
return value
|
|
127
141
|
|
|
128
142
|
@model_validator(mode="after")
|
|
129
143
|
def _check_n_retrain(self) -> "BacktestParams":
|
|
@@ -209,11 +223,24 @@ class EstimatorOptions(BaseModel):
|
|
|
209
223
|
default=EstimatorMode.NORMAL,
|
|
210
224
|
description="Estimator mode: 'normal' = normal run, 'hpo' = hyperparameter optimization, 'ensemble' = ensemble learning.",
|
|
211
225
|
)
|
|
226
|
+
search_space: Path | None = Field(
|
|
227
|
+
default=None,
|
|
228
|
+
description="YAML defining the HPO search space. If omitted, hpo_search_space from model's MLProject is used.",
|
|
229
|
+
)
|
|
212
230
|
metric: str | None = Field(
|
|
213
231
|
default=None,
|
|
214
232
|
description="Metric used for HPO or ensemble. Default will be used if none provided. Ignored in normal mode.",
|
|
215
233
|
)
|
|
216
234
|
searcher: SearcherType | None = Field(
|
|
217
235
|
default=None,
|
|
218
|
-
description="Searcher used for HPO. If not provided, a default
|
|
236
|
+
description="Searcher used for HPO. If not provided, a default TPESearcher will be used. Ignored in normal and ensemble modes.",
|
|
237
|
+
)
|
|
238
|
+
max_trials: int | None = Field(
|
|
239
|
+
default=None,
|
|
240
|
+
gt=0,
|
|
241
|
+
description="Maximum HPO trials. If omitted, grid search is exhaustive while random/TPE use the default HPO trial count.",
|
|
242
|
+
)
|
|
243
|
+
seed: int | None = Field(
|
|
244
|
+
default=None,
|
|
245
|
+
description="Random seed used by stochastic search strategies.",
|
|
219
246
|
)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Database-backed facet dimensions for backtest plots.
|
|
2
|
+
|
|
3
|
+
These dimensions know how to (a) enumerate their available coordinate values with
|
|
4
|
+
cheap database queries and (b) translate a requested coordinate into SQL filters,
|
|
5
|
+
so a single subplot can be served by loading only the matching forecast rows
|
|
6
|
+
instead of flattening the entire backtest in memory.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
from sqlalchemy import false, tuple_
|
|
14
|
+
from sqlmodel import select
|
|
15
|
+
|
|
16
|
+
from chap_core.assessment.backtest_plots import FacetDimension
|
|
17
|
+
from chap_core.assessment.flat_representations import (
|
|
18
|
+
FlatForecasts,
|
|
19
|
+
FlatObserved,
|
|
20
|
+
convert_backtest_observations_to_flat_observations,
|
|
21
|
+
convert_backtest_to_flat_forecasts,
|
|
22
|
+
horizon_diff,
|
|
23
|
+
)
|
|
24
|
+
from chap_core.database.dataset_manager import DataSetManager
|
|
25
|
+
from chap_core.database.tables import BacktestForecast
|
|
26
|
+
from chap_core.plotting.backtest_plot import clean_time
|
|
27
|
+
from chap_core.time_period import TimePeriod
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from sqlalchemy.sql.elements import ColumnElement
|
|
31
|
+
from sqlmodel import Session
|
|
32
|
+
|
|
33
|
+
from chap_core.assessment.evaluation import FlatEvaluationData
|
|
34
|
+
from chap_core.database.tables import Backtest
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DBFacetDimension(FacetDimension):
|
|
38
|
+
"""A facet dimension whose coordinates and filters can be served from the database."""
|
|
39
|
+
|
|
40
|
+
def distinct_values(self, session: Session, backtest: Backtest) -> list[Any]:
|
|
41
|
+
"""Coordinate values available for this dimension, in display form."""
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
|
|
44
|
+
def forecast_filters(self, value: Any, session: Session, backtest: Backtest) -> list[ColumnElement]:
|
|
45
|
+
"""SQL clauses narrowing `BacktestForecast` to the rows for `value`."""
|
|
46
|
+
return []
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class LocationDimension(DBFacetDimension):
|
|
50
|
+
"""Facet by org unit (`location`), matching `BacktestForecast.org_unit` directly."""
|
|
51
|
+
|
|
52
|
+
def distinct_values(self, session: Session, backtest: Backtest) -> list[Any]:
|
|
53
|
+
if backtest.org_units:
|
|
54
|
+
return sorted(backtest.org_units)
|
|
55
|
+
rows = session.exec(
|
|
56
|
+
select(BacktestForecast.org_unit).where(BacktestForecast.backtest_id == backtest.id).distinct()
|
|
57
|
+
).all()
|
|
58
|
+
return sorted(rows)
|
|
59
|
+
|
|
60
|
+
def forecast_filters(self, value: Any, session: Session, backtest: Backtest) -> list[ColumnElement]:
|
|
61
|
+
return [BacktestForecast.org_unit == value]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _split_display(last_seen_period: str) -> str:
|
|
65
|
+
"""The split-period coordinate a forecast with this `last_seen_period` falls under.
|
|
66
|
+
|
|
67
|
+
Mirrors the plot's `clean_time(time_period - horizon * delta)`, which reduces to
|
|
68
|
+
`clean_time(last_seen_period - delta)`.
|
|
69
|
+
"""
|
|
70
|
+
tp = TimePeriod.parse(last_seen_period)
|
|
71
|
+
return str(clean_time((tp - tp.time_delta).to_string()))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _distinct_last_seen(session: Session, backtest: Backtest) -> list[str]:
|
|
75
|
+
return list(
|
|
76
|
+
session.exec(
|
|
77
|
+
select(BacktestForecast.last_seen_period).where(BacktestForecast.backtest_id == backtest.id).distinct()
|
|
78
|
+
).all()
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class SplitPeriodDimension(DBFacetDimension):
|
|
83
|
+
"""Facet by split period, derived from `BacktestForecast.last_seen_period`."""
|
|
84
|
+
|
|
85
|
+
def distinct_values(self, session: Session, backtest: Backtest) -> list[Any]:
|
|
86
|
+
return sorted({_split_display(ls) for ls in _distinct_last_seen(session, backtest)})
|
|
87
|
+
|
|
88
|
+
def forecast_filters(self, value: Any, session: Session, backtest: Backtest) -> list[ColumnElement]:
|
|
89
|
+
matching = [ls for ls in _distinct_last_seen(session, backtest) if _split_display(ls) == value]
|
|
90
|
+
if not matching:
|
|
91
|
+
return [false()]
|
|
92
|
+
return [BacktestForecast.last_seen_period.in_(matching)] # type: ignore[attr-defined]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _distinct_period_pairs(session: Session, backtest: Backtest) -> list[tuple[str, str]]:
|
|
96
|
+
rows = session.exec(
|
|
97
|
+
select(BacktestForecast.period, BacktestForecast.last_seen_period)
|
|
98
|
+
.where(BacktestForecast.backtest_id == backtest.id)
|
|
99
|
+
.distinct()
|
|
100
|
+
).all()
|
|
101
|
+
return [(str(period), str(last_seen)) for period, last_seen in rows]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class HorizonDistanceDimension(DBFacetDimension):
|
|
105
|
+
"""Facet by 1-based horizon distance.
|
|
106
|
+
|
|
107
|
+
Coordinates run `1..max_horizon_distance` when the backtest records it, falling
|
|
108
|
+
back to deriving distinct horizons from the forecasts for legacy rows.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def distinct_values(self, session: Session, backtest: Backtest) -> list[Any]:
|
|
112
|
+
if backtest.max_horizon_distance is not None:
|
|
113
|
+
return list(range(1, backtest.max_horizon_distance + 1))
|
|
114
|
+
horizons = {horizon_diff(period, last_seen) for period, last_seen in _distinct_period_pairs(session, backtest)}
|
|
115
|
+
return sorted(horizons)
|
|
116
|
+
|
|
117
|
+
def forecast_filters(self, value: Any, session: Session, backtest: Backtest) -> list[ColumnElement]:
|
|
118
|
+
pairs = [
|
|
119
|
+
(period, last_seen)
|
|
120
|
+
for period, last_seen in _distinct_period_pairs(session, backtest)
|
|
121
|
+
if horizon_diff(period, last_seen) == int(value)
|
|
122
|
+
]
|
|
123
|
+
if not pairs:
|
|
124
|
+
return [false()]
|
|
125
|
+
return [tuple_(BacktestForecast.period, BacktestForecast.last_seen_period).in_(pairs)] # type: ignore[arg-type]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def load_filtered_flat_data(
|
|
129
|
+
session: Session,
|
|
130
|
+
backtest: Backtest,
|
|
131
|
+
coords: dict[str, Any],
|
|
132
|
+
dimensions: list[FacetDimension],
|
|
133
|
+
) -> FlatEvaluationData:
|
|
134
|
+
"""Load only the forecast/observation rows matching `coords`, as flat frames.
|
|
135
|
+
|
|
136
|
+
Coordinates whose dimension is a `DBFacetDimension` are pushed down to SQL; any
|
|
137
|
+
other coordinate is ignored here and left to the plot's in-memory filter.
|
|
138
|
+
"""
|
|
139
|
+
from chap_core.assessment.evaluation import FlatEvaluationData
|
|
140
|
+
|
|
141
|
+
dim_by_name = {d.clean_name: d for d in dimensions if isinstance(d, DBFacetDimension)}
|
|
142
|
+
|
|
143
|
+
forecast_clauses: list[Any] = [BacktestForecast.backtest_id == backtest.id]
|
|
144
|
+
for name, value in coords.items():
|
|
145
|
+
dim = dim_by_name.get(name)
|
|
146
|
+
if dim is None:
|
|
147
|
+
continue
|
|
148
|
+
forecast_clauses += dim.forecast_filters(value, session, backtest)
|
|
149
|
+
|
|
150
|
+
forecasts = session.exec(select(BacktestForecast).where(*forecast_clauses)).all()
|
|
151
|
+
|
|
152
|
+
# Observations narrow only by org unit (any location coordinate); other dimensions
|
|
153
|
+
# filter the forecasts and rely on the plot's in-memory filter for observations.
|
|
154
|
+
org_units = [value for name, value in coords.items() if isinstance(dim_by_name.get(name), LocationDimension)]
|
|
155
|
+
observations = DataSetManager(session).observations(
|
|
156
|
+
backtest.dataset_id,
|
|
157
|
+
org_units=org_units or None,
|
|
158
|
+
feature_names=["disease_cases"],
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
forecasts_df = convert_backtest_to_flat_forecasts(list(forecasts))
|
|
162
|
+
observations_df = convert_backtest_observations_to_flat_observations(list(observations))
|
|
163
|
+
|
|
164
|
+
return FlatEvaluationData(
|
|
165
|
+
forecasts=FlatForecasts.validate(forecasts_df),
|
|
166
|
+
observations=FlatObserved.validate(observations_df),
|
|
167
|
+
)
|
|
@@ -10,6 +10,7 @@ import numpy as np
|
|
|
10
10
|
import pandas as pd
|
|
11
11
|
|
|
12
12
|
from chap_core.assessment.backtest_plots import ChartType, FacetDimension, FacetedBacktestPlot, backtest_plot
|
|
13
|
+
from chap_core.assessment.backtest_plots.db_dimensions import LocationDimension, SplitPeriodDimension
|
|
13
14
|
from chap_core.plotting.backtest_plot import clean_time
|
|
14
15
|
from chap_core.time_period import TimePeriod
|
|
15
16
|
|
|
@@ -57,8 +58,8 @@ class EvaluationPlot(FacetedBacktestPlot):
|
|
|
57
58
|
"""Shows forecasts with uncertainty bands and observed values with historical context."""
|
|
58
59
|
|
|
59
60
|
facet_dimensions: list[FacetDimension] = [
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
SplitPeriodDimension(field_name="split_period:O", display_name="Split Period"),
|
|
62
|
+
LocationDimension(field_name="location:N", display_name="Location"),
|
|
62
63
|
]
|
|
63
64
|
|
|
64
65
|
def _preprocess(
|
{chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/backtest_plots/horizon_location_grid.py
RENAMED
|
@@ -9,7 +9,8 @@ line charts stacked below.
|
|
|
9
9
|
import altair as alt
|
|
10
10
|
import pandas as pd
|
|
11
11
|
|
|
12
|
-
from chap_core.assessment.backtest_plots import
|
|
12
|
+
from chap_core.assessment.backtest_plots import ChartType, FacetDimension, FacetedBacktestPlot, backtest_plot
|
|
13
|
+
from chap_core.assessment.backtest_plots.db_dimensions import HorizonDistanceDimension, LocationDimension
|
|
13
14
|
from chap_core.assessment.backtest_plots.evaluation_plot import _compute_quantiles_from_forecasts
|
|
14
15
|
from chap_core.assessment.flat_representations import FlatForecasts, FlatObserved
|
|
15
16
|
from chap_core.assessment.metrics.crps import CRPSLog1pMetric, CRPSMetric
|
|
@@ -22,13 +23,13 @@ CELL_HEIGHT = 150
|
|
|
22
23
|
METRIC_HEIGHT = 60
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
def _empty_placeholder(width: int, height: int
|
|
26
|
-
"""Return an empty chart placeholder that won't trip
|
|
26
|
+
def _empty_placeholder(width: int, height: int) -> ChartType:
|
|
27
|
+
"""Return an empty chart placeholder that won't trip Altair/Vega type checks."""
|
|
27
28
|
return ( # type: ignore[no-any-return]
|
|
28
29
|
alt.Chart(pd.DataFrame({"x": [0]}))
|
|
29
30
|
.mark_point(opacity=0)
|
|
30
31
|
.encode(x=alt.value(0), y=alt.value(0))
|
|
31
|
-
.properties(width=width, height=height
|
|
32
|
+
.properties(width=width, height=height)
|
|
32
33
|
)
|
|
33
34
|
|
|
34
35
|
|
|
@@ -48,7 +49,7 @@ def _build_forecast_cell(
|
|
|
48
49
|
obs = obs[obs["time_period"].isin(fq["time_period"])]
|
|
49
50
|
|
|
50
51
|
if fq.empty:
|
|
51
|
-
return _empty_placeholder(CELL_WIDTH, CELL_HEIGHT
|
|
52
|
+
return _empty_placeholder(CELL_WIDTH, CELL_HEIGHT)
|
|
52
53
|
|
|
53
54
|
base_forecast = alt.Chart(fq)
|
|
54
55
|
|
|
@@ -70,7 +71,7 @@ def _build_forecast_cell(
|
|
|
70
71
|
layers = error_10_90 + error_25_75 + median_line
|
|
71
72
|
|
|
72
73
|
# Only add observations layer if there is data; an empty DataFrame with
|
|
73
|
-
# object-typed columns causes
|
|
74
|
+
# object-typed columns causes Altair/Vega to fail on temporal encoding.
|
|
74
75
|
if not obs.empty:
|
|
75
76
|
obs_line = (
|
|
76
77
|
alt.Chart(obs)
|
|
@@ -82,9 +83,7 @@ def _build_forecast_cell(
|
|
|
82
83
|
)
|
|
83
84
|
layers = layers + obs_line
|
|
84
85
|
|
|
85
|
-
return layers.properties( # type: ignore[no-any-return]
|
|
86
|
-
width=CELL_WIDTH, height=CELL_HEIGHT, title=f"{location} | horizon {horizon}"
|
|
87
|
-
)
|
|
86
|
+
return layers.properties(width=CELL_WIDTH, height=CELL_HEIGHT) # type: ignore[no-any-return]
|
|
88
87
|
|
|
89
88
|
|
|
90
89
|
def _build_metric_chart(
|
|
@@ -152,68 +151,136 @@ def _build_summary_table(
|
|
|
152
151
|
)
|
|
153
152
|
|
|
154
153
|
|
|
154
|
+
def _metric_instances(historical_observations: pd.DataFrame | None) -> list:
|
|
155
|
+
"""The metrics shown in each grid cell, in display order."""
|
|
156
|
+
return [
|
|
157
|
+
CRPSMetric(historical_observations=historical_observations),
|
|
158
|
+
CRPSLog1pMetric(historical_observations=historical_observations),
|
|
159
|
+
WinklerScore10_90Metric(historical_observations=historical_observations),
|
|
160
|
+
WinklerScore10_90Log1pMetric(historical_observations=historical_observations),
|
|
161
|
+
OutbreakAccuracyMetric(historical_observations=historical_observations),
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _metric_dfs_from_preprocessed(df: pd.DataFrame) -> list[tuple[str, pd.DataFrame]]:
|
|
166
|
+
"""Recover the per-metric frames from the packed preprocessed frame, in order."""
|
|
167
|
+
metric_rows = df[df["role"] == "metric"]
|
|
168
|
+
return [(str(name), group) for name, group in metric_rows.groupby("metric_name", sort=False)]
|
|
169
|
+
|
|
170
|
+
|
|
155
171
|
@backtest_plot(
|
|
156
172
|
plot_id="horizon_location_grid",
|
|
157
173
|
name="Forecast Grid (Locations x Horizons)",
|
|
158
174
|
description="Grid of forecast intervals and metrics across locations and forecast horizons.",
|
|
159
175
|
needs_historical=True,
|
|
160
176
|
)
|
|
161
|
-
class HorizonLocationGridPlot(
|
|
162
|
-
"""Grid plot with locations as rows and forecast horizons as columns.
|
|
177
|
+
class HorizonLocationGridPlot(FacetedBacktestPlot):
|
|
178
|
+
"""Grid plot with locations as rows and forecast horizons as columns.
|
|
179
|
+
|
|
180
|
+
Each cell is a composite (forecast band + observed + per-metric line charts),
|
|
181
|
+
so the all-cells view (`get_full_plot`) keeps the hand-built grid, while
|
|
182
|
+
`facet_coords` / `get_subplot` expose a single location x horizon cell for the
|
|
183
|
+
database-backed faceting workflow.
|
|
184
|
+
"""
|
|
163
185
|
|
|
164
|
-
|
|
186
|
+
facet_dimensions: list[FacetDimension] = [
|
|
187
|
+
HorizonDistanceDimension(field_name="horizon_distance:O", display_name="Horizon Distance"),
|
|
188
|
+
LocationDimension(field_name="location:N", display_name="Location"),
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
def _preprocess(
|
|
165
192
|
self,
|
|
166
193
|
observations: pd.DataFrame,
|
|
167
194
|
forecasts: pd.DataFrame,
|
|
168
195
|
historical_observations: pd.DataFrame | None = None,
|
|
169
|
-
) ->
|
|
170
|
-
|
|
171
|
-
|
|
196
|
+
) -> pd.DataFrame:
|
|
197
|
+
"""Pack forecast quantiles, observed values and metric series into one frame.
|
|
198
|
+
|
|
199
|
+
Each row carries `location` + `horizon_distance` (observations replicated
|
|
200
|
+
across horizons) so the generic coordinate filter can slice to one cell, plus
|
|
201
|
+
a `role` discriminator the cell renderer splits back out.
|
|
202
|
+
"""
|
|
203
|
+
# No forecasts for this coordinate (e.g. a location x horizon cell with no
|
|
204
|
+
# data): skip metric computation and let the cell render as a placeholder.
|
|
205
|
+
if forecasts.empty:
|
|
206
|
+
return pd.DataFrame(columns=["location", "time_period", "horizon_distance", "role"])
|
|
207
|
+
|
|
208
|
+
flat_obs = FlatObserved.validate(observations)
|
|
209
|
+
flat_fc = FlatForecasts.validate(forecasts)
|
|
210
|
+
horizons = sorted(forecasts["horizon_distance"].unique())
|
|
172
211
|
|
|
173
|
-
# Compute quantiles for forecast cells
|
|
174
212
|
forecast_quantiles = _compute_quantiles_from_forecasts(forecasts)
|
|
175
213
|
forecast_quantiles["time_period"] = forecast_quantiles["time_period"].apply(clean_time)
|
|
214
|
+
forecast_quantiles["role"] = "forecast"
|
|
176
215
|
|
|
177
216
|
obs_df = observations.copy()
|
|
178
217
|
obs_df["time_period"] = obs_df["time_period"].apply(clean_time)
|
|
218
|
+
# Observations have no horizon; replicate across horizons so the cell filter keeps them.
|
|
219
|
+
obs_df = obs_df.merge(pd.DataFrame({"horizon_distance": horizons}), how="cross")
|
|
220
|
+
obs_df["role"] = "observed"
|
|
179
221
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
# Compute metrics
|
|
184
|
-
metric_instances = [
|
|
185
|
-
CRPSMetric(historical_observations=historical_observations),
|
|
186
|
-
CRPSLog1pMetric(historical_observations=historical_observations),
|
|
187
|
-
WinklerScore10_90Metric(historical_observations=historical_observations),
|
|
188
|
-
WinklerScore10_90Log1pMetric(historical_observations=historical_observations),
|
|
189
|
-
OutbreakAccuracyMetric(historical_observations=historical_observations),
|
|
190
|
-
]
|
|
191
|
-
|
|
192
|
-
metric_dfs: list[tuple[str, pd.DataFrame]] = []
|
|
193
|
-
for m in metric_instances:
|
|
222
|
+
frames: list[pd.DataFrame] = [forecast_quantiles, obs_df]
|
|
223
|
+
for m in _metric_instances(historical_observations):
|
|
194
224
|
if not m.is_applicable(flat_obs):
|
|
195
225
|
continue
|
|
196
226
|
detailed = m.get_detailed_metric(flat_obs, flat_fc)
|
|
197
227
|
detailed["time_period"] = detailed["time_period"].apply(clean_time)
|
|
198
|
-
|
|
228
|
+
detailed["role"] = "metric"
|
|
229
|
+
detailed["metric_name"] = m.get_name()
|
|
230
|
+
frames.append(detailed)
|
|
231
|
+
|
|
232
|
+
return pd.concat(frames, ignore_index=True)
|
|
233
|
+
|
|
234
|
+
def _plot(self, df: pd.DataFrame) -> ChartType:
|
|
235
|
+
"""Render one location x horizon cell from the packed frame."""
|
|
236
|
+
if df.empty:
|
|
237
|
+
return _empty_placeholder(CELL_WIDTH, CELL_HEIGHT)
|
|
238
|
+
|
|
239
|
+
forecast_rows = df[df["role"] == "forecast"]
|
|
240
|
+
observed_rows = df[df["role"] == "observed"]
|
|
241
|
+
key_src = forecast_rows if not forecast_rows.empty else df
|
|
242
|
+
location = key_src["location"].iloc[0]
|
|
243
|
+
horizon = int(key_src["horizon_distance"].iloc[0])
|
|
244
|
+
|
|
245
|
+
cell_parts: list[ChartType] = [_build_forecast_cell(forecast_rows, observed_rows, location, horizon)]
|
|
246
|
+
for metric_name, mdf in _metric_dfs_from_preprocessed(df):
|
|
247
|
+
cell_parts.append(_build_metric_chart(mdf, location, horizon, metric_name))
|
|
248
|
+
|
|
249
|
+
return alt.vconcat(*cell_parts).properties(spacing=2) # type: ignore[no-any-return]
|
|
250
|
+
|
|
251
|
+
def get_full_plot(
|
|
252
|
+
self,
|
|
253
|
+
observations: pd.DataFrame,
|
|
254
|
+
forecasts: pd.DataFrame,
|
|
255
|
+
historical_observations: pd.DataFrame | None = None,
|
|
256
|
+
) -> ChartType:
|
|
257
|
+
"""The full grid: composite cells (locations as rows, horizons as columns) plus a summary table.
|
|
258
|
+
|
|
259
|
+
Overrides the base native-faceting implementation, which cannot tile the
|
|
260
|
+
composite (vconcat) cells this plot renders.
|
|
261
|
+
"""
|
|
262
|
+
df = self._preprocess(observations, forecasts, historical_observations)
|
|
263
|
+
locations = sorted(forecasts["location"].unique())
|
|
264
|
+
horizons = sorted(forecasts["horizon_distance"].unique())
|
|
199
265
|
|
|
200
|
-
# Build grid: rows = locations, columns = horizons
|
|
201
266
|
location_rows: list[ChartType] = []
|
|
202
267
|
for loc in locations:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
268
|
+
# The grid layout is the only thing identifying a cell, so label each
|
|
269
|
+
# one here; the single-cell subplot view leaves identification to the
|
|
270
|
+
# frontend's facet selection.
|
|
271
|
+
horizon_cells = [
|
|
272
|
+
self._plot(df[(df["location"] == loc) & (df["horizon_distance"] == hz)]).properties(
|
|
273
|
+
title=f"{loc} | horizon {hz}"
|
|
274
|
+
)
|
|
275
|
+
for hz in horizons
|
|
276
|
+
]
|
|
209
277
|
location_rows.append(alt.hconcat(*horizon_cells).properties(spacing=10))
|
|
210
278
|
|
|
211
|
-
|
|
279
|
+
metric_dfs = _metric_dfs_from_preprocessed(df)
|
|
212
280
|
if metric_dfs:
|
|
213
|
-
|
|
214
|
-
location_rows.append(summary)
|
|
281
|
+
location_rows.append(_build_summary_table(metric_dfs, horizons))
|
|
215
282
|
|
|
216
|
-
return alt.vconcat(*location_rows).properties( # type: ignore[return
|
|
283
|
+
return alt.vconcat(*location_rows).properties( # type: ignore[no-any-return]
|
|
217
284
|
spacing=15,
|
|
218
285
|
title="Forecast Grid: Locations x Horizons",
|
|
219
286
|
)
|
|
@@ -8,10 +8,10 @@ over- and under-prediction at high case counts immediately visible.
|
|
|
8
8
|
|
|
9
9
|
import pandas as pd
|
|
10
10
|
|
|
11
|
-
from chap_core.assessment.backtest_plots import
|
|
11
|
+
from chap_core.assessment.backtest_plots import ChartType, backtest_plot
|
|
12
12
|
from chap_core.assessment.backtest_plots.predicted_vs_actual_plot import (
|
|
13
|
+
PredictedVsActualPlot,
|
|
13
14
|
build_predicted_vs_actual_chart,
|
|
14
|
-
median_forecasts_joined_with_observations,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
|
|
@@ -20,16 +20,16 @@ from chap_core.assessment.backtest_plots.predicted_vs_actual_plot import (
|
|
|
20
20
|
name="Predicted vs Actual (linear)",
|
|
21
21
|
description="Scatter plots of predicted (median) vs actual values on linear axes, faceted by horizon and colored by location.",
|
|
22
22
|
)
|
|
23
|
-
class PredictedVsActualLinearPlot(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
class PredictedVsActualLinearPlot(PredictedVsActualPlot):
|
|
24
|
+
"""Linear-axes variant of :class:`PredictedVsActualPlot`.
|
|
25
|
+
|
|
26
|
+
Inherits the horizon faceting and the median-vs-observed preprocessing; only
|
|
27
|
+
the per-facet chart differs (raw values instead of log1p-transformed axes).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def _plot(self, data: pd.DataFrame) -> ChartType:
|
|
31
31
|
return build_predicted_vs_actual_chart(
|
|
32
|
-
|
|
32
|
+
data,
|
|
33
33
|
x_field="median_forecast",
|
|
34
34
|
y_field="disease_cases",
|
|
35
35
|
title="Predicted vs Actual (linear scale)",
|
{chap_core-2.0.0 → chap_core-2.2.0}/chap_core/assessment/backtest_plots/predicted_vs_actual_plot.py
RENAMED
|
@@ -10,6 +10,7 @@ import numpy as np
|
|
|
10
10
|
import pandas as pd
|
|
11
11
|
|
|
12
12
|
from chap_core.assessment.backtest_plots import ChartType, FacetDimension, FacetedBacktestPlot, backtest_plot
|
|
13
|
+
from chap_core.assessment.backtest_plots.db_dimensions import HorizonDistanceDimension
|
|
13
14
|
from chap_core.plotting.backtest_plot import clean_time
|
|
14
15
|
|
|
15
16
|
|
|
@@ -93,7 +94,7 @@ def build_predicted_vs_actual_chart(
|
|
|
93
94
|
)
|
|
94
95
|
class PredictedVsActualPlot(FacetedBacktestPlot):
|
|
95
96
|
facet_dimensions: list[FacetDimension] = [
|
|
96
|
-
|
|
97
|
+
HorizonDistanceDimension(field_name="horizon_distance:O", display_name="Horizon Distance"),
|
|
97
98
|
]
|
|
98
99
|
|
|
99
100
|
resolve_scale_x = "shared"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sample bias plots for backtests.
|
|
3
|
+
|
|
4
|
+
This module provides backtest plots that show the ratio of forecast samples
|
|
5
|
+
above truth, one by horizon distance and one by time period.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import altair as alt
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from chap_core.assessment.backtest_plots import ChartType, FacetDimension, FacetedBacktestPlot, backtest_plot
|
|
12
|
+
from chap_core.assessment.flat_representations import FlatForecasts, FlatObserved
|
|
13
|
+
from chap_core.assessment.metrics import RatioAboveTruthMetric
|
|
14
|
+
|
|
15
|
+
_BIAS_DESCRIPTION = (
|
|
16
|
+
"Shows biases in the samples returned by the model, specifically whether the samples "
|
|
17
|
+
"generally are over or under the true observation. This can be used to assess model "
|
|
18
|
+
"calibration and tendency to over- or under-predict."
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SampleBiasPlotBase(FacetedBacktestPlot):
|
|
23
|
+
"""
|
|
24
|
+
Shared preprocessing for the sample bias plots.
|
|
25
|
+
|
|
26
|
+
Computes the ratio of forecast samples that are above the true observation
|
|
27
|
+
value per forecast row. The plots are aggregates with no per-coordinate
|
|
28
|
+
decomposition, so they declare no facet dimensions; they conform to the
|
|
29
|
+
faceting interface only so every registered plot shares the same shape.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
facet_dimensions: list[FacetDimension] = []
|
|
33
|
+
|
|
34
|
+
def _preprocess(
|
|
35
|
+
self,
|
|
36
|
+
observations: pd.DataFrame,
|
|
37
|
+
forecasts: pd.DataFrame,
|
|
38
|
+
historical_observations: pd.DataFrame | None = None,
|
|
39
|
+
) -> pd.DataFrame:
|
|
40
|
+
"""Compute the ratio-of-samples-above-truth metric per forecast row."""
|
|
41
|
+
flat_observations = FlatObserved.validate(observations)
|
|
42
|
+
flat_forecasts = FlatForecasts.validate(forecasts)
|
|
43
|
+
metric = RatioAboveTruthMetric()
|
|
44
|
+
return metric.get_detailed_metric(flat_observations, flat_forecasts)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@backtest_plot(
|
|
48
|
+
plot_id="sample_bias_by_horizon",
|
|
49
|
+
name="Sample Bias by Horizon",
|
|
50
|
+
description=f"Ratio of forecast samples above truth per forecast horizon. {_BIAS_DESCRIPTION}",
|
|
51
|
+
)
|
|
52
|
+
class SampleBiasByHorizonPlot(SampleBiasPlotBase):
|
|
53
|
+
"""Bar chart of the mean ratio of samples above truth per forecast horizon."""
|
|
54
|
+
|
|
55
|
+
def _plot(self, metric_df: pd.DataFrame) -> ChartType:
|
|
56
|
+
horizon_df = metric_df.groupby(["horizon_distance"]).agg({"metric": "mean"}).reset_index()
|
|
57
|
+
return ( # type: ignore[no-any-return]
|
|
58
|
+
alt.Chart(horizon_df)
|
|
59
|
+
.mark_bar()
|
|
60
|
+
.encode(
|
|
61
|
+
x=alt.X("horizon_distance:O", title="Horizon (periods ahead)"),
|
|
62
|
+
y=alt.Y("metric:Q", title="Ratio of samples above truth"),
|
|
63
|
+
tooltip=["horizon_distance", "metric"],
|
|
64
|
+
)
|
|
65
|
+
.properties(height=300)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@backtest_plot(
|
|
70
|
+
plot_id="sample_bias_by_time_period",
|
|
71
|
+
name="Sample Bias by Time Period",
|
|
72
|
+
description=(
|
|
73
|
+
f"Ratio of forecast samples above truth over time periods, one line per location. {_BIAS_DESCRIPTION}"
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
class SampleBiasByTimePeriodPlot(SampleBiasPlotBase):
|
|
77
|
+
"""Line chart of the mean ratio of samples above truth per time period and location."""
|
|
78
|
+
|
|
79
|
+
def _plot(self, metric_df: pd.DataFrame) -> ChartType:
|
|
80
|
+
time_df = metric_df.groupby(["time_period", "location"]).agg({"metric": "mean"}).reset_index()
|
|
81
|
+
return ( # type: ignore[no-any-return]
|
|
82
|
+
alt.Chart(time_df)
|
|
83
|
+
.mark_line(point=True)
|
|
84
|
+
.encode(
|
|
85
|
+
x=alt.X("time_period:O", title="Time period"),
|
|
86
|
+
y=alt.Y("metric:Q", title="Ratio of samples above truth"),
|
|
87
|
+
color=alt.Color("location:N", title="Location"),
|
|
88
|
+
tooltip=["time_period", "location", "metric"],
|
|
89
|
+
)
|
|
90
|
+
.properties(height=300)
|
|
91
|
+
)
|