swiftops-backend / docs /features /regions /STANDARDIZED_REGIONS.md
kamau1's picture
feat(filtering): implement complete end-to-end server-side filtering infrastructure and migrate tickets/projects to new system
7b90d84

Standardized Regions Feature

Overview

Implemented a standardized regions system for Kenya (47 counties, 290+ sub-counties) to ensure data consistency and prevent duplicate project regions.

What Was Built

1. Data Layer (src/app/data/regions/)

  • βœ… Kenya regions data with all 47 counties and sub-counties
  • βœ… Helper functions for validation and lookups
  • βœ… Extensible structure for adding more countries

2. API Endpoints (/api/v1/locations)

  • βœ… GET /locations/countries - List supported countries
  • βœ… GET /locations/kenya/counties - Get all 47 counties
  • βœ… GET /locations/kenya/sub-counties - Get all sub-counties (with optional county filter)
  • βœ… GET /locations/kenya/sub-counties/{code} - Get specific sub-county details
  • βœ… GET /locations/kenya/validate - Validate sub-county code

3. Validation (Service Layer)

  • βœ… Validates county exists in Kenya
  • βœ… Validates sub-county exists in that county
  • βœ… Prevents duplicate regions (same county/sub-county per project)
  • βœ… Provides helpful error messages with API links

4. Schema Updates

  • βœ… Updated ProjectRegionCreate to require region (county) and city (sub-county)
  • βœ… Added validation in Pydantic schemas
  • βœ… No database changes required - uses existing fields

Database Mapping

Existing schema already supports this:

-- Existing fields (no changes needed)
country TEXT DEFAULT 'Kenya'  -- "Kenya"
region TEXT                   -- County: "Nairobi", "Kiambu"
city TEXT                     -- Sub-county: "Embakasi East", "Ruiru"
region_name TEXT              -- Custom hub name: "Nairobi East Hub"

Frontend Integration

Step 1: Fetch Counties

const { data: counties } = useQuery({
  queryKey: ['locations', 'kenya', 'counties'],
  queryFn: () => api.get('/api/v1/locations/kenya/counties')
});

Step 2: Fetch Sub-Counties (when county selected)

const { data: subCounties } = useQuery({
  queryKey: ['locations', 'kenya', 'sub-counties', selectedCounty],
  queryFn: () => api.get('/api/v1/locations/kenya/sub-counties', {
    params: { county: selectedCounty }
  }),
  enabled: !!selectedCounty
});

Step 3: Create Region

const regionData = {
  region_name: "Nairobi East Hub",  // Custom name
  country: "Kenya",
  region: "Nairobi",                // County (from dropdown)
  city: "Embakasi East",            // Sub-county (from dropdown)
  address_line1: "123 Main St",
  // ... other fields
};

await api.post(`/api/v1/projects/${projectId}/regions`, regionData);

Example API Responses

Get Counties

GET /api/v1/locations/kenya/counties

[
  {
    "country": "Kenya",
    "name": "Nairobi",
    "code": 47,
    "capital": "Nairobi City",
    "sub_county_count": 17
  },
  {
    "country": "Kenya",
    "name": "Kiambu",
    "code": 22,
    "capital": "Kiambu",
    "sub_county_count": 12
  }
]

Get Sub-Counties

GET /api/v1/locations/kenya/sub-counties?county=Nairobi

[
  {
    "country": "Kenya",
    "county": "Nairobi",
    "county_code": 47,
    "county_capital": "Nairobi City",
    "sub_county": "Embakasi East",
    "sub_county_code": "47-EME",
    "display_name": "Embakasi East, Nairobi",
    "full_display_name": "Embakasi East, Nairobi, Kenya"
  },
  {
    "country": "Kenya",
    "county": "Nairobi",
    "county_code": 47,
    "county_capital": "Nairobi City",
    "sub_county": "Westlands",
    "sub_county_code": "47-WES",
    "display_name": "Westlands, Nairobi",
    "full_display_name": "Westlands, Nairobi, Kenya"
  }
]

Validation Examples

Success

POST /api/v1/projects/{id}/regions
{
  "region_name": "Nairobi East Hub",
  "country": "Kenya",
  "region": "Nairobi",
  "city": "Embakasi East"
}

βœ… 201 Created

Invalid County

POST /api/v1/projects/{id}/regions
{
  "region_name": "Test Hub",
  "country": "Kenya",
  "region": "InvalidCounty",
  "city": "SomePlace"
}

❌ 400 Bad Request
{
  "detail": "Invalid county 'InvalidCounty' for Kenya. Use /api/v1/locations/kenya/counties to get valid counties."
}

Invalid Sub-County

POST /api/v1/projects/{id}/regions
{
  "region_name": "Test Hub",
  "country": "Kenya",
  "region": "Nairobi",
  "city": "InvalidSubCounty"
}

❌ 400 Bad Request
{
  "detail": "Invalid sub-county 'InvalidSubCounty' for county 'Nairobi'. Use /api/v1/locations/kenya/sub-counties?county=Nairobi to get valid sub-counties."
}

Duplicate Region

POST /api/v1/projects/{id}/regions
{
  "region_name": "Another Hub",
  "country": "Kenya",
  "region": "Nairobi",
  "city": "Embakasi East"  # Already exists
}

❌ 409 Conflict
{
  "detail": "A region already exists for Embakasi East, Nairobi in this project. Region name: 'Nairobi East Hub'"
}

Benefits

  1. Data Consistency: No more "Nairobi" vs "nairobi" vs "NRB"
  2. Prevents Duplicates: One hub per sub-county per project
  3. Better Analytics: Can aggregate by county/sub-county reliably
  4. User-Friendly: Dropdowns instead of free-text entry
  5. Scalable: Easy to add Uganda, Tanzania, etc.
  6. No Migration: Uses existing database schema

Future Enhancements

  1. Add Uganda regions
  2. Add Tanzania regions
  3. Add Nigeria regions (states/LGAs)
  4. Add region-based analytics dashboard
  5. Add bulk region import from CSV

Testing

# Test the API endpoints
curl http://localhost:8000/api/v1/locations/kenya/counties
curl http://localhost:8000/api/v1/locations/kenya/sub-counties?county=Nairobi
curl http://localhost:8000/api/v1/locations/kenya/validate?sub_county_code=47-EME

# Test region creation with validation
# (requires authentication)

Files Changed

  • βœ… src/app/data/regions/__init__.py - Core data structures
  • βœ… src/app/data/regions/kenya.py - Kenya regions data
  • βœ… src/app/api/v1/locations.py - API endpoints
  • βœ… src/app/api/v1/router.py - Router registration
  • βœ… src/app/schemas/project.py - Schema updates
  • βœ… src/app/services/project_service.py - Validation logic
  • βœ… src/app/data/regions/README.md - Technical documentation
  • βœ… docs/features/STANDARDIZED_REGIONS.md - Feature documentation

Time to Implement

  • Data structure: 30 mins βœ…
  • API endpoints: 20 mins βœ…
  • Validation: 15 mins βœ…
  • Documentation: 15 mins βœ…
  • Total: ~1.5 hours βœ…

Status

βœ… COMPLETE - Ready for frontend integration