diff --git a/.streamlit/secrets.toml b/.streamlit/secrets.toml index e356f22..78245c7 100644 --- a/.streamlit/secrets.toml +++ b/.streamlit/secrets.toml @@ -1,4 +1,4 @@ -auth_type='oidc' +auth_type='simple' user='admin' password='password' diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7603d4f --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +#!make +include .env + +build: + docker build -f oasisui_st_app.Dockerfile . -t ${PYTHONUI_IMG}:${VERS_UI} + +push: + docker push ${PYTHONUI_IMG}:${VERS_UI} + +scenarios_portfolios: + python ./scripts/add_test_portfolios.py -c ./scenarios/portfolios.json + +scenarios_settings: + python ./scripts/add_settings_template.py -c ./scenarios/a_settings.json + +scenarios: scenarios_portfolios scenarios_settings + +build_and_push: build push diff --git a/README.md b/README.md index aa06eb1..e2de835 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,16 @@ echo "127.0.0.1 ui.oasis.local" | sudo tee -a /etc/hosts ./install.sh ``` -The installer clones the PiWind demo model, processes OIDC templates (if -applicable), builds the UI image, and starts all services. It will prompt -before redeploying if a previous installation is detected. +The installer stages the model data for the selected model set, processes OIDC +templates (if applicable), pulls the images, and starts all services. It will +prompt before redeploying if a previous installation is detected. + +| Option | Description | +|--------|-------------| +| `-m`, `--model-set ` | Model set to deploy. Defaults to `MODEL_SET` from `.env`, or `piwind`. | +| `--build-ui` | Build the UI image locally instead of pulling `PYTHONUI_IMG:VERS_UI`. | +| `-u`, `--uninstall` | Bring the stack down and delete its volumes. | +| `-h`, `--help` | Show usage. | To tear everything down (removes containers and volumes): @@ -104,19 +111,82 @@ All services are reachable on port 80 via Traefik after a successful install: ## Docker Compose Architecture -The stack is assembled from multiple Compose files depending on auth type: +`install.sh` layers several Compose files into a single `docker compose` +command. The model workers live in a file of their own, so the core platform, +the UI and the auth stack stay model-agnostic: ``` Always loaded: - docker-compose.yml # Core platform: server, worker, databases, broker - docker-compose.ui.yml # Streamlit UI + Traefik reverse proxy + docker-compose.yml # Core platform: Traefik, server, websocket, databases, broker, filestore + docker-compose.models..yml # Model workers and model registration for one model set + docker-compose.ui.yml # Streamlit UI Conditionally loaded: - docker-compose.keycloak.yml # Keycloak + its PostgreSQL DB (API_AUTH_TYPE=keycloak) + docker-compose.keycloak.yml # Keycloak + its PostgreSQL DB (API_AUTH_TYPE=keycloak) docker-compose.authentik.yml # Authentik + its PostgreSQL DB (API_AUTH_TYPE=authentik) ``` -`install.sh` builds the correct `docker compose -f ... up` command automatically. +### Model sets — `docker-compose.models..yml` + +`docker-compose.yml` defines no model workers at all. Everything +model-specific is confined to a `docker-compose.models..yml` file: + +- one worker service per model, carrying its `OASIS_MODEL_SUPPLIER_ID`, + `OASIS_MODEL_ID`, `OASIS_MODEL_VERSION_ID` and `OASIS_RUN_MODE` +- the volume mount that supplies that model's data +- any `model-registration` job needed to register the models with the API + +Exactly one model set is loaded per deployment. Pick it with `-m/--model-set`, +or set `MODEL_SET` in `.env` so a bare `./install.sh` deploys it: + +```bash +./install.sh -m piwind # default: the PiWind demo model +./install.sh -m scenarios # PiWind plus the public scenario models +``` + +Model sets shipped in this repository: + +| File | Contents | +|------|----------| +| `docker-compose.models.piwind.yml` | A `model-registration` one-shot and `piwind-worker`, with model data from `./OasisPiWind/`. | +| `docker-compose.models.scenarios.yml` | `piwind-worker` plus the scenario workers (Impact Forecasting, JBA, ARA, IPE), with model data from `${SCENARIOS_PATH}`. | + +Because the split is by filename, adding a model set means dropping two new +files into the root directory — no edits to `docker-compose.yml`, `install.sh` +or the auth files: + +1. Write `docker-compose.models..yml` with the worker services. The + workers join the core stack, so they can depend on `server`, `celery-db` and + `broker` and mount the shared `filestore-data` volume directly. +2. Optionally add `get-.sh` to fetch the model data (see below). +3. Deploy with `./install.sh -m `. + +`install.sh --uninstall` passes *every* `docker-compose.models.*.yml` to +`docker compose down`, so the model workers are torn down whichever set was +deployed. + +#### Model data — `get-.sh` + +The Compose file describes how a model runs; the matching `get-.sh` +puts its data on disk. Before bringing the stack up, `install.sh` runs +`get-$MODEL_SET.sh` if it exists, and otherwise assumes the data is already in +place. + +| Script | What it does | +|--------|--------------| +| `get-piwind.sh` | Clones `OasisLMF/OasisPiWind` at `VERS_PIWIND` into `./OasisPiWind/`. No-ops once cloned. | +| `get-scenarios.sh` | Runs `get-piwind.sh`, clones `OasisLMF/Scenarios` into `$SCENARIOS_PATH`, then runs that repository's `get_s3_data_reduced.sh` to download the model files. Each step is skipped if it has already been done. | + +The scripts are idempotent and safe to run on their own, which is the easy way +to pre-stage model data on a server before deploying, or to refresh it without +a redeploy: + +```bash +SCENARIOS_PATH=/home/ubuntu/Scenarios ./get-scenarios.sh +``` + +When writing your own, keep it re-runnable: `install.sh` calls it on every +deploy, including redeploys over an existing installation. ## Key Environment Variables @@ -131,6 +201,25 @@ Conditionally loaded: See the `.env.*` templates for the full list with inline comments. +### Deployment overrides + +These variables decide *what* gets deployed and *what the UI shows*, rather +than how the platform is wired together. They are read straight from `.env` by +`install.sh` and the Compose files, and each falls back to the layout in this +repository — so a plain checkout still deploys unchanged with none of them set. +They are not in the `.env.*` templates; add the ones a deployment needs. + +| Variable | Default | Purpose | +|----------|---------|---------| +| `MODEL_SET` | `piwind` | Which `docker-compose.models..yml` / `get-.sh` pair to deploy. `-m/--model-set` overrides it for one run. | +| `SCENARIOS_PATH` | *(unset)* | Host directory holding the scenario model data. Required by the `scenarios` model set — both `get-scenarios.sh` and the worker mounts read it. | +| `UI_CONFIG` | `./ui-config.json` | The UI's config file: pages, post-login page, model-to-exposure map, footer, and `skip_login`. | +| `UI_DEFAULTS` | `./defaults/` | Per-model default analysis settings the UI pre-fills when creating an analysis. | +| `UI_ASSETS` | `./ui_assets/` | Files `UI_CONFIG` refers to as `ui_assets/` — footer text, logos, anything a deployment supplies. | +| `UI_STREAMLIT` | `./.streamlit/` | The Streamlit directory mounted read-only: `secrets.toml` and `config.toml`. | +| `PYTHONUI_IMG` / `VERS_UI` | `coreoasis/oasispythonui_app` / `latest` | UI image pulled at deploy time, and the tag `make build` / `make push` produce. | + + ## Adding Users ### Simple auth @@ -166,6 +255,10 @@ Usually first thing to try before anything is clearing browser cache/cookies for - The IdP database container must be healthy first: `docker compose ps`. - First startup can take 2–3 minutes while blueprints and realms are imported. +**`install.sh` exits with `no model set ''`** +- The model set has no `docker-compose.models..yml` in the root directory. + Check the spelling of `-m/--model-set` or `MODEL_SET`. + **Logs and status** ```bash docker compose ps # service health diff --git a/defaults/10_ghana-e760461_jba-analysis_settings.json b/defaults/10_ghana-e760461_jba-analysis_settings.json deleted file mode 100644 index 176a163..0000000 --- a/defaults/10_ghana-e760461_jba-analysis_settings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "gul_output": true, - "gul_summaries": [ - { - "aalcalc": true, - "eltcalc": true, - "id": 1, - "return_period_file": false, - "lec_output": false, - "leccalc": { - "full_uncertainty_aep": true, - "full_uncertainty_oep": true, - "return_period_file": true - } - } - ], - "gul_threshold": 0, - "il_output": false, - "model_settings": { - }, - "model_name_id": "Ghana-E760461", - "model_supplier_id": "JBA", - "number_of_samples": 10 -} diff --git a/defaults/11_nepal-e151185_jba-analysis_settings.json b/defaults/11_nepal-e151185_jba-analysis_settings.json deleted file mode 100644 index fef0975..0000000 --- a/defaults/11_nepal-e151185_jba-analysis_settings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "gul_output": true, - "gul_summaries": [ - { - "aalcalc": true, - "eltcalc": true, - "id": 1, - "return_period_file": false, - "lec_output": false, - "leccalc": { - "full_uncertainty_aep": true, - "full_uncertainty_oep": true, - "return_period_file": true - } - } - ], - "gul_threshold": 0, - "il_output": false, - "model_settings": { - }, - "model_name_id": "Nepal-E151185", - "model_supplier_id": "JBA", - "number_of_samples": 10 -} diff --git a/defaults/12_nepal-e432557_jba-analysis_settings.json b/defaults/12_nepal-e432557_jba-analysis_settings.json deleted file mode 100644 index 55adbc0..0000000 --- a/defaults/12_nepal-e432557_jba-analysis_settings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "gul_output": true, - "gul_summaries": [ - { - "aalcalc": true, - "eltcalc": true, - "id": 1, - "return_period_file": false, - "lec_output": false, - "leccalc": { - "full_uncertainty_aep": true, - "full_uncertainty_oep": true, - "return_period_file": true - } - } - ], - "gul_threshold": 0, - "il_output": false, - "model_settings": { - }, - "model_name_id": "Nepal-E432557", - "model_supplier_id": "JBA", - "number_of_samples": 10 -} diff --git a/defaults/13_nepal-e505423_jba-analysis_settings.json b/defaults/13_nepal-e505423_jba-analysis_settings.json deleted file mode 100644 index 8120c0e..0000000 --- a/defaults/13_nepal-e505423_jba-analysis_settings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "gul_output": true, - "gul_summaries": [ - { - "aalcalc": true, - "eltcalc": true, - "id": 1, - "return_period_file": false, - "lec_output": false, - "leccalc": { - "full_uncertainty_aep": true, - "full_uncertainty_oep": true, - "return_period_file": true - } - } - ], - "gul_threshold": 0, - "il_output": false, - "model_settings": { - }, - "model_name_id": "Nepal-E505423", - "model_supplier_id": "JBA", - "number_of_samples": 10 -} diff --git a/defaults/14_france-hail_ipe-analysis_settings.json b/defaults/14_france-hail_ipe-analysis_settings.json deleted file mode 100644 index eda5db7..0000000 --- a/defaults/14_france-hail_ipe-analysis_settings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "gul_output": true, - "gul_summaries": [ - { - "aalcalc": true, - "eltcalc": true, - "id": 1, - "return_period_file": false, - "lec_output": false, - "leccalc": { - "full_uncertainty_aep": true, - "full_uncertainty_oep": true, - "return_period_file": true - } - } - ], - "gul_threshold": 0, - "il_output": false, - "model_settings": { - }, - "model_name_id": "France-Hail", - "model_supplier_id": "IPE", - "number_of_samples": 10 -} diff --git a/defaults/2_maeq_impact-forecasting-analysis_settings.json b/defaults/2_maeq_impact-forecasting-analysis_settings.json deleted file mode 100644 index cd41e90..0000000 --- a/defaults/2_maeq_impact-forecasting-analysis_settings.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - - "source_tag": "MDK", - "analysis_tag": 1, - "model_name_id": "maeq", - "model_supplier_id": "ImpactForecasting", - "number_of_samples": 100, - "model_settings": { - "event_set": "" - }, - "gul_output": true, - "gul_summaries": [ - { - "id": 1, - "summarycalc": false, - "aalcalc": false, - "eltcalc": true, - "lec_output": false, - "leccalc": { - "outputs": { - "full_uncertainty_aep": false, - "full_uncertainty_oep": false, - "sample_mean_aep": false, - "sample_mean_oep": false - }, - "return_period_file": false - } - } - ], - "gul_threshold": 0 - -} diff --git a/defaults/3_euws_impact-forecasting-analysis_settings.json b/defaults/3_euws_impact-forecasting-analysis_settings.json deleted file mode 100644 index 60a36b3..0000000 --- a/defaults/3_euws_impact-forecasting-analysis_settings.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - - "model_supplier_id": "ImpactForecasting", - "model_name_id": "EUWS", - "number_of_samples": 10, - "model_settings": { - "event_set": "ciaran" - }, - "gul_output": true, - "gul_summaries": [ - { - "id": 1, - "summarycalc": false, - "aalcalc": false, - "eltcalc": true, - "lec_output": false, - "leccalc": { - "outputs": { - "full_uncertainty_aep": false, - "full_uncertainty_oep": false, - "sample_mean_aep": false, - "sample_mean_oep": false - }, - "return_period_file": false - } - } - ], - "gul_threshold": 0 -} diff --git a/defaults/4_treq_impact-forecasting-analysis_settings.json b/defaults/4_treq_impact-forecasting-analysis_settings.json deleted file mode 100644 index 7fe9c2b..0000000 --- a/defaults/4_treq_impact-forecasting-analysis_settings.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - - "source_tag": "MDK", - "analysis_tag": 1, - "model_version_id": "0.0.0.1", - "model_name_id": "treq", - "module_supplier_id": "ImpactForecasting", - "number_of_samples": 10, - "model_settings": { - "event_set": "", - "peril_wind": true - }, - "gul_output": true, - "gul_summaries": [ - { - "id": 1, - "summarycalc": false, - "aalcalc": false, - "eltcalc": true, - "lec_output": false, - "leccalc": { - "outputs": { - "full_uncertainty_aep": false, - "full_uncertainty_oep": false, - "sample_mean_aep": false, - "sample_mean_oep": false - }, - "return_period_file": false - } - } - ], - "gul_threshold": 0 - -} diff --git a/defaults/6_philippines-taiwan-undefended_jba-analysis_settings.json b/defaults/6_philippines-taiwan-undefended_jba-analysis_settings.json deleted file mode 100644 index ef83b2a..0000000 --- a/defaults/6_philippines-taiwan-undefended_jba-analysis_settings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "gul_output": true, - "gul_summaries": [ - { - "aalcalc": true, - "eltcalc": true, - "id": 1, - "return_period_file": false, - "lec_output": false, - "leccalc": { - "full_uncertainty_aep": true, - "full_uncertainty_oep": true, - "return_period_file": true - } - } - ], - "gul_threshold": 0, - "il_output": false, - "model_settings": { - }, - "model_name_id": "PHTY-U", - "model_supplier_id": "JBA", - "number_of_samples": 10 -} diff --git a/defaults/7_us-hurricane_ara-analysis_settings.json b/defaults/7_us-hurricane_ara-analysis_settings.json deleted file mode 100644 index 6fb2e39..0000000 --- a/defaults/7_us-hurricane_ara-analysis_settings.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "gul_output": true, - "gul_summaries": [ - { - "aalcalc": true, - "eltcalc": true, - "id": 1, - "ord_output": { - "ept_per_sample_mean_aep": true - }, - "return_period_file": true - - } - ], - "gul_threshold": 0, - "il_output": false, - "il_summaries": [ - { - "aalcalc": true, - "eltcalc": true, - "id": 1, - "lec_output": true, - "leccalc": { - "outputs": { - "full_uncertainty_aep": true, - "full_uncertainty_oep": true - }, - "return_period_file": true - } - } - ], - "gul_threshold": 0, - "model_settings": { - }, - "model_name_id": "US_Hurricane", - "model_supplier_id": "ARA", - "number_of_samples": 10 -} diff --git a/defaults/8_ghana-e635900_jba-analysis_settings.json b/defaults/8_ghana-e635900_jba-analysis_settings.json deleted file mode 100644 index 9e2c117..0000000 --- a/defaults/8_ghana-e635900_jba-analysis_settings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "model_name_id": "Ghana-E635900", - "model_supplier_id": "JBA", - "gul_output": true, - "gul_summaries": [ - { - "aalcalc": true, - "eltcalc": true, - "id": 1, - "return_period_file": false, - "lec_output": false, - "leccalc": { - "full_uncertainty_aep": true, - "full_uncertainty_oep": true, - "return_period_file": true - } - } - ], - "gul_threshold": 0, - "il_output": false, - "model_settings": { - }, - "number_of_samples": 10 -} diff --git a/defaults/9_ghana-e689982_jba-analysis_settings.json b/defaults/9_ghana-e689982_jba-analysis_settings.json deleted file mode 100644 index b067095..0000000 --- a/defaults/9_ghana-e689982_jba-analysis_settings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "gul_output": true, - "gul_summaries": [ - { - "aalcalc": true, - "eltcalc": true, - "id": 1, - "return_period_file": false, - "lec_output": false, - "leccalc": { - "full_uncertainty_aep": true, - "full_uncertainty_oep": true, - "return_period_file": true - } - } - ], - "gul_threshold": 0, - "il_output": false, - "model_settings": { - }, - "model_name_id": "Ghana-E689982", - "model_supplier_id": "JBA", - "number_of_samples": 10 -} diff --git a/docker-compose.authentik.yml b/docker-compose.authentik.yml index edc03c8..fa93b7d 100644 --- a/docker-compose.authentik.yml +++ b/docker-compose.authentik.yml @@ -2,7 +2,7 @@ volumes: authentik-db-data: services: - model-registration: + server: depends_on: authentik-server: condition: service_healthy diff --git a/docker-compose.keycloak.yml b/docker-compose.keycloak.yml index d360fcc..9a4f43b 100644 --- a/docker-compose.keycloak.yml +++ b/docker-compose.keycloak.yml @@ -2,7 +2,7 @@ volumes: keycloak-db-data: services: - model-registration: + server: depends_on: keycloak: condition: service_healthy diff --git a/docker-compose.models.piwind.yml b/docker-compose.models.piwind.yml new file mode 100644 index 0000000..2ee738c --- /dev/null +++ b/docker-compose.models.piwind.yml @@ -0,0 +1,103 @@ +# ============================================================================ +# PiWind demo model +# ============================================================================ +# The default model set, loaded by install.sh (MODEL_COMPOSE=piwind, or +# --models piwind). Always used together with docker-compose.yml, which +# provides the server, databases, broker and the filestore volume. +# + +x-shared-env: &shared-env + # Debug and basic config + OASIS_DEBUG: ${OASIS_DEBUG:-1} + OASIS_URL_SUB_PATH: 0 + + # Hostname configuration (matches Minikube ingress) + INGRESS_EXTERNAL_HOST: ${OASIS_UI_HOSTNAME:-localhost} + INGRESS_INTERNAL_HOST: traefik + + # Authentication configuration + OASIS_SERVER_API_AUTH_TYPE: ${API_AUTH_TYPE:-simple} + OASIS_SERVER_ALLOWED_OIDC_AUTH_PROVIDERS: ${OASIS_SERVER_ALLOWED_OIDC_AUTH_PROVIDERS} + + # Database connections + OASIS_SERVER_DB_ENGINE: django.db.backends.postgresql + OASIS_SERVER_DB_HOST: ${OASIS_SERVER_DB_HOST} + OASIS_SERVER_DB_PORT: ${OASIS_SERVER_DB_PORT} + OASIS_SERVER_DB_NAME: ${OASIS_SERVER_DB_NAME} + OASIS_SERVER_DB_USER: ${OASIS_SERVER_DB_USER} + OASIS_SERVER_DB_PASS: ${OASIS_SERVER_DB_PASS} + + OASIS_CELERY_DB_ENGINE: db+postgresql+psycopg + OASIS_CELERY_DB_HOST: ${OASIS_CELERY_DB_HOST} + OASIS_CELERY_DB_PORT: ${OASIS_CELERY_DB_PORT} + OASIS_CELERY_DB_NAME: ${OASIS_CELERY_DB_NAME} + OASIS_CELERY_DB_USER: ${OASIS_CELERY_DB_USER} + OASIS_CELERY_DB_PASS: ${OASIS_CELERY_DB_PASS} + + # Broker and channel layer + OASIS_CELERY_BROKER_URL: ${OASIS_CELERY_BROKER_URL} + OASIS_SERVER_CHANNEL_LAYER_HOST: ${REDIS_HOST} + OASIS_SERVER_CHANNEL_LAYER_PORT: ${REDIS_PORT:-6379} + OASIS_SERVER_CHANNEL_LAYER_SSL: "false" + + # Task queues + OASIS_TASK_CONTROLLER_QUEUE: task-controller + OASIS_INPUT_GENERATION_CONTROLLER_QUEUE: task-controller + OASIS_LOSSES_GENERATION_CONTROLLER_QUEUE: task-controller + + # Service account credentials (set dynamically by install.sh) + OASIS_SERVICE_USERNAME_OR_ID: ${OASIS_SERVICE_USERNAME_OR_ID} + OASIS_SERVICE_PASSWORD_OR_SECRET: ${OASIS_SERVICE_PASSWORD_OR_SECRET} + OASIS_USE_OIDC: ${OASIS_USE_OIDC} + + # OIDC configuration (only used when API_AUTH_TYPE != simple) + OASIS_SERVER_OIDC_ENDPOINT: ${OASIS_SERVER_OIDC_ENDPOINT:-} + OASIS_SERVER_OIDC_CLIENT_NAME: ${OASIS_SERVER_OIDC_CLIENT_NAME:-} + OASIS_SERVER_OIDC_CLIENT_SECRET: ${OASIS_SERVER_OIDC_CLIENT_SECRET:-} + OASIS_SERVER_OIDC_SERVICE_CLIENT_NAME: ${OASIS_SERVER_OIDC_SERVICE_CLIENT_NAME:-} + OASIS_SERVER_OIDC_SERVICE_CLIENT_SECRET: ${OASIS_SERVER_OIDC_SERVICE_CLIENT_SECRET:-} + +services: + model-registration: + restart: "no" + image: ${SERVER_IMG:-coreoasis/api_server}:${VERS_API:-latest} + command: ["python3", "/scripts/model_registration.py"] + environment: + <<: *shared-env + OASIS_API_URL: "http://server:8000" + OASIS_MODEL_SUPPLIER_ID: OasisLMF + OASIS_MODEL_ID: PiWind + OASIS_MODEL_VERSION_ID: v2 + OASIS_RUN_MODE: v2 + OASIS_MODEL_DATA_DIRECTORY: /model/meta-data + volumes: + - ./OasisPiWind/:/model:ro + - ./scripts:/scripts:ro + depends_on: + server: + condition: service_healthy + + piwind-worker: + restart: always + image: ${WORKER_IMG:-coreoasis/model_worker}:${VERS_WORKER:-latest} + environment: + <<: *shared-env + OASIS_MODEL_SUPPLIER_ID: OasisLMF + OASIS_MODEL_ID: PiWind + OASIS_MODEL_VERSION_ID: v2 + OASIS_RUN_MODE: v2 + OASIS_OASISLMF_VERSION: ${OASIS_OASISLMF_VERSION} + OASIS_ODS_VERSION: ${OASIS_ODS_VERSION} + OASIS_ODM_VERSION: ${OASIS_ODM_VERSION} + OASIS_OED_SCHEMA_INFO: ${OASIS_OED_SCHEMA_INFO} + volumes: + - ./OasisPiWind/:/home/worker/model + - filestore-data:/shared-fs:rw + depends_on: + celery-db: + condition: service_healthy + broker: + condition: service_healthy + model-registration: + condition: service_completed_successfully + diff --git a/docker-compose.models.scenarios.yml b/docker-compose.models.scenarios.yml new file mode 100644 index 0000000..379fde2 --- /dev/null +++ b/docker-compose.models.scenarios.yml @@ -0,0 +1,249 @@ +# ============================================================================ +# PiWind demo model +# ============================================================================ +# The default model set, loaded by install.sh (MODEL_COMPOSE=piwind, or +# --models piwind). Always used together with docker-compose.yml, which +# provides the server, databases, broker and the filestore volume. +# + +x-shared-env: &worker-env + # Debug and basic config + OASIS_DEBUG: ${OASIS_DEBUG:-1} + OASIS_URL_SUB_PATH: 0 + + # Hostname configuration (matches Minikube ingress) + INGRESS_EXTERNAL_HOST: ${OASIS_UI_HOSTNAME:-localhost} + INGRESS_INTERNAL_HOST: traefik + + # Authentication configuration + OASIS_SERVER_API_AUTH_TYPE: ${API_AUTH_TYPE:-simple} + OASIS_SERVER_ALLOWED_OIDC_AUTH_PROVIDERS: ${OASIS_SERVER_ALLOWED_OIDC_AUTH_PROVIDERS} + + # Database connections + OASIS_SERVER_DB_ENGINE: django.db.backends.postgresql + OASIS_SERVER_DB_HOST: ${OASIS_SERVER_DB_HOST} + OASIS_SERVER_DB_PORT: ${OASIS_SERVER_DB_PORT} + OASIS_SERVER_DB_NAME: ${OASIS_SERVER_DB_NAME} + OASIS_SERVER_DB_USER: ${OASIS_SERVER_DB_USER} + OASIS_SERVER_DB_PASS: ${OASIS_SERVER_DB_PASS} + + OASIS_CELERY_DB_ENGINE: db+postgresql+psycopg + OASIS_CELERY_DB_HOST: ${OASIS_CELERY_DB_HOST} + OASIS_CELERY_DB_PORT: ${OASIS_CELERY_DB_PORT} + OASIS_CELERY_DB_NAME: ${OASIS_CELERY_DB_NAME} + OASIS_CELERY_DB_USER: ${OASIS_CELERY_DB_USER} + OASIS_CELERY_DB_PASS: ${OASIS_CELERY_DB_PASS} + + # Broker and channel layer + OASIS_CELERY_BROKER_URL: ${OASIS_CELERY_BROKER_URL} + OASIS_SERVER_CHANNEL_LAYER_HOST: ${REDIS_HOST} + OASIS_SERVER_CHANNEL_LAYER_PORT: ${REDIS_PORT:-6379} + OASIS_SERVER_CHANNEL_LAYER_SSL: "false" + + # Task queues + OASIS_TASK_CONTROLLER_QUEUE: task-controller + OASIS_INPUT_GENERATION_CONTROLLER_QUEUE: task-controller + OASIS_LOSSES_GENERATION_CONTROLLER_QUEUE: task-controller + + # Service account credentials (set dynamically by install.sh) + OASIS_SERVICE_USERNAME_OR_ID: ${OASIS_SERVICE_USERNAME_OR_ID} + OASIS_SERVICE_PASSWORD_OR_SECRET: ${OASIS_SERVICE_PASSWORD_OR_SECRET} + OASIS_USE_OIDC: ${OASIS_USE_OIDC} + + # OIDC configuration (only used when API_AUTH_TYPE != simple) + OASIS_SERVER_OIDC_ENDPOINT: ${OASIS_SERVER_OIDC_ENDPOINT:-} + OASIS_SERVER_OIDC_CLIENT_NAME: ${OASIS_SERVER_OIDC_CLIENT_NAME:-} + OASIS_SERVER_OIDC_CLIENT_SECRET: ${OASIS_SERVER_OIDC_CLIENT_SECRET:-} + OASIS_SERVER_OIDC_SERVICE_CLIENT_NAME: ${OASIS_SERVER_OIDC_SERVICE_CLIENT_NAME:-} + OASIS_SERVER_OIDC_SERVICE_CLIENT_SECRET: ${OASIS_SERVER_OIDC_SERVICE_CLIENT_SECRET:-} + +x-worker: &worker + restart: always + image: ${WORKER_IMG:-coreoasis/model_worker}:${VERS_WORKER:-latest} + depends_on: + server: + condition: service_healthy + celery-db: + condition: service_healthy + broker: + condition: service_healthy + + +services: + piwind-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: OasisLMF + OASIS_MODEL_ID: PiWind + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + volumes: + - ./OasisPiWind/:/home/worker/model + - filestore-data:/shared-fs:rw + + maeq-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: Impact Forecasting + OASIS_MODEL_ID: MAEQ + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + volumes: + - ${SCENARIOS_PATH}/ImpactForecasting/MAEQ/1.0.0/:/home/worker/model + - filestore-data:/shared-fs:rw + + euws-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: Impact Forecasting + OASIS_MODEL_ID: EUWS + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + volumes: + - ${SCENARIOS_PATH}/ImpactForecasting/EUWS/1.0.0/:/home/worker/model + - filestore-data:/shared-fs:rw + + treq-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: Impact Forecasting + OASIS_MODEL_ID: TREQ + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + volumes: + - ${SCENARIOS_PATH}/ImpactForecasting/TREQ/1.0.0/:/home/worker/model + - filestore-data:/shared-fs:rw + + philippines_taiwan_defended-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: JBA + OASIS_MODEL_ID: Philippines-Taiwan-Defended + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + OASIS_OASISLMF_CONFIG: /home/worker/model/oasislmf.json + volumes: + - ${SCENARIOS_PATH}/JBA/Philippines-Taiwan/combined-defended/:/home/worker/model + - filestore-data:/shared-fs:rw + + philippines_taiwan_undefended-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: JBA + OASIS_MODEL_ID: Philippines-Taiwan-Undefended + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + OASIS_OASISLMF_CONFIG: /home/worker/model/oasislmf.json + volumes: + - ${SCENARIOS_PATH}/JBA/Philippines-Taiwan/combined-undefended/:/home/worker/model + - filestore-data:/shared-fs:rw + + ghana-E635900-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: JBA + OASIS_MODEL_ID: Ghana-E635900 + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + OASIS_OASISLMF_CONFIG: /home/worker/model/oasislmf.json + volumes: + - ${SCENARIOS_PATH}/JBA/Ghana/E635900/combined/:/home/worker/model + - filestore-data:/shared-fs:rw + + ghana-E689982-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: JBA + OASIS_MODEL_ID: Ghana-E689982 + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + OASIS_OASISLMF_CONFIG: /home/worker/model/oasislmf.json + volumes: + - ${SCENARIOS_PATH}/JBA/Ghana/E689982/combined/:/home/worker/model + - filestore-data:/shared-fs:rw + + ghana-E760461-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: JBA + OASIS_MODEL_ID: Ghana-E760461 + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + OASIS_OASISLMF_CONFIG: /home/worker/model/oasislmf.json + volumes: + - ${SCENARIOS_PATH}/JBA/Ghana/E760461/combined/:/home/worker/model + - filestore-data:/shared-fs:rw + + nepal-E151185-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: JBA + OASIS_MODEL_ID: Nepal-E151185 + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + OASIS_OASISLMF_CONFIG: /home/worker/model/oasislmf.json + volumes: + - ${SCENARIOS_PATH}/JBA/Nepal/E151185/combined/:/home/worker/model + - filestore-data:/shared-fs:rw + + nepal-E432557-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: JBA + OASIS_MODEL_ID: Nepal-E432557 + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + OASIS_OASISLMF_CONFIG: /home/worker/model/oasislmf.json + volumes: + - ${SCENARIOS_PATH}/JBA/Nepal/E432557/combined/:/home/worker/model + - filestore-data:/shared-fs:rw + + nepal-E505423-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: JBA + OASIS_MODEL_ID: Nepal-E505423 + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + OASIS_OASISLMF_CONFIG: /home/worker/model/oasislmf.json + volumes: + - ${SCENARIOS_PATH}/JBA/Nepal/E505423/combined/:/home/worker/model + - filestore-data:/shared-fs:rw + + us_hurricane-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: ARA + OASIS_MODEL_ID: US-Hurricane + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + OASIS_OASISLMF_CONFIG: /home/worker/model/oasislmf.json + volumes: + - ${SCENARIOS_PATH}/ARA/US_Hurricane/:/home/worker/model + - filestore-data:/shared-fs:rw + + france_hail-worker: + <<: *worker + environment: + <<: *worker-env + OASIS_MODEL_SUPPLIER_ID: IPE + OASIS_MODEL_ID: France-Hail + OASIS_MODEL_VERSION_ID: 'v2' + OASIS_RUN_MODE: v2 + OASIS_OASISLMF_CONFIG: /home/worker/model/oasislmf.json + volumes: + - ${SCENARIOS_PATH}/IPE/FranceHail/:/home/worker/model + - filestore-data:/shared-fs:rw diff --git a/docker-compose.ui.yml b/docker-compose.ui.yml index 3d11d41..0d58bbc 100644 --- a/docker-compose.ui.yml +++ b/docker-compose.ui.yml @@ -1,7 +1,7 @@ services: pythonui: restart: always - image: ${PYTHONUI_IMG}:${VERS_UI} + image: ${PYTHONUI_IMG-coreoasis/oasispythonui_app}:${VERS_UI-latest} build: dockerfile: ./oasisui_st_app.Dockerfile ports: @@ -25,9 +25,10 @@ services: - OASIS_HOSTNAME=${OASIS_UI_HOSTNAME} - OASIS_PROTOCOL=${OASIS_PROTOCOL:-http} volumes: - - ./defaults/:/usr/src/app/defaults/ - - ./ui-config.json:/usr/src/app/ui-config.json:ro - - ./.streamlit/:/usr/src/app/.streamlit:ro + - ${UI_DEFAULTS:-./defaults/}:/usr/src/app/defaults/ + - ${UI_CONFIG:-./ui-config.json}:/usr/src/app/ui-config.json:ro + - ${UI_STREAMLIT:-./.streamlit/}:/usr/src/app/.streamlit:ro + - ${UI_ASSETS:-./ui_assets/}:/usr/src/app/ui_assets:ro healthcheck: test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8501/_stcore/health')"] interval: 30s diff --git a/docker-compose.yml b/docker-compose.yml index a72e3cf..f411c5d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,9 +77,9 @@ services: - "--ping=true" - "--api.insecure=true" ports: - - "80:80" - - "443:8443" - - "8090:8080" + - "${OASIS_HTTP_PORT-80}:80" + - "${OASIS_HTTPS_PORT-443}:8443" + - "${TRAEFIK_DASHBOARD_PORT-8090}:8080" volumes: - ${DOCKER_SOCK:-/var/run/docker.sock}:/var/run/docker.sock:ro healthcheck: @@ -206,49 +206,6 @@ services: broker: condition: service_healthy - model-registration: - restart: "no" - image: ${SERVER_IMG:-coreoasis/api_server}:${VERS_API:-latest} - command: ["python3", "/scripts/model_registration.py"] - environment: - <<: *shared-env - OASIS_API_URL: "http://server:8000" - OASIS_MODEL_SUPPLIER_ID: OasisLMF - OASIS_MODEL_ID: PiWind - OASIS_MODEL_VERSION_ID: v2 - OASIS_RUN_MODE: v2 - OASIS_MODEL_DATA_DIRECTORY: /model/meta-data - volumes: - - ./OasisPiWind/:/model:ro - - ./scripts:/scripts:ro - depends_on: - server: - condition: service_healthy - - piwind-worker: - restart: always - image: ${WORKER_IMG:-coreoasis/model_worker}:${VERS_WORKER:-latest} - environment: - <<: *shared-env - OASIS_MODEL_SUPPLIER_ID: OasisLMF - OASIS_MODEL_ID: PiWind - OASIS_MODEL_VERSION_ID: v2 - OASIS_RUN_MODE: v2 - OASIS_OASISLMF_VERSION: ${OASIS_OASISLMF_VERSION} - OASIS_ODS_VERSION: ${OASIS_ODS_VERSION} - OASIS_ODM_VERSION: ${OASIS_ODM_VERSION} - OASIS_OED_SCHEMA_INFO: ${OASIS_OED_SCHEMA_INFO} - volumes: - - ./OasisPiWind/:/home/worker/model - - filestore-data:/shared-fs:rw - depends_on: - celery-db: - condition: service_healthy - broker: - condition: service_healthy - model-registration: - condition: service_completed_successfully - server-db: restart: always image: postgres:15-alpine diff --git a/get-piwind.sh b/get-piwind.sh new file mode 100644 index 0000000..2575395 --- /dev/null +++ b/get-piwind.sh @@ -0,0 +1,15 @@ +#!/bin/bash +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +GIT_PIWIND=OasisPiWind + +if [ ! -d "$SCRIPT_DIR/$GIT_PIWIND/.git" ]; then + echo "--- Cloning PiWind model ---" + mkdir -p "$SCRIPT_DIR/$GIT_PIWIND" + cd "$SCRIPT_DIR/$GIT_PIWIND" + git clone --depth 1 --branch "${VERS_PIWIND}" "https://github.com/OasisLMF/$GIT_PIWIND.git" . + cd "$SCRIPT_DIR" + echo "" +else + echo " -> PiWind model already cloned" + echo "" +fi diff --git a/get-scenarios.sh b/get-scenarios.sh new file mode 100644 index 0000000..341ae87 --- /dev/null +++ b/get-scenarios.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# +# Put the scenario model data in place for docker-compose.models.scenarios.yml. +# +# Run by install.sh when the scenarios model set is selected, and safe to run +# on its own. Idempotent: it clones the Scenarios repository and pulls down the +# model files once, then no-ops. +# +# Reads SCENARIOS_PATH from the environment (install.sh exports it from .env): +# the directory the scenario workers mount their model data from. + +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +SCENARIOS_REPO="https://github.com/OasisLMF/Scenarios.git" +# One of the model directories the compose file mounts, used to tell whether +# the model files have been pulled down from s3 yet +SENTINEL_MODEL="ImpactForecasting/MAEQ/1.0.0" + +if [ -z "$SCENARIOS_PATH" ]; then + echo "ERROR: SCENARIOS_PATH is not set." + echo " Set it in .env to the directory the scenario models live in, for" + echo " example SCENARIOS_PATH=/home/ubuntu/Scenarios. This script clones" + echo " $SCENARIOS_REPO there if it is missing." + exit 1 +fi + +# The scenarios set runs PiWind alongside the scenario models +bash "$SCRIPT_DIR/get-piwind.sh" + +if [ -d "$SCENARIOS_PATH" ]; then + echo " -> Scenario models already checked out at $SCENARIOS_PATH" +else + echo " -> Cloning scenario models into $SCENARIOS_PATH" + git clone --depth 1 "$SCENARIOS_REPO" "$SCENARIOS_PATH" +fi + +if [ -d "$SCENARIOS_PATH/$SENTINEL_MODEL" ]; then + echo " -> Scenario model files already downloaded" + exit 0 +fi + +if [ ! -x "$SCENARIOS_PATH/get_s3_data_reduced.sh" ]; then + echo "ERROR: $SCENARIOS_PATH/get_s3_data_reduced.sh not found." + exit 1 +fi + +echo " -> Downloading scenario model files (this takes a while)" +( cd "$SCENARIOS_PATH" && ./get_s3_data_reduced.sh ) diff --git a/install.sh b/install.sh index 6f4f71c..f0adcc8 100755 --- a/install.sh +++ b/install.sh @@ -9,18 +9,65 @@ set -e SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# ============================================================================ +# Options +# ============================================================================ + +MODEL_SET_ARG="" +BUILD_UI=false +UNINSTALL=false + +usage() { + cat <<'USAGE' +Usage: ./install.sh [options] + + -m, --model-set Name of model set to deploy, defaults to + 'piwind'. Expects to find + `docker-compose.models..yml file and + optionally a get-.sh script to deploy the + model in the root directory. + --build-ui Rebuild the UI docker container. + -u, --uninstall Bring the stack down and delete its volumes. + -h, --help Show this message. +USAGE +} + +require_value() { + if [ -z "$2" ]; then + echo "ERROR: $1 needs a value" >&2 + exit 1 + fi +} + +while [ $# -gt 0 ]; do + case "$1" in + -m|--model-set) require_value "$1" "${2:-}"; MODEL_SET_ARG="$2"; shift 2 ;; + --build-ui) BUILD_UI=true; shift ;; + -u|--uninstall) UNINSTALL=true; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "ERROR: unknown option '$1'" >&2; usage >&2; exit 1 ;; + esac +done + # ============================================================================ # Uninstall mode # ============================================================================ -if [[ "$1" == "--uninstall" || "$1" == "-u" ]]; then +if [ "$UNINSTALL" = true ]; then echo "Uninstalling Oasis platform (docker compose down only)..." + # Every model set, so model workers come down whichever one was deployed + MODEL_FILES=() + for model_file in "$SCRIPT_DIR"/docker-compose.models.*.yml; do + [ -f "$model_file" ] && MODEL_FILES+=(-f "$model_file") + done + set +e docker compose -f "$SCRIPT_DIR/docker-compose.yml" \ -f "$SCRIPT_DIR/docker-compose.ui.yml" \ -f "$SCRIPT_DIR/docker-compose.keycloak.yml" \ -f "$SCRIPT_DIR/docker-compose.authentik.yml" \ + "${MODEL_FILES[@]}" \ down --remove-orphans -v 2>/dev/null # Also try old files in case of migration docker compose -f "$SCRIPT_DIR/oasis-platform.yml" \ @@ -50,6 +97,7 @@ set -a source "$SCRIPT_DIR/.env" set +a + # ============================================================================ # Auto-detect Docker socket if not set # ============================================================================ @@ -64,11 +112,25 @@ elif [ ! -S "$DOCKER_SOCK" ]; then export DOCKER_SOCK=/var/run/docker.sock fi +# ============================================================================ +# Resolve the model compose files +# ============================================================================ + +MODEL_SET_NAME="${MODEL_SET_ARG:-${MODEL_SET:-piwind}}" +MODEL_COMPOSE_FILE="$SCRIPT_DIR/docker-compose.models.$MODEL_SET_NAME.yml" +MODEL_SETUP_SCRIPT="$SCRIPT_DIR/get-$MODEL_SET_NAME.sh" +if [ ! -f "$MODEL_COMPOSE_FILE" ]; then + echo "ERROR: no model set '$MODEL_SET_NAME': $(basename "$MODEL_COMPOSE_FILE") not found" + echo "" + exit 1 +fi + echo "========================================" echo " OasisPythonUI Installer" echo "========================================" echo "" echo " Auth type: $API_AUTH_TYPE" +echo " Model set: $MODEL_SET_NAME" echo " Hostname: $OASIS_UI_HOSTNAME" echo " Protocol: $OASIS_PROTOCOL" echo " Docker socket: $DOCKER_SOCK" @@ -139,7 +201,7 @@ fi # Build compose file list # ============================================================================ -COMPOSE_FILES="-f $SCRIPT_DIR/docker-compose.yml -f $SCRIPT_DIR/docker-compose.ui.yml" +COMPOSE_FILES="-f $SCRIPT_DIR/docker-compose.yml -f $MODEL_COMPOSE_FILE -f $SCRIPT_DIR/docker-compose.ui.yml" if [ "$API_AUTH_TYPE" = "keycloak" ]; then COMPOSE_FILES="$COMPOSE_FILES -f $SCRIPT_DIR/docker-compose.keycloak.yml" @@ -152,23 +214,18 @@ fi echo " -> Compose files: $COMPOSE_FILES" echo "" + # ============================================================================ -# Clone PiWind model (if not present) +# Get model data # ============================================================================ +echo "--- Retrieving Model Data ---" -GIT_PIWIND=OasisPiWind - -if [ ! -d "$SCRIPT_DIR/$GIT_PIWIND/.git" ]; then - echo "--- Cloning PiWind model ---" - mkdir -p "$SCRIPT_DIR/$GIT_PIWIND" - cd "$SCRIPT_DIR/$GIT_PIWIND" - git clone --depth 1 --branch "${VERS_PIWIND}" "https://github.com/OasisLMF/$GIT_PIWIND.git" . - cd "$SCRIPT_DIR" - echo "" +if [ -f "$MODEL_SETUP_SCRIPT" ]; then + bash "$MODEL_SETUP_SCRIPT" else - echo " -> PiWind model already cloned" - echo "" + echo " -> No get-$MODEL_NAME.sh, expecting the $MODEL_NAME model data to be in place" fi +echo "" # ============================================================================ # Check for previous install @@ -197,20 +254,30 @@ echo "--- Pulling images ---" set +e docker pull "${SERVER_IMG:-coreoasis/api_server}:${VERS_API:-latest}" docker pull "${WORKER_IMG:-coreoasis/model_worker}:${VERS_WORKER:-latest}" -docker pull "${PYTHONUI_IMG:-coreoasis/oasispythonui_app}:${VERS_UI:-latest}" set -e echo "" +# ============================================================================ +# Build UI if necessary +# ============================================================================ + +if [ "$BUILD_UI" = true ]; then + echo " -> Building UI image" + docker compose $COMPOSE_FILES build --no-cache pythonui +else + echo " -> Pulling UI image ${PYTHONUI_IMG:-coreoasis/oasispythonui_app}:${VERS_UI:-latest}" + set +e + docker pull "${PYTHONUI_IMG:-coreoasis/oasispythonui_app}:${VERS_UI:-latest}" + set -e +fi + # ============================================================================ # Deploy services # ============================================================================ echo "--- Deploying services ---" -# Build UI -docker compose $COMPOSE_FILES build --no-cache pythonui - # Start all services docker compose $COMPOSE_FILES up -d diff --git a/modules/nav.py b/modules/nav.py index 86f3b45..1c5468f 100644 --- a/modules/nav.py +++ b/modules/nav.py @@ -18,7 +18,7 @@ def SidebarNav(no_client=False): f'' '') else: - if st.button("Logout", use_container_width=True): + if st.button("Logout", width='stretch'): logout() else: st.page_link('app.py', label="Login") diff --git a/pages/analyses.py b/pages/analyses.py index aeb2f46..bc3756b 100644 --- a/pages/analyses.py +++ b/pages/analyses.py @@ -221,7 +221,7 @@ def run_analysis(re_handler): left, middle, right = st.columns(3, vertical_alignment='center') left.markdown("2) Generate Inputs:") - if middle.button("Generate", use_container_width=True, disabled=not validations.is_valid()): + if middle.button("Generate", width='stretch', disabled=not validations.is_valid()): try: client.analyses.generate(selected['id']) st.success('Input generation started.') @@ -290,7 +290,7 @@ def set_analysis_settings(analysis): validations.add_validation(NotNoneValidation('Analysis'), selected) validations.add_validation(KeyInValuesValidation('Status'), selected, 'status', ['READY', 'NEW', 'RUN_ERROR', 'RUN_CANCELLED', 'RUN_COMPLETED']) if middle.button("Upload Settings File", disabled=not validations.is_valid(), - use_container_width=True): + width='stretch'): upload_settings_file(selected) # Set settings button @@ -302,7 +302,7 @@ def set_analysis_settings(analysis): if not enable_button: msg = validations.message if right.button("Set Analysis Settings", disabled=not enable_button, help=msg, - use_container_width=True): + width='stretch'): set_analysis_settings(selected) created_analysis_settings = consume_analysis_settings() @@ -341,7 +341,7 @@ def set_analysis_settings(analysis): if not run_enabled: msg = validations.message - if middle.button("Run", use_container_width=True, disabled=not run_enabled, help=msg): + if middle.button("Run", width='stretch', disabled=not run_enabled, help=msg): try: client_interface.run(selected['id']) @@ -359,7 +359,7 @@ def set_analysis_settings(analysis): validation = NotNoneValidation('Selected analysis') button_enabled = validation.is_valid(selected) - if left.button("Delete", use_container_width=True, disabled = not button_enabled, help=validation.message): + if left.button("Delete", width='stretch', disabled = not button_enabled, help=validation.message): try: client.analyses.delete(selected['id']) st.rerun() @@ -375,7 +375,7 @@ def set_analysis_settings(analysis): if not run_enabled: msg = validations.message - if middle.button("Logs", use_container_width=True, disabled=not run_enabled, help=msg): + if middle.button("Logs", width='stretch', disabled=not run_enabled, help=msg): @st.dialog("Log", width='large') def error_log_dialog(analysis_id): input_gen_traceback = client_interface.analyses.get_traceback(analysis_id, 'input_generation') diff --git a/pages/components/create.py b/pages/components/create.py index e1e11ab..3c2c0c5 100644 --- a/pages/components/create.py +++ b/pages/components/create.py @@ -339,7 +339,7 @@ def summary_settings_fragment(oed_fields, perspective): st.session_state[f'editing_level_{perspective}'] = False if col1.button('Add Level', key=f'{perspective}_summary_add_button', - use_container_width=True): + width='stretch'): st.session_state[f'adding_level_{perspective}'] = not st.session_state[f'adding_level_{perspective}'] st.session_state[f'{perspective}_summaries'] = original_summaries @@ -356,14 +356,14 @@ def summary_settings_fragment(oed_fields, perspective): st.rerun(scope='fragment') if col2.button('Delete Level', key=f'{perspective}_summary_delete_button', - use_container_width=True, disabled=selected is None): + width='stretch', disabled=selected is None): pos = [i for i, el in enumerate(curr_summaries) if el['id'] == selected][0] curr_summaries.pop(pos) st.session_state[f'{perspective}_summaries'] = curr_summaries st.rerun(scope='fragment') if col3.button('Edit Level', key=f'{perspective}_summary_edit_button', - use_container_width=True, disabled=selected is None): + width='stretch', disabled=selected is None): st.session_state[f'editing_level_{perspective}'] = not st.session_state[f'editing_level_{perspective}'] st.session_state[f'{perspective}_summaries'] = original_summaries diff --git a/pages/components/display.py b/pages/components/display.py index 67b2d42..4e6380c 100644 --- a/pages/components/display.py +++ b/pages/components/display.py @@ -87,14 +87,14 @@ def display(self, max_rows=1000, key=None): if self.data.empty: st.dataframe(pd.DataFrame(columns=self.display_cols), hide_index=self.hide_index, column_config=self.column_config, - column_order=self.display_cols, use_container_width=True, + column_order=self.display_cols, width='stretch', key=key) return None args = { 'hide_index': self.hide_index, 'column_config': self.column_config, - 'use_container_width': True, + 'width': 'stretch', 'column_order': self.display_cols, 'key': key } @@ -280,7 +280,7 @@ def find_zoom_level(lon_range): len=500 )) - st.plotly_chart(fig, use_container_width=True) + st.plotly_chart(fig, width='stretch') def generate_choropleth(self): # Get country GeoJSON diff --git a/pages/components/output.py b/pages/components/output.py index 8da840f..8112873 100644 --- a/pages/components/output.py +++ b/pages/components/output.py @@ -432,7 +432,7 @@ def elt_ord_table(result, perspective, oed_fields = None, key_prefix=None, table_df = table_df.sort_values(by=order_col, ascending=False) # OED Filters - with st.popover("OED Filters", use_container_width=True): + with st.popover("OED Filters", width='stretch'): for oed_field in oed_fields: options = table_df[oed_field].unique() oed_filter = st.multiselect(f"{oed_field} Filter:", options, @@ -765,7 +765,7 @@ def generate_aalcalc_fragment(p, vis): if breakdown_field_invalid: st.error("Too many values in group field.") - st.plotly_chart(graph, use_container_width=True) + st.plotly_chart(graph, width='stretch') @st.fragment def generate_alt_fragment(p, vis, output_type='alt_meanonly'): @@ -802,7 +802,7 @@ def generate_alt_fragment(p, vis, output_type='alt_meanonly'): if breakdown_field_invalid: st.error("Too many values in group field.") - st.plotly_chart(graph, use_container_width=True, key=f'{output_type}_graph') + st.plotly_chart(graph, width='stretch', key=f'{output_type}_graph') def generate_leccalc_fragment(p, vis, lec_outputs): lec_options = [option for option in lec_outputs.keys() if lec_outputs[option]] @@ -1296,7 +1296,7 @@ def generate_aalcalc_comparison_fragment(p, outputs, names = None): labels = {'mean': 'Mean', 'name': 'Analysis Name'}, color_discrete_sequence= px.colors.sequential.RdBu, category_orders={'name': names}) - st.plotly_chart(graph, use_container_width=True) + st.plotly_chart(graph, width='stretch') if breakdown_field_invalid: st.error("Too many values in group field.") diff --git a/pages/scenarios.py b/pages/scenarios.py index 6032b19..77d2293 100644 --- a/pages/scenarios.py +++ b/pages/scenarios.py @@ -147,7 +147,7 @@ def filter_valid_rows(df, key, valid_map, filter_col): cols = st.columns([0.25, 0.25, 0.25, 0.25]) with cols[0]: - with st.popover("Create Analysis", disabled=not enable_popover, help=msg, use_container_width=True): + with st.popover("Create Analysis", disabled=not enable_popover, help=msg, width='stretch'): if enable_popover: resp = create_analysis_form(portfolios=[selected_portfolio.to_dict()], models=[selected_model.to_dict()]) if resp: @@ -165,7 +165,7 @@ def filter_valid_rows(df, key, valid_map, filter_col): enable_map_button = validation.is_valid(selected_portfolio) if st.button("Exposure Map", disabled=not enable_map_button, - help=validation.get_message(), use_container_width=True): + help=validation.get_message(), width='stretch'): @st.dialog("Locations Map", width='large') def show_locations_map(): with st.spinner('Loading map...'): @@ -189,7 +189,7 @@ def show_locations_map(): enable_model_details = validation.is_valid(selected_model) if st.button("Scenario Details", disabled=not enable_model_details, - help = validation.get_message(), use_container_width=True): + help = validation.get_message(), width='stretch'): try: model_settings = client_interface.models.settings.get(selected_model['id']) except HTTPError as e: @@ -279,7 +279,7 @@ def analysis_fragment(): run_started = False with columns[0]: - if st.button('Run', disabled = not run_enabled, help=msg, use_container_width=True): + if st.button('Run', disabled = not run_enabled, help=msg, width='stretch'): try: # Load from platform templates = client_interface.models.setting_templates.get(selected['model']) @@ -420,7 +420,7 @@ def generate_perspective_visualisation(perspective, summaries_settings): download_enabled = validations.is_valid() with columns[1]: - if st.button("Show Output", use_container_width=True, disabled = not download_enabled): + if st.button("Show Output", width='stretch', disabled = not download_enabled): display_outputs(client_interface, selected["id"], selected['model']) diff --git a/scenarios/.streamlit/config.toml b/scenarios/.streamlit/config.toml new file mode 100644 index 0000000..fe84af0 --- /dev/null +++ b/scenarios/.streamlit/config.toml @@ -0,0 +1,14 @@ +[client] +showSidebarNavigation = false +showErrorDetails = "type" +toolbarMode = "minimal" + +[server] +fileWatcherType = "none" +headless = true +enableXsrfProtection = false +enableCORS = false + +[browser] +gatherUsageStats = false +showSidebarNavigation = false diff --git a/scenarios/.streamlit/secrets.toml b/scenarios/.streamlit/secrets.toml new file mode 100644 index 0000000..3c61bf5 --- /dev/null +++ b/scenarios/.streamlit/secrets.toml @@ -0,0 +1,4 @@ +auth_type='simple' + +user='admin' +password='password' diff --git a/scenarios/a_settings.json b/scenarios/a_settings.json index e41a5da..e30f085 100644 --- a/scenarios/a_settings.json +++ b/scenarios/a_settings.json @@ -1,58 +1,58 @@ [ { "model_name_id": "PiWind", - "settings_path": "./defaults/1_piwind_oasislmf-analysis_settings.json" + "settings_path": "./scenarios/defaults/1_piwind_oasislmf-analysis_settings.json" }, { "model_name_id": "EUWS", - "settings_path": "./defaults/3_euws_impact-forecasting-analysis_settings.json" + "settings_path": "./scenarios/defaults/3_euws_impact-forecasting-analysis_settings.json" }, { "model_name_id": "MAEQ", - "settings_path": "./defaults/2_maeq_impact-forecasting-analysis_settings.json" + "settings_path": "./scenarios/defaults/2_maeq_impact-forecasting-analysis_settings.json" }, { "model_name_id": "Philippines-Taiwan-Undefended", - "settings_path": "./defaults/6_philippines-taiwan-undefended_jba-analysis_settings.json" + "settings_path": "./scenarios/defaults/6_philippines-taiwan-undefended_jba-analysis_settings.json" }, { "model_name_id": "Philippines-Taiwan-Defended", - "settings_path": "./defaults/5_philippines-taiwan-defended_jba-analysis_settings.json" + "settings_path": "./scenarios/defaults/5_philippines-taiwan-defended_jba-analysis_settings.json" }, { "model_name_id": "France-Hail", - "settings_path": "./defaults/14_france-hail_ipe-analysis_settings.json" + "settings_path": "./scenarios/defaults/14_france-hail_ipe-analysis_settings.json" }, { "model_name_id": "Nepal-E432557", - "settings_path": "./defaults/12_nepal-e432557_jba-analysis_settings.json" + "settings_path": "./scenarios/defaults/12_nepal-e432557_jba-analysis_settings.json" }, { "model_name_id": "Ghana-E760461", - "settings_path": "./defaults/10_ghana-e760461_jba-analysis_settings.json" + "settings_path": "./scenarios/defaults/10_ghana-e760461_jba-analysis_settings.json" }, { "model_name_id": "Ghana-E635900", - "settings_path": "./defaults/8_ghana-e635900_jba-analysis_settings.json" + "settings_path": "./scenarios/defaults/8_ghana-e635900_jba-analysis_settings.json" }, { "model_name_id": "Nepal-E151185", - "settings_path": "./defaults/11_nepal-e151185_jba-analysis_settings.json" + "settings_path": "./scenarios/defaults/11_nepal-e151185_jba-analysis_settings.json" }, { "model_name_id": "Nepal-E505423", - "settings_path": "./defaults/13_nepal-e505423_jba-analysis_settings.json" + "settings_path": "./scenarios/defaults/13_nepal-e505423_jba-analysis_settings.json" }, { "model_name_id": "Ghana-E689982", - "settings_path": "./defaults/9_ghana-e689982_jba-analysis_settings.json" + "settings_path": "./scenarios/defaults/9_ghana-e689982_jba-analysis_settings.json" }, { "model_name_id": "TREQ", - "settings_path": "./defaults/4_treq_impact-forecasting-analysis_settings.json" + "settings_path": "./scenarios/defaults/4_treq_impact-forecasting-analysis_settings.json" }, { "model_name_id": "US-Hurricane", - "settings_path": "./defaults/7_us-hurricane_ara-analysis_settings.json" + "settings_path": "./scenarios/defaults/7_us-hurricane_ara-analysis_settings.json" } ] diff --git a/scenarios/defaults/10_ghana-e760461_jba-analysis_settings.json b/scenarios/defaults/10_ghana-e760461_jba-analysis_settings.json new file mode 100644 index 0000000..157a141 --- /dev/null +++ b/scenarios/defaults/10_ghana-e760461_jba-analysis_settings.json @@ -0,0 +1,19 @@ +{ + "gul_output": true, + "gul_summaries": [ + { + "ord_output": { + "elt_moment": true, + "alt_period": true + }, + "id": 1 + } + ], + "gul_threshold": 0, + "il_output": false, + "model_settings": { + }, + "model_name_id": "Ghana-E760461", + "model_supplier_id": "JBA", + "number_of_samples": 10 +} diff --git a/scenarios/defaults/11_nepal-e151185_jba-analysis_settings.json b/scenarios/defaults/11_nepal-e151185_jba-analysis_settings.json new file mode 100644 index 0000000..7b21ffb --- /dev/null +++ b/scenarios/defaults/11_nepal-e151185_jba-analysis_settings.json @@ -0,0 +1,19 @@ +{ + "gul_output": true, + "gul_summaries": [ + { + "ord_output": { + "elt_moment": true, + "alt_period": true + }, + "id": 1 + } + ], + "gul_threshold": 0, + "il_output": false, + "model_settings": { + }, + "model_name_id": "Nepal-E151185", + "model_supplier_id": "JBA", + "number_of_samples": 10 +} diff --git a/scenarios/defaults/12_nepal-e432557_jba-analysis_settings.json b/scenarios/defaults/12_nepal-e432557_jba-analysis_settings.json new file mode 100644 index 0000000..48e204a --- /dev/null +++ b/scenarios/defaults/12_nepal-e432557_jba-analysis_settings.json @@ -0,0 +1,19 @@ +{ + "gul_output": true, + "gul_summaries": [ + { + "ord_output": { + "elt_moment": true, + "alt_period": true + }, + "id": 1 + } + ], + "gul_threshold": 0, + "il_output": false, + "model_settings": { + }, + "model_name_id": "Nepal-E432557", + "model_supplier_id": "JBA", + "number_of_samples": 10 +} diff --git a/scenarios/defaults/13_nepal-e505423_jba-analysis_settings.json b/scenarios/defaults/13_nepal-e505423_jba-analysis_settings.json new file mode 100644 index 0000000..53b9613 --- /dev/null +++ b/scenarios/defaults/13_nepal-e505423_jba-analysis_settings.json @@ -0,0 +1,19 @@ +{ + "gul_output": true, + "gul_summaries": [ + { + "ord_output": { + "elt_moment": true, + "alt_period": true + }, + "id": 1 + } + ], + "gul_threshold": 0, + "il_output": false, + "model_settings": { + }, + "model_name_id": "Nepal-E505423", + "model_supplier_id": "JBA", + "number_of_samples": 10 +} diff --git a/scenarios/defaults/14_france-hail_ipe-analysis_settings.json b/scenarios/defaults/14_france-hail_ipe-analysis_settings.json new file mode 100644 index 0000000..daec002 --- /dev/null +++ b/scenarios/defaults/14_france-hail_ipe-analysis_settings.json @@ -0,0 +1,19 @@ +{ + "gul_output": true, + "gul_summaries": [ + { + "ord_output": { + "elt_moment": true, + "alt_period": true + }, + "id": 1 + } + ], + "gul_threshold": 0, + "il_output": false, + "model_settings": { + }, + "model_name_id": "France-Hail", + "model_supplier_id": "IPE", + "number_of_samples": 10 +} diff --git a/scenarios/defaults/1_piwind_oasislmf-analysis_settings.json b/scenarios/defaults/1_piwind_oasislmf-analysis_settings.json new file mode 100644 index 0000000..3b5431f --- /dev/null +++ b/scenarios/defaults/1_piwind_oasislmf-analysis_settings.json @@ -0,0 +1,31 @@ +{ + "model_name_id": "PiWind", + "model_supplier_id": "OasisLMF", + "gul_threshold": 0, + "gul_output": true, + "model_settings": { + "event_set": "p", + "event_occurrence_id": "lt" + }, + "gul_summaries": [ + { + "eltcalc": true, + "aalcalc": false, + "lec_output": false, + "leccalc": { + "full_uncertainty_aep": true, + "full_uncertainty_oep": true, + "return_period_file": true + }, + "id": 1 + } + ], + "il_output": true, + "il_summaries": [ + { + "eltcalc": true, + "id": 1 + } + ], + "number_of_samples": 10 +} diff --git a/scenarios/defaults/2_maeq_impact-forecasting-analysis_settings.json b/scenarios/defaults/2_maeq_impact-forecasting-analysis_settings.json new file mode 100644 index 0000000..e9a78c7 --- /dev/null +++ b/scenarios/defaults/2_maeq_impact-forecasting-analysis_settings.json @@ -0,0 +1,21 @@ +{ + + "source_tag": "MDK", + "analysis_tag": 1, + "model_name_id": "maeq", + "model_supplier_id": "ImpactForecasting", + "number_of_samples": 100, + "model_settings": { + "event_set": "" + }, + "gul_output": true, + "gul_summaries": [ + { + "id": 1, + "ord_output": { + "elt_moment": true + } + } + ], + "gul_threshold": 0 +} diff --git a/scenarios/defaults/3_euws_impact-forecasting-analysis_settings.json b/scenarios/defaults/3_euws_impact-forecasting-analysis_settings.json new file mode 100644 index 0000000..fa661ed --- /dev/null +++ b/scenarios/defaults/3_euws_impact-forecasting-analysis_settings.json @@ -0,0 +1,19 @@ +{ + + "model_supplier_id": "ImpactForecasting", + "model_name_id": "EUWS", + "number_of_samples": 10, + "model_settings": { + "event_set": "ciaran" + }, + "gul_output": true, + "gul_summaries": [ + { + "id": 1, + "ord_output": { + "elt_moment": true + } + } + ], + "gul_threshold": 0 +} diff --git a/scenarios/defaults/4_treq_impact-forecasting-analysis_settings.json b/scenarios/defaults/4_treq_impact-forecasting-analysis_settings.json new file mode 100644 index 0000000..7839a21 --- /dev/null +++ b/scenarios/defaults/4_treq_impact-forecasting-analysis_settings.json @@ -0,0 +1,24 @@ +{ + + "source_tag": "MDK", + "analysis_tag": 1, + "model_version_id": "0.0.0.1", + "model_name_id": "treq", + "module_supplier_id": "ImpactForecasting", + "number_of_samples": 10, + "model_settings": { + "event_set": "", + "peril_wind": true + }, + "gul_output": true, + "gul_summaries": [ + { + "id": 1, + "ord_output": { + "elt_moment": true + } + } + ], + "gul_threshold": 0 + +} diff --git a/defaults/5_philippines-taiwan-defended_jba-analysis_settings.json b/scenarios/defaults/5_philippines-taiwan-defended_jba-analysis_settings.json similarity index 51% rename from defaults/5_philippines-taiwan-defended_jba-analysis_settings.json rename to scenarios/defaults/5_philippines-taiwan-defended_jba-analysis_settings.json index 8721101..8b9fefe 100644 --- a/defaults/5_philippines-taiwan-defended_jba-analysis_settings.json +++ b/scenarios/defaults/5_philippines-taiwan-defended_jba-analysis_settings.json @@ -2,15 +2,12 @@ "gul_output": true, "gul_summaries": [ { - "aalcalc": true, - "eltcalc": true, "id": 1, - "return_period_file": false, - "lec_output": false, - "leccalc": { - "full_uncertainty_aep": true, - "full_uncertainty_oep": true, - "return_period_file": true + "ord_output": { + "elt_moment": true, + "ept_full_uncertainty_aep": true, + "ept_full_uncertainty_oep": true, + "return_period_file": true } } ], diff --git a/scenarios/defaults/6_philippines-taiwan-undefended_jba-analysis_settings.json b/scenarios/defaults/6_philippines-taiwan-undefended_jba-analysis_settings.json new file mode 100644 index 0000000..4466fdb --- /dev/null +++ b/scenarios/defaults/6_philippines-taiwan-undefended_jba-analysis_settings.json @@ -0,0 +1,19 @@ +{ + "gul_output": true, + "gul_summaries": [ + { + "ord_output": { + "elt_moment": true, + "alt_period": true + }, + "id": 1 + } + ], + "gul_threshold": 0, + "il_output": false, + "model_settings": { + }, + "model_name_id": "PHTY-U", + "model_supplier_id": "JBA", + "number_of_samples": 10 +} diff --git a/scenarios/defaults/7_us-hurricane_ara-analysis_settings.json b/scenarios/defaults/7_us-hurricane_ara-analysis_settings.json new file mode 100644 index 0000000..6b1733d --- /dev/null +++ b/scenarios/defaults/7_us-hurricane_ara-analysis_settings.json @@ -0,0 +1,21 @@ +{ + "gul_output": true, + "gul_summaries": [ + { + "ord_output": { + "elt_moment": true, + "alt_period": true, + "ept_per_sample_mean_aep": true + }, + "id": 1, + "return_period_file": true + + } + ], + "gul_threshold": 0, + "model_settings": { + }, + "model_name_id": "US_Hurricane", + "model_supplier_id": "ARA", + "number_of_samples": 10 +} diff --git a/scenarios/defaults/8_ghana-e635900_jba-analysis_settings.json b/scenarios/defaults/8_ghana-e635900_jba-analysis_settings.json new file mode 100644 index 0000000..06a6840 --- /dev/null +++ b/scenarios/defaults/8_ghana-e635900_jba-analysis_settings.json @@ -0,0 +1,19 @@ +{ + "model_name_id": "Ghana-E635900", + "model_supplier_id": "JBA", + "gul_output": true, + "gul_summaries": [ + { + "ord_output": { + "elt_moment": true, + "alt_period": true + }, + "id": 1 + } + ], + "gul_threshold": 0, + "il_output": false, + "model_settings": { + }, + "number_of_samples": 10 +} diff --git a/scenarios/defaults/9_ghana-e689982_jba-analysis_settings.json b/scenarios/defaults/9_ghana-e689982_jba-analysis_settings.json new file mode 100644 index 0000000..7aaf90a --- /dev/null +++ b/scenarios/defaults/9_ghana-e689982_jba-analysis_settings.json @@ -0,0 +1,19 @@ +{ + "gul_output": true, + "gul_summaries": [ + { + "ord_output": { + "elt_moment": true, + "alt_period": true + }, + "id": 1 + } + ], + "gul_threshold": 0, + "il_output": false, + "model_settings": { + }, + "model_name_id": "Ghana-E689982", + "model_supplier_id": "JBA", + "number_of_samples": 10 +} diff --git a/scenarios/deploy_scenarios_ui.sh b/scenarios/deploy_scenarios_ui.sh old mode 100644 new mode 100755 diff --git a/scenarios/ui-config.json b/scenarios/ui-config.json index 4b3fe47..5d4a9bd 100644 --- a/scenarios/ui-config.json +++ b/scenarios/ui-config.json @@ -22,7 +22,7 @@ "France-Hail": ["france-hail-flat"] }, "footer" : { - "path": "scenarios/assets/footer.md", + "path": "ui_assets/footer.md", "pages": [ "scenarios", "comparison" diff --git a/scripts/add_settings_template.py b/scripts/add_settings_template.py index 90a6a6c..5ebcffa 100644 --- a/scripts/add_settings_template.py +++ b/scripts/add_settings_template.py @@ -12,12 +12,12 @@ def add_model_analysis_settings(client, model_id, analysis_settings_path): models = client.models.search({'model_id': str(model_id)}).json() if not models: - logger.warn(f"model id: {model_id} not found") + logger.warning(f"model id: {model_id} not found") return analysis_settings_path = Path(analysis_settings_path) if not analysis_settings_path.is_file(): - logger.warn(f"analysis settings file for model_id {model_id} not found") + logger.warning(f"analysis settings file for model_id {model_id} not found") return with open(analysis_settings_path, "r") as f: diff --git a/ui_assets/README.md b/ui_assets/README.md new file mode 100644 index 0000000..b429a55 --- /dev/null +++ b/ui_assets/README.md @@ -0,0 +1,14 @@ +# UI Assets + +Files the UI's `ui-config.json` points at — a footer, a logo, anything a +deployment wants to drop in without rebuilding the image. + +This directory is mounted read-only at `/usr/src/app/ui_assets` in the UI +container, so a config refers to what lands here as `ui_assets/`: + +Point `UI_ASSETS` in `.env` at a different directory to swap the set, the way +`UI_CONFIG` swaps the page set. The scenarios deployment uses +`UI_ASSETS=./scenarios/assets`. + +Not to be confused with `../assets/`, which holds map data the application code +reads directly and which ships inside the image.