An ArcGIS layer without supportsPagination accepts resultOffset and ignores it, answering every page with the first one. The VHA profile reader advanced its offset by the page length and stopped at the announced count, so for a count that is a multiple of the page size it collected N copies of page one — and its completeness check, len(features) == candidate_count, passed. Four announced records became four stored records, two of them duplicates, filed under an official provenance. That is the substitution bounded acquisition exists to prevent, arriving through the front door. The reader now refuses a record it already collected. It fails rather than silently dropping the duplicate: a provider that cannot page is a provider whose count proves nothing, so a smaller-but-clean result would still be unverifiable. Its watercourse-name loop was worse — a bare `while True` that ended only when the provider stopped setting exceededTransferLimit, with names deduplicated by code so a stuck provider produced no visible change while the requests continued. It now refuses a repeated page body, and both loops have the page budget the sibling readers already had. Those siblings turned out to be fine. GRB and official vector already refuse a repeated page URL, bound the page count, and deduplicate on feature identity — but none of it had a test, so none of it was known to work. Exercised now, including the case where distinct URLs defeat the loop check and the budget is the only backstop. A duplicate across two genuinely different pages is kept once rather than failing, because a cursor over a changing table produces that legitimately. Also: _bash_path fell back to the raw path whenever wslpath failed, except on timeout, which propagated and reddened the suite when starting WSL took more than ten seconds under load. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
203 lines
8.3 KiB
Bash
203 lines
8.3 KiB
Bash
# Backend
|
|
GEOINTEL_ENV=development
|
|
GEOINTEL_API_PREFIX=/api/v1
|
|
DATABASE_URL=postgresql+psycopg://geointel:geointel@localhost:5432/geointel?connect_timeout=1
|
|
STORAGE_ROOT=./storage
|
|
MAX_UPLOAD_MB=500
|
|
CORS_ORIGINS=http://localhost:1202,http://127.0.0.1:1202
|
|
|
|
# Optional single-operator access gate. Store only a PBKDF2-SHA256 hash and
|
|
# a unique 32+ character signing secret. Guest access is enabled by default
|
|
# whenever this gate is active; disable it explicitly on non-demo instances.
|
|
GEOINTEL_AUTH_ENABLED=false
|
|
GEOINTEL_AUTH_USERNAME=
|
|
GEOINTEL_AUTH_PASSWORD_HASH=
|
|
GEOINTEL_AUTH_SESSION_SECRET=
|
|
GEOINTEL_AUTH_SESSION_TTL_SECONDS=43200
|
|
GEOINTEL_GUEST_ACCESS_ENABLED=true
|
|
GEOINTEL_GUEST_DISPLAY_NAME=Gast
|
|
GEOINTEL_GUEST_SESSION_TTL_SECONDS=7200
|
|
ORTHOPHOTO_ENABLED=true
|
|
ORTHOPHOTO_WMS_URL=https://geo.api.vlaanderen.be/OMWRGBMRVL/wms
|
|
SPW_ORTHOPHOTO_WMS_URL=https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer
|
|
BRUSSELS_ORTHOPHOTO_WMS_URL=https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows
|
|
ORTHOPHOTO_WMS_LAYER=Ortho
|
|
ORTHOPHOTO_RESOLUTION_M=1.0
|
|
ORTHOPHOTO_MIN_SIDE_M=128
|
|
ORTHOPHOTO_MAX_SIDE_M=1024
|
|
ORTHOPHOTO_CACHE_TTL_HOURS=24
|
|
SOURCE_CATALOG_PROBE_ENABLED=true
|
|
SOURCE_CATALOG_GRB_WFS_URL=https://geo.api.vlaanderen.be/GRB/wfs
|
|
GRB_ENABLED=true
|
|
GRB_OGC_API_URL=https://geo.api.vlaanderen.be/GRB/ogc/features/v1
|
|
GRB_MIN_SIDE_M=10
|
|
GRB_MAX_SIDE_M=20000
|
|
GRB_PAGE_SIZE=1000
|
|
GRB_MAX_PAGES=200
|
|
GRB_MAX_FEATURES=150000
|
|
GRB_TIMEOUT_SECONDS=180
|
|
GRB_MAX_RESPONSE_MB=20
|
|
GRB_MAX_TOTAL_RESPONSE_MB=256
|
|
GRB_CACHE_TTL_HOURS=24
|
|
OFFICIAL_VECTOR_ENABLED=true
|
|
BWK_WFS_URL=https://geo.api.vlaanderen.be/BWK/wfs
|
|
DOV_SOIL_WFS_URL=https://www.dov.vlaanderen.be/geoserver/wfs
|
|
SPW_PICC_ENABLED=true
|
|
SPW_PICC_MAPSERVER_URL=https://geoservices.wallonie.be/arcgis/rest/services/TOPOGRAPHIE/PICC_VDIFF/MapServer
|
|
SPW_FLOOD_HAZARD_ENABLED=true
|
|
SPW_FLOOD_HAZARD_MAPSERVER_URL=https://geoservices.wallonie.be/arcgis/rest/services/EAU/ALEA_INOND/MapServer
|
|
URBIS_ENABLED=true
|
|
URBIS_WFS_URL=https://geoservices-vector.irisnet.be/geoserver/urbisvector/ows
|
|
OFFICIAL_VECTOR_MIN_SIDE_M=10
|
|
OFFICIAL_VECTOR_MAX_SIDE_M=20000
|
|
OFFICIAL_VECTOR_PAGE_SIZE=1000
|
|
OFFICIAL_VECTOR_MAX_PAGES=200
|
|
OFFICIAL_VECTOR_MAX_FEATURES=100000
|
|
OFFICIAL_VECTOR_TIMEOUT_SECONDS=180
|
|
OFFICIAL_VECTOR_MAX_RESPONSE_MB=20
|
|
OFFICIAL_VECTOR_MAX_TOTAL_RESPONSE_MB=256
|
|
OFFICIAL_VECTOR_CACHE_TTL_HOURS=24
|
|
SOURCE_CATALOG_STATBEL_DCAT_URL=https://doc.statbel.be/publications/DCAT/DCAT_opendata_datasets.ttl
|
|
SOURCE_CATALOG_STATBEL_MAX_RESPONSE_MB=5
|
|
SOURCE_CATALOG_ALZ_RELEASE_URL=https://landbouwcijfers.vlaanderen.be/open-geodata-landbouwgebruikspercelen
|
|
SOURCE_CATALOG_PROBE_TIMEOUT_SECONDS=10
|
|
SOURCE_CATALOG_PROBE_MAX_RESPONSE_MB=2
|
|
SOURCE_CATALOG_PROBE_CACHE_TTL_SECONDS=900
|
|
DHMV_ENABLED=true
|
|
DHMV_WCS_URL=https://geo.api.vlaanderen.be/DHMV/wcs
|
|
DHMV_RESOLUTION_M=5.0
|
|
DHMV_MIN_SIDE_M=10
|
|
DHMV_MAX_SIDE_M=20000
|
|
DHMV_MAX_PIXELS=12000000
|
|
DHMV_TIMEOUT_SECONDS=300
|
|
DHMV_MAX_RESPONSE_MB=160
|
|
FLOOD_HAZARD_ENABLED=true
|
|
FLOOD_HAZARD_WCS_URL=https://geoservice.waterinfo.be/OGRK/wcs
|
|
FLOOD_HAZARD_RESOLUTION_M=5.0
|
|
FLOOD_HAZARD_MIN_SIDE_M=10
|
|
FLOOD_HAZARD_MAX_SIDE_M=20000
|
|
FLOOD_HAZARD_MAX_PIXELS=12000000
|
|
FLOOD_HAZARD_TIMEOUT_SECONDS=300
|
|
FLOOD_HAZARD_MAX_RESPONSE_MB=160
|
|
BATHYMETRY_PROFILES_ENABLED=true
|
|
BATHYMETRY_PROFILES_LAYER_URL=https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/0
|
|
BATHYMETRY_WATERCOURSE_LAYER_URL=https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/1
|
|
BATHYMETRY_PROFILES_PAGE_SIZE=1000
|
|
BATHYMETRY_PROFILES_MAX_PAGES=200
|
|
BATHYMETRY_PROFILES_MAX_FEATURES=50000
|
|
BATHYMETRY_PROFILES_TIMEOUT_SECONDS=120
|
|
BATHYMETRY_PROFILES_MAX_RESPONSE_MB=32
|
|
MDK_BATHYMETRY_PROBE_ENABLED=true
|
|
MDK_BATHYMETRY_WCS_URL=https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs
|
|
MDK_BATHYMETRY_PROBE_TIMEOUT_SECONDS=20
|
|
MDK_BATHYMETRY_PROBE_MAX_RESPONSE_MB=4
|
|
# Bounded MDK acquisition stays fail-closed until the readiness probe reports
|
|
# "reachable" and an advertised coverage id is configured explicitly.
|
|
MDK_BATHYMETRY_ACQUISITION_ENABLED=false
|
|
MDK_BATHYMETRY_COVERAGE_ID=
|
|
MDK_BATHYMETRY_REQUEST_CRS=EPSG:4326
|
|
MDK_BATHYMETRY_MAX_BBOX_DEG2=0.25
|
|
MDK_BATHYMETRY_ACQUISITION_TIMEOUT_SECONDS=120
|
|
MDK_BATHYMETRY_ACQUISITION_MAX_RESPONSE_MB=160
|
|
THEMATIC_RASTER_ENABLED=true
|
|
THEMATIC_RASTER_WCS_URL=https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs
|
|
THEMATIC_RASTER_MIN_SIDE_M=100
|
|
THEMATIC_RASTER_MAX_SIDE_M=60000
|
|
THEMATIC_RASTER_MAX_PIXELS=30000000
|
|
THEMATIC_RASTER_TIMEOUT_SECONDS=300
|
|
THEMATIC_RASTER_MAX_RESPONSE_MB=160
|
|
WALOUS_ENABLED=true
|
|
WALOUS_SOURCE_DIR=/app/storage/source-cache/walous
|
|
WALOUS_ANALYSIS_RESOLUTION_M=10
|
|
WALOUS_MAX_SIDE_M=60000
|
|
WALOUS_MAX_PIXELS=36000000
|
|
YOLO_ENABLED=false
|
|
YOLO_MODELS_DIR=/app/models
|
|
YOLO_MODEL_PATH=
|
|
YOLO_MODEL_ID=yolo-configured
|
|
YOLO_MODEL_DISPLAY_NAME=Configured YOLO detector
|
|
YOLO_MODEL_VERSION=
|
|
YOLO_MODEL_CLASSES=building
|
|
YOLO_ENFORCE_VALIDATION_SCOPE=false
|
|
# Required when scope enforcement is enabled. The manifest is bound to exact
|
|
# model bytes and contains the allowed EPSG:4326 validation geometry.
|
|
YOLO_VALIDATION_SCOPE_MANIFEST_PATH=
|
|
YOLO_VALIDATION_SCOPE_MANIFEST_SHA256=
|
|
# Deprecated display metadata; never used as an inference authorization gate.
|
|
YOLO_VALIDATED_AREA_NAMES=Mol,Kempen
|
|
YOLO_CONFIG_DIR=./storage/ultralytics
|
|
YOLO_DEVICE=cpu
|
|
YOLO_REQUIRE_CUDA=false
|
|
YOLO_IMAGE_SIZE=640
|
|
YOLO_MAX_TILES=100
|
|
YOLO_MAX_DETECTIONS=1000
|
|
YOLO_DUPLICATE_IOU_THRESHOLD=0.5
|
|
# Tiles per model.predict call. 1 leaves most of a modern GPU idle on a run of
|
|
# a hundred tiles; raise it until VRAM is the limit.
|
|
YOLO_BATCH_SIZE=1
|
|
# Drop boxes truncated by an interior tile edge. Because tiles overlap, the
|
|
# neighbouring tile saw the same object whole, so the truncated half is a
|
|
# duplicate and a shape error at once. Boxes on the outer raster edge are kept.
|
|
YOLO_SUPPRESS_TILE_EDGE_DETECTIONS=true
|
|
# Intersection over the smaller box. scripts/evaluate_belgium_building_candidate.py
|
|
# freezes this during calibration (--containment-nms) before the protected test.
|
|
# Serving a promoted model at a different value means the runtime suppresses
|
|
# detections its gate counted, so set this to the value the candidate was gated at.
|
|
YOLO_CONTAINMENT_NMS_THRESHOLD=0.85
|
|
# Segmentation carries its own value: masks and boxes overlap differently,
|
|
# so one threshold need not fit both.
|
|
SEGMENTATION_CONTAINMENT_NMS_THRESHOLD=0.85
|
|
|
|
# Local segmentation models. GeoIntel never downloads model weights
|
|
# automatically; point these to existing local files to enable inference.
|
|
YOLO_SEG_ENABLED=false
|
|
YOLO_SEG_MODEL_PATH=
|
|
YOLO_SEG_MODEL_ID=yolo-seg-configured
|
|
YOLO_SEG_MODEL_DISPLAY_NAME=Configured YOLO segmentation
|
|
YOLO_SEG_MODEL_VERSION=
|
|
SAM_ENABLED=false
|
|
SAM_MODEL_PATH=
|
|
SAM_MODEL_ID=sam-configured
|
|
SAM_MODEL_DISPLAY_NAME=Configured SAM segmentation
|
|
SAM_MODEL_VERSION=
|
|
SEGMENTATION_MAX_MASKS_PER_TILE=300
|
|
SEGMENTATION_DUPLICATE_IOU_THRESHOLD=0.5
|
|
ENABLE_GRB_WFS=false
|
|
GRB_WFS_URL=
|
|
OSM_OVERPASS_URL=https://overpass-api.de/api/interpreter
|
|
|
|
# Install backend raster dependencies when needed:
|
|
# python -m pip install rasterio
|
|
|
|
# Frontend
|
|
VITE_API_BASE_URL=
|
|
VITE_API_PROXY_TARGET=http://localhost:8000
|
|
# Leave empty to use the local/demo OpenStreetMap fallback with visible attribution.
|
|
# Set this to a managed MapLibre style URL for production or heavier tile traffic.
|
|
VITE_MAP_STYLE_URL=
|
|
|
|
# Docker Compose / Unraid
|
|
GEOINTEL_FRONTEND_PORT=1202
|
|
GEOINTEL_BACKEND_PORT=8000
|
|
GEOINTEL_INSTALL_AI=false
|
|
GEOINTEL_STORAGE_PATH=./storage
|
|
GEOINTEL_BACKUPS_PATH=./backups
|
|
GEOINTEL_MODELS_PATH=./models
|
|
GEOINTEL_POSTGIS_DATA_PATH=./postgres-data
|
|
GEOINTEL_POSTGRES_DB=geointel
|
|
GEOINTEL_POSTGRES_USER=geointel
|
|
GEOINTEL_POSTGRES_PASSWORD=geointel
|
|
GEOINTEL_CORS_ORIGINS=https://geointel.itworx.tech,http://geointel.itworx.tech,http://localhost:1202,http://127.0.0.1:1202,http://192.168.10.150:1202
|
|
GEOINTEL_MAX_UPLOAD_MB=500
|
|
GEOINTEL_AOI_WORKER_ENABLED=false
|
|
GEOINTEL_AOI_WORKER_POLL_SECONDS=2
|
|
# Executes queued detection.run / segmentation.run jobs from POST
|
|
# /detection/run-async, so tiled GPU inference never blocks an HTTP request.
|
|
GEOINTEL_ANALYSIS_WORKER_ENABLED=false
|
|
GEOINTEL_ANALYSIS_WORKER_POLL_SECONDS=2
|
|
# Analysis consumes only artifacts under STORAGE_ROOT: a tile manifest path
|
|
# arrives in the request and a manifest entry may name an absolute tile
|
|
# path, so without this an API field is an unbounded filesystem reference.
|
|
# Provisioning workflows that stage tiles elsewhere before ingest can opt out.
|
|
GEOINTEL_ALLOW_EXTERNAL_ARTIFACT_PATHS=false
|