12. Data Models — Property Domain Schemas¶
Core database schemas for the KRG Digital Real Estate & Municipality Platform. All tables use PostgreSQL 16 with PostGIS 3.4 for spatial columns. Identity-related tables (
users,roles) are managed by the shared KRDPASS / Keycloak system.
12.1 Land Parcels¶
CREATE TABLE land_parcels (
parcel_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
parcel_number VARCHAR(30) UNIQUE NOT NULL, -- Official cadastral number
parent_parcel_id UUID REFERENCES land_parcels(parcel_id), -- Before subdivision
geometry GEOMETRY(MultiPolygon, 4326) NOT NULL,
area_sqm DECIMAL(14,2) NOT NULL,
land_use VARCHAR(50), -- residential, commercial, agricultural, industrial, mixed
zoning_id UUID REFERENCES zoning_districts(zone_id),
governorate VARCHAR(50) NOT NULL,
district VARCHAR(100),
municipality VARCHAR(100),
neighborhood VARCHAR(100),
block_number VARCHAR(20),
survey_number VARCHAR(30),
elevation_m DECIMAL(8,2),
terrain_type VARCHAR(30), -- flat, hilly, mountainous
road_frontage_m DECIMAL(8,2),
is_encumbered BOOLEAN DEFAULT FALSE,
status VARCHAR(20) DEFAULT 'active', -- active, merged, subdivided, disputed
registered_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_parcels_geom ON land_parcels USING GIST(geometry);
CREATE INDEX idx_parcels_governorate ON land_parcels(governorate, district);
CREATE INDEX idx_parcels_number ON land_parcels(parcel_number);
12.2 Properties (Buildings & Units)¶
CREATE TABLE properties (
property_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
parcel_id UUID NOT NULL REFERENCES land_parcels(parcel_id),
property_type VARCHAR(30) NOT NULL, -- house, apartment, villa, commercial_building, warehouse, farm
property_number VARCHAR(30) UNIQUE,
building_footprint GEOMETRY(Polygon, 4326),
floor_count INTEGER,
unit_number VARCHAR(20), -- For apartment units
total_area_sqm DECIMAL(10,2),
built_area_sqm DECIMAL(10,2),
year_built INTEGER,
construction_type VARCHAR(30), -- concrete, steel, brick, mixed
condition VARCHAR(20), -- excellent, good, fair, poor, ruin
has_garage BOOLEAN DEFAULT FALSE,
has_garden BOOLEAN DEFAULT FALSE,
utility_water BOOLEAN DEFAULT TRUE,
utility_electricity BOOLEAN DEFAULT TRUE,
utility_sewage BOOLEAN DEFAULT FALSE,
description_ckb TEXT,
description_en TEXT,
photos JSONB, -- [{url, hash, type, uploaded_at}]
status VARCHAR(20) DEFAULT 'active', -- active, demolished, under_construction
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_properties_parcel ON properties(parcel_id);
CREATE INDEX idx_properties_type ON properties(property_type, status);
12.3 Ownership Records¶
CREATE TABLE ownership_records (
ownership_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
property_id UUID NOT NULL REFERENCES properties(property_id),
parcel_id UUID REFERENCES land_parcels(parcel_id),
owner_citizen_id UUID NOT NULL, -- FK to KRDPASS citizen
ownership_type VARCHAR(30), -- freehold, leasehold, shared, government
share_percentage DECIMAL(5,2) DEFAULT 100.00,
acquired_via VARCHAR(30), -- purchase, inheritance, gift, court_order, government_grant
acquisition_date DATE NOT NULL,
deed_number VARCHAR(30),
deed_document_id UUID, -- Reference to document vault
is_primary_owner BOOLEAN DEFAULT TRUE,
status VARCHAR(20) DEFAULT 'current', -- current, transferred, deceased, disputed
notes TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_ownership_property ON ownership_records(property_id, status);
CREATE INDEX idx_ownership_citizen ON ownership_records(owner_citizen_id, status);
12.4 Ownership History (Immutable Ledger)¶
CREATE TABLE ownership_history (
history_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
property_id UUID NOT NULL REFERENCES properties(property_id),
from_citizen_id UUID, -- NULL for first registration
to_citizen_id UUID NOT NULL,
transfer_type VARCHAR(30), -- sale, inheritance, gift, court_order, first_registration
transfer_date DATE NOT NULL,
sale_price DECIMAL(15,2),
currency VARCHAR(3) DEFAULT 'IQD',
deed_number VARCHAR(30),
application_id UUID REFERENCES service_applications(application_id),
notary_name VARCHAR(200),
recorded_by UUID, -- Staff user
recorded_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_history_property ON ownership_history(property_id, transfer_date);
12.5 Property Transactions (Active Transfers)¶
CREATE TABLE property_transactions (
transaction_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
application_id UUID REFERENCES service_applications(application_id),
property_id UUID NOT NULL REFERENCES properties(property_id),
transaction_type VARCHAR(30) NOT NULL, -- sale, gift, inheritance, mortgage, release
seller_citizen_id UUID,
buyer_citizen_id UUID,
agreed_price DECIMAL(15,2),
currency VARCHAR(3) DEFAULT 'IQD',
tax_amount DECIMAL(15,2),
fee_amount DECIMAL(15,2),
notary_id UUID, -- Notary actor
agent_id UUID, -- Real estate agent actor
status VARCHAR(20) DEFAULT 'initiated',
-- initiated → documents_submitted → under_review → approved → registered | rejected
submitted_at TIMESTAMP DEFAULT NOW(),
reviewed_at TIMESTAMP,
reviewed_by UUID,
completed_at TIMESTAMP,
rejection_reason TEXT
);
CREATE INDEX idx_transactions_property ON property_transactions(property_id, status);
CREATE INDEX idx_transactions_status ON property_transactions(status, submitted_at);
12.6 Building Permits¶
CREATE TABLE building_permits (
permit_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
application_id UUID REFERENCES service_applications(application_id),
parcel_id UUID NOT NULL REFERENCES land_parcels(parcel_id),
permit_type VARCHAR(30), -- new_construction, renovation, demolition, extension, change_of_use
applicant_id UUID NOT NULL,
architect_name VARCHAR(200),
architect_license VARCHAR(50),
planned_floors INTEGER,
planned_area_sqm DECIMAL(10,2),
construction_type VARCHAR(30),
estimated_cost DECIMAL(15,2),
site_plan_doc_id UUID, -- Document vault reference
architectural_doc_id UUID,
structural_doc_id UUID,
status VARCHAR(20) DEFAULT 'submitted',
-- submitted → technical_review → site_inspection → approved → construction → completion_cert | rejected
issued_at TIMESTAMP,
expires_at TIMESTAMP,
inspection_dates JSONB, -- [{date, inspector, result, notes}]
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_permits_parcel ON building_permits(parcel_id);
CREATE INDEX idx_permits_status ON building_permits(status, created_at);
12.7 Property Valuations¶
CREATE TABLE property_valuations (
valuation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
property_id UUID NOT NULL REFERENCES properties(property_id),
parcel_id UUID REFERENCES land_parcels(parcel_id),
valuation_type VARCHAR(30), -- market, tax_assessment, insurance, court_ordered
assessed_value DECIMAL(15,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'IQD',
value_per_sqm DECIMAL(10,2),
valuation_method VARCHAR(30), -- comparable_sales, cost_approach, income_approach, ml_model
comparable_ids UUID[], -- Array of property_ids used as comparables
assessor_id UUID,
assessor_notes TEXT,
ml_confidence DECIMAL(5,4), -- If ML-based
valid_from DATE NOT NULL,
valid_until DATE,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_valuations_property ON property_valuations(property_id, valid_from);
12.8 Encumbrances & Liens¶
CREATE TABLE encumbrances (
encumbrance_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
property_id UUID NOT NULL REFERENCES properties(property_id),
encumbrance_type VARCHAR(30), -- mortgage, lien, easement, court_freeze, government_restriction
holder_name VARCHAR(200), -- Bank name, court, government entity
holder_reference VARCHAR(100), -- Loan number, court case, etc.
amount DECIMAL(15,2), -- For monetary encumbrances
currency VARCHAR(3) DEFAULT 'IQD',
description TEXT,
registered_at DATE NOT NULL,
expires_at DATE,
released_at DATE,
status VARCHAR(20) DEFAULT 'active', -- active, released, expired
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_encumbrances_property ON encumbrances(property_id, status);
12.9 Zoning Districts¶
CREATE TABLE zoning_districts (
zone_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
zone_code VARCHAR(20) UNIQUE NOT NULL, -- R1, C2, I1, AG, etc.
zone_name_ckb VARCHAR(200),
zone_name_en VARCHAR(200),
zone_type VARCHAR(30), -- residential, commercial, industrial, agricultural, mixed, protected
geometry GEOMETRY(MultiPolygon, 4326) NOT NULL,
max_building_height_m DECIMAL(6,2),
max_floor_count INTEGER,
max_coverage_pct DECIMAL(5,2), -- Max % of lot that can be built on
min_setback_m DECIMAL(5,2),
allowed_uses TEXT[], -- Array of permitted land uses
restricted_uses TEXT[],
governing_body VARCHAR(100),
effective_date DATE NOT NULL,
expires_at DATE,
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_zoning_geom ON zoning_districts USING GIST(geometry);
12.10 Rental Contracts¶
CREATE TABLE rental_contracts (
contract_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
property_id UUID NOT NULL REFERENCES properties(property_id),
landlord_id UUID NOT NULL, -- Citizen ID
tenant_id UUID NOT NULL, -- Citizen ID
contract_type VARCHAR(20), -- residential, commercial, agricultural
monthly_rent DECIMAL(12,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'IQD',
deposit_amount DECIMAL(12,2),
start_date DATE NOT NULL,
end_date DATE NOT NULL,
payment_day INTEGER CHECK (payment_day BETWEEN 1 AND 28),
auto_renew BOOLEAN DEFAULT FALSE,
contract_doc_id UUID, -- Document vault
status VARCHAR(20) DEFAULT 'active', -- active, expired, terminated, disputed
terminated_at DATE,
termination_reason TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_rental_property ON rental_contracts(property_id, status);
CREATE INDEX idx_rental_landlord ON rental_contracts(landlord_id, status);
CREATE INDEX idx_rental_tenant ON rental_contracts(tenant_id, status);
12.11 Addresses¶
CREATE TABLE addresses (
address_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
property_id UUID REFERENCES properties(property_id),
parcel_id UUID REFERENCES land_parcels(parcel_id),
address_ckb TEXT,
address_en TEXT,
street_name VARCHAR(200),
building_number VARCHAR(20),
postal_code VARCHAR(10),
governorate VARCHAR(50) NOT NULL,
district VARCHAR(100),
municipality VARCHAR(100),
neighborhood VARCHAR(100),
point GEOMETRY(Point, 4326),
is_verified BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_addresses_point ON addresses USING GIST(point);
CREATE INDEX idx_addresses_property ON addresses(property_id);
12.12 Citizen Feedback¶
CREATE TABLE service_feedback (
feedback_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
application_id UUID REFERENCES service_applications(application_id),
citizen_id UUID NOT NULL,
service_id UUID REFERENCES services(service_id),
office_id UUID REFERENCES offices(office_id),
rating INTEGER CHECK (rating BETWEEN 1 AND 5),
comment TEXT,
sentiment VARCHAR(20), -- positive, neutral, negative (AI-derived)
channel VARCHAR(20), -- app, web, kiosk, sms
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_feedback_service ON service_feedback(service_id, created_at);
CREATE INDEX idx_feedback_office ON service_feedback(office_id, created_at);
12.13 CMS Content¶
CREATE TABLE cms_content (
content_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
content_type VARCHAR(30), -- announcement, faq, guide, news
title_ckb VARCHAR(300),
title_kmr VARCHAR(300),
title_en VARCHAR(300),
body_ckb TEXT,
body_kmr TEXT,
body_en TEXT,
category VARCHAR(100), -- property, permits, zoning, municipal
tags TEXT[],
featured_image_url TEXT,
published BOOLEAN DEFAULT FALSE,
published_at TIMESTAMP,
author_id UUID,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);