fix(runtime): serialize deploys and clarify regional sources
This commit is contained in:
@@ -22,6 +22,14 @@
|
|||||||
end-user selector. The Detection Lab now states the local Mol/Kempen
|
end-user selector. The Detection Lab now states the local Mol/Kempen
|
||||||
validation scope and explicitly warns that the model is not nationally
|
validation scope and explicitly warns that the model is not nationally
|
||||||
validated.
|
validated.
|
||||||
|
- Made national map source presentation zone-aware: broad Belgium work areas
|
||||||
|
now show a neutral official-per-region source contract instead of presenting
|
||||||
|
the last Brussels product as if it covered the country. The concrete source
|
||||||
|
is still resolved from the drawn selection before acquisition.
|
||||||
|
- Serialized release deployment and DockerMan container replacement with file
|
||||||
|
locks, and wait for asynchronous container removal to finish before starting
|
||||||
|
the replacement. This prevents concurrent repo deployments from leaving the
|
||||||
|
Unraid container in a half-removed state.
|
||||||
|
|
||||||
- Implemented real local segmentation inference: `YoloSegmentationAdapter` and
|
- Implemented real local segmentation inference: `YoloSegmentationAdapter` and
|
||||||
`SamSegmentationAdapter` (ultralytics interface) run over existing raster
|
`SamSegmentationAdapter` (ultralytics interface) run over existing raster
|
||||||
|
|||||||
@@ -36,6 +36,17 @@ def test_release_deploy_preserves_immutable_and_previous_images() -> None:
|
|||||||
assert "Deployed immutable image" in script
|
assert "Deployed immutable image" in script
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_and_container_replacement_are_serialized() -> None:
|
||||||
|
release_script = (ROOT / "deploy" / "unraid" / "deploy-release.sh").read_text(encoding="utf-8")
|
||||||
|
run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "GEOINTEL_DEPLOY_LOCK_FILE" in release_script
|
||||||
|
assert "flock -n 9" in release_script
|
||||||
|
assert "GEOINTEL_CONTAINER_LOCK_FILE" in run_script
|
||||||
|
assert "flock -w 300 8" in run_script
|
||||||
|
assert "GeoIntel container removal did not complete within 60 seconds" in run_script
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_configuration_is_validated_before_container_replacement() -> None:
|
def test_runtime_configuration_is_validated_before_container_replacement() -> None:
|
||||||
run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8")
|
run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ def test_unraid_template_exposes_every_operator_owned_runtime_setting() -> None:
|
|||||||
bridged_or_internal = {
|
bridged_or_internal = {
|
||||||
"GEOINTEL_FRONTEND_PORT",
|
"GEOINTEL_FRONTEND_PORT",
|
||||||
"GEOINTEL_BACKUPS_PATH",
|
"GEOINTEL_BACKUPS_PATH",
|
||||||
|
"GEOINTEL_CONTAINER_LOCK_FILE",
|
||||||
"GEOINTEL_IMAGE",
|
"GEOINTEL_IMAGE",
|
||||||
"GEOINTEL_MODELS_PATH",
|
"GEOINTEL_MODELS_PATH",
|
||||||
"GEOINTEL_POSTGIS_DATA_PATH",
|
"GEOINTEL_POSTGIS_DATA_PATH",
|
||||||
|
|||||||
@@ -4,6 +4,17 @@ set -euo pipefail
|
|||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
|
||||||
|
GEOINTEL_DEPLOY_LOCK_FILE="${GEOINTEL_DEPLOY_LOCK_FILE:-/tmp/geointel-release-deploy.lock}"
|
||||||
|
if ! command -v flock >/dev/null 2>&1; then
|
||||||
|
echo "GeoIntel release deployment requires flock to prevent concurrent container replacement." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
exec 9>"$GEOINTEL_DEPLOY_LOCK_FILE"
|
||||||
|
if ! flock -n 9; then
|
||||||
|
echo "Another GeoIntel release deployment is already running." >&2
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -f .env ]; then
|
if [ -f .env ]; then
|
||||||
set -a
|
set -a
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
|
|||||||
@@ -4,6 +4,17 @@ set -euo pipefail
|
|||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
|
||||||
|
GEOINTEL_CONTAINER_LOCK_FILE="${GEOINTEL_CONTAINER_LOCK_FILE:-/tmp/geointel-container-replacement.lock}"
|
||||||
|
if ! command -v flock >/dev/null 2>&1; then
|
||||||
|
echo "GeoIntel container replacement requires flock to prevent concurrent Docker mutations." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
exec 8>"$GEOINTEL_CONTAINER_LOCK_FILE"
|
||||||
|
if ! flock -w 300 8; then
|
||||||
|
echo "Timed out waiting for another GeoIntel container replacement to finish." >&2
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -f .env ]; then
|
if [ -f .env ]; then
|
||||||
set -a
|
set -a
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
@@ -188,7 +199,17 @@ install_dockerman_metadata
|
|||||||
docker compose down --remove-orphans || true
|
docker compose down --remove-orphans || true
|
||||||
|
|
||||||
if docker ps -a --format '{{.Names}}' | grep -qx geointel; then
|
if docker ps -a --format '{{.Names}}' | grep -qx geointel; then
|
||||||
docker rm -f geointel
|
docker rm -f geointel >/dev/null 2>&1 || true
|
||||||
|
for attempt in $(seq 1 60); do
|
||||||
|
if ! docker ps -a --format '{{.Names}}' | grep -qx geointel; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ "$attempt" -eq 60 ]; then
|
||||||
|
echo "GeoIntel container removal did not complete within 60 seconds." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$GEOINTEL_STORAGE_PATH" "$GEOINTEL_MODELS_PATH" "$GEOINTEL_BACKUPS_PATH" "$GEOINTEL_POSTGIS_DATA_PATH"
|
mkdir -p "$GEOINTEL_STORAGE_PATH" "$GEOINTEL_MODELS_PATH" "$GEOINTEL_BACKUPS_PATH" "$GEOINTEL_POSTGIS_DATA_PATH"
|
||||||
|
|||||||
@@ -11039,3 +11039,27 @@ Validated during implementation:
|
|||||||
- the final repository readiness gate passed with 1,084 backend tests, 31
|
- the final repository readiness gate passed with 1,084 backend tests, 31
|
||||||
frontend unit tests, compile, typecheck, production build, Alembic head
|
frontend unit tests, compile, typecheck, production build, Alembic head
|
||||||
`202607160001` and all script syntax/contract checks.
|
`202607160001` and all script syntax/contract checks.
|
||||||
|
|
||||||
|
## 2026-07-22 - Live national deployment verification follow-up
|
||||||
|
|
||||||
|
Implemented after deploying the national-scope revision:
|
||||||
|
|
||||||
|
- reproduced and removed a national UI ambiguity where the Belgium land scope
|
||||||
|
displayed the final Brussels UrbIS catalog item as the active country-wide
|
||||||
|
source; multiple applicable regional products are now grouped as an official
|
||||||
|
source-per-region contract until the actual rectangle resolves its zones;
|
||||||
|
- added exclusive locks for release deployment and container replacement plus
|
||||||
|
a bounded wait for Docker's asynchronous removal, after an interrupted Codex
|
||||||
|
session exposed a concurrent replacement race on Tower.
|
||||||
|
|
||||||
|
Live evidence:
|
||||||
|
|
||||||
|
- immutable AI image `0aff8e3b8c551a9d1aa29a8495a17e5a858205ab-ai` became healthy on
|
||||||
|
port 1202 and applied Alembic migrations during startup;
|
||||||
|
- `scripts/live_migration_smoke.sh` passed against the embedded PostGIS 3.6
|
||||||
|
runtime with all required tables/indexes and single head `202607160001`;
|
||||||
|
- the production model catalog exposes exactly the configured approved local
|
||||||
|
model instead of 25 training artifacts;
|
||||||
|
- coverage resolution is zone-correct for Brussels, Wallonia and all three
|
||||||
|
Belgian maritime legal zones. Unimplemented WALOUS, Walloon raster and MDK
|
||||||
|
acquisition stays explicitly `not_configured`.
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ interface OnDemandMapProduct extends MapThemeAcquisition {
|
|||||||
availabilityLabel: string
|
availabilityLabel: string
|
||||||
attribution: string
|
attribution: string
|
||||||
limitationMessage: string
|
limitationMessage: string
|
||||||
|
coverageZones: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlannedOnDemandMapProduct extends OnDemandMapProduct {
|
interface PlannedOnDemandMapProduct extends OnDemandMapProduct {
|
||||||
@@ -1051,6 +1052,7 @@ export function MapWorkspace({
|
|||||||
availabilityLabel: `${product.native_resolution_m} m · ${product.observation_year} · automatisch bij selectie`,
|
availabilityLabel: `${product.native_resolution_m} m · ${product.observation_year} · automatisch bij selectie`,
|
||||||
attribution: product.attribution,
|
attribution: product.attribution,
|
||||||
limitationMessage: product.limitation_message,
|
limitationMessage: product.limitation_message,
|
||||||
|
coverageZones: ['flanders'],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for (const product of officialMapProducts.grb) {
|
for (const product of officialMapProducts.grb) {
|
||||||
@@ -1062,6 +1064,7 @@ export function MapWorkspace({
|
|||||||
availabilityLabel: 'officiële vectorbron · automatisch bij selectie',
|
availabilityLabel: 'officiële vectorbron · automatisch bij selectie',
|
||||||
attribution: product.attribution,
|
attribution: product.attribution,
|
||||||
limitationMessage: product.limitation_message,
|
limitationMessage: product.limitation_message,
|
||||||
|
coverageZones: ['flanders'],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for (const source of officialMapProducts.bathymetry.filter(
|
for (const source of officialMapProducts.bathymetry.filter(
|
||||||
@@ -1078,6 +1081,7 @@ export function MapWorkspace({
|
|||||||
availabilityLabel: 'historische profielpunten · automatisch bij selectie',
|
availabilityLabel: 'historische profielpunten · automatisch bij selectie',
|
||||||
attribution: source.attribution,
|
attribution: source.attribution,
|
||||||
limitationMessage: source.limitation_message,
|
limitationMessage: source.limitation_message,
|
||||||
|
coverageZones: ['flanders'],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1092,6 +1096,7 @@ export function MapWorkspace({
|
|||||||
availabilityLabel: `${product.observation_label} · officiële vectorbron · automatisch bij selectie`,
|
availabilityLabel: `${product.observation_label} · officiële vectorbron · automatisch bij selectie`,
|
||||||
attribution: product.attribution,
|
attribution: product.attribution,
|
||||||
limitationMessage: product.limitation_message,
|
limitationMessage: product.limitation_message,
|
||||||
|
coverageZones: product.coverage_zones,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const dhmvProduct = includesFlanders
|
const dhmvProduct = includesFlanders
|
||||||
@@ -1106,6 +1111,7 @@ export function MapWorkspace({
|
|||||||
availabilityLabel: `${dhmvProduct.native_resolution_m} m · ${dhmvProduct.acquisition_period} · automatisch bij selectie`,
|
availabilityLabel: `${dhmvProduct.native_resolution_m} m · ${dhmvProduct.acquisition_period} · automatisch bij selectie`,
|
||||||
attribution: dhmvProduct.attribution,
|
attribution: dhmvProduct.attribution,
|
||||||
limitationMessage: dhmvProduct.limitation_message,
|
limitationMessage: dhmvProduct.limitation_message,
|
||||||
|
coverageZones: ['flanders'],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const floodProduct = includesFlanders
|
const floodProduct = includesFlanders
|
||||||
@@ -1122,6 +1128,7 @@ export function MapWorkspace({
|
|||||||
availabilityLabel: `${floodProduct.native_resolution_m} m · ${floodProduct.climate_context} · T${floodProduct.return_period_years} · automatisch bij selectie`,
|
availabilityLabel: `${floodProduct.native_resolution_m} m · ${floodProduct.climate_context} · T${floodProduct.return_period_years} · automatisch bij selectie`,
|
||||||
attribution: floodProduct.attribution,
|
attribution: floodProduct.attribution,
|
||||||
limitationMessage: floodProduct.limitation_message,
|
limitationMessage: floodProduct.limitation_message,
|
||||||
|
coverageZones: ['flanders'],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@@ -1134,8 +1141,34 @@ export function MapWorkspace({
|
|||||||
])
|
])
|
||||||
const onDemandProductMap = useMemo(() => {
|
const onDemandProductMap = useMemo(() => {
|
||||||
const result = new Map<DataThemeId, OnDemandMapProduct>()
|
const result = new Map<DataThemeId, OnDemandMapProduct>()
|
||||||
|
const productsByTheme = new Map<DataThemeId, OnDemandMapProduct[]>()
|
||||||
for (const product of onDemandProductsForZones(selectedCoverageZones)) {
|
for (const product of onDemandProductsForZones(selectedCoverageZones)) {
|
||||||
result.set(product.theme, product)
|
productsByTheme.set(product.theme, [...(productsByTheme.get(product.theme) ?? []), product])
|
||||||
|
}
|
||||||
|
for (const [theme, products] of productsByTheme) {
|
||||||
|
if (products.length === 1) {
|
||||||
|
const product = products[0]
|
||||||
|
const scopeZones = product.coverageZones.filter((zone) => selectedCoverageZones?.includes(zone) ?? true)
|
||||||
|
result.set(theme, selectedCoverageZones && selectedCoverageZones.length > 1
|
||||||
|
? {
|
||||||
|
...product,
|
||||||
|
availabilityLabel: `${product.availabilityLabel} · alleen ${scopeZones.map(coverageZoneLabel).join(', ')}`,
|
||||||
|
}
|
||||||
|
: product)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const coverageZones = Array.from(new Set(
|
||||||
|
products.flatMap((product) => product.coverageZones)
|
||||||
|
.filter((zone) => selectedCoverageZones?.includes(zone) ?? true),
|
||||||
|
))
|
||||||
|
result.set(theme, {
|
||||||
|
...products[0],
|
||||||
|
displayName: 'Officiële bron per regio',
|
||||||
|
availabilityLabel: `${coverageZones.map(coverageZoneLabel).join(', ')} · bron wordt na selectie bepaald`,
|
||||||
|
attribution: 'Officiële Belgische en gewestelijke databronnen',
|
||||||
|
limitationMessage: 'GeoIntel bepaalt na de getekende selectie welke regionale bron van toepassing is en voegt alleen semantisch gelijkwaardige resultaten samen.',
|
||||||
|
coverageZones,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}, [onDemandProductsForZones, selectedCoverageZones])
|
}, [onDemandProductsForZones, selectedCoverageZones])
|
||||||
|
|||||||
Reference in New Issue
Block a user