scripts/evaluate_belgium_building_candidate.py freezes its post-processing before the protected test — NMS IoU and a containment threshold selected during calibration, defaulting to 1.0. The runtime applied a hardcoded 0.85, so a promoted candidate was served under suppression its evaluation never measured and dropped detections the gate had counted. Neither report showed the difference. That constant was mine, added without noticing the evaluation pipeline already had a tuned value for the same concept. Containment is now configuration, recorded on every run beside the duplicate IoU threshold, so an operator can serve a candidate at the value it was gated at and afterwards see which value produced a given score. Two runs that post-processed differently produced different candidate sets from the same model output, so the comparison endpoint refuses to rank them. Runs recorded before those values were persisted carry none, and absence is not treated as a difference. The remaining gap is deliberate and documented rather than closed: the gate scores the model on its protected test set, the workbench scores the whole pipeline including coverage clipping and the tile-edge filter. A promoted candidate will not reproduce its gate F1 exactly, and pretending otherwise would be the worse answer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
472 lines
23 KiB
Python
472 lines
23 KiB
Python
from pydantic import Field, field_validator, model_validator
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
extra="ignore",
|
|
populate_by_name=True,
|
|
)
|
|
|
|
app_env: str = Field(default="development", validation_alias="GEOINTEL_ENV")
|
|
app_version: str = Field(
|
|
default="1.0.0",
|
|
validation_alias="GEOINTEL_APP_VERSION",
|
|
)
|
|
build_sha: str | None = Field(default=None, validation_alias="GEOINTEL_BUILD_SHA")
|
|
build_time: str | None = Field(default=None, validation_alias="GEOINTEL_BUILD_TIME")
|
|
api_prefix: str = Field(default="/api/v1", validation_alias="GEOINTEL_API_PREFIX")
|
|
auth_enabled: bool = Field(default=False, validation_alias="GEOINTEL_AUTH_ENABLED")
|
|
auth_username: str | None = Field(default=None, validation_alias="GEOINTEL_AUTH_USERNAME")
|
|
auth_password_hash: str | None = Field(default=None, validation_alias="GEOINTEL_AUTH_PASSWORD_HASH")
|
|
auth_session_secret: str | None = Field(default=None, validation_alias="GEOINTEL_AUTH_SESSION_SECRET")
|
|
auth_session_ttl_seconds: int = Field(
|
|
default=43_200,
|
|
ge=900,
|
|
le=604_800,
|
|
validation_alias="GEOINTEL_AUTH_SESSION_TTL_SECONDS",
|
|
)
|
|
guest_access_enabled: bool = Field(
|
|
default=True,
|
|
validation_alias="GEOINTEL_GUEST_ACCESS_ENABLED",
|
|
)
|
|
guest_display_name: str = Field(
|
|
default="Gast",
|
|
min_length=1,
|
|
max_length=64,
|
|
validation_alias="GEOINTEL_GUEST_DISPLAY_NAME",
|
|
)
|
|
guest_session_ttl_seconds: int = Field(
|
|
default=7_200,
|
|
ge=900,
|
|
le=86_400,
|
|
validation_alias="GEOINTEL_GUEST_SESSION_TTL_SECONDS",
|
|
)
|
|
database_url: str = Field(
|
|
default="postgresql+psycopg://geointel:geointel@localhost:5432/geointel?connect_timeout=1",
|
|
validation_alias="DATABASE_URL",
|
|
)
|
|
storage_root: str = Field(default="./storage", validation_alias="STORAGE_ROOT")
|
|
# Analysis consumes only artifacts under storage_root. Provisioning
|
|
# workflows that stage tiles elsewhere before ingest can opt out.
|
|
allow_external_artifact_paths: bool = Field(
|
|
default=False, validation_alias="GEOINTEL_ALLOW_EXTERNAL_ARTIFACT_PATHS"
|
|
)
|
|
max_upload_mb: int = Field(default=500, validation_alias="MAX_UPLOAD_MB")
|
|
orthophoto_enabled: bool = Field(default=True, validation_alias="ORTHOPHOTO_ENABLED")
|
|
orthophoto_wms_url: str = Field(
|
|
default="https://geo.api.vlaanderen.be/OMWRGBMRVL/wms",
|
|
validation_alias="ORTHOPHOTO_WMS_URL",
|
|
)
|
|
orthophoto_wms_layer: str = Field(default="Ortho", validation_alias="ORTHOPHOTO_WMS_LAYER")
|
|
spw_orthophoto_wms_url: str = Field(
|
|
default="https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer",
|
|
validation_alias="SPW_ORTHOPHOTO_WMS_URL",
|
|
)
|
|
brussels_orthophoto_wms_url: str = Field(
|
|
default="https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows",
|
|
validation_alias="BRUSSELS_ORTHOPHOTO_WMS_URL",
|
|
)
|
|
orthophoto_resolution_m: float = Field(default=1.0, gt=0, validation_alias="ORTHOPHOTO_RESOLUTION_M")
|
|
orthophoto_min_side_m: float = Field(default=128.0, gt=0, validation_alias="ORTHOPHOTO_MIN_SIDE_M")
|
|
orthophoto_max_side_m: float = Field(default=1024.0, gt=0, validation_alias="ORTHOPHOTO_MAX_SIDE_M")
|
|
orthophoto_timeout_seconds: int = Field(default=120, ge=1, validation_alias="ORTHOPHOTO_TIMEOUT_SECONDS")
|
|
orthophoto_max_response_mb: int = Field(default=32, ge=1, validation_alias="ORTHOPHOTO_MAX_RESPONSE_MB")
|
|
orthophoto_cache_ttl_hours: int = Field(default=24, ge=0, validation_alias="ORTHOPHOTO_CACHE_TTL_HOURS")
|
|
source_catalog_probe_enabled: bool = Field(default=True, validation_alias="SOURCE_CATALOG_PROBE_ENABLED")
|
|
source_catalog_grb_wfs_url: str = Field(
|
|
default="https://geo.api.vlaanderen.be/GRB/wfs",
|
|
validation_alias="SOURCE_CATALOG_GRB_WFS_URL",
|
|
)
|
|
source_catalog_alz_release_url: str = Field(
|
|
default="https://landbouwcijfers.vlaanderen.be/open-geodata-landbouwgebruikspercelen",
|
|
validation_alias="SOURCE_CATALOG_ALZ_RELEASE_URL",
|
|
)
|
|
source_catalog_statbel_dcat_url: str = Field(
|
|
default="https://doc.statbel.be/publications/DCAT/DCAT_opendata_datasets.ttl",
|
|
validation_alias="SOURCE_CATALOG_STATBEL_DCAT_URL",
|
|
)
|
|
source_catalog_statbel_max_response_mb: int = Field(
|
|
default=5,
|
|
ge=1,
|
|
le=10,
|
|
validation_alias="SOURCE_CATALOG_STATBEL_MAX_RESPONSE_MB",
|
|
)
|
|
source_catalog_probe_timeout_seconds: int = Field(
|
|
default=10,
|
|
ge=1,
|
|
le=60,
|
|
validation_alias="SOURCE_CATALOG_PROBE_TIMEOUT_SECONDS",
|
|
)
|
|
source_catalog_probe_max_response_mb: int = Field(
|
|
default=2,
|
|
ge=1,
|
|
le=10,
|
|
validation_alias="SOURCE_CATALOG_PROBE_MAX_RESPONSE_MB",
|
|
)
|
|
source_catalog_probe_cache_ttl_seconds: int = Field(
|
|
default=900,
|
|
ge=0,
|
|
le=86_400,
|
|
validation_alias="SOURCE_CATALOG_PROBE_CACHE_TTL_SECONDS",
|
|
)
|
|
grb_enabled: bool = Field(default=True, validation_alias="GRB_ENABLED")
|
|
grb_ogc_api_url: str = Field(
|
|
default="https://geo.api.vlaanderen.be/GRB/ogc/features/v1",
|
|
validation_alias="GRB_OGC_API_URL",
|
|
)
|
|
grb_min_side_m: float = Field(default=10.0, gt=0, validation_alias="GRB_MIN_SIDE_M")
|
|
grb_max_side_m: float = Field(default=20_000.0, gt=0, validation_alias="GRB_MAX_SIDE_M")
|
|
grb_page_size: int = Field(default=1000, ge=1, le=1000, validation_alias="GRB_PAGE_SIZE")
|
|
grb_max_pages: int = Field(default=200, ge=1, le=1000, validation_alias="GRB_MAX_PAGES")
|
|
grb_max_features: int = Field(default=150_000, ge=1, validation_alias="GRB_MAX_FEATURES")
|
|
grb_timeout_seconds: int = Field(default=180, ge=1, le=600, validation_alias="GRB_TIMEOUT_SECONDS")
|
|
grb_max_response_mb: int = Field(default=20, ge=1, le=100, validation_alias="GRB_MAX_RESPONSE_MB")
|
|
grb_max_total_response_mb: int = Field(
|
|
default=256,
|
|
ge=1,
|
|
le=2048,
|
|
validation_alias="GRB_MAX_TOTAL_RESPONSE_MB",
|
|
)
|
|
grb_cache_ttl_hours: int = Field(default=24, ge=0, le=8760, validation_alias="GRB_CACHE_TTL_HOURS")
|
|
official_vector_enabled: bool = Field(default=True, validation_alias="OFFICIAL_VECTOR_ENABLED")
|
|
bwk_wfs_url: str = Field(
|
|
default="https://geo.api.vlaanderen.be/BWK/wfs",
|
|
validation_alias="BWK_WFS_URL",
|
|
)
|
|
dov_soil_wfs_url: str = Field(
|
|
default="https://www.dov.vlaanderen.be/geoserver/wfs",
|
|
validation_alias="DOV_SOIL_WFS_URL",
|
|
)
|
|
official_vector_min_side_m: float = Field(
|
|
default=10.0,
|
|
gt=0,
|
|
validation_alias="OFFICIAL_VECTOR_MIN_SIDE_M",
|
|
)
|
|
official_vector_max_side_m: float = Field(
|
|
default=20_000.0,
|
|
gt=0,
|
|
validation_alias="OFFICIAL_VECTOR_MAX_SIDE_M",
|
|
)
|
|
official_vector_page_size: int = Field(
|
|
default=1000,
|
|
ge=1,
|
|
le=2000,
|
|
validation_alias="OFFICIAL_VECTOR_PAGE_SIZE",
|
|
)
|
|
official_vector_max_pages: int = Field(
|
|
default=200,
|
|
ge=1,
|
|
le=1000,
|
|
validation_alias="OFFICIAL_VECTOR_MAX_PAGES",
|
|
)
|
|
official_vector_max_features: int = Field(
|
|
default=100_000,
|
|
ge=1,
|
|
validation_alias="OFFICIAL_VECTOR_MAX_FEATURES",
|
|
)
|
|
official_vector_timeout_seconds: int = Field(
|
|
default=180,
|
|
ge=1,
|
|
le=600,
|
|
validation_alias="OFFICIAL_VECTOR_TIMEOUT_SECONDS",
|
|
)
|
|
official_vector_max_response_mb: int = Field(
|
|
default=20,
|
|
ge=1,
|
|
le=100,
|
|
validation_alias="OFFICIAL_VECTOR_MAX_RESPONSE_MB",
|
|
)
|
|
official_vector_max_total_response_mb: int = Field(
|
|
default=256,
|
|
ge=1,
|
|
le=2048,
|
|
validation_alias="OFFICIAL_VECTOR_MAX_TOTAL_RESPONSE_MB",
|
|
)
|
|
official_vector_cache_ttl_hours: int = Field(
|
|
default=24,
|
|
ge=0,
|
|
le=8760,
|
|
validation_alias="OFFICIAL_VECTOR_CACHE_TTL_HOURS",
|
|
)
|
|
spw_picc_enabled: bool = Field(default=True, validation_alias="SPW_PICC_ENABLED")
|
|
spw_picc_mapserver_url: str = Field(
|
|
default=(
|
|
"https://geoservices.wallonie.be/arcgis/rest/services/"
|
|
"TOPOGRAPHIE/PICC_VDIFF/MapServer"
|
|
),
|
|
validation_alias="SPW_PICC_MAPSERVER_URL",
|
|
)
|
|
spw_flood_hazard_enabled: bool = Field(default=True, validation_alias="SPW_FLOOD_HAZARD_ENABLED")
|
|
spw_flood_hazard_mapserver_url: str = Field(
|
|
default=(
|
|
"https://geoservices.wallonie.be/arcgis/rest/services/"
|
|
"EAU/ALEA_INOND/MapServer"
|
|
),
|
|
validation_alias="SPW_FLOOD_HAZARD_MAPSERVER_URL",
|
|
)
|
|
urbis_enabled: bool = Field(default=True, validation_alias="URBIS_ENABLED")
|
|
urbis_wfs_url: str = Field(
|
|
default="https://geoservices-vector.irisnet.be/geoserver/urbisvector/ows",
|
|
validation_alias="URBIS_WFS_URL",
|
|
)
|
|
dhmv_enabled: bool = Field(default=True, validation_alias="DHMV_ENABLED")
|
|
dhmv_wcs_url: str = Field(
|
|
default="https://geo.api.vlaanderen.be/DHMV/wcs",
|
|
validation_alias="DHMV_WCS_URL",
|
|
)
|
|
dhmv_resolution_m: float = Field(default=5.0, ge=1.0, le=10.0, validation_alias="DHMV_RESOLUTION_M")
|
|
dhmv_min_side_m: float = Field(default=10.0, gt=0, validation_alias="DHMV_MIN_SIDE_M")
|
|
dhmv_max_side_m: float = Field(default=20_000.0, gt=0, validation_alias="DHMV_MAX_SIDE_M")
|
|
dhmv_max_pixels: int = Field(default=12_000_000, ge=1, validation_alias="DHMV_MAX_PIXELS")
|
|
dhmv_timeout_seconds: int = Field(default=300, ge=1, validation_alias="DHMV_TIMEOUT_SECONDS")
|
|
dhmv_max_response_mb: int = Field(default=160, ge=1, validation_alias="DHMV_MAX_RESPONSE_MB")
|
|
flood_hazard_enabled: bool = Field(default=True, validation_alias="FLOOD_HAZARD_ENABLED")
|
|
flood_hazard_wcs_url: str = Field(
|
|
default="https://geoservice.waterinfo.be/OGRK/wcs",
|
|
validation_alias="FLOOD_HAZARD_WCS_URL",
|
|
)
|
|
flood_hazard_resolution_m: float = Field(default=5.0, ge=2.0, le=20.0, validation_alias="FLOOD_HAZARD_RESOLUTION_M")
|
|
flood_hazard_min_side_m: float = Field(default=10.0, gt=0, validation_alias="FLOOD_HAZARD_MIN_SIDE_M")
|
|
flood_hazard_max_side_m: float = Field(default=20_000.0, gt=0, validation_alias="FLOOD_HAZARD_MAX_SIDE_M")
|
|
flood_hazard_max_pixels: int = Field(default=12_000_000, ge=1, validation_alias="FLOOD_HAZARD_MAX_PIXELS")
|
|
flood_hazard_timeout_seconds: int = Field(default=300, ge=1, validation_alias="FLOOD_HAZARD_TIMEOUT_SECONDS")
|
|
flood_hazard_max_response_mb: int = Field(default=160, ge=1, validation_alias="FLOOD_HAZARD_MAX_RESPONSE_MB")
|
|
bathymetry_profiles_enabled: bool = Field(default=True, validation_alias="BATHYMETRY_PROFILES_ENABLED")
|
|
bathymetry_profiles_layer_url: str = Field(
|
|
default="https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/0",
|
|
validation_alias="BATHYMETRY_PROFILES_LAYER_URL",
|
|
)
|
|
bathymetry_watercourse_layer_url: str = Field(
|
|
default="https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/1",
|
|
validation_alias="BATHYMETRY_WATERCOURSE_LAYER_URL",
|
|
)
|
|
bathymetry_profiles_page_size: int = Field(
|
|
default=1000,
|
|
ge=1,
|
|
le=2000,
|
|
validation_alias="BATHYMETRY_PROFILES_PAGE_SIZE",
|
|
)
|
|
bathymetry_profiles_max_features: int = Field(
|
|
default=50_000,
|
|
ge=1,
|
|
le=250_000,
|
|
validation_alias="BATHYMETRY_PROFILES_MAX_FEATURES",
|
|
)
|
|
bathymetry_profiles_timeout_seconds: int = Field(
|
|
default=120,
|
|
ge=1,
|
|
le=600,
|
|
validation_alias="BATHYMETRY_PROFILES_TIMEOUT_SECONDS",
|
|
)
|
|
bathymetry_profiles_max_response_mb: int = Field(
|
|
default=32,
|
|
ge=1,
|
|
le=256,
|
|
validation_alias="BATHYMETRY_PROFILES_MAX_RESPONSE_MB",
|
|
)
|
|
bathymetry_raster_max_pixels: int = Field(
|
|
default=30_000_000,
|
|
ge=1,
|
|
validation_alias="BATHYMETRY_RASTER_MAX_PIXELS",
|
|
)
|
|
mdk_bathymetry_probe_enabled: bool = Field(default=True, validation_alias="MDK_BATHYMETRY_PROBE_ENABLED")
|
|
mdk_bathymetry_wcs_url: str = Field(
|
|
default="https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs",
|
|
validation_alias="MDK_BATHYMETRY_WCS_URL",
|
|
)
|
|
mdk_bathymetry_probe_timeout_seconds: int = Field(
|
|
default=20,
|
|
ge=1,
|
|
le=120,
|
|
validation_alias="MDK_BATHYMETRY_PROBE_TIMEOUT_SECONDS",
|
|
)
|
|
mdk_bathymetry_probe_max_response_mb: int = Field(
|
|
default=4,
|
|
ge=1,
|
|
le=16,
|
|
validation_alias="MDK_BATHYMETRY_PROBE_MAX_RESPONSE_MB",
|
|
)
|
|
thematic_raster_enabled: bool = Field(default=True, validation_alias="THEMATIC_RASTER_ENABLED")
|
|
thematic_raster_wcs_url: str = Field(
|
|
default="https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs",
|
|
validation_alias="THEMATIC_RASTER_WCS_URL",
|
|
)
|
|
mdk_bathymetry_acquisition_enabled: bool = Field(
|
|
default=False,
|
|
validation_alias="MDK_BATHYMETRY_ACQUISITION_ENABLED",
|
|
)
|
|
mdk_bathymetry_coverage_id: str | None = Field(default=None, validation_alias="MDK_BATHYMETRY_COVERAGE_ID")
|
|
mdk_bathymetry_request_crs: str = Field(default="EPSG:4326", validation_alias="MDK_BATHYMETRY_REQUEST_CRS")
|
|
mdk_bathymetry_max_bbox_deg2: float = Field(
|
|
default=0.25,
|
|
gt=0,
|
|
validation_alias="MDK_BATHYMETRY_MAX_BBOX_DEG2",
|
|
)
|
|
mdk_bathymetry_acquisition_timeout_seconds: int = Field(
|
|
default=120,
|
|
ge=1,
|
|
validation_alias="MDK_BATHYMETRY_ACQUISITION_TIMEOUT_SECONDS",
|
|
)
|
|
mdk_bathymetry_acquisition_max_response_mb: int = Field(
|
|
default=160,
|
|
ge=1,
|
|
validation_alias="MDK_BATHYMETRY_ACQUISITION_MAX_RESPONSE_MB",
|
|
)
|
|
thematic_raster_min_side_m: float = Field(default=100.0, gt=0, validation_alias="THEMATIC_RASTER_MIN_SIDE_M")
|
|
thematic_raster_max_side_m: float = Field(default=60_000.0, gt=0, validation_alias="THEMATIC_RASTER_MAX_SIDE_M")
|
|
thematic_raster_max_pixels: int = Field(default=30_000_000, ge=1, validation_alias="THEMATIC_RASTER_MAX_PIXELS")
|
|
thematic_raster_timeout_seconds: int = Field(default=300, ge=1, validation_alias="THEMATIC_RASTER_TIMEOUT_SECONDS")
|
|
thematic_raster_max_response_mb: int = Field(default=160, ge=1, validation_alias="THEMATIC_RASTER_MAX_RESPONSE_MB")
|
|
walous_enabled: bool = Field(default=True, validation_alias="WALOUS_ENABLED")
|
|
walous_source_dir: str = Field(
|
|
default="/app/storage/source-cache/walous",
|
|
validation_alias="WALOUS_SOURCE_DIR",
|
|
)
|
|
walous_analysis_resolution_m: float = Field(
|
|
default=10.0,
|
|
ge=1.0,
|
|
le=100.0,
|
|
validation_alias="WALOUS_ANALYSIS_RESOLUTION_M",
|
|
)
|
|
walous_max_side_m: float = Field(default=60_000.0, gt=0, validation_alias="WALOUS_MAX_SIDE_M")
|
|
walous_max_pixels: int = Field(default=36_000_000, ge=1, validation_alias="WALOUS_MAX_PIXELS")
|
|
spw_terrain_enabled: bool = Field(default=True, validation_alias="SPW_TERRAIN_ENABLED")
|
|
spw_terrain_source_dir: str = Field(
|
|
default="/app/storage/source-cache/spw-terrain",
|
|
validation_alias="SPW_TERRAIN_SOURCE_DIR",
|
|
)
|
|
spw_terrain_analysis_resolution_m: float = Field(
|
|
default=5.0,
|
|
ge=1.0,
|
|
le=10.0,
|
|
validation_alias="SPW_TERRAIN_ANALYSIS_RESOLUTION_M",
|
|
)
|
|
spw_terrain_max_side_m: float = Field(default=20_000.0, gt=0, validation_alias="SPW_TERRAIN_MAX_SIDE_M")
|
|
spw_terrain_max_pixels: int = Field(default=12_000_000, ge=1, validation_alias="SPW_TERRAIN_MAX_PIXELS")
|
|
redis_url: str | None = Field(default=None, validation_alias="REDIS_URL")
|
|
log_level: str = Field(default="INFO", validation_alias="GEOINTEL_LOG_LEVEL")
|
|
sql_log_level: str = Field(default="WARNING", validation_alias="GEOINTEL_SQL_LOG_LEVEL")
|
|
reconcile_interrupted_runs_on_startup: bool = Field(
|
|
default=False,
|
|
validation_alias="GEOINTEL_RECONCILE_INTERRUPTED_RUNS_ON_STARTUP",
|
|
)
|
|
aoi_worker_enabled: bool = Field(default=False, validation_alias="GEOINTEL_AOI_WORKER_ENABLED")
|
|
aoi_worker_poll_seconds: float = Field(default=2.0, ge=0.5, le=60.0, validation_alias="GEOINTEL_AOI_WORKER_POLL_SECONDS")
|
|
# Executes queued detection.run / segmentation.run jobs so tiled GPU
|
|
# inference never blocks an HTTP request.
|
|
analysis_worker_enabled: bool = Field(default=False, validation_alias="GEOINTEL_ANALYSIS_WORKER_ENABLED")
|
|
analysis_worker_poll_seconds: float = Field(
|
|
default=2.0, ge=0.5, le=60.0, validation_alias="GEOINTEL_ANALYSIS_WORKER_POLL_SECONDS"
|
|
)
|
|
database_statement_timeout_ms: int = Field(default=5_000, validation_alias="DATABASE_STATEMENT_TIMEOUT_MS")
|
|
yolo_enabled: bool = Field(default=False, validation_alias="YOLO_ENABLED")
|
|
yolo_models_dir: str = Field(default="/app/models", validation_alias="YOLO_MODELS_DIR")
|
|
yolo_model_path: str | None = Field(default=None, validation_alias="YOLO_MODEL_PATH")
|
|
yolo_model_id: str = Field(default="yolo-configured", validation_alias="YOLO_MODEL_ID")
|
|
yolo_model_display_name: str = Field(default="Configured YOLO detector", validation_alias="YOLO_MODEL_DISPLAY_NAME")
|
|
yolo_model_version: str | None = Field(default=None, validation_alias="YOLO_MODEL_VERSION")
|
|
yolo_model_classes: str = Field(default="building", validation_alias="YOLO_MODEL_CLASSES")
|
|
yolo_enforce_validation_scope: bool = Field(default=False, validation_alias="YOLO_ENFORCE_VALIDATION_SCOPE")
|
|
yolo_validation_scope_manifest_path: str | None = Field(
|
|
default=None,
|
|
validation_alias="YOLO_VALIDATION_SCOPE_MANIFEST_PATH",
|
|
)
|
|
yolo_validation_scope_manifest_sha256: str | None = Field(
|
|
default=None,
|
|
validation_alias="YOLO_VALIDATION_SCOPE_MANIFEST_SHA256",
|
|
)
|
|
# Deprecated compatibility field. Mutable Area names are never an
|
|
# inference authorization boundary; deployments must use the immutable
|
|
# checksum-bound scope manifest above.
|
|
yolo_validated_area_names: str = Field(default="Mol,Kempen", validation_alias="YOLO_VALIDATED_AREA_NAMES")
|
|
yolo_device: str = Field(default="cpu", validation_alias="YOLO_DEVICE")
|
|
yolo_require_cuda: bool = Field(default=False, validation_alias="YOLO_REQUIRE_CUDA")
|
|
yolo_image_size: int = Field(default=640, validation_alias="YOLO_IMAGE_SIZE")
|
|
yolo_max_tiles: int = Field(default=100, validation_alias="YOLO_MAX_TILES")
|
|
yolo_max_detections: int = Field(default=1000, validation_alias="YOLO_MAX_DETECTIONS")
|
|
yolo_duplicate_iou_threshold: float = Field(default=0.5, ge=0.0, le=1.0, validation_alias="YOLO_DUPLICATE_IOU_THRESHOLD")
|
|
yolo_suppress_tile_edge_detections: bool = Field(
|
|
default=True, validation_alias="YOLO_SUPPRESS_TILE_EDGE_DETECTIONS"
|
|
)
|
|
# Intersection over the smaller box. The candidate evaluation freezes this
|
|
# during calibration; serving a promoted model at a different value means
|
|
# the runtime suppresses detections the gate counted.
|
|
yolo_containment_nms_threshold: float = Field(
|
|
default=0.85, ge=0.0, le=1.0, validation_alias="YOLO_CONTAINMENT_NMS_THRESHOLD"
|
|
)
|
|
yolo_batch_size: int = Field(default=1, validation_alias="YOLO_BATCH_SIZE")
|
|
yolo_seg_enabled: bool = Field(default=False, validation_alias="YOLO_SEG_ENABLED")
|
|
yolo_seg_model_path: str | None = Field(default=None, validation_alias="YOLO_SEG_MODEL_PATH")
|
|
yolo_seg_model_id: str = Field(default="yolo-seg-configured", validation_alias="YOLO_SEG_MODEL_ID")
|
|
yolo_seg_model_display_name: str = Field(
|
|
default="Configured YOLO segmentation",
|
|
validation_alias="YOLO_SEG_MODEL_DISPLAY_NAME",
|
|
)
|
|
yolo_seg_model_version: str | None = Field(default=None, validation_alias="YOLO_SEG_MODEL_VERSION")
|
|
sam_enabled: bool = Field(default=False, validation_alias="SAM_ENABLED")
|
|
sam_model_path: str | None = Field(default=None, validation_alias="SAM_MODEL_PATH")
|
|
sam_model_id: str = Field(default="sam-configured", validation_alias="SAM_MODEL_ID")
|
|
sam_model_display_name: str = Field(
|
|
default="Configured SAM segmentation",
|
|
validation_alias="SAM_MODEL_DISPLAY_NAME",
|
|
)
|
|
sam_model_version: str | None = Field(default=None, validation_alias="SAM_MODEL_VERSION")
|
|
segmentation_max_masks_per_tile: int = Field(default=300, ge=1, validation_alias="SEGMENTATION_MAX_MASKS_PER_TILE")
|
|
segmentation_duplicate_iou_threshold: float = Field(
|
|
default=0.5,
|
|
ge=0.0,
|
|
le=1.0,
|
|
validation_alias="SEGMENTATION_DUPLICATE_IOU_THRESHOLD",
|
|
)
|
|
ollama_enabled: bool = Field(default=False, validation_alias="OLLAMA_ENABLED")
|
|
ollama_base_url: str = Field(default="http://127.0.0.1:11434", validation_alias="OLLAMA_BASE_URL")
|
|
ollama_default_model: str = Field(default="qwen3.5:9b", validation_alias="OLLAMA_DEFAULT_MODEL")
|
|
ollama_timeout_seconds: int = Field(default=120, ge=5, le=600, validation_alias="OLLAMA_TIMEOUT_SECONDS")
|
|
ollama_max_output_tokens: int = Field(default=1_200, ge=100, le=4_000, validation_alias="OLLAMA_MAX_OUTPUT_TOKENS")
|
|
ollama_context_tokens: int = Field(default=16_384, ge=4_096, le=131_072, validation_alias="OLLAMA_CONTEXT_TOKENS")
|
|
cors_origins: list[str] | str = Field(
|
|
default=["http://localhost:5173", "http://127.0.0.1:5173"],
|
|
validation_alias="CORS_ORIGINS",
|
|
)
|
|
|
|
@field_validator("cors_origins", mode="before")
|
|
@classmethod
|
|
def parse_cors_origins(cls, value: object) -> list[str]:
|
|
if isinstance(value, str):
|
|
return [item.strip() for item in value.split(",") if item.strip()]
|
|
if isinstance(value, list):
|
|
return value
|
|
if value is None:
|
|
return ["http://localhost:5173", "http://127.0.0.1:5173"]
|
|
return [str(value)]
|
|
|
|
@field_validator("ollama_base_url")
|
|
@classmethod
|
|
def validate_ollama_base_url(cls, value: str) -> str:
|
|
normalized = value.strip().rstrip("/")
|
|
if not normalized.startswith(("http://", "https://")):
|
|
raise ValueError("OLLAMA_BASE_URL must use http or https")
|
|
return normalized
|
|
|
|
@model_validator(mode="after")
|
|
def validate_operator_auth(self) -> "Settings":
|
|
self.guest_display_name = self.guest_display_name.strip()
|
|
if not self.guest_display_name:
|
|
raise ValueError("GEOINTEL_GUEST_DISPLAY_NAME must not be blank")
|
|
if not self.auth_enabled:
|
|
return self
|
|
if not (self.auth_username or "").strip():
|
|
raise ValueError("GEOINTEL_AUTH_USERNAME is required when authentication is enabled")
|
|
if not (self.auth_password_hash or "").startswith("pbkdf2_sha256$"):
|
|
raise ValueError("GEOINTEL_AUTH_PASSWORD_HASH must be a PBKDF2-SHA256 hash")
|
|
if len(self.auth_session_secret or "") < 32:
|
|
raise ValueError("GEOINTEL_AUTH_SESSION_SECRET must contain at least 32 characters")
|
|
return self
|
|
|
|
|
|
def get_settings() -> Settings:
|
|
return Settings()
|