Data Model

Core Entities

ParcelDataset

Represents a logical dataset/version of parcels.

Important fields:

  • id (UUID primary key)
  • name
  • slug (unique, stable key)
  • description
  • is_active
  • created_by, created_at, updated_at

RasterDataset

Catalog row for DEM/landcover background overlays.

Important fields:

  • id
  • name, slug (unique)
  • dataset_type (DEM, LANDCOVER)
  • status (PENDING, READY, FAILED)
  • is_active, is_default
  • min_zoom, max_zoom
  • bounds fields: minx, miny, maxx, maxy
  • default_style
  • style_variants JSON (label, tile_url_template, default_opacity)

Parcel

Represents a land parcel version row (not just a single mutable record).

Important fields:

  • id (UUID primary key)
  • user (owner)
  • dataset (FK to ParcelDataset)
  • parcel_uid (stable logical parcel identity across versions)
  • parcel_id (business parcel identifier; one active row per dataset + parcel_id)
  • name, parcel_type, address, description
  • workflow fields: status, priority, tags
  • valuation/survey fields: estimated_value, last_surveyed, boundary_accuracy
  • geometry (PolygonField, frozen deployment SRID from PARCEL_STORAGE_SRID, projected meter-based)
  • versioning fields: valid_from, valid_to, record_status, parent_version, transaction
  • is_public, timestamps

Active-row constraints:

  • partial unique on dataset + parcel_id where valid_to IS NULL AND record_status = active
  • partial unique on dataset + parcel_uid where valid_to IS NULL AND record_status = active

Computed helpers include:

  • area_sqm, area_hectares, area_acres, area_sqft
  • centroid (returned in display CRS EPSG:4326 for browser/readout use)
  • perimeter_meters

Geometry contract:

  • Authoritative parcel storage is one installation-wide projected CRS chosen before database initialization
  • Browser map payloads and transient interactive geometry use display CRS EPSG:4326 and are transformed at the server boundary.
  • GeoJSON and KML are export-only file formats. Production file imports use CRS-bearing projected GeoPackage or zipped Shapefile input, record source and storage CRS, and transform directly to the storage CRS before authoritative use.
  • Model save paths normalize incoming display geometry into the frozen storage CRS before persistence

ParcelTransaction

Audit event that records why version rows changed.

Important fields:

  • id (UUID)
  • dataset
  • transaction_type (e.g. edit, map_inline_edit, batch_group_edit, upload_replace, void)
  • source
  • effective_at
  • instrument_ref, notes
  • created_by, created_at

Parcel overlap governance

  • ParcelDraft.proposal_geometry stores the canonical EPSG:32620 proposal used by indexed conflict queries; overlap_validation_summary stores the latest advisory evidence.
  • ParcelOverlapExemption records one canonical stable-UID pair and a server-derived MultiPolygon baseline. Baseline fields are immutable; revocation is explicit.
  • ParcelOverlapExemptionUse is append-only and records only lifecycle or authoritative-write decisions that relied on an exemption, including the PostgreSQL transaction ID.
  • A partial GiST index covers non-null proposal geometry in DRAFT, SUBMITTED, and REVIEWED states.
  • A PostgreSQL trigger serializes writes by dataset and rejects material unrelated overlap for intersecting validity intervals with SQLSTATE 23514 and constraint parcels_no_material_overlap.

ParcelUpload

Tracks upload jobs and outcomes.

Important fields:

  • id (UUID)
  • user
  • dataset
  • filename, file_type
  • total_features, processed_features, failed_features
  • status, error_log, timestamps

UserProfile

Extends Django user with:

  • organization
  • phone
  • active dataset selection

ParcelMapUserSettings

Stores one constrained snapping-preference row per authenticated user, created lazily on first access.

  • point, edge, and follow-edge enabled: true
  • point and edge pixel tolerance: 12, constrained to 1..50
  • maximum snap distance: 1.00 metres, constrained to 0.01..100.00
  • minimum snap zoom: 18, constrained to 0..24
  • created/updated timestamps, with updated_at used as the settings revision

Disabling edge snapping makes follow edge unavailable at runtime but preserves its saved preference.

ParcelDraft.payload.snap_provenance

Optional versioned JSON stored beside canonical storage-CRS EWKT. It records proposal coordinate indexes and official target parcel-version UUIDs, ring/vertex/segment paths, storage-derived edge fractions, and confirmed trace direction. It does not contain projected coordinates and is ignored when draft attributes are mapped to an official Parcel.

Draft create/update canonicalizes geometry and provenance atomically. Submit, review, and final approval re-resolve and lock referenced targets, replay exact coordinates/paths, and rerun shared parcel geometry validation. A geometry change without valid snap context clears old provenance.

Shared Parcel Geometry Validation

apps/parcels/geometry_validation.py is used by the parcel form, workflow, and snap canonicalizer. It requires a non-empty valid Polygon, normalizes to the frozen storage CRS, checks the display centroid against the Saint Kitts and Nevis pilot envelope, and enforces at least 10 square metres.

Key Relationships

  • User 1 -> N Parcel
  • User 1 -> N ParcelTransaction (created_by)
  • ParcelDataset 1 -> N Parcel
  • ParcelDataset 1 -> N ParcelTransaction
  • User 1 -> N ParcelUpload
  • ParcelDataset 1 -> N ParcelUpload
  • User 1 -> 1 UserProfile
  • User 1 -> 1 ParcelMapUserSettings
  • Parcel (version row) N -> 1 ParcelTransaction (nullable bootstrap cases)
  • Parcel (child version) N -> 1 Parcel (parent version)

Dataset Scope Rule

All major parcel query entry points are constrained to the active dataset. Writes default to the active dataset when not explicitly set. Only one dataset may be active at a time.