Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions app/h3t_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

# --- zoom / resolution ---------------------------------------------------

# mirrors int-app/app/global.R:175-181 (and api-h3t/h3t_query.R:7-13)
# mirrors db-viz-hex/app/global.R:175-181 (and api-h3t/h3t_query.R:7-13)
def _make_zoom_breaks() -> list[float]:
min_res, max_res = 1, 10
n_breaks = (max_res - min_res + 1) + 1 # 11
Expand Down Expand Up @@ -98,7 +98,14 @@ def tile_bbox(z: int, x: int, y: int) -> TileBBox:
" value,\n"
" n\n"
"FROM cells\n"
"WHERE h3_cell_to_lng(_cell) BETWEEN {lm:.10f} AND {lM:.10f}\n"
# antimeridian-aware: a cell straddling +/-180 must be returned to BOTH
# edge tiles (its centroid is on one side, but its geometry overhangs the
# other), so also match the +/-360 wrapped longitude against the (buffered)
# tile bbox. the client then places each cell on the side of the tile it
# is rendering.
"WHERE (h3_cell_to_lng(_cell) BETWEEN {lm:.10f} AND {lM:.10f}\n"
" OR h3_cell_to_lng(_cell) + 360 BETWEEN {lm:.10f} AND {lM:.10f}\n"
" OR h3_cell_to_lng(_cell) - 360 BETWEEN {lm:.10f} AND {lM:.10f})\n"
" AND h3_cell_to_lat(_cell) BETWEEN {am:.10f} AND {aM:.10f}\n"
"LIMIT {max_rows:d}"
)
Expand Down
20 changes: 10 additions & 10 deletions deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Varnish keeps hitting the R service until step 6.
# always serve plain JSON.
H3T_APP_GZIP: "false"
volumes:
- /share/github/int-app/data:/data:ro
- /share/github/db-viz-hex/data:/data:ro
expose:
- "8889"
```
Expand Down Expand Up @@ -144,11 +144,11 @@ ETags match. If any divergence shows up, **do not proceed**.
> `docker network ls | grep server` will reveal it
> (typically `server_default` if your compose project is in `server/`).

### Alternative — extend parity to int-app real queries
### Alternative — extend parity to db-viz-hex real queries

For a more thorough test, extend `scripts/parity_check.py`'s `QUERIES`
dict with the actual SQL templates from
`int-app/app/functions_h3t.R` (species + env queries with real species
`db-viz-hex/app/functions_h3t.R` (species + env queries with real species
ids and date ranges). Run again before flipping Varnish.

## Step 6 — flip Varnish to the new backend
Expand Down Expand Up @@ -200,14 +200,14 @@ sudo docker compose exec varnish varnishadm ban 'req.url ~ "^/h3t/"'
curl -s https://h3t.calcofi.io/h3t/health | jq .
# expect: {"ok":true, "default_db":"default", "dbs":{...}}
# — note the new shape (R returned a different shape).
# If int-app or other clients parsed /health, audit them.
# If db-viz-hex or other clients parsed /health, audit them.

curl -sI "https://h3t.calcofi.io/h3t/4/3/6.h3t?q=$Q&release=v2026.04.08" \
| grep -Ei '^(HTTP|X-Cache|ETag|Cache-Control|X-Calcofi-Release)'
# first hit: X-Cache: MISS; repeat: X-Cache: HIT
```

Open `int-app` (`https://app.calcofi.io` or wherever it's routed) and
Open `db-viz-hex` (`https://app.calcofi.io` or wherever it's routed) and
pan/zoom on the map. Confirm:
- Tiles render without visible seams.
- Legend populates (stats endpoint works).
Expand Down Expand Up @@ -266,7 +266,7 @@ Same flow as the R service:
```bash
# 1. flip the symlink to the new release
sudo ln -sfn calcofi_v2026.MM.DD.duckdb \
/share/github/int-app/data/calcofi_latest.duckdb
/share/github/db-viz-hex/data/calcofi_latest.duckdb

# 2. bounce the API so it reopens the DuckDB file
sudo docker compose restart h3t_api_py
Expand All @@ -275,10 +275,10 @@ sudo docker compose restart h3t_api_py
# won't hit them, but this kills any stale entries explicitly)
sudo docker compose exec varnish varnishadm ban 'req.url ~ "^/h3t/"'

# 4. update H3T_RELEASE in the int-app's .Renviron and restart it
# 4. update H3T_RELEASE in the db-viz-hex's .Renviron and restart it
echo 'H3T_RELEASE=v2026.MM.DD' \
| sudo tee /srv/shiny-server/int-app/.Renviron > /dev/null
sudo touch /srv/shiny-server/int-app/restart.txt
| sudo tee /srv/shiny-server/db-viz-hex/.Renviron > /dev/null
sudo touch /srv/shiny-server/db-viz-hex/restart.txt
```

## Troubleshooting
Expand All @@ -302,7 +302,7 @@ sudo docker compose exec varnish curl -sI \
## Breaking-change call-outs

These are intentional changes from the R service. Clients other than
`int-app` should be audited:
`db-viz-hex` should be audited:

1. **`/h3t/health` response shape** — R returned
`{"ok":true, "db":"...", "db_mtime":"..."}`. Python returns
Expand Down
2 changes: 1 addition & 1 deletion scripts/parity_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@


# Curated query fixture — keep these synthetic so they don't depend on the
# specific table schema. Extend with real int-app queries before cutover.
# specific table schema. Extend with real db-viz-hex queries before cutover.
QUERIES: dict[str, str] = {
"minimal_const": (
"SELECT 599405990948208639::BIGINT AS cell_id, 1.0 AS value, 1 AS n"
Expand Down
82 changes: 75 additions & 7 deletions tests/fixtures/r_golden.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,79 @@
{
"zoom_to_res": {
"z": [-1, 0, 0.5, 1, 1.5, 2.2, 2.5, 3.4, 4.6, 5.8, 7, 8.2, 9.4, 10.6, 11.8, 12, 13, 15, 20, 22, 22.5, 25],
"res": [1, 1, 1, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 10, 10, 10, 10]
"z": [
-1,
0,
0.5,
1,
1.5,
2.2,
2.5,
3.4,
4.6,
5.8,
7,
8.2,
9.4,
10.6,
11.8,
12,
13,
15,
20,
22,
22.5,
25
],
"res": [
1,
1,
1,
1,
1,
2,
2,
3,
4,
5,
6,
7,
8,
9,
10,
10,
10,
10,
10,
10,
10,
10
]
},
"h3_edge_length_deg": {
"r": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"deg": [3.75703205141601, 1.42002463939223, 0.536718864488001, 0.202860662770318, 0.0766741234982859, 0.028980094681474, 0.0109534462140408, 0.00414001352592486, 0.00156477803057726, 0.000591430503703552]
"r": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10
],
"deg": [
3.75703205141601,
1.42002463939223,
0.536718864488001,
0.202860662770318,
0.0766741234982859,
0.028980094681474,
0.0109534462140408,
0.00414001352592486,
0.00156477803057726,
0.000591430503703552
]
},
"tile_bbox": [
{
Expand Down Expand Up @@ -72,7 +140,7 @@
"lat_max": 37.2827946491104
}
],
"wrap_tile_sql_has_n": "WITH user_q AS (\nSELECT hex_h3res5 AS cell_id, AVG(std_tally) AS value, COUNT(*) AS n FROM bio_obs WHERE species_id = 42 GROUP BY 1\n),\ncells AS (\n SELECT\n CAST(cell_id AS BIGINT) AS _cell,\n value::DOUBLE AS value,\n TRY_CAST(n AS BIGINT) AS n\n FROM user_q\n WHERE value IS NOT NULL\n AND NOT isnan(value::DOUBLE)\n AND isfinite(value::DOUBLE)\n)\nSELECT\n h3_h3_to_string(_cell) AS h3id,\n value,\n n\nFROM cells\nWHERE h3_cell_to_lng(_cell) BETWEEN -146.3650111852 AND -134.8849888148\n AND h3_cell_to_lat(_cell) BETWEEN 31.8371510528 AND 41.0949092549\nLIMIT 50000",
"wrap_tile_sql_no_n": "WITH user_q AS (\nSELECT hex_h3res5 AS cell_id, AVG(temperature) AS value FROM env_obs\n),\ncells AS (\n SELECT\n CAST(cell_id AS BIGINT) AS _cell,\n value::DOUBLE AS value,\n NULL::BIGINT AS n\n FROM user_q\n WHERE value IS NOT NULL\n AND NOT isnan(value::DOUBLE)\n AND isfinite(value::DOUBLE)\n)\nSELECT\n h3_h3_to_string(_cell) AS h3id,\n value,\n n\nFROM cells\nWHERE h3_cell_to_lng(_cell) BETWEEN -146.2500000000 AND -135.0000000000\n AND h3_cell_to_lat(_cell) BETWEEN 31.9521622380 AND 40.9798980696\nLIMIT 50000",
"wrap_tile_sql_has_n": "WITH user_q AS (\nSELECT hex_h3res5 AS cell_id, AVG(std_tally) AS value, COUNT(*) AS n FROM bio_obs WHERE species_id = 42 GROUP BY 1\n),\ncells AS (\n SELECT\n CAST(cell_id AS BIGINT) AS _cell,\n value::DOUBLE AS value,\n TRY_CAST(n AS BIGINT) AS n\n FROM user_q\n WHERE value IS NOT NULL\n AND NOT isnan(value::DOUBLE)\n AND isfinite(value::DOUBLE)\n)\nSELECT\n h3_h3_to_string(_cell) AS h3id,\n value,\n n\nFROM cells\nWHERE (h3_cell_to_lng(_cell) BETWEEN -146.3650111852 AND -134.8849888148\n OR h3_cell_to_lng(_cell) + 360 BETWEEN -146.3650111852 AND -134.8849888148\n OR h3_cell_to_lng(_cell) - 360 BETWEEN -146.3650111852 AND -134.8849888148)\n AND h3_cell_to_lat(_cell) BETWEEN 31.8371510528 AND 41.0949092549\nLIMIT 50000",
"wrap_tile_sql_no_n": "WITH user_q AS (\nSELECT hex_h3res5 AS cell_id, AVG(temperature) AS value FROM env_obs\n),\ncells AS (\n SELECT\n CAST(cell_id AS BIGINT) AS _cell,\n value::DOUBLE AS value,\n NULL::BIGINT AS n\n FROM user_q\n WHERE value IS NOT NULL\n AND NOT isnan(value::DOUBLE)\n AND isfinite(value::DOUBLE)\n)\nSELECT\n h3_h3_to_string(_cell) AS h3id,\n value,\n n\nFROM cells\nWHERE (h3_cell_to_lng(_cell) BETWEEN -146.2500000000 AND -135.0000000000\n OR h3_cell_to_lng(_cell) + 360 BETWEEN -146.2500000000 AND -135.0000000000\n OR h3_cell_to_lng(_cell) - 360 BETWEEN -146.2500000000 AND -135.0000000000)\n AND h3_cell_to_lat(_cell) BETWEEN 31.9521622380 AND 40.9798980696\nLIMIT 50000",
"wrap_stats_sql": "WITH user_q AS (\nSELECT hex_h3res5 AS cell_id, AVG(std_tally) AS value, COUNT(*) AS n FROM bio_obs WHERE species_id = 42 GROUP BY 1\n)\nSELECT\n MIN(value::DOUBLE) AS min,\n MAX(value::DOUBLE) AS max,\n approx_quantile(value::DOUBLE, 0.02) AS p02,\n approx_quantile(value::DOUBLE, 0.98) AS p98,\n COUNT(*) AS n\nFROM user_q\nWHERE value IS NOT NULL\n AND NOT isnan(value::DOUBLE)\n AND isfinite(value::DOUBLE)"
}
}