Skip to content

Commit 7cebfcc

Browse files
committed
feat: rename normalized chemistry results to major chemistry results and update related configurations
1 parent e15d366 commit 7cebfcc

12 files changed

Lines changed: 150 additions & 72 deletions

.github/workflows/tests.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ jobs:
3737

3838
- name: Start database (PostGIS)
3939
run: |
40-
docker compose build db_dev
41-
docker compose up -d db_dev
40+
docker compose build db
41+
docker compose up -d db
4242
4343
- name: Wait for database readiness
4444
run: |
@@ -122,8 +122,8 @@ jobs:
122122

123123
- name: Start database (PostGIS)
124124
run: |
125-
docker compose build db_dev
126-
docker compose up -d db_dev
125+
docker compose build db
126+
docker compose up -d db
127127
128128
- name: Wait for database readiness
129129
run: |

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,14 @@ docker compose up --build
199199

200200
Notes:
201201
* Requires Docker Desktop.
202-
* By default, spins up two containers: `db_dev` (PostGIS/PostgreSQL) and `app` (FastAPI API service).
203-
* `db_test` is opt-in via profile: `docker compose --profile test up`.
202+
* By default, spins up two containers: `db` (PostGIS/PostgreSQL) and `app` (FastAPI API service).
203+
* `db` initializes both application databases in the same Postgres service:
204+
* `ocotilloapi_dev`
205+
* `ocotilloapi_test`
204206
* `alembic upgrade head` runs on app startup after `docker compose up`.
205207
* Compose uses hardcoded DB names:
206208
* dev: `ocotilloapi_dev`
207-
* test: `ocotilloapi_test`
209+
* test: `ocotilloapi_test` (created by init SQL in `docker/db/init/01-create-test-db.sql`)
208210
* The database listens on port `5432` both inside the container and on your host. Ensure `POSTGRES_PORT=5432` and `POSTGRES_DB=ocotilloapi_dev` in your `.env` to run local commands against the Docker dev DB (e.g., `uv run pytest`, `uv run python -m transfers.transfer`).
209211

210212
#### Staging Data

alembic/versions/b6f7a8b9c0d1_add_normalized_chemistry_results_materialized_view.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,11 @@ def _static_analyte_unit_columns() -> str:
8080
)
8181

8282

83-
def _create_normalized_chemistry_results_view() -> str:
83+
def _create_major_chemistry_results_view() -> str:
8484
static_columns = _static_analyte_select_columns()
8585
static_unit_columns = _static_analyte_unit_columns()
8686
return f"""
87-
CREATE MATERIALIZED VIEW ogc_normalized_chemistry_results AS
87+
CREATE MATERIALIZED VIEW ogc_major_chemistry_results AS
8888
WITH latest_location AS (
8989
{LATEST_LOCATION_CTE}
9090
),
@@ -100,7 +100,10 @@ def _create_normalized_chemistry_results_view() -> str:
100100
FROM "NMA_MajorChemistry" AS mc
101101
JOIN "NMA_Chemistry_SampleInfo" AS csi
102102
ON csi.id = mc.chemistry_sample_info_id
103+
JOIN thing AS t
104+
ON t.id = csi.thing_id
103105
WHERE mc."SampleValue" IS NOT NULL
106+
AND t.thing_type = 'water well'
104107
),
105108
normalized_rows AS (
106109
SELECT
@@ -246,7 +249,6 @@ def _create_normalized_chemistry_results_view() -> str:
246249
JOIN latest_location AS ll ON ll.thing_id = t.id
247250
JOIN location AS l ON l.id = ll.location_id
248251
WHERE lr.rn = 1
249-
AND t.thing_type = 'water well'
250252
GROUP BY t.id, ll.location_id, t.name, t.thing_type, l.point
251253
"""
252254

@@ -266,29 +268,31 @@ def upgrade() -> None:
266268
if not required_tables.issubset(existing_tables):
267269
missing = sorted(t for t in required_tables if t not in existing_tables)
268270
raise RuntimeError(
269-
"Cannot create ogc_normalized_chemistry_results. Missing required tables: "
271+
"Cannot create ogc_major_chemistry_results. Missing required tables: "
270272
+ ", ".join(missing)
271273
)
272274

275+
op.execute(text("DROP MATERIALIZED VIEW IF EXISTS ogc_major_chemistry_results"))
273276
op.execute(
274277
text("DROP MATERIALIZED VIEW IF EXISTS ogc_normalized_chemistry_results")
275278
)
276-
op.execute(text(_create_normalized_chemistry_results_view()))
279+
op.execute(text(_create_major_chemistry_results_view()))
277280
op.execute(
278281
text(
279-
"COMMENT ON MATERIALIZED VIEW ogc_normalized_chemistry_results IS "
282+
"COMMENT ON MATERIALIZED VIEW ogc_major_chemistry_results IS "
280283
"'Latest major-chemistry analyte values per location, pivoted into static analyte columns.'"
281284
)
282285
)
283286
op.execute(
284287
text(
285-
"CREATE UNIQUE INDEX ux_ogc_normalized_chemistry_results_id "
286-
"ON ogc_normalized_chemistry_results (id)"
288+
"CREATE UNIQUE INDEX ux_ogc_major_chemistry_results_id "
289+
"ON ogc_major_chemistry_results (id)"
287290
)
288291
)
289292

290293

291294
def downgrade() -> None:
295+
op.execute(text("DROP MATERIALIZED VIEW IF EXISTS ogc_major_chemistry_results"))
292296
op.execute(
293297
text("DROP MATERIALIZED VIEW IF EXISTS ogc_normalized_chemistry_results")
294298
)

alembic/versions/c7f8a9b0d1e2_add_minor_chemistry_wells_materialized_view.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ def _create_minor_chemistry_wells_view() -> str:
137137
mtc.id AS result_id,
138138
COALESCE(mtc.analysis_date::timestamp, csi."CollectionDate") AS observation_datetime,
139139
trim(mtc.analyte) AS analyte_name,
140-
trim(mtc.symbol) AS symbol_name,
141140
mtc.sample_value::double precision AS sample_value,
142141
mtc.units AS units
143142
FROM "NMA_MinorTraceChemistry" AS mtc
@@ -162,15 +161,6 @@ def _create_minor_chemistry_wells_view() -> str:
162161
),
163162
''
164163
) AS analyte_token,
165-
NULLIF(
166-
regexp_replace(
167-
lower(trim(coalesce(cr.symbol_name, ''))),
168-
'[^a-z0-9]+',
169-
'',
170-
'g'
171-
),
172-
''
173-
) AS symbol_token,
174164
cr.sample_value,
175165
cr.units
176166
FROM chemistry_rows AS cr

cli/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class SmokePopulation(str, Enum):
5555
"ogc_avg_tds_wells",
5656
"ogc_depth_to_water_trend_wells",
5757
"ogc_water_well_summary",
58-
"ogc_normalized_chemistry_results",
58+
"ogc_major_chemistry_results",
5959
"ogc_minor_chemistry_wells",
6060
)
6161

core/pygeoapi-config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ resources:
172172
table: ogc_water_well_summary
173173
geom_field: point
174174

175-
normalized_chemistry_results:
175+
major_chemistry_results:
176176
type: collection
177177
title: Major Chemistry (Water Wells)
178178
description: Latest major chemistry analyte values for water wells, represented as static analyte columns.
@@ -192,7 +192,7 @@ resources:
192192
password: {postgres_password_env}
193193
search_path: [public]
194194
id_field: id
195-
table: ogc_normalized_chemistry_results
195+
table: ogc_major_chemistry_results
196196
geom_field: point
197197

198198
minor_chemistry_wells:

docker-compose.yml

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# keep docker-compose.yml in root directory to configure with root .env
22

33
services:
4-
db_dev:
4+
db:
55
build:
66
context: .
77
dockerfile: ./docker/db/Dockerfile
@@ -14,33 +14,13 @@ services:
1414
- 5432:5432
1515
volumes:
1616
- postgres_data_dev:/var/lib/postgresql/data
17+
- ./docker/db/init:/docker-entrypoint-initdb.d:ro
1718
healthcheck:
1819
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ocotilloapi_dev"]
1920
interval: 2s
2021
timeout: 5s
2122
retries: 20
2223

23-
db_test:
24-
profiles:
25-
- test
26-
build:
27-
context: .
28-
dockerfile: ./docker/db/Dockerfile
29-
platform: linux/amd64
30-
environment:
31-
- POSTGRES_USER=${POSTGRES_USER}
32-
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
33-
- POSTGRES_DB=ocotilloapi_test
34-
ports:
35-
- 5433:5432
36-
volumes:
37-
- postgres_data_test:/var/lib/postgresql/data
38-
healthcheck:
39-
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ocotilloapi_test"]
40-
interval: 2s
41-
timeout: 5s
42-
retries: 20
43-
4424
app:
4525
build:
4626
context: .
@@ -49,20 +29,19 @@ services:
4929
- POSTGRES_USER=${POSTGRES_USER}
5030
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
5131
- POSTGRES_DB=ocotilloapi_dev
52-
- POSTGRES_HOST=db_dev
32+
- POSTGRES_HOST=db
5333
- POSTGRES_PORT=5432
5434
- MODE=${MODE}
5535
- AUTHENTIK_DISABLE_AUTHENTICATION=${AUTHENTIK_DISABLE_AUTHENTICATION}
5636
ports:
5737
- 8000:8000
5838
depends_on:
59-
db_dev:
39+
db:
6040
condition: service_healthy # <-- wait for DB to be ready
6141
links:
62-
- db_dev
42+
- db
6343
volumes:
6444
- .:/app
6545

6646
volumes:
6747
postgres_data_dev:
68-
postgres_data_test:
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- Initialize test database inside the same Postgres service used for dev.
2+
-- This script runs only when the data directory is first initialized.
3+
4+
CREATE DATABASE ocotilloapi_test;
5+
6+
\connect ocotilloapi_dev
7+
CREATE EXTENSION IF NOT EXISTS postgis;
8+
9+
\connect ocotilloapi_test
10+
CREATE EXTENSION IF NOT EXISTS postgis;

tests/__init__.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,29 @@
1414
# limitations under the License.
1515
# ===============================================================================
1616
import os
17+
import socket
1718
from functools import lru_cache
1819

1920
from dotenv import load_dotenv
2021

2122
# Load .env file BEFORE importing anything else
22-
# Use override=True to override conflicting shell environment variables
23-
load_dotenv(override=True)
23+
# Use override=False so explicit shell environment variables can override .env
24+
load_dotenv(override=False)
25+
26+
27+
def _normalize_test_db_host() -> None:
28+
"""Fallback docker-compose hostnames to localhost for host-run tests."""
29+
for env_name in ("POSTGRES_HOST", "PYGEOAPI_POSTGRES_HOST"):
30+
host = (os.environ.get(env_name) or "").strip()
31+
if host != "db":
32+
continue
33+
try:
34+
socket.gethostbyname(host)
35+
except OSError:
36+
os.environ[env_name] = "localhost"
37+
38+
39+
_normalize_test_db_host()
2440

2541
# for safety don't test on the production database port
2642
os.environ["POSTGRES_PORT"] = "5432"

tests/conftest.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import socket
23

34
import pytest
45
from alembic import command
@@ -16,7 +17,15 @@
1617

1718

1819
def pytest_configure():
19-
load_dotenv(override=True)
20+
load_dotenv(override=False)
21+
for env_name in ("POSTGRES_HOST", "PYGEOAPI_POSTGRES_HOST"):
22+
host = (os.environ.get(env_name) or "").strip()
23+
if host != "db":
24+
continue
25+
try:
26+
socket.gethostbyname(host)
27+
except OSError:
28+
os.environ[env_name] = "localhost"
2029
os.environ.setdefault("POSTGRES_PORT", "54321")
2130
# NOTE: This hardcoded secret key is for tests only and must NEVER be used in production.
2231
os.environ.setdefault("SESSION_SECRET_KEY", "test-session-secret-key")

0 commit comments

Comments
 (0)