Troubleshooting

Parcel Versioning Deploy Runbook

Pre-Deploy

  • Take a PostgreSQL backup/snapshot (or ensure point-in-time restore is available).
  • Confirm app build includes migration parcels.0007_parceltransaction_and_more.
  • Confirm maintenance window for schema migration + smoke tests.

Deploy

  1. Deploy application code.
  2. Run migrations:
  3. ./.venv/bin/python manage.py migrate
  4. Run Django checks:
  5. ./.venv/bin/python manage.py check

Post-Deploy Verification

  1. Validate versioning invariants:
  2. ./.venv/bin/python manage.py verify_parcel_versioning
  3. Run parcel test suite (or smoke subset in production-like env):
  4. ./.venv/bin/python manage.py test apps.parcels.tests --verbosity=1
  5. UI smoke checks:
  6. Parcel update creates a new active version and supersedes prior row.
  7. Map inline geometry edit creates a new version.
  8. Batch delete marks rows as void (no hard delete).
  9. Parcel detail page shows version history.

Rollback Strategy

  • Preferred: forward-fix with a hotfix release after 0007 is applied.
  • If full rollback is required, restore DB from pre-deploy backup and redeploy the matching pre-versioning app build.
  • Do not use ad-hoc manual SQL rollback in production unless tested on a restore copy.

Upload Error: Duplicate Key on (dataset_id, parcel_id)

Symptom:

  • duplicate key value violates unique constraint "parcels_dataset_parcel_id_unique"

Meaning:

  • The upload is trying to create/replace a parcel ID that already exists in the target dataset.

Actions:

  • Verify mapped parcel ID source field
  • Confirm target active dataset is expected
  • Use preview step to inspect matched records before apply

Upload Form Says File Required After Selecting a File

If file input appears cleared during submit, verify form submit lock logic is active and no custom JS resets file fields. Current upload form uses data-disable-on-submit="true" to prevent duplicate submits.

make check Appears to Hang on Black

Potential causes:

  • Running formatter on very large trees (including temporary directories)
  • Slow filesystem/mounted drive performance

Current Makefile excludes tmp, .venv, venv, .git, and staticfiles from formatter/lint scans.

ARIA Warning About aria-hidden and Focus

Warning example:

  • Blocked aria-hidden on an element because its descendant retained focus

Meaning:

  • A focused element was inside a container that became aria-hidden="true".

Fix direction:

  • Move focus before hiding, or use inert to both hide and prevent focus.

Workspace Map Tools Do Not Load

  • Confirm basemap_runtime_js.html loads Proj4 before the shared inspection and workspace modules.
  • Check that the raster catalog and sample endpoints return authenticated responses.
  • If surrounding parcels fail, verify the workspace subject is accessible and the request bbox contains WGS84 longitude/latitude values.
  • Fresh create intentionally disables surroundings when no active Editor dataset is selected.
  • Run Playwright through guarded make e2e; the fixture command refuses non-debug or non-opted-in environments.

Black or make check Hangs on /mnt/e

Black has repeatedly stalled on this workspace, both when run directly and when invoked by make check. Single-worker settings reduce parallelism but do not reliably prevent the stall.

  • Automated agents must not run Black or make check without explicit project-owner confirmation.
  • If approved, run with BLACK_NUM_WORKERS=1, direct Black --workers 1, and a bounded timeout.
  • Stop the process when it stalls. Record it as a formatter/environment issue, not as a test failure.
  • Run independent syntax, critical flake8, Node, and git diff --check validation where appropriate; these do not replace an owner-approved formatter gate.

Parcel Overlap Enforcement Migration Is Blocked

  • Run ./.venv/bin/python manage.py audit_parcel_overlaps --format json --include-permitted --fail-on-unresolved.
  • The activation scan and audit cover only present or future authoritative intervals. Expired historical overlaps remain immutable history and do not block activation.
  • A missing ParcelTopologySettings row must be restored before stage 2. Stage 1 seeds row id=1; if it was manually removed, open Topology Settings in admin or call ParcelTopologySettings.get_or_create_singleton() in a controlled Django shell.
  • Resolve active drafts reported with MISSING_PROJECTED_GEOMETRY; ambiguous SRID-free legacy WKT is intentionally not assumed to use the storage CRS.
  • Correct blocking authoritative pairs or create a bounded exemption through Django admin, then retry migration 0024 and continue immediately through 0025.
  • If a historical import or versioning operation failed while the database was at 0024, first apply 0025, then retry the entire failed operation. The rejected transaction did not persist a partial parcel version. Do not grandfather an expired historical overlap solely to make that retry pass.
  • To roll back enforcement, migrate to parcels.0023_overlap_governance_schema before changing stage-1 schema. Do not remove audit records first.

Export Fails with ogr2ogr Error

Checklist:

  • Confirm ogr2ogr is installed and in PATH
  • Confirm PostGIS backend is in use for export
  • Check DB connection settings in environment
  • Review server response body for exact command stderr

Dokku: Persistent Storage for Media + Raster

Use persistent mounts, not git-tracked media/ files and not re-ingest on every deploy.

  1. Create storage directories on Dokku host and mount them.
  2. Set MEDIA_ROOT, RASTER_TILES_ROOT, and RASTER_QUERY_ROOT to mounted paths.
  3. Deploy app code.
  4. Run migrate and collectstatic.
  5. Ingest rasters only when data changes.

One-line helper script example:

./scripts/setup_dokku_raster_storage.sh srv.wg lmstool data/raster_inputs/dem01 data/raster_inputs/landcover01

Commands

APP=lmstool2

# Ensure app storage root exists
dokku storage:ensure-directory "$APP"

# Host directories
mkdir -p /var/lib/dokku/data/storage/$APP/staticfiles
mkdir -p /var/lib/dokku/data/storage/$APP/media

# Mount paths
dokku storage:mount "$APP" /var/lib/dokku/data/storage/$APP/staticfiles:/app/staticfiles
dokku storage:mount "$APP" /var/lib/dokku/data/storage/$APP/media:/app/media

# Runtime paths
dokku config:set "$APP" STATIC_ROOT=/app/staticfiles STATIC_URL=/static/
dokku config:set "$APP" MEDIA_ROOT=/app/media MEDIA_URL=/media/
dokku config:set "$APP" RASTER_TILES_ROOT=/app/media/raster_tiles
dokku config:set "$APP" RASTER_QUERY_ROOT=/app/media/raster_query
dokku config:set "$APP" RASTER_TILES_URL=/raster-tiles/

# Deploy + bootstrap
git push dokku main
dokku run "$APP" python manage.py migrate
dokku run "$APP" python manage.py collectstatic --noinput

# Ingest when raster data changes
dokku run "$APP" python manage.py ingest_raster --dataset-dir data/raster_inputs/<dem-folder> --set-default --overwrite
dokku run "$APP" python manage.py ingest_raster --dataset-dir data/raster_inputs/<landcover-folder> --set-default --overwrite

Quick Verification

APP=lmstool2

dokku run "$APP" sh -lc 'ls -lah /app/media/raster_tiles'
dokku run "$APP" sh -lc 'ls -lah /app/media/raster_query'